puppeteer-ruby 0.41.0 → 0.43.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/docs/api_coverage.md +221 -206
- data/lib/puppeteer/browser.rb +109 -75
- data/lib/puppeteer/browser_connector.rb +67 -0
- data/lib/puppeteer/chrome_target_manager.rb +256 -0
- data/lib/puppeteer/connection.rb +16 -0
- data/lib/puppeteer/element_handle.rb +10 -22
- data/lib/puppeteer/events.rb +9 -0
- data/lib/puppeteer/firefox_target_manager.rb +158 -0
- data/lib/puppeteer/frame.rb +4 -0
- data/lib/puppeteer/frame_manager.rb +69 -38
- data/lib/puppeteer/launcher/chrome.rb +3 -56
- data/lib/puppeteer/launcher/firefox.rb +1 -55
- data/lib/puppeteer/lifecycle_watcher.rb +2 -1
- data/lib/puppeteer/page.rb +38 -29
- data/lib/puppeteer/puppeteer.rb +1 -1
- data/lib/puppeteer/remote_object.rb +4 -0
- data/lib/puppeteer/target.rb +14 -1
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer.rb +3 -0
- data/puppeteer-ruby.gemspec +1 -1
- metadata +7 -4
data/lib/puppeteer/connection.rb
CHANGED
@@ -58,6 +58,7 @@ class Puppeteer::Connection
|
|
58
58
|
|
59
59
|
@sessions = Concurrent::Hash.new
|
60
60
|
@closed = false
|
61
|
+
@manually_attached = Set.new
|
61
62
|
end
|
62
63
|
|
63
64
|
# used only in Browser#connected?
|
@@ -243,12 +244,22 @@ class Puppeteer::Connection
|
|
243
244
|
session_id = message['params']['sessionId']
|
244
245
|
session = Puppeteer::CDPSession.new(self, message['params']['targetInfo']['type'], session_id)
|
245
246
|
@sessions[session_id] = session
|
247
|
+
emit_event('sessionattached', session)
|
248
|
+
if message['sessionId']
|
249
|
+
parent_session = @sessions[message['sessionId']]
|
250
|
+
parent_session&.emit_event('sessionattached', session)
|
251
|
+
end
|
246
252
|
when 'Target.detachedFromTarget'
|
247
253
|
session_id = message['params']['sessionId']
|
248
254
|
session = @sessions[session_id]
|
249
255
|
if session
|
250
256
|
session.handle_closed
|
251
257
|
@sessions.delete(session_id)
|
258
|
+
emit_event('sessiondetached', session)
|
259
|
+
if message['sessionId']
|
260
|
+
parent_session = @sessions[message['sessionId']]
|
261
|
+
parent_session&.emit_event('sessiondetached', session)
|
262
|
+
end
|
252
263
|
end
|
253
264
|
end
|
254
265
|
|
@@ -307,9 +318,14 @@ class Puppeteer::Connection
|
|
307
318
|
@transport.close
|
308
319
|
end
|
309
320
|
|
321
|
+
def auto_attached?(target_id)
|
322
|
+
@manually_attached.include?(target_id)
|
323
|
+
end
|
324
|
+
|
310
325
|
# @param {Protocol.Target.TargetInfo} targetInfo
|
311
326
|
# @return [CDPSession]
|
312
327
|
def create_session(target_info)
|
328
|
+
@manually_attached << target_info.target_id
|
313
329
|
result = send_message('Target.attachToTarget', targetId: target_info.target_id, flatten: true)
|
314
330
|
session_id = result['sessionId']
|
315
331
|
@sessions[session_id]
|
@@ -169,37 +169,25 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
169
169
|
|
170
170
|
def scroll_into_view_if_needed
|
171
171
|
js = <<~JAVASCRIPT
|
172
|
-
async(element
|
172
|
+
async(element) => {
|
173
173
|
if (!element.isConnected)
|
174
174
|
return 'Node is detached from document';
|
175
175
|
if (element.nodeType !== Node.ELEMENT_NODE)
|
176
176
|
return 'Node is not of type HTMLElement';
|
177
|
-
|
178
|
-
if (element.scrollIntoViewIfNeeded) {
|
179
|
-
element.scrollIntoViewIfNeeded({block: 'center', inline: 'center', behavior: 'instant'});
|
180
|
-
} else {
|
181
|
-
// force-scroll if page's javascript is disabled.
|
182
|
-
if (!pageJavascriptEnabled) {
|
183
|
-
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
184
|
-
return false;
|
185
|
-
}
|
186
|
-
const visibleRatio = await new Promise(resolve => {
|
187
|
-
const observer = new IntersectionObserver(entries => {
|
188
|
-
resolve(entries[0].intersectionRatio);
|
189
|
-
observer.disconnect();
|
190
|
-
});
|
191
|
-
observer.observe(element);
|
192
|
-
});
|
193
|
-
if (visibleRatio !== 1.0)
|
194
|
-
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
195
|
-
}
|
196
177
|
return false;
|
197
178
|
}
|
198
179
|
JAVASCRIPT
|
199
|
-
error = evaluate(js
|
180
|
+
error = evaluate(js) # returns String or false
|
200
181
|
if error
|
201
182
|
raise ScrollIntoViewError.new(error)
|
202
183
|
end
|
184
|
+
begin
|
185
|
+
@remote_object.scroll_into_view_if_needed(@client)
|
186
|
+
rescue => err
|
187
|
+
# Just ignore 'Node does not have a layout object' for backward-compatibility.
|
188
|
+
raise unless err.message =~ /Node does not have a layout object/
|
189
|
+
end
|
190
|
+
|
203
191
|
# clickpoint is often calculated before scrolling is completed.
|
204
192
|
# So, just sleep about 10 frames
|
205
193
|
sleep 0.16
|
@@ -569,7 +557,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
569
557
|
capture_beyond_viewport: capture_beyond_viewport,
|
570
558
|
from_surface: from_surface)
|
571
559
|
ensure
|
572
|
-
if needs_viewport_reset
|
560
|
+
if needs_viewport_reset && viewport
|
573
561
|
@page.viewport = viewport
|
574
562
|
end
|
575
563
|
end
|
data/lib/puppeteer/events.rb
CHANGED
@@ -182,3 +182,12 @@ module PageEmittedEvents ; end
|
|
182
182
|
# {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API WebWorker} is destroyed by the page.
|
183
183
|
WorkerDestroyed: 'workerdestroyed',
|
184
184
|
}.define_const_into(PageEmittedEvents)
|
185
|
+
|
186
|
+
module TargetManagerEmittedEvents ; end
|
187
|
+
|
188
|
+
{
|
189
|
+
TargetDiscovered: 'targetDiscovered',
|
190
|
+
TargetAvailable: 'targetAvailable',
|
191
|
+
TargetGone: 'targetGone',
|
192
|
+
TargetChanged: 'targetChanged',
|
193
|
+
}.define_const_into(TargetManagerEmittedEvents)
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# FirefoxTargetManager implements target management using
|
2
|
+
# `Target.setDiscoverTargets` without using auto-attach. It, therefore, creates
|
3
|
+
# targets that lazily establish their CDP sessions.
|
4
|
+
#
|
5
|
+
# Although the approach is potentially flaky, there is no other way for Firefox
|
6
|
+
# because Firefox's CDP implementation does not support auto-attach.
|
7
|
+
#
|
8
|
+
# Firefox does not support targetInfoChanged and detachedFromTarget events:
|
9
|
+
# - https://bugzilla.mozilla.org/show_bug.cgi?id=1610855
|
10
|
+
# - https://bugzilla.mozilla.org/show_bug.cgi?id=1636979
|
11
|
+
class Puppeteer::FirefoxTargetManager
|
12
|
+
include Puppeteer::EventCallbackable
|
13
|
+
|
14
|
+
def initialize(connection:, target_factory:, target_filter_callback:)
|
15
|
+
@discovered_targets_by_target_id = {}
|
16
|
+
@available_targets_by_target_id = {}
|
17
|
+
@available_targets_by_session_id = {}
|
18
|
+
@ignored_targets = Set.new
|
19
|
+
@target_ids_for_init = Set.new
|
20
|
+
|
21
|
+
@connection = connection
|
22
|
+
@target_filter_callback = target_filter_callback
|
23
|
+
@target_factory = target_factory
|
24
|
+
@target_interceptors = {}
|
25
|
+
@initialize_promise = resolvable_future
|
26
|
+
|
27
|
+
@connection_event_listeners = []
|
28
|
+
@connection_event_listeners << @connection.add_event_listener(
|
29
|
+
'Target.targetCreated',
|
30
|
+
&method(:handle_target_created)
|
31
|
+
)
|
32
|
+
@connection_event_listeners << @connection.add_event_listener(
|
33
|
+
'Target.targetDestroyed',
|
34
|
+
&method(:handle_target_destroyed)
|
35
|
+
)
|
36
|
+
@connection_event_listeners << @connection.add_event_listener(
|
37
|
+
'sessiondetached',
|
38
|
+
&method(:handle_session_detached)
|
39
|
+
)
|
40
|
+
|
41
|
+
setup_attachment_listeners(@connection)
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_target_interceptor(client, interceptor)
|
45
|
+
interceptors = @target_interceptors[client] || []
|
46
|
+
interceptors << interceptor
|
47
|
+
@target_interceptors[client] = interceptors
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_target_interceptor(client, interceptor)
|
51
|
+
@target_interceptors[client]&.delete_if { |current| current == interceptor }
|
52
|
+
end
|
53
|
+
|
54
|
+
private def setup_attachment_listeners(session)
|
55
|
+
@attachment_listener_ids ||= {}
|
56
|
+
@attachment_listener_ids[session] ||= []
|
57
|
+
|
58
|
+
@attachment_listener_ids[session] << session.add_event_listener('Target.attachedToTarget') do |event|
|
59
|
+
handle_attached_to_target(session, event)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private def handle_session_detached(session)
|
64
|
+
remove_session_listeners(session)
|
65
|
+
@target_interceptors.delete(session)
|
66
|
+
end
|
67
|
+
|
68
|
+
private def remove_session_listeners(session)
|
69
|
+
return unless @attachment_listener_ids
|
70
|
+
listener_ids = @attachment_listener_ids.delete(session)
|
71
|
+
return if listener_ids.empty?
|
72
|
+
session.remove_event_listener(*listener_ids)
|
73
|
+
end
|
74
|
+
|
75
|
+
def available_targets
|
76
|
+
@available_targets_by_target_id
|
77
|
+
end
|
78
|
+
|
79
|
+
def dispose
|
80
|
+
@connection.remove_event_listener(*@connection_event_listeners)
|
81
|
+
remove_session_listeners(@connection)
|
82
|
+
end
|
83
|
+
|
84
|
+
def init
|
85
|
+
@connection.send_message('Target.setDiscoverTargets', discover: true)
|
86
|
+
@target_ids_for_init.merge(@discovered_targets_by_target_id.keys)
|
87
|
+
@initialize_promise.value!
|
88
|
+
end
|
89
|
+
|
90
|
+
private def handle_target_created(event)
|
91
|
+
target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
|
92
|
+
return if @discovered_targets_by_target_id[target_info.target_id]
|
93
|
+
@discovered_targets_by_target_id[target_info.target_id] = target_info
|
94
|
+
|
95
|
+
if target_info.type == 'browser' && target_info.attached
|
96
|
+
target = @target_factory.call(target_info, nil)
|
97
|
+
@available_targets_by_target_id[target_info.target_id] = target
|
98
|
+
finish_initialization_if_ready(target.target_id)
|
99
|
+
end
|
100
|
+
|
101
|
+
unless @target_filter_callback.call(target_info)
|
102
|
+
@ignored_targets << target_info.target_id
|
103
|
+
finish_initialization_if_ready(target_info.target_id)
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
target = @target_factory.call(target_info, nil)
|
108
|
+
@available_targets_by_target_id[target_info.target_id] = target
|
109
|
+
emit_event(TargetManagerEmittedEvents::TargetAvailable, target)
|
110
|
+
finish_initialization_if_ready(target.target_id)
|
111
|
+
end
|
112
|
+
|
113
|
+
private def handle_target_destroyed(event)
|
114
|
+
target_id = event['targetId']
|
115
|
+
target_info = @discovered_targets_by_target_id.delete(target_id)
|
116
|
+
finish_initialization_if_ready(target_id)
|
117
|
+
|
118
|
+
target = @available_targets_by_target_id.delete(target_id)
|
119
|
+
emit_event(TargetManagerEmittedEvents::TargetGone, target)
|
120
|
+
end
|
121
|
+
|
122
|
+
class SessionNotCreatedError < StandardError ; end
|
123
|
+
|
124
|
+
private def handle_attached_to_target(parent_session, event)
|
125
|
+
target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
|
126
|
+
session_id = event['sessionId']
|
127
|
+
session = @connection.session(session_id)
|
128
|
+
unless session
|
129
|
+
raise SessionNotCreatedError.new("Session #{session_id} was not created.")
|
130
|
+
end
|
131
|
+
|
132
|
+
target = @available_targets_by_target_id[target_info.target_id] or raise "Target #{target_info.target_id} is missing"
|
133
|
+
setup_attachment_listeners(session)
|
134
|
+
|
135
|
+
@available_targets_by_session_id[session_id] = target
|
136
|
+
|
137
|
+
@target_interceptors[parent_session]&.each do |hook|
|
138
|
+
if parent_session.is_a?(Puppeteer::Connection)
|
139
|
+
hook.call(target, nil)
|
140
|
+
else
|
141
|
+
# Sanity check: if parent session is not a connection, it should be
|
142
|
+
# present in #attachedTargetsBySessionId.
|
143
|
+
available_target = @available_targets_by_session_id[parent_session.id]
|
144
|
+
unless available_target
|
145
|
+
raise "No target found for the parent session: #{parent_session.id}"
|
146
|
+
end
|
147
|
+
hook.call(target, available_target)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private def finish_initialization_if_ready(target_id)
|
153
|
+
@target_ids_for_init.delete(target_id)
|
154
|
+
if @target_ids_for_init.empty?
|
155
|
+
@initialize_promise.fulfill(nil) unless @initialize_promise.resolved?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/lib/puppeteer/frame.rb
CHANGED
@@ -27,6 +27,13 @@ class Puppeteer::FrameManager
|
|
27
27
|
# @type {!Set<string>}
|
28
28
|
@isolated_worlds = Set.new
|
29
29
|
|
30
|
+
# Keeps track of OOPIF targets/frames (target ID == frame ID for OOPIFs)
|
31
|
+
# that are being initialized.
|
32
|
+
@frames_pending_target_init = {}
|
33
|
+
|
34
|
+
# Keeps track of frames that are in the process of being attached in #onFrameAttached.
|
35
|
+
@frames_pending_attachment = {}
|
36
|
+
|
30
37
|
setup_listeners(@client)
|
31
38
|
end
|
32
39
|
|
@@ -61,27 +68,17 @@ class Puppeteer::FrameManager
|
|
61
68
|
client.on_event('Page.lifecycleEvent') do |event|
|
62
69
|
handle_lifecycle_event(event)
|
63
70
|
end
|
64
|
-
client.on_event('Target.attachedToTarget') do |event|
|
65
|
-
handle_attached_to_target(event)
|
66
|
-
end
|
67
|
-
client.on_event('Target.detachedFromTarget') do |event|
|
68
|
-
handle_detached_from_target(event)
|
69
|
-
end
|
70
71
|
end
|
71
72
|
|
72
73
|
attr_reader :client, :timeout_settings
|
73
74
|
|
74
|
-
private def init(cdp_session = nil)
|
75
|
+
private def init(target_id, cdp_session = nil)
|
76
|
+
@frames_pending_target_init[target_id] ||= resolvable_future
|
75
77
|
client = cdp_session || @client
|
76
78
|
|
77
79
|
promises = [
|
78
80
|
client.async_send_message('Page.enable'),
|
79
81
|
client.async_send_message('Page.getFrameTree'),
|
80
|
-
cdp_session&.async_send_message('Target.setAutoAttach', {
|
81
|
-
autoAttach: true,
|
82
|
-
waitForDebuggerOnStart: false,
|
83
|
-
flatten: true,
|
84
|
-
}),
|
85
82
|
].compact
|
86
83
|
results = await_all(*promises)
|
87
84
|
frame_tree = results[1]['frameTree']
|
@@ -97,6 +94,8 @@ class Puppeteer::FrameManager
|
|
97
94
|
return if err.message.include?('Target closed') || err.message.include?('Session closed')
|
98
95
|
|
99
96
|
raise
|
97
|
+
ensure
|
98
|
+
@frames_pending_target_init.delete(target_id)&.fulfill(nil)
|
100
99
|
end
|
101
100
|
|
102
101
|
define_async_method :async_init
|
@@ -121,12 +120,13 @@ class Puppeteer::FrameManager
|
|
121
120
|
option_timeout = timeout || @timeout_settings.navigation_timeout
|
122
121
|
|
123
122
|
watcher = Puppeteer::LifecycleWatcher.new(self, frame, option_wait_until, option_timeout)
|
123
|
+
ensure_new_document_navigation = false
|
124
124
|
|
125
125
|
begin
|
126
126
|
navigate = future do
|
127
127
|
result = @client.send_message('Page.navigate', navigate_params)
|
128
128
|
loader_id = result['loaderId']
|
129
|
-
|
129
|
+
ensure_new_document_navigation = !!loader_id
|
130
130
|
if result['errorText']
|
131
131
|
raise NavigationError.new("#{result['errorText']} at #{url}")
|
132
132
|
end
|
@@ -137,9 +137,12 @@ class Puppeteer::FrameManager
|
|
137
137
|
)
|
138
138
|
|
139
139
|
await_any(
|
140
|
-
watcher.new_document_navigation_promise,
|
141
|
-
watcher.same_document_navigation_promise,
|
142
140
|
watcher.timeout_or_termination_promise,
|
141
|
+
if ensure_new_document_navigation
|
142
|
+
watcher.new_document_navigation_promise
|
143
|
+
else
|
144
|
+
watcher.same_document_navigation_promise
|
145
|
+
end,
|
143
146
|
)
|
144
147
|
rescue Puppeteer::TimeoutError => err
|
145
148
|
raise NavigationError.new(err)
|
@@ -175,20 +178,19 @@ class Puppeteer::FrameManager
|
|
175
178
|
end
|
176
179
|
|
177
180
|
# @param event [Hash]
|
178
|
-
def handle_attached_to_target(
|
179
|
-
return if
|
180
|
-
|
181
|
-
frame = @frames[event['targetInfo']['targetId']]
|
182
|
-
session = Puppeteer::Connection.from_session(@client).session(event['sessionId'])
|
181
|
+
def handle_attached_to_target(target)
|
182
|
+
return if target.target_info.type != 'iframe'
|
183
183
|
|
184
|
+
frame = @frames[target.target_info.target_id]
|
185
|
+
session = target.session
|
184
186
|
frame&.send(:update_client, session)
|
185
187
|
setup_listeners(session)
|
186
|
-
async_init(session)
|
188
|
+
async_init(target.target_info.target_id, session)
|
187
189
|
end
|
188
190
|
|
189
191
|
# @param event [Hash]
|
190
|
-
def handle_detached_from_target(
|
191
|
-
frame = @frames[
|
192
|
+
def handle_detached_from_target(target)
|
193
|
+
frame = @frames[target.target_id]
|
192
194
|
if frame && frame.oop_frame?
|
193
195
|
# When an OOP iframe is removed from the page, it
|
194
196
|
# will only get a Target.detachedFromTarget event.
|
@@ -256,7 +258,7 @@ class Puppeteer::FrameManager
|
|
256
258
|
|
257
259
|
# @param session [Puppeteer::CDPSession]
|
258
260
|
# @param frameId [String]
|
259
|
-
# @param parentFrameId [String
|
261
|
+
# @param parentFrameId [String]
|
260
262
|
def handle_frame_attached(session, frame_id, parent_frame_id)
|
261
263
|
if @frames.has_key?(frame_id)
|
262
264
|
frame = @frames[frame_id]
|
@@ -268,28 +270,57 @@ class Puppeteer::FrameManager
|
|
268
270
|
end
|
269
271
|
return
|
270
272
|
end
|
271
|
-
if !parent_frame_id
|
272
|
-
raise ArgymentError.new('parent_frame_id must not be nil')
|
273
|
-
end
|
274
273
|
parent_frame = @frames[parent_frame_id]
|
275
|
-
|
276
|
-
|
274
|
+
if parent_frame
|
275
|
+
attach_child_frame(parent_frame, parent_frame_id, frame_id, session)
|
276
|
+
return
|
277
|
+
end
|
277
278
|
|
279
|
+
if @frames_pending_target_init[parent_frame_id]
|
280
|
+
@frames_pending_attachment[frame_id] ||= resolvable_future
|
281
|
+
@frames_pending_target_init[parent_frame_id].then do |_|
|
282
|
+
attach_child_frame(@frames[parent_frame_id], parent_frame_id, frame_id, session)
|
283
|
+
@frames_pending_attachment.delete(frame_id)&.fulfill(nil)
|
284
|
+
end
|
285
|
+
return
|
286
|
+
end
|
287
|
+
|
288
|
+
raise FrameNotFoundError.new("Parent frame #{parent_frame_id} not found.")
|
289
|
+
end
|
290
|
+
|
291
|
+
class FrameNotFoundError < StandardError ; end
|
292
|
+
|
293
|
+
private def attach_child_frame(parent_frame, parent_frame_id, frame_id, session)
|
294
|
+
unless parent_frame
|
295
|
+
raise FrameNotFoundError.new("Parent frame #{parent_frame_id} not found.")
|
296
|
+
end
|
297
|
+
|
298
|
+
frame = Puppeteer::Frame.new(self, parent_frame, frame_id, session)
|
299
|
+
@frames[frame.id] = frame
|
278
300
|
emit_event(FrameManagerEmittedEvents::FrameAttached, frame)
|
301
|
+
frame
|
279
302
|
end
|
280
303
|
|
281
304
|
# @param frame_payload [Hash]
|
282
305
|
def handle_frame_navigated(frame_payload)
|
306
|
+
frame_id = frame_payload['id']
|
283
307
|
is_main_frame = !frame_payload['parentId']
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
@frames[
|
308
|
+
|
309
|
+
|
310
|
+
if @frames_pending_attachment[frame_id]
|
311
|
+
@frames_pending_attachment[frame_id].then do |_|
|
312
|
+
frame = is_main_frame ? @main_frame : @frames[frame_id]
|
313
|
+
reattach_frame(frame, frame_id, is_main_frame, frame_payload)
|
289
314
|
end
|
315
|
+
else
|
316
|
+
frame = is_main_frame ? @main_frame : @frames[frame_id]
|
317
|
+
reattach_frame(frame, frame_id, is_main_frame, frame_payload)
|
318
|
+
end
|
319
|
+
end
|
290
320
|
|
321
|
+
private def reattach_frame(frame, frame_id, is_main_frame, frame_payload)
|
291
322
|
if !is_main_frame && !frame
|
292
|
-
raise
|
323
|
+
raise "Missing frame isMainFrame=#{is_main_frame}, frameId=#{frame_id}"
|
293
324
|
end
|
294
325
|
|
295
326
|
# Detach all child frames first.
|
@@ -304,12 +335,12 @@ class Puppeteer::FrameManager
|
|
304
335
|
if frame
|
305
336
|
# Update frame id to retain frame identity on cross-process navigation.
|
306
337
|
@frames.delete(frame.id)
|
307
|
-
frame.id =
|
338
|
+
frame.id = frame_id
|
308
339
|
else
|
309
340
|
# Initial main frame navigation.
|
310
|
-
frame = Puppeteer::Frame.new(self, nil,
|
341
|
+
frame = Puppeteer::Frame.new(self, nil, frame_id, @client)
|
311
342
|
end
|
312
|
-
@frames[
|
343
|
+
@frames[frame_id] = frame
|
313
344
|
@main_frame = frame
|
314
345
|
end
|
315
346
|
|
@@ -81,6 +81,7 @@ module Puppeteer::Launcher
|
|
81
81
|
)
|
82
82
|
|
83
83
|
Puppeteer::Browser.create(
|
84
|
+
product: product,
|
84
85
|
connection: connection,
|
85
86
|
context_ids: [],
|
86
87
|
ignore_https_errors: @browser_options.ignore_https_errors?,
|
@@ -126,7 +127,8 @@ module Puppeteer::Launcher
|
|
126
127
|
'--disable-extensions',
|
127
128
|
# TODO: remove AvoidUnnecessaryBeforeUnloadCheckSync below
|
128
129
|
# once crbug.com/1324138 is fixed and released.
|
129
|
-
|
130
|
+
# AcceptCHFrame disabled because of crbug.com/1348106.
|
131
|
+
'--disable-features=Translate,BackForwardCache,AcceptCHFrame,AvoidUnnecessaryBeforeUnloadCheckSync',
|
130
132
|
'--disable-hang-monitor',
|
131
133
|
'--disable-ipc-flooding-protection',
|
132
134
|
'--disable-popup-blocking',
|
@@ -181,61 +183,6 @@ module Puppeteer::Launcher
|
|
181
183
|
DefaultArgs.new(ChromeArgOptions.new(options || {}))
|
182
184
|
end
|
183
185
|
|
184
|
-
# @return [Puppeteer::Browser]
|
185
|
-
def connect(options = {})
|
186
|
-
@browser_options = BrowserOptions.new(options)
|
187
|
-
browser_ws_endpoint = options[:browser_ws_endpoint]
|
188
|
-
browser_url = options[:browser_url]
|
189
|
-
transport = options[:transport]
|
190
|
-
|
191
|
-
connection =
|
192
|
-
if browser_ws_endpoint && browser_url.nil? && transport.nil?
|
193
|
-
connect_with_browser_ws_endpoint(browser_ws_endpoint)
|
194
|
-
elsif browser_ws_endpoint.nil? && browser_url && transport.nil?
|
195
|
-
connect_with_browser_url(browser_url)
|
196
|
-
elsif browser_ws_endpoint.nil? && browser_url.nil? && transport
|
197
|
-
connect_with_transport(transport)
|
198
|
-
else
|
199
|
-
raise ArgumentError.new("Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect")
|
200
|
-
end
|
201
|
-
|
202
|
-
result = connection.send_message('Target.getBrowserContexts')
|
203
|
-
browser_context_ids = result['browserContextIds']
|
204
|
-
|
205
|
-
Puppeteer::Browser.create(
|
206
|
-
connection: connection,
|
207
|
-
context_ids: browser_context_ids,
|
208
|
-
ignore_https_errors: @browser_options.ignore_https_errors?,
|
209
|
-
default_viewport: @browser_options.default_viewport,
|
210
|
-
process: nil,
|
211
|
-
close_callback: -> { connection.send_message('Browser.close') },
|
212
|
-
target_filter_callback: @browser_options.target_filter,
|
213
|
-
is_page_target_callback: @browser_options.is_page_target,
|
214
|
-
)
|
215
|
-
end
|
216
|
-
|
217
|
-
# @return [Puppeteer::Connection]
|
218
|
-
private def connect_with_browser_ws_endpoint(browser_ws_endpoint)
|
219
|
-
transport = Puppeteer::WebSocketTransport.create(browser_ws_endpoint)
|
220
|
-
Puppeteer::Connection.new(browser_ws_endpoint, transport, @browser_options.slow_mo)
|
221
|
-
end
|
222
|
-
|
223
|
-
# @return [Puppeteer::Connection]
|
224
|
-
private def connect_with_browser_url(browser_url)
|
225
|
-
require 'net/http'
|
226
|
-
uri = URI(browser_url)
|
227
|
-
uri.path = '/json/version'
|
228
|
-
response_body = Net::HTTP.get(uri)
|
229
|
-
json = JSON.parse(response_body)
|
230
|
-
connection_url = json['webSocketDebuggerUrl']
|
231
|
-
connect_with_browser_ws_endpoint(connection_url)
|
232
|
-
end
|
233
|
-
|
234
|
-
# @return [Puppeteer::Connection]
|
235
|
-
private def connect_with_transport(transport)
|
236
|
-
Puppeteer::Connection.new('', transport, @browser_options.slow_mo)
|
237
|
-
end
|
238
|
-
|
239
186
|
# @return {string}
|
240
187
|
def executable_path(channel: nil)
|
241
188
|
if channel
|
@@ -79,6 +79,7 @@ module Puppeteer::Launcher
|
|
79
79
|
)
|
80
80
|
|
81
81
|
Puppeteer::Browser.create(
|
82
|
+
product: product,
|
82
83
|
connection: connection,
|
83
84
|
context_ids: [],
|
84
85
|
ignore_https_errors: @browser_options.ignore_https_errors?,
|
@@ -106,61 +107,6 @@ module Puppeteer::Launcher
|
|
106
107
|
browser
|
107
108
|
end
|
108
109
|
|
109
|
-
# @return [Puppeteer::Browser]
|
110
|
-
def connect(options = {})
|
111
|
-
@browser_options = BrowserOptions.new(options)
|
112
|
-
browser_ws_endpoint = options[:browser_ws_endpoint]
|
113
|
-
browser_url = options[:browser_url]
|
114
|
-
transport = options[:transport]
|
115
|
-
|
116
|
-
connection =
|
117
|
-
if browser_ws_endpoint && browser_url.nil? && transport.nil?
|
118
|
-
connect_with_browser_ws_endpoint(browser_ws_endpoint)
|
119
|
-
elsif browser_ws_endpoint.nil? && browser_url && transport.nil?
|
120
|
-
connect_with_browser_url(browser_url)
|
121
|
-
elsif browser_ws_endpoint.nil? && browser_url.nil? && transport
|
122
|
-
connect_with_transport(transport)
|
123
|
-
else
|
124
|
-
raise ArgumentError.new("Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect")
|
125
|
-
end
|
126
|
-
|
127
|
-
result = connection.send_message('Target.getBrowserContexts')
|
128
|
-
browser_context_ids = result['browserContextIds']
|
129
|
-
|
130
|
-
Puppeteer::Browser.create(
|
131
|
-
connection: connection,
|
132
|
-
context_ids: browser_context_ids,
|
133
|
-
ignore_https_errors: @browser_options.ignore_https_errors?,
|
134
|
-
default_viewport: @browser_options.default_viewport,
|
135
|
-
process: nil,
|
136
|
-
close_callback: -> { connection.send_message('Browser.close') },
|
137
|
-
target_filter_callback: nil,
|
138
|
-
is_page_target_callback: nil,
|
139
|
-
)
|
140
|
-
end
|
141
|
-
|
142
|
-
# @return [Puppeteer::Connection]
|
143
|
-
private def connect_with_browser_ws_endpoint(browser_ws_endpoint)
|
144
|
-
transport = Puppeteer::WebSocketTransport.create(browser_ws_endpoint)
|
145
|
-
Puppeteer::Connection.new(browser_ws_endpoint, transport, @browser_options.slow_mo)
|
146
|
-
end
|
147
|
-
|
148
|
-
# @return [Puppeteer::Connection]
|
149
|
-
private def connect_with_browser_url(browser_url)
|
150
|
-
require 'net/http'
|
151
|
-
uri = URI(browser_url)
|
152
|
-
uri.path = '/json/version'
|
153
|
-
response_body = Net::HTTP.get(uri)
|
154
|
-
json = JSON.parse(response_body)
|
155
|
-
connection_url = json['webSocketDebuggerUrl']
|
156
|
-
connect_with_browser_ws_endpoint(connection_url)
|
157
|
-
end
|
158
|
-
|
159
|
-
# @return [Puppeteer::Connection]
|
160
|
-
private def connect_with_transport(transport)
|
161
|
-
Puppeteer::Connection.new('', transport, @browser_options.slow_mo)
|
162
|
-
end
|
163
|
-
|
164
110
|
# @return {string}
|
165
111
|
def executable_path(channel: nil)
|
166
112
|
if channel
|
@@ -65,6 +65,7 @@ class Puppeteer::LifecycleWatcher
|
|
65
65
|
@expected_lifecycle = ExpectedLifecycle.new(wait_until)
|
66
66
|
@frame_manager = frame_manager
|
67
67
|
@frame = frame
|
68
|
+
@initial_loader_id = frame.loader_id
|
68
69
|
@timeout = timeout
|
69
70
|
|
70
71
|
@listener_ids = {}
|
@@ -162,7 +163,7 @@ class Puppeteer::LifecycleWatcher
|
|
162
163
|
if @has_same_document_navigation && @same_document_navigation_promise.pending?
|
163
164
|
@same_document_navigation_promise.fulfill(true)
|
164
165
|
end
|
165
|
-
if (@swapped || @
|
166
|
+
if (@swapped || @frame.loader_id != @initial_loader_id) && @new_document_navigation_promise.pending?
|
166
167
|
@new_document_navigation_promise.fulfill(true)
|
167
168
|
end
|
168
169
|
end
|