puppeteer-ruby 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|