puppeteer-ruby 0.37.2 → 0.39.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a83ed41b8bd2c8068dc4a1738fa16abe80e88e59fb21c4768994ae640bb5b567
4
- data.tar.gz: 6e67fb0d381644bb376d09d01213320170aa5f9ec429694c9b98888bd2006722
3
+ metadata.gz: e09022ffac5da45a7c40bca6b612c8a67afece662142675625794b15b89a044b
4
+ data.tar.gz: 9af45888cfcfaff3bc368facbd1b5fb28572924ccf0c8d33b05ba310894638bb
5
5
  SHA512:
6
- metadata.gz: b229ccf33f63425298aa0d70051d6d6a1d61d9509bf6030499521b11494fb7b9ece03fa63ab5c31be6b0a552f28b5d7b571b6c930027445cee60bbcadaada24c
7
- data.tar.gz: 6f35fb3ea22f4cd557e062576fdb95939f16e62c611f09469bf8d7647f72ff380000e1b94ac8e1412b03f9107124a5ce21914c4e2b839e893ecd275ed73b9ef3
6
+ metadata.gz: 580410458c1e0f2ac6dd8fb678fce42ed5f12ee6a1ff70b90600b6601b945af4342db53f6b98fe73ba249a9d85e3e8d64efb664534f7b1231265dbfbeb2f8e09
7
+ data.tar.gz: adecd0293ec7b7ade882d9c331b794b4a14e2e52999c164880e7f4b0fa3d723e7bc6b44a238a5ad04c50ad0bfc9c3d5d33e60b50ea40904dc49af1506ce3e72e
data/.rubocop.yml CHANGED
@@ -149,6 +149,8 @@ Style/MethodCallWithArgsParentheses:
149
149
  # utils
150
150
  - debug_print
151
151
  - debug_puts
152
+ - exit
153
+ - puts
152
154
 
153
155
  Style/MethodCallWithoutArgsParentheses:
154
156
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,7 +1,37 @@
1
- ### master [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.2...master)]
1
+ ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.39.0...main)]
2
2
 
3
3
  - xxx
4
4
 
5
+ ### 0.39.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.38.0...0.39.0)]
6
+
7
+ New features:
8
+
9
+ - Puppeteer 12.0 functionalities
10
+
11
+ ### 0.38.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.4...0.38.0)]
12
+
13
+ New features:
14
+
15
+ - Puppeteer 11.0 functionalities
16
+ * OOP iframe support
17
+ * Customize debugging port
18
+ * HTTPRequest#initiator
19
+ * Customize temp directory for user data
20
+ * Add webp to screenshot quality option allow list
21
+
22
+
23
+ ### 0.37.4 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.3...0.37.4)]
24
+
25
+ Bugfix:
26
+
27
+ - Fix ElementHandle#screenshot to work.
28
+
29
+ ### 0.37.3 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.2...0.37.3)]
30
+
31
+ Improvement:
32
+
33
+ - Improve the logic of detection of Chrome/Firefox executable path on Linux.
34
+
5
35
  ### 0.37.2 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.1...0.37.2)]
6
36
 
7
37
  Bugfix:
data/README.md CHANGED
@@ -41,7 +41,7 @@ NOTE: `require 'puppeteer-ruby'` is not necessary in Rails.
41
41
  ```ruby
42
42
  require 'puppeteer-ruby'
43
43
 
44
- Puppeteer.launch(headless: false, slow_mo: 50, args: ['--guest', '--window-size=1280,800']) do |browser|
44
+ Puppeteer.launch(headless: false, slow_mo: 50, args: ['--window-size=1280,800']) do |browser|
45
45
  page = browser.new_page
46
46
  page.viewport = Puppeteer::Viewport.new(width: 1280, height: 800)
47
47
  page.goto("https://github.com/", wait_until: 'domcontentloaded')
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
- - Puppeteer version: v11.0.0
3
- - puppeteer-ruby version: 0.37.2
2
+ - Puppeteer version: v12.0.0
3
+ - puppeteer-ruby version: 0.39.0
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -134,7 +134,7 @@
134
134
  * viewport
135
135
  * ~~waitFor~~
136
136
  * waitForFileChooser => `#wait_for_file_chooser`
137
- * ~~waitForFrame~~
137
+ * waitForFrame => `#wait_for_frame`
138
138
  * waitForFunction => `#wait_for_function`
139
139
  * waitForNavigation => `#wait_for_navigation`
140
140
  * ~~waitForNetworkIdle~~
@@ -227,7 +227,7 @@
227
227
  * goto
228
228
  * hover
229
229
  * isDetached => `#detached?`
230
- * ~~isOOPFrame~~
230
+ * isOOPFrame => `#oop_frame?`
231
231
  * name
232
232
  * parentFrame => `#parent_frame`
233
233
  * select
@@ -308,7 +308,7 @@
308
308
  * finalizeInterceptions => `#finalize_interceptions`
309
309
  * frame
310
310
  * headers
311
- * ~~initiator~~
311
+ * initiator
312
312
  * isNavigationRequest => `#navigation_request?`
313
313
  * method
314
314
  * postData => `#post_data`
@@ -360,7 +360,7 @@
360
360
 
361
361
  * connection
362
362
  * detach
363
- * ~~id~~
363
+ * id
364
364
  * send
365
365
 
366
366
  ## Coverage
@@ -7,10 +7,10 @@ class Puppeteer::AriaQueryHandler
7
7
  private def parse_aria_selector(selector)
8
8
  known_attributes = %w(name role)
9
9
  query_options = {}
10
- attribute_regexp = /\[\s*(?<attribute>\w+)\s*=\s*"(?<value>\\.|[^"\\]*)"\s*\]/
10
+ attribute_regexp = /\[\s*(?<attribute>\w+)\s*=\s*(?<quote>"|')(?<value>\\.|.*?(?=\k<quote>))\k<quote>\s*\]/
11
11
  default_name = selector.gsub(attribute_regexp) do
12
12
  attribute = $1.strip
13
- value = $2
13
+ value = $3
14
14
  unless known_attributes.include?(attribute)
15
15
  raise ArgumentError.new("Unkown aria attribute \"#{attribute}\" in selector")
16
16
  end
@@ -39,7 +39,7 @@ class Puppeteer::AriaQueryHandler
39
39
  def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil)
40
40
  binding_function = Puppeteer::DOMWorld::BindingFunction.new(
41
41
  name: 'ariaQuerySelector',
42
- proc: -> (selector) { query_one(dom_world.send(:document), selector) },
42
+ proc: -> (sel) { query_one(dom_world.send(:document), sel) },
43
43
  )
44
44
  dom_world.send(:wait_for_selector_in_page,
45
45
  '(_, selector) => globalThis.ariaQuerySelector(selector)',
@@ -4,13 +4,17 @@ require 'timeout'
4
4
 
5
5
  # https://github.com/puppeteer/puppeteer/blob/master/lib/Launcher.js
6
6
  class Puppeteer::BrowserRunner
7
+ include Puppeteer::DebugPrint
8
+
7
9
  # @param {string} executablePath
8
10
  # @param {!Array<string>} processArguments
9
11
  # @param {string=} tempDirectory
10
- def initialize(executable_path, process_arguments, temp_directory)
12
+ def initialize(for_firefox, executable_path, process_arguments, user_data_dir, using_temp_user_data_dir)
13
+ @for_firefox = for_firefox
11
14
  @executable_path = executable_path
12
15
  @process_arguments = process_arguments
13
- @temp_directory = temp_directory
16
+ @user_data_dir = user_data_dir
17
+ @using_temp_user_data_dir = using_temp_user_data_dir
14
18
  @proc = nil
15
19
  @connection = nil
16
20
  @closed = true
@@ -90,8 +94,8 @@ class Puppeteer::BrowserRunner
90
94
  @process_closing = -> {
91
95
  @proc.dispose
92
96
  @closed = true
93
- if @temp_directory
94
- FileUtils.rm_rf(@temp_directory)
97
+ if @using_temp_user_data_dir
98
+ FileUtils.rm_rf(@user_data_dir)
95
99
  end
96
100
  }
97
101
  at_exit do
@@ -122,7 +126,7 @@ class Puppeteer::BrowserRunner
122
126
  def close
123
127
  return if @closed
124
128
 
125
- if @temp_directory
129
+ if @using_temp_user_data_dir && !@for_firefox
126
130
  kill
127
131
  elsif @connection
128
132
  begin
@@ -137,11 +141,18 @@ class Puppeteer::BrowserRunner
137
141
 
138
142
  # @return {Promise}
139
143
  def kill
140
- if @temp_directory
141
- FileUtils.rm_rf(@temp_directory)
142
- end
143
- unless @closed
144
- @proc.kill
144
+ # If the process failed to launch (for example if the browser executable path
145
+ # is invalid), then the process does not get a pid assigned. A call to
146
+ # `proc.kill` would error, as the `pid` to-be-killed can not be found.
147
+ @proc&.kill
148
+
149
+ # Attempt to remove temporary profile directory to avoid littering.
150
+ begin
151
+ if @using_temp_user_data_dir
152
+ FileUtils.rm_rf(@temp_directory)
153
+ end
154
+ rescue => err
155
+ debug_puts(err)
145
156
  end
146
157
  end
147
158
 
@@ -15,6 +15,11 @@ class Puppeteer::CDPSession
15
15
  @session_id = session_id
16
16
  end
17
17
 
18
+ # @internal
19
+ def id
20
+ @session_id
21
+ end
22
+
18
23
  attr_reader :connection
19
24
 
20
25
  # @param method [String]
@@ -45,10 +45,14 @@ class Puppeteer::DOMWorld
45
45
  end
46
46
  end
47
47
 
48
- # @param {!Puppeteer.FrameManager} frameManager
49
- # @param {!Puppeteer.Frame} frame
50
- # @param {!Puppeteer.TimeoutSettings} timeoutSettings
51
- def initialize(frame_manager, frame, timeout_settings)
48
+ # @param client [Puppeteer::CDPSession]
49
+ # @param frame_manager [Puppeteer::FrameManager]
50
+ # @param frame [Puppeteer::Frame]
51
+ # @param timeout_settings [Puppeteer::TimeoutSettings]
52
+ def initialize(client, frame_manager, frame, timeout_settings)
53
+ # Keep own reference to client because it might differ from the FrameManager's
54
+ # client for OOP iframes.
55
+ @client = client
52
56
  @frame_manager = frame_manager
53
57
  @frame = frame
54
58
  @timeout_settings = timeout_settings
@@ -58,7 +62,7 @@ class Puppeteer::DOMWorld
58
62
  @ctx_bindings = Set.new
59
63
  @detached = false
60
64
 
61
- frame_manager.client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
65
+ @client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
62
66
  end
63
67
 
64
68
  attr_reader :frame
@@ -323,7 +323,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
323
323
  end
324
324
  end
325
325
 
326
- def screenshot(options = {})
326
+ def screenshot(type: nil, path: nil, full_page: nil, clip: nil, quality: nil, omit_background: nil, encoding: nil)
327
327
  needs_viewport_reset = false
328
328
 
329
329
  box = bounding_box
@@ -358,14 +358,16 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
358
358
  page_x = layout_metrics["layoutViewport"]["pageX"]
359
359
  page_y = layout_metrics["layoutViewport"]["pageY"]
360
360
 
361
- clip = {
362
- x: page_x + box.x,
363
- y: page_y + box.y,
364
- width: box.width,
365
- height: box.height,
366
- }
361
+ if clip.nil?
362
+ clip = {
363
+ x: page_x + box.x,
364
+ y: page_y + box.y,
365
+ width: box.width,
366
+ height: box.height,
367
+ }
368
+ end
367
369
 
368
- @page.screenshot({ clip: clip }.merge(options))
370
+ @page.screenshot(type: type, path: path, full_page: full_page, clip: clip, quality: quality, omit_background: omit_background, encoding: encoding)
369
371
  ensure
370
372
  if needs_viewport_reset
371
373
  @page.viewport = viewport
@@ -0,0 +1,28 @@
1
+ class Puppeteer::ExecutablePathFinder
2
+ # @param executable_names [Array<String>] executable file names to find.
3
+ def initialize(*executable_names)
4
+ @executable_names = executable_names
5
+ end
6
+
7
+ def find_executables_in_path
8
+ Enumerator.new do |result|
9
+ @executable_names.each do |name|
10
+ # Find the first existing path.
11
+ paths.each do |path|
12
+ candidate = File.join(path, name)
13
+ next unless File.exist?(candidate)
14
+ result << candidate
15
+ break
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def find_first
22
+ find_executables_in_path.first
23
+ end
24
+
25
+ private def paths
26
+ ENV['PATH'].split(File::PATH_SEPARATOR)
27
+ end
28
+ end
@@ -1,25 +1,47 @@
1
1
  class Puppeteer::Frame
2
2
  using Puppeteer::DefineAsyncMethod
3
3
 
4
- # @param {!FrameManager} frameManager
5
- # @param {!Puppeteer.CDPSession} client
6
- # @param {?Frame} parentFrame
7
- # @param {string} frameId
8
- def initialize(frame_manager, client, parent_frame, frame_id)
4
+ # @param frame_manager [Puppeteer::FrameManager]
5
+ # @param parent_frame [Puppeteer::Frame|nil]
6
+ # @param frame_id [String]
7
+ # @param client [Puppeteer::CDPSession]
8
+ def initialize(frame_manager, parent_frame, frame_id, client)
9
9
  @frame_manager = frame_manager
10
- @client = client
11
10
  @parent_frame = parent_frame
12
11
  @id = frame_id
13
12
  @detached = false
14
13
 
15
14
  @loader_id = ''
16
15
  @lifecycle_events = Set.new
17
- @main_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
18
- @secondary_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
19
16
  @child_frames = Set.new
20
17
  if parent_frame
21
18
  parent_frame._child_frames << self
22
19
  end
20
+
21
+ update_client(client)
22
+ end
23
+
24
+ def inspect
25
+ values = %i[id parent_frame detached loader_id lifecycle_events child_frames].map do |sym|
26
+ value = instance_variable_get(:"@#{sym}")
27
+ "@#{sym}=#{value}"
28
+ end
29
+ "#<Puppeteer::Frame #{values.join(' ')}>"
30
+ end
31
+
32
+ def _client
33
+ @client
34
+ end
35
+
36
+ # @param client [Puppeteer::CDPSession]
37
+ private def update_client(client)
38
+ @client = client
39
+ @main_world = Puppeteer::DOMWorld.new(@client, @frame_manager, self, @frame_manager.timeout_settings)
40
+ @secondary_world = Puppeteer::DOMWorld.new(@client, @frame_manager, self, @frame_manager.timeout_settings)
41
+ end
42
+
43
+ def oop_frame?
44
+ @client != @frame_manager.client
23
45
  end
24
46
 
25
47
  attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :secondary_world
@@ -27,50 +27,67 @@ class Puppeteer::FrameManager
27
27
  # @type {!Set<string>}
28
28
  @isolated_worlds = Set.new
29
29
 
30
- @client.on_event('Page.frameAttached') do |event|
31
- handle_frame_attached(event['frameId'], event['parentFrameId'])
30
+ setup_listeners(@client)
31
+ end
32
+
33
+ private def setup_listeners(client)
34
+ client.on_event('Page.frameAttached') do |event|
35
+ handle_frame_attached(client, event['frameId'], event['parentFrameId'])
32
36
  end
33
- @client.on_event('Page.frameNavigated') do |event|
37
+ client.on_event('Page.frameNavigated') do |event|
34
38
  handle_frame_navigated(event['frame'])
35
39
  end
36
- @client.on_event('Page.navigatedWithinDocument') do |event|
40
+ client.on_event('Page.navigatedWithinDocument') do |event|
37
41
  handle_frame_navigated_within_document(event['frameId'], event['url'])
38
42
  end
39
- @client.on_event('Page.frameDetached') do |event|
40
- handle_frame_detached(event['frameId'])
43
+ client.on_event('Page.frameDetached') do |event|
44
+ handle_frame_detached(event['frameId'], event['reason'])
41
45
  end
42
- @client.on_event('Page.frameStoppedLoading') do |event|
46
+ client.on_event('Page.frameStoppedLoading') do |event|
43
47
  handle_frame_stopped_loading(event['frameId'])
44
48
  end
45
- @client.on_event('Runtime.executionContextCreated') do |event|
46
- handle_execution_context_created(event['context'])
49
+ client.on_event('Runtime.executionContextCreated') do |event|
50
+ handle_execution_context_created(event['context'], client)
47
51
  end
48
- @client.on_event('Runtime.executionContextDestroyed') do |event|
49
- handle_execution_context_destroyed(event['executionContextId'])
52
+ client.on_event('Runtime.executionContextDestroyed') do |event|
53
+ handle_execution_context_destroyed(event['executionContextId'], client)
50
54
  end
51
- @client.on_event('Runtime.executionContextsCleared') do |event|
52
- handle_execution_contexts_cleared
55
+ client.on_event('Runtime.executionContextsCleared') do |event|
56
+ handle_execution_contexts_cleared(client)
53
57
  end
54
- @client.on_event('Page.lifecycleEvent') do |event|
58
+ client.on_event('Page.lifecycleEvent') do |event|
55
59
  handle_lifecycle_event(event)
56
60
  end
61
+ client.on_event('Target.attachedToTarget') do |event|
62
+ handle_attached_to_target(event)
63
+ end
64
+ client.on_event('Target.detachedFromTarget') do |event|
65
+ handle_detached_from_target(event)
66
+ end
57
67
  end
58
68
 
59
69
  attr_reader :client, :timeout_settings
60
70
 
61
- private def init
71
+ private def init(cdp_session = nil)
72
+ client = cdp_session || @client
73
+
62
74
  results = await_all(
63
- @client.async_send_message('Page.enable'),
64
- @client.async_send_message('Page.getFrameTree'),
75
+ client.async_send_message('Page.enable'),
76
+ client.async_send_message('Page.getFrameTree'),
65
77
  )
66
78
  frame_tree = results.last['frameTree']
67
- handle_frame_tree(frame_tree)
79
+ handle_frame_tree(client, frame_tree)
68
80
  await_all(
69
- @client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
70
- @client.async_send_message('Runtime.enable'),
81
+ client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
82
+ client.async_send_message('Runtime.enable'),
71
83
  )
72
- ensure_isolated_world(UTILITY_WORLD_NAME)
73
- @network_manager.init
84
+ ensure_isolated_world(client, UTILITY_WORLD_NAME)
85
+ @network_manager.init unless cdp_session
86
+ rescue => err
87
+ # The target might have been closed before the initialization finished.
88
+ return if err.message.include?('Target closed') || err.message.include?('Session closed')
89
+
90
+ raise
74
91
  end
75
92
 
76
93
  define_async_method :async_init
@@ -154,6 +171,28 @@ class Puppeteer::FrameManager
154
171
  watcher.navigation_response
155
172
  end
156
173
 
174
+ # @param event [Hash]
175
+ def handle_attached_to_target(event)
176
+ return if event['targetInfo']['type'] != 'iframe'
177
+
178
+ frame = @frames[event['targetInfo']['targetId']]
179
+ session = Puppeteer::Connection.from_session(@client).session(event['sessionId'])
180
+
181
+ frame.send(:update_client, session)
182
+ setup_listeners(session)
183
+ async_init(session)
184
+ end
185
+
186
+ # @param event [Hash]
187
+ def handle_detached_from_target(event)
188
+ frame = @frames[event['targetId']]
189
+ if frame && frame.oop_frame?
190
+ # When an OOP iframe is removed from the page, it
191
+ # will only get a Target.detachedFromTarget event.
192
+ remove_frame_recursively(frame)
193
+ end
194
+ end
195
+
157
196
  # @param event [Hash]
158
197
  def handle_lifecycle_event(event)
159
198
  frame = @frames[event['frameId']]
@@ -170,16 +209,17 @@ class Puppeteer::FrameManager
170
209
  emit_event(FrameManagerEmittedEvents::LifecycleEvent, frame)
171
210
  end
172
211
 
212
+ # @param session [Puppeteer::CDPSession]
173
213
  # @param frame_tree [Hash]
174
- def handle_frame_tree(frame_tree)
214
+ def handle_frame_tree(session, frame_tree)
175
215
  if frame_tree['frame']['parentId']
176
- handle_frame_attached(frame_tree['frame']['id'], frame_tree['frame']['parentId'])
216
+ handle_frame_attached(session, frame_tree['frame']['id'], frame_tree['frame']['parentId'])
177
217
  end
178
218
  handle_frame_navigated(frame_tree['frame'])
179
219
  return if !frame_tree['childFrames']
180
220
 
181
221
  frame_tree['childFrames'].each do |child|
182
- handle_frame_tree(child)
222
+ handle_frame_tree(session, child)
183
223
  end
184
224
  end
185
225
 
@@ -204,15 +244,25 @@ class Puppeteer::FrameManager
204
244
  @frames[frame_id]
205
245
  end
206
246
 
207
- # @param {string} frameId
208
- # @param {?string} parentFrameId
209
- def handle_frame_attached(frame_id, parent_frame_id)
210
- return if @frames.has_key?(frame_id)
247
+ # @param session [Puppeteer::CDPSession]
248
+ # @param frameId [String]
249
+ # @param parentFrameId [String|nil]
250
+ def handle_frame_attached(session, frame_id, parent_frame_id)
251
+ if @frames.has_key?(frame_id)
252
+ frame = @frames[frame_id]
253
+ if session && frame.oop_frame?
254
+ # If an OOP iframes becomes a normal iframe again
255
+ # it is first attached to the parent page before
256
+ # the target is removed.
257
+ frame.send(:update_client, session)
258
+ end
259
+ return
260
+ end
211
261
  if !parent_frame_id
212
262
  raise ArgymentError.new('parent_frame_id must not be nil')
213
263
  end
214
264
  parent_frame = @frames[parent_frame_id]
215
- frame = Puppeteer::Frame.new(self, @client, parent_frame, frame_id)
265
+ frame = Puppeteer::Frame.new(self, parent_frame, frame_id, session)
216
266
  @frames[frame_id] = frame
217
267
 
218
268
  emit_event(FrameManagerEmittedEvents::FrameAttached, frame)
@@ -247,7 +297,7 @@ class Puppeteer::FrameManager
247
297
  frame.id = frame_payload['id']
248
298
  else
249
299
  # Initial main frame navigation.
250
- frame = Puppeteer::Frame.new(self, @client, nil, frame_payload['id'])
300
+ frame = Puppeteer::Frame.new(self, nil, frame_payload['id'], @client)
251
301
  end
252
302
  @frames[frame_payload['id']] = frame
253
303
  @main_frame = frame
@@ -259,22 +309,26 @@ class Puppeteer::FrameManager
259
309
  emit_event(FrameManagerEmittedEvents::FrameNavigated, frame)
260
310
  end
261
311
 
312
+ # @param session [Puppeteer::CDPSession]
262
313
  # @param name [String]
263
- def ensure_isolated_world(name)
264
- return if @isolated_worlds.include?(name)
265
- @isolated_worlds << name
314
+ private def ensure_isolated_world(session, name)
315
+ key = "#{session.id}:#{name}"
316
+ return if @isolated_worlds.include?(key)
317
+ @isolated_worlds << key
266
318
 
267
- @client.send_message('Page.addScriptToEvaluateOnNewDocument',
319
+ session.send_message('Page.addScriptToEvaluateOnNewDocument',
268
320
  source: "//# sourceURL=#{Puppeteer::ExecutionContext::EVALUATION_SCRIPT_URL}",
269
321
  worldName: name,
270
322
  )
271
- create_isolated_worlds_promises = frames.map do |frame|
272
- @client.async_send_message('Page.createIsolatedWorld',
273
- frameId: frame.id,
274
- grantUniveralAccess: true,
275
- worldName: name,
276
- )
277
- end
323
+ create_isolated_worlds_promises = frames.
324
+ select { |frame| frame._client == session }.
325
+ map do |frame|
326
+ session.async_send_message('Page.createIsolatedWorld',
327
+ frameId: frame.id,
328
+ grantUniveralAccess: true,
329
+ worldName: name,
330
+ )
331
+ end
278
332
  await_all(*create_isolated_worlds_promises)
279
333
  end
280
334
 
@@ -289,19 +343,31 @@ class Puppeteer::FrameManager
289
343
  end
290
344
 
291
345
  # @param frame_id [String]
292
- def handle_frame_detached(frame_id)
346
+ # @param reason [String]
347
+ def handle_frame_detached(frame_id, reason)
293
348
  frame = @frames[frame_id]
294
- if frame
295
- remove_frame_recursively(frame)
349
+ if reason == 'remove'
350
+ # Only remove the frame if the reason for the detached event is
351
+ # an actual removement of the frame.
352
+ # For frames that become OOP iframes, the reason would be 'swap'.
353
+ if frame
354
+ remove_frame_recursively(frame)
355
+ end
296
356
  end
297
357
  end
298
358
 
299
359
  # @param context_payload [Hash]
300
- def handle_execution_context_created(context_payload)
360
+ # @pram session [Puppeteer::CDPSession]
361
+ def handle_execution_context_created(context_payload, session)
301
362
  frame = if_present(context_payload.dig('auxData', 'frameId')) { |frame_id| @frames[frame_id] }
302
363
 
303
364
  world = nil
304
365
  if frame
366
+ # commented out the original implementation for allowing us to use Frame#evaluate on OOP iframe.
367
+ #
368
+ # # Only care about execution contexts created for the current session.
369
+ # return if @client != session
370
+
305
371
  if context_payload.dig('auxData', 'isDefault')
306
372
  world = frame.main_world
307
373
  elsif context_payload['name'] == UTILITY_WORLD_NAME && !frame.secondary_world.has_context?
@@ -316,34 +382,45 @@ class Puppeteer::FrameManager
316
382
  @isolated_worlds << context_payload['name']
317
383
  end
318
384
 
319
- context = Puppeteer::ExecutionContext.new(@client, context_payload, world)
385
+ context = Puppeteer::ExecutionContext.new(frame&._client || @client, context_payload, world)
320
386
  if world
321
387
  world.context = context
322
388
  end
323
- @context_id_to_context[context_payload['id']] = context
389
+ key = "#{session.id}:#{context_payload['id']}"
390
+ @context_id_to_context[key] = context
324
391
  end
325
392
 
326
- # @param {number} executionContextId
327
- def handle_execution_context_destroyed(execution_context_id)
328
- context = @context_id_to_context[execution_context_id]
393
+ # @param execution_context_id [Integer]
394
+ # @param session [Puppeteer::CDPSEssion]
395
+ def handle_execution_context_destroyed(execution_context_id, session)
396
+ key = "#{session.id}:#{execution_context_id}"
397
+ context = @context_id_to_context[key]
329
398
  return unless context
330
- @context_id_to_context.delete(execution_context_id)
399
+ @context_id_to_context.delete(key)
331
400
  if context.world
332
401
  context.world.delete_context(execution_context_id)
333
402
  end
334
403
  end
335
404
 
336
- def handle_execution_contexts_cleared
337
- @context_id_to_context.each do |execution_context_id, context|
338
- if context.world
339
- context.world.delete_context(execution_context_id)
405
+ # @param session [Puppeteer::CDPSession]
406
+ def handle_execution_contexts_cleared(session)
407
+ @context_id_to_context.select! do |execution_context_id, context|
408
+ # Make sure to only clear execution contexts that belong
409
+ # to the current session.
410
+ if context.client != session
411
+ true # keep
412
+ else
413
+ if context.world
414
+ context.world.delete_context(execution_context_id)
415
+ end
416
+ false # remove
340
417
  end
341
418
  end
342
- @context_id_to_context.clear
343
419
  end
344
420
 
345
- def execution_context_by_id(context_id)
346
- @context_id_to_context[context_id] or raise "INTERNAL ERROR: missing context with id = #{context_id}"
421
+ def execution_context_by_id(context_id, session)
422
+ key = "#{session.id}:#{context_id}"
423
+ @context_id_to_context[key] or raise "INTERNAL ERROR: missing context with id = #{context_id}"
347
424
  end
348
425
 
349
426
  # @param {!Frame} frame