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.
@@ -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, pageJavascriptEnabled) => {
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, @page.javascript_enabled) # returns String or false
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
@@ -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
@@ -41,6 +41,10 @@ class Puppeteer::Frame
41
41
  @secondary_world = Puppeteer::DOMWorld.new(@client, @frame_manager, self, @frame_manager.timeout_settings)
42
42
  end
43
43
 
44
+ def page
45
+ @frame_manager.page
46
+ end
47
+
44
48
  def oop_frame?
45
49
  @client != @frame_manager.client
46
50
  end
@@ -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(event)
179
- return if event['targetInfo']['type'] != 'iframe'
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(event)
191
- frame = @frames[event['targetId']]
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|nil]
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
- frame = Puppeteer::Frame.new(self, parent_frame, frame_id, session)
276
- @frames[frame_id] = frame
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
- frame =
285
- if is_main_frame
286
- @main_frame
287
- else
288
- @frames[frame_payload['id']]
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 ArgumentError.new('We either navigate top level or have old version of the navigated frame')
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 = frame_payload['id']
338
+ frame.id = frame_id
308
339
  else
309
340
  # Initial main frame navigation.
310
- frame = Puppeteer::Frame.new(self, nil, frame_payload['id'], @client)
341
+ frame = Puppeteer::Frame.new(self, nil, frame_id, @client)
311
342
  end
312
- @frames[frame_payload['id']] = frame
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
- '--disable-features=Translate,BackForwardCache,AvoidUnnecessaryBeforeUnloadCheckSync',
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 || @new_document_navigation) && @new_document_navigation_promise.pending?
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