puppeteer-ruby 0.51.0 → 0.52.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74f00095ae82e1e4502260b99ff766cc46c758ae5e762050a6e7c46b7249f469
4
- data.tar.gz: a197f92fc8b7ccbe2904c088d52e60409af119d94449a6a4e9f518601c4c5788
3
+ metadata.gz: dc3601ac8a64edb1a6054663944d5244eb0fcbc774b2d2f73529eef194031689
4
+ data.tar.gz: c364f8b73fc4dc6a591baca37e2e116d3147c5572f42dd026de77e36b0842851
5
5
  SHA512:
6
- metadata.gz: df595ba0c4996b100b1d8b22bb35e0e0ce9b6552d368c11fc25b9c8cf60c1a270b651f6f96e6c1b65b64d32550b71c402a059a0fd4d3a1393a394b65de472aef
7
- data.tar.gz: f82344aba53b730d5fdb7273ef29fdb07d0b8aadc831d680fd5611d55df980955cb5fd5cbdbe78d0de88c70d689f738952187193b4f006386a2170970f928dd2
6
+ metadata.gz: a4de65d9249823c8532150d9cbeace0670d5e0c35de747193bee4923edb751b3dcc7a46aa6ec5f58393e20fabe9933b12eaca0ac47ead1d91c5793fec8ca0e9b
7
+ data.tar.gz: 4d5d39fe7dac8d74a3f90478f3bae1bd7e9dcb14018518551ca4ca7d59f48ed1e9f9e7205c33956d82591b2985b4fecd5f1eb4f0af8efc03f4a1b3f22a0b2281
data/docs/api_coverage.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # API coverages
2
- - Puppeteer version: v24.37.0
2
+ - Puppeteer version: v24.42.0
3
3
  - puppeteer-ruby version: 0.51.0
4
4
 
5
5
  ## Puppeteer
@@ -25,8 +25,9 @@
25
25
  * ~~deleteCookie~~
26
26
  * ~~deleteMatchingCookies~~
27
27
  * disconnect
28
+ * extensions
28
29
  * ~~getWindowBounds~~
29
- * ~~installExtension~~
30
+ * installExtension => `#install_extension`
30
31
  * isConnected => `#connected?`
31
32
  * newPage => `#new_page`
32
33
  * pages
@@ -38,7 +39,7 @@
38
39
  * ~~setWindowBounds~~
39
40
  * target
40
41
  * targets
41
- * ~~uninstallExtension~~
42
+ * uninstallExtension => `#uninstall_extension`
42
43
  * userAgent => `#user_agent`
43
44
  * version
44
45
  * waitForTarget => `#wait_for_target`
@@ -126,7 +127,7 @@
126
127
  * $$eval => `#eval_on_selector_all`
127
128
  * $eval => `#eval_on_selector`
128
129
  * asLocator => `#as_locator`
129
- * ~~autofill~~
130
+ * autofill
130
131
  * ~~backendNodeId~~
131
132
  * boundingBox => `#bounding_box`
132
133
  * boxModel => `#box_model`
@@ -165,6 +166,12 @@
165
166
  * ~~once~~
166
167
  * ~~removeAllListeners~~
167
168
 
169
+ ## Extension
170
+
171
+ * pages
172
+ * triggerAction => `#trigger_action`
173
+ * workers
174
+
168
175
  ## ~~ExtensionTransport~~
169
176
 
170
177
  * ~~close~~
@@ -190,6 +197,7 @@
190
197
  * content
191
198
  * evaluate
192
199
  * evaluateHandle => `#evaluate_handle`
200
+ * extensionRealms => `#extension_realms`
193
201
  * focus
194
202
  * frameElement => `#frame_element`
195
203
  * goto
@@ -344,6 +352,7 @@
344
352
  * evaluateHandle => `#evaluate_handle`
345
353
  * evaluateOnNewDocument => `#evaluate_on_new_document`
346
354
  * exposeFunction => `#expose_function`
355
+ * extensionRealms => `#extension_realms`
347
356
  * focus
348
357
  * frames
349
358
  * ~~getDefaultNavigationTimeout~~
@@ -351,6 +360,7 @@
351
360
  * goBack => `#go_back`
352
361
  * goForward => `#go_forward`
353
362
  * goto
363
+ * ~~hasDevTools~~
354
364
  * hover
355
365
  * isClosed => `#closed?`
356
366
  * isDragInterceptionEnabled => `#drag_interception_enabled?`
@@ -387,6 +397,7 @@
387
397
  * tap
388
398
  * target
389
399
  * title
400
+ * triggerExtensionAction => `#trigger_extension_action`
390
401
  * type => `#type_text`
391
402
  * url
392
403
  * viewport
@@ -405,6 +416,13 @@
405
416
  ## ~~ProtocolError~~
406
417
 
407
418
 
419
+ ## ~~Realm~~
420
+
421
+ * ~~evaluate~~
422
+ * ~~evaluateHandle~~
423
+ * ~~extension~~
424
+ * ~~waitForFunction~~
425
+
408
426
  ## ~~ScreenRecorder~~
409
427
 
410
428
  * ~~stop~~
@@ -420,7 +438,7 @@
420
438
 
421
439
  ## Target
422
440
 
423
- * ~~asPage~~
441
+ * asPage => `#as_page`
424
442
  * browser
425
443
  * browserContext => `#browser_context`
426
444
  * createCDPSession => `#create_cdp_session`
@@ -451,6 +469,17 @@
451
469
  ## ~~UnsupportedOperation~~
452
470
 
453
471
 
472
+ ## ~~WebMCP~~
473
+
474
+ * ~~tools~~
475
+
476
+ ## ~~WebMCPTool~~
477
+
478
+ * ~~execute~~
479
+
480
+ ## ~~WebMCPToolCall~~
481
+
482
+
454
483
  ## WebWorker
455
484
 
456
485
  * close
@@ -14,6 +14,8 @@ class Puppeteer::Browser
14
14
  # @rbs ignore_https_errors: bool -- Ignore HTTPS errors
15
15
  # @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
16
16
  # @rbs network_enabled: bool -- Whether network events are enabled
17
+ # @rbs issues_enabled: bool -- Whether issues events are enabled
18
+ # @rbs block_list: Array[String]? -- URL block list patterns
17
19
  # @rbs process: Puppeteer::BrowserRunner::BrowserProcess? -- Browser process handle
18
20
  # @rbs close_callback: Proc -- Close callback
19
21
  # @rbs target_filter_callback: Proc? -- Target filter callback
@@ -25,6 +27,8 @@ class Puppeteer::Browser
25
27
  ignore_https_errors:,
26
28
  default_viewport:,
27
29
  network_enabled: true,
30
+ issues_enabled: true,
31
+ block_list: nil,
28
32
  process:,
29
33
  close_callback:,
30
34
  target_filter_callback:,
@@ -36,6 +40,8 @@ class Puppeteer::Browser
36
40
  ignore_https_errors: ignore_https_errors,
37
41
  default_viewport: default_viewport,
38
42
  network_enabled: network_enabled,
43
+ issues_enabled: issues_enabled,
44
+ block_list: block_list,
39
45
  process: process,
40
46
  close_callback: close_callback,
41
47
  target_filter_callback: target_filter_callback,
@@ -51,6 +57,8 @@ class Puppeteer::Browser
51
57
  # @rbs ignore_https_errors: bool -- Ignore HTTPS errors
52
58
  # @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
53
59
  # @rbs network_enabled: bool -- Whether network events are enabled
60
+ # @rbs issues_enabled: bool -- Whether issues events are enabled
61
+ # @rbs block_list: Array[String]? -- URL block list patterns
54
62
  # @rbs process: Puppeteer::BrowserRunner::BrowserProcess? -- Browser process handle
55
63
  # @rbs close_callback: Proc -- Close callback
56
64
  # @rbs target_filter_callback: Proc? -- Target filter callback
@@ -62,6 +70,8 @@ class Puppeteer::Browser
62
70
  ignore_https_errors:,
63
71
  default_viewport:,
64
72
  network_enabled: true,
73
+ issues_enabled: true,
74
+ block_list: nil,
65
75
  process:,
66
76
  close_callback:,
67
77
  target_filter_callback:,
@@ -73,6 +83,8 @@ class Puppeteer::Browser
73
83
  @ignore_https_errors = ignore_https_errors
74
84
  @default_viewport = default_viewport
75
85
  @network_enabled = network_enabled
86
+ @issues_enabled = issues_enabled
87
+ @block_list = block_list
76
88
  @process = process
77
89
  @connection = connection
78
90
  @close_callback = close_callback
@@ -89,7 +101,9 @@ class Puppeteer::Browser
89
101
  connection: connection,
90
102
  target_factory: method(:create_target),
91
103
  target_filter_callback: @target_filter_callback,
104
+ block_list: block_list,
92
105
  )
106
+ @extensions = {}
93
107
  end
94
108
 
95
109
  private def default_target_filter_callback(target_info)
@@ -163,6 +177,10 @@ class Puppeteer::Browser
163
177
  @target_manager
164
178
  end
165
179
 
180
+ private def connection
181
+ @connection
182
+ end
183
+
166
184
  # @rbs return: Puppeteer::BrowserContext -- New incognito browser context
167
185
  def create_incognito_browser_context
168
186
  result = @connection.send_message('Target.createBrowserContext')
@@ -376,6 +394,63 @@ class Puppeteer::Browser
376
394
  Version.fetch(@connection).user_agent
377
395
  end
378
396
 
397
+ # @rbs page_target_id: String -- Page target id
398
+ # @rbs return: String? -- DevTools target id for page
399
+ def _has_devtools_target(page_target_id)
400
+ result = @connection.send_message('Target.getDevToolsTarget', targetId: page_target_id)
401
+ result['targetId']
402
+ end
403
+
404
+ # @rbs path: String -- Extension path
405
+ # @rbs return: String -- Installed extension id
406
+ def install_extension(path)
407
+ result = @connection.send_message('Extensions.loadUnpacked', path: path)
408
+ extension_id = result['id']
409
+ @extensions.delete(extension_id)
410
+ extension_id
411
+ end
412
+
413
+ # @rbs extension_id: String -- Extension id
414
+ # @rbs return: void -- No return value
415
+ def uninstall_extension(extension_id)
416
+ @connection.send_message('Extensions.uninstall', id: extension_id)
417
+ @extensions.delete(extension_id)
418
+ end
419
+
420
+ # @rbs return: Hash[String, Puppeteer::Extension] -- Installed extensions
421
+ def extensions
422
+ response = @connection.send_message('Extensions.getExtensions')
423
+ extension_map = {}
424
+ response.fetch('extensions', []).each do |payload|
425
+ extension_id = payload['id']
426
+ existing_extension = @extensions[extension_id]
427
+ extension_map[extension_id] =
428
+ if existing_extension
429
+ existing_extension
430
+ else
431
+ Puppeteer::Extension.new(
432
+ id: extension_id,
433
+ version: payload['version'],
434
+ name: payload['name'],
435
+ path: payload['path'],
436
+ enabled: payload['enabled'],
437
+ browser: self,
438
+ )
439
+ end
440
+ end
441
+ @extensions = extension_map
442
+ end
443
+
444
+ # @rbs return: bool -- Whether issue events are enabled
445
+ def issues_enabled?
446
+ @issues_enabled
447
+ end
448
+
449
+ # @rbs return: Array[String]? -- URL block list patterns
450
+ def block_list
451
+ @block_list
452
+ end
453
+
379
454
  # @rbs return: void -- No return value
380
455
  def close
381
456
  @close_callback.call
@@ -28,6 +28,8 @@ class Puppeteer::BrowserConnector
28
28
  ignore_https_errors: @browser_options.ignore_https_errors?,
29
29
  default_viewport: @browser_options.default_viewport,
30
30
  network_enabled: @browser_options.network_enabled,
31
+ issues_enabled: @browser_options.issues_enabled,
32
+ block_list: @browser_options.block_list,
31
33
  process: nil,
32
34
  close_callback: -> { connection.send_message('Browser.close') },
33
35
  target_filter_callback: @browser_options.target_filter,
@@ -2,7 +2,7 @@ class Puppeteer::ChromeTargetManager
2
2
  include Puppeteer::DebugPrint
3
3
  include Puppeteer::EventCallbackable
4
4
 
5
- def initialize(connection:, target_factory:, target_filter_callback:)
5
+ def initialize(connection:, target_factory:, target_filter_callback:, block_list: nil)
6
6
  @discovered_targets_by_target_id = {}
7
7
  @attached_targets_by_target_id = {}
8
8
  @attached_targets_by_session_id = {}
@@ -13,8 +13,13 @@ class Puppeteer::ChromeTargetManager
13
13
  @connection = connection
14
14
  @target_filter_callback = target_filter_callback
15
15
  @target_factory = target_factory
16
+ @block_list = block_list
17
+ if @block_list && !@block_list.is_a?(Array)
18
+ raise ArgumentError.new('block_list must be an Array of URL patterns')
19
+ end
16
20
  @target_interceptors = {}
17
21
  @initialize_promise = Async::Promise.new
22
+ @initial_attach_done = false
18
23
 
19
24
  @connection_event_listeners = []
20
25
  @connection_event_listeners << @connection.add_event_listener(
@@ -52,7 +57,7 @@ class Puppeteer::ChromeTargetManager
52
57
 
53
58
  private def store_existing_targets_for_init
54
59
  @discovered_targets_by_target_id.each do |target_id, target_info|
55
- if @target_filter_callback.call(target_info) && target_info.type != 'browser'
60
+ if @target_filter_callback.call(target_info) && target_info.type != 'browser' && url_allowed?(target_info.url)
56
61
  @target_ids_for_init << target_id
57
62
  end
58
63
  end
@@ -64,6 +69,7 @@ class Puppeteer::ChromeTargetManager
64
69
  flatten: true,
65
70
  autoAttach: true,
66
71
  })
72
+ @initial_attach_done = true
67
73
  finish_initialization_if_ready
68
74
  @initialize_promise.wait
69
75
  end
@@ -204,6 +210,12 @@ class Puppeteer::ChromeTargetManager
204
210
 
205
211
  return unless @connection.auto_attached?(target_info.target_id)
206
212
 
213
+ if !@initial_attach_done && !url_allowed?(target_info.url)
214
+ finish_initialization_if_ready(target_info.target_id)
215
+ silent_detach.call
216
+ return
217
+ end
218
+
207
219
  # Special case for service workers: being attached to service workers will
208
220
  # prevent them from ever being destroyed. Therefore, we silently detach
209
221
  # from service workers unless the connection was manually created via
@@ -270,6 +282,7 @@ class Puppeteer::ChromeTargetManager
270
282
  flatten: true,
271
283
  autoAttach: true,
272
284
  }))
285
+ maybe_setup_network_conditions(session)
273
286
  Puppeteer::AsyncUtils.await(session.async_send_message('Runtime.runIfWaitingForDebugger'))
274
287
  rescue => err
275
288
  Logger.new($stderr).warn(err)
@@ -292,4 +305,38 @@ class Puppeteer::ChromeTargetManager
292
305
  @attached_targets_by_target_id.delete(target.target_id)
293
306
  emit_event(TargetManagerEmittedEvents::TargetGone, target)
294
307
  end
308
+
309
+ private def url_allowed?(url)
310
+ return true if @block_list.nil? || @block_list.empty?
311
+ return true if url.nil? || url.empty? || url == 'about:blank'
312
+
313
+ @block_list.none? do |pattern|
314
+ File.fnmatch?(pattern, url, File::FNM_EXTGLOB)
315
+ rescue ArgumentError
316
+ false
317
+ end
318
+ end
319
+
320
+ private def maybe_setup_network_conditions(session)
321
+ return if @block_list.nil? || @block_list.empty?
322
+
323
+ matched_network_conditions = @block_list.map do |pattern|
324
+ {
325
+ urlPattern: pattern,
326
+ latency: 0,
327
+ downloadThroughput: -1,
328
+ uploadThroughput: -1,
329
+ }
330
+ end
331
+ Puppeteer::AsyncUtils.await(session.async_send_message('Network.enable'))
332
+ Puppeteer::AsyncUtils.await(session.async_send_message('Network.emulateNetworkConditionsByRule', {
333
+ matchedNetworkConditions: matched_network_conditions,
334
+ offline: true,
335
+ }))
336
+ rescue => err
337
+ message = err.message.to_s.downcase
338
+ return if message.include?('target closed') || message.include?('session closed') || message.include?('not found')
339
+
340
+ raise
341
+ end
295
342
  end
@@ -1,20 +1,16 @@
1
1
  require 'logger'
2
2
 
3
3
  module Puppeteer::DebugPrint
4
- if Puppeteer.env.debug?
5
- def debug_puts(*args, **kwargs)
6
- @__debug_logger ||= Logger.new($stdout)
7
- @__debug_logger.debug(*args, **kwargs)
8
- end
4
+ def debug_puts(*args, **kwargs)
5
+ return unless Puppeteer.env.debug?
9
6
 
10
- def debug_print(*args)
11
- print(*args)
12
- end
13
- else
14
- def debug_puts(*args, **kwargs)
15
- end
7
+ @__debug_logger ||= Logger.new($stdout)
8
+ @__debug_logger.debug(*args, **kwargs)
9
+ end
10
+
11
+ def debug_print(*args)
12
+ return unless Puppeteer.env.debug?
16
13
 
17
- def debug_print(*args)
18
- end
14
+ print(*args)
19
15
  end
20
16
  end
@@ -540,6 +540,22 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
540
540
  end
541
541
  end
542
542
 
543
+ # @rbs credit_card: Hash[Symbol | String, untyped]? -- Credit card autofill payload
544
+ # @rbs address: Hash[Symbol | String, untyped]? -- Address autofill payload
545
+ # @rbs return: void -- No return value
546
+ def autofill(credit_card: nil, address: nil)
547
+ node_info = @remote_object.node_info(@client)
548
+ field_id = node_info.dig('node', 'backendNodeId')
549
+ @client.send_message('Autofill.trigger', {
550
+ fieldId: field_id,
551
+ frameId: @frame.id,
552
+ card: credit_card,
553
+ address: address,
554
+ }.compact)
555
+ end
556
+
557
+ define_async_method :async_autofill
558
+
543
559
  # @rbs block: Proc? -- Optional block for Object#tap usage
544
560
  # @rbs return: Puppeteer::ElementHandle | nil -- Element handle or nil
545
561
  def tap(&block)
@@ -135,6 +135,9 @@ module PageEmittedEvents ; end
135
135
  # Emitted when a frame is navigated to a new URL. Will contain a {@link Frame}.
136
136
  FrameNavigated: 'framenavigated',
137
137
 
138
+ # Emitted when a DevTools issue is reported.
139
+ Issue: 'issue',
140
+
138
141
  # Emitted when the JavaScript
139
142
  # {https://developer.mozilla.org/en-US/docs/Web/Events/load | load} event is dispatched.
140
143
  Load: 'load',
@@ -52,13 +52,18 @@ class Puppeteer::ExecutionContext
52
52
  # @param context_id [String]
53
53
  # @return [Object|JSHandle]
54
54
  def evaluate_with(client:, context_id:)
55
- result = client.send_message('Runtime.evaluate',
56
- expression: expression_with_source_url,
57
- contextId: context_id,
58
- returnByValue: @return_by_value,
59
- awaitPromise: true,
60
- userGesture: true,
61
- )
55
+ result =
56
+ begin
57
+ client.send_message('Runtime.evaluate',
58
+ expression: expression_with_source_url,
59
+ contextId: context_id,
60
+ returnByValue: @return_by_value,
61
+ awaitPromise: true,
62
+ userGesture: true,
63
+ )
64
+ rescue Puppeteer::Connection::ProtocolError => err
65
+ raise @execution_context.send(:rewrite_evaluation_error, err)
66
+ end
62
67
  # }).catch(rewriteError);
63
68
 
64
69
  exception_details = result['exceptionDetails']
@@ -110,14 +115,19 @@ class Puppeteer::ExecutionContext
110
115
  # Original puppeteer implementation take it into consideration.
111
116
  # But we don't support the syntax here.
112
117
 
113
- result = client.send_message('Runtime.callFunctionOn',
114
- functionDeclaration: function_declaration,
115
- executionContextId: context_id,
116
- arguments: converted_args,
117
- returnByValue: @return_by_value,
118
- awaitPromise: true,
119
- userGesture: true,
120
- ) # .catch(rewriteError);
118
+ result =
119
+ begin
120
+ client.send_message('Runtime.callFunctionOn',
121
+ functionDeclaration: function_declaration,
122
+ executionContextId: context_id,
123
+ arguments: converted_args,
124
+ returnByValue: @return_by_value,
125
+ awaitPromise: true,
126
+ userGesture: true,
127
+ )
128
+ rescue Puppeteer::Connection::ProtocolError => err
129
+ raise @execution_context.send(:rewrite_evaluation_error, err)
130
+ end # .catch(rewriteError);
121
131
 
122
132
  exception_details = result['exceptionDetails']
123
133
  if exception_details
@@ -337,6 +347,18 @@ class Puppeteer::ExecutionContext
337
347
  )
338
348
  end
339
349
 
350
+ # @param error [Puppeteer::Connection::ProtocolError]
351
+ # @return [StandardError]
352
+ private def rewrite_evaluation_error(error)
353
+ message = error.message
354
+ if message.end_with?('Cannot find context with specified id') ||
355
+ message.end_with?('Inspected target navigated or closed')
356
+ return Puppeteer::Error.new('Execution context was destroyed, most likely because of a navigation.')
357
+ end
358
+
359
+ error
360
+ end
361
+
340
362
  # @rbs page_function: String -- JavaScript code to check
341
363
  # @rbs return: bool -- Whether the code represents a function
342
364
  private def function_string?(page_function)
@@ -0,0 +1,70 @@
1
+ # rbs_inline: enabled
2
+
3
+ class Puppeteer::Extension
4
+ # @rbs id: String -- Extension id
5
+ # @rbs version: String -- Extension version
6
+ # @rbs name: String -- Extension name
7
+ # @rbs path: String -- Extension path
8
+ # @rbs enabled: bool -- Whether extension is enabled
9
+ # @rbs browser: Puppeteer::Browser -- Browser instance
10
+ # @rbs return: void -- No return value
11
+ def initialize(id:, version:, name:, path:, enabled:, browser:)
12
+ @id = id
13
+ @version = version
14
+ @name = name
15
+ @path = path
16
+ @enabled = enabled
17
+ @browser = browser
18
+ end
19
+
20
+ # @rbs return: String -- Extension id
21
+ attr_reader :id
22
+
23
+ # @rbs return: String -- Extension version
24
+ attr_reader :version
25
+
26
+ # @rbs return: String -- Extension name
27
+ attr_reader :name
28
+
29
+ # @rbs return: String -- Extension path
30
+ attr_reader :path
31
+
32
+ # @rbs return: bool -- Whether extension is enabled
33
+ attr_reader :enabled
34
+
35
+ # @rbs return: Array[Puppeteer::CdpWebWorker] -- Extension workers
36
+ def workers
37
+ extension_prefix = "chrome-extension://#{@id}"
38
+ extension_targets = @browser.targets.select do |target|
39
+ target.type == 'service_worker' && target.url.start_with?(extension_prefix)
40
+ end
41
+ extension_targets.filter_map do |target|
42
+ target.worker
43
+ rescue
44
+ nil
45
+ end
46
+ end
47
+
48
+ # @rbs return: Array[Puppeteer::Page] -- Extension pages
49
+ def pages
50
+ extension_prefix = "chrome-extension://#{@id}"
51
+ extension_targets = @browser.targets.select do |target|
52
+ target_url = target.url
53
+ ['page', 'background_page'].include?(target.type) && target_url.start_with?(extension_prefix)
54
+ end
55
+ extension_targets.filter_map do |target|
56
+ target.as_page
57
+ rescue
58
+ nil
59
+ end
60
+ end
61
+
62
+ # @rbs page: Puppeteer::Page -- Target page
63
+ # @rbs return: void -- No return value
64
+ def trigger_action(page)
65
+ page.browser.send(:connection).send_message('Extensions.triggerAction', {
66
+ id: @id,
67
+ targetId: page._tab_id,
68
+ })
69
+ end
70
+ end
@@ -19,6 +19,7 @@ class Puppeteer::Frame
19
19
  @url = 'about:blank'
20
20
  @lifecycle_events = Set.new
21
21
  @child_frames = Set.new
22
+ @extension_worlds = {}
22
23
  if parent_frame
23
24
  parent_frame._child_frames << self
24
25
  end
@@ -71,7 +72,7 @@ class Puppeteer::Frame
71
72
  @client != @frame_manager.client
72
73
  end
73
74
 
74
- attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :puppeteer_world
75
+ attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :puppeteer_world, :extension_worlds
75
76
  attr_reader :client
76
77
 
77
78
  # @rbs other: Object -- Other object to compare
@@ -229,6 +230,11 @@ class Puppeteer::Frame
229
230
  @url
230
231
  end
231
232
 
233
+ # @rbs return: Array[untyped] -- Extension execution realms for this frame
234
+ def extension_realms
235
+ @extension_worlds.values
236
+ end
237
+
232
238
  # @rbs return: Puppeteer::Frame? -- Parent frame
233
239
  def parent_frame
234
240
  @parent_frame
@@ -425,6 +431,7 @@ class Puppeteer::Frame
425
431
  @detached = true
426
432
  @main_world.detach
427
433
  @puppeteer_world.detach
434
+ @extension_worlds.each_value(&:detach)
428
435
  if @parent_frame
429
436
  @parent_frame._child_frames.delete(self)
430
437
  end