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.
@@ -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