puppeteer-ruby 0.51.0 → 0.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/api_coverage.md +34 -5
- data/lib/puppeteer/browser.rb +75 -0
- data/lib/puppeteer/browser_connector.rb +43 -6
- data/lib/puppeteer/chrome_target_manager.rb +49 -2
- data/lib/puppeteer/chrome_user_data_dir.rb +89 -0
- data/lib/puppeteer/debug_print.rb +9 -13
- data/lib/puppeteer/element_handle.rb +16 -0
- data/lib/puppeteer/events.rb +3 -0
- data/lib/puppeteer/execution_context.rb +37 -15
- data/lib/puppeteer/extension.rb +70 -0
- data/lib/puppeteer/frame.rb +8 -1
- data/lib/puppeteer/frame_manager.rb +62 -2
- data/lib/puppeteer/isolated_world.rb +22 -1
- data/lib/puppeteer/issue.rb +16 -0
- data/lib/puppeteer/js_coverage.rb +14 -1
- data/lib/puppeteer/launcher/browser_options.rb +6 -1
- data/lib/puppeteer/launcher/chrome.rb +16 -1
- data/lib/puppeteer/launcher/chrome_arg_options.rb +2 -1
- data/lib/puppeteer/network_manager.rb +16 -16
- data/lib/puppeteer/page.rb +36 -4
- data/lib/puppeteer/puppeteer.rb +22 -2
- data/lib/puppeteer/remote_object.rb +2 -1
- data/lib/puppeteer/target.rb +17 -0
- data/lib/puppeteer/version.rb +2 -2
- data/lib/puppeteer.rb +2 -0
- data/sig/_supplementary.rbs +5 -0
- data/sig/puppeteer/browser.rbs +29 -2
- data/sig/puppeteer/chrome_user_data_dir.rbs +30 -0
- data/sig/puppeteer/element_handle.rbs +5 -0
- data/sig/puppeteer/execution_context.rbs +4 -0
- data/sig/puppeteer/extension.rbs +37 -0
- data/sig/puppeteer/frame.rbs +5 -0
- data/sig/puppeteer/issue.rbs +13 -0
- data/sig/puppeteer/page.rbs +14 -0
- data/sig/puppeteer/puppeteer.rbs +8 -2
- data/sig/puppeteer/remote_object.rbs +2 -0
- metadata +8 -2
|
@@ -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
|
data/lib/puppeteer/frame.rb
CHANGED
|
@@ -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
|
|
@@ -5,6 +5,7 @@ class Puppeteer::FrameManager
|
|
|
5
5
|
using Puppeteer::DefineAsyncMethod
|
|
6
6
|
|
|
7
7
|
UTILITY_WORLD_NAME = '__puppeteer_utility_world__'
|
|
8
|
+
CHROME_EXTENSION_PREFIX = 'chrome-extension://'
|
|
8
9
|
|
|
9
10
|
# @param {!Puppeteer.CDPSession} client
|
|
10
11
|
# @param {!Puppeteer.Page} page
|
|
@@ -89,6 +90,9 @@ class Puppeteer::FrameManager
|
|
|
89
90
|
handle_lifecycle_event(event)
|
|
90
91
|
end
|
|
91
92
|
end
|
|
93
|
+
client.on_event('Audits.issueAdded') do |event|
|
|
94
|
+
@page.emit_event(PageEmittedEvents::Issue, Puppeteer::Issue.new(event['issue']))
|
|
95
|
+
end
|
|
92
96
|
end
|
|
93
97
|
|
|
94
98
|
attr_reader :client, :timeout_settings
|
|
@@ -109,7 +113,9 @@ class Puppeteer::FrameManager
|
|
|
109
113
|
Puppeteer::AsyncUtils.await_promise_all(
|
|
110
114
|
client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
|
|
111
115
|
client.async_send_message('Runtime.enable'),
|
|
116
|
+
@page.browser.issues_enabled? ? client.async_send_message('Audits.enable') : nil,
|
|
112
117
|
)
|
|
118
|
+
maybe_setup_block_list(client)
|
|
113
119
|
ensure_isolated_world(client, UTILITY_WORLD_NAME)
|
|
114
120
|
@network_manager.init unless cdp_session
|
|
115
121
|
rescue => err
|
|
@@ -224,7 +230,11 @@ class Puppeteer::FrameManager
|
|
|
224
230
|
session = target.session
|
|
225
231
|
frame&.send(:update_client, session)
|
|
226
232
|
setup_listeners(session)
|
|
227
|
-
|
|
233
|
+
Async do
|
|
234
|
+
async_init(target.target_info.target_id, session).wait
|
|
235
|
+
rescue => err
|
|
236
|
+
debug_puts(err)
|
|
237
|
+
end
|
|
228
238
|
end
|
|
229
239
|
|
|
230
240
|
# @param event [Hash]
|
|
@@ -493,6 +503,7 @@ class Puppeteer::FrameManager
|
|
|
493
503
|
# @pram session [Puppeteer::CDPSession]
|
|
494
504
|
def handle_execution_context_created(context_payload, session)
|
|
495
505
|
frame = if_present(context_payload.dig('auxData', 'frameId')) { |frame_id| @frames[frame_id] }
|
|
506
|
+
origin = context_payload['origin']
|
|
496
507
|
|
|
497
508
|
world = nil
|
|
498
509
|
if frame
|
|
@@ -508,6 +519,17 @@ class Puppeteer::FrameManager
|
|
|
508
519
|
# connections so we might end up creating multiple isolated worlds.
|
|
509
520
|
# We can use either.
|
|
510
521
|
world = frame.puppeteer_world
|
|
522
|
+
elsif extension_origin?(origin)
|
|
523
|
+
extension_id = extract_extension_id(origin)
|
|
524
|
+
if extension_id
|
|
525
|
+
world = frame.extension_worlds[extension_id]
|
|
526
|
+
unless world
|
|
527
|
+
world = Puppeteer::IsolaatedWorld.new(frame._client || @client, self, frame, @timeout_settings)
|
|
528
|
+
frame.extension_worlds[extension_id] = world
|
|
529
|
+
end
|
|
530
|
+
world.origin = origin
|
|
531
|
+
world.world_id = extension_id
|
|
532
|
+
end
|
|
511
533
|
end
|
|
512
534
|
end
|
|
513
535
|
|
|
@@ -523,6 +545,19 @@ class Puppeteer::FrameManager
|
|
|
523
545
|
@context_id_to_context[key] = context
|
|
524
546
|
end
|
|
525
547
|
|
|
548
|
+
private def extension_origin?(origin)
|
|
549
|
+
origin.is_a?(String) && origin.start_with?(CHROME_EXTENSION_PREFIX)
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
private def extract_extension_id(origin)
|
|
553
|
+
return nil unless extension_origin?(origin)
|
|
554
|
+
|
|
555
|
+
path_part = origin[CHROME_EXTENSION_PREFIX.length..]
|
|
556
|
+
return nil unless path_part
|
|
557
|
+
slash_index = path_part.index('/')
|
|
558
|
+
slash_index ? path_part[0...slash_index] : path_part
|
|
559
|
+
end
|
|
560
|
+
|
|
526
561
|
# @param execution_context_id [Integer]
|
|
527
562
|
# @param session [Puppeteer::CDPSEssion]
|
|
528
563
|
def handle_execution_context_destroyed(execution_context_id, session)
|
|
@@ -537,7 +572,7 @@ class Puppeteer::FrameManager
|
|
|
537
572
|
def handle_execution_contexts_cleared(session)
|
|
538
573
|
session_id = session.id
|
|
539
574
|
@context_id_to_context.select! do |execution_context_id, context|
|
|
540
|
-
key_session_id,
|
|
575
|
+
key_session_id, _context_id = execution_context_id.split(':', 2)
|
|
541
576
|
# Make sure to only clear execution contexts that belong
|
|
542
577
|
# to the current session.
|
|
543
578
|
if key_session_id != session_id
|
|
@@ -554,6 +589,31 @@ class Puppeteer::FrameManager
|
|
|
554
589
|
@context_id_to_context[key] or raise "INTERNAL ERROR: missing context with id = #{context_id}"
|
|
555
590
|
end
|
|
556
591
|
|
|
592
|
+
private def maybe_setup_block_list(client)
|
|
593
|
+
block_list = @page.browser.block_list
|
|
594
|
+
return if block_list.nil? || block_list.empty?
|
|
595
|
+
|
|
596
|
+
client.send_message('Network.enable')
|
|
597
|
+
matched_network_conditions = block_list.map do |pattern|
|
|
598
|
+
{
|
|
599
|
+
urlPattern: pattern,
|
|
600
|
+
latency: 0,
|
|
601
|
+
downloadThroughput: -1,
|
|
602
|
+
uploadThroughput: -1,
|
|
603
|
+
}
|
|
604
|
+
end
|
|
605
|
+
client.send_message('Network.emulateNetworkConditionsByRule', {
|
|
606
|
+
matchedNetworkConditions: matched_network_conditions,
|
|
607
|
+
offline: true,
|
|
608
|
+
})
|
|
609
|
+
rescue Puppeteer::Connection::ProtocolError => err
|
|
610
|
+
if err.message.include?('Method not available') || err.message.include?("wasn't found")
|
|
611
|
+
client.send_message('Network.setBlockedURLs', urls: block_list)
|
|
612
|
+
else
|
|
613
|
+
raise
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
557
617
|
# @param {!Frame} frame
|
|
558
618
|
private def remove_frame_recursively(frame)
|
|
559
619
|
frame.child_frames.each do |child|
|
|
@@ -62,11 +62,13 @@ class Puppeteer::IsolaatedWorld
|
|
|
62
62
|
@ctx_bindings = Set.new
|
|
63
63
|
@detached = false
|
|
64
64
|
@context = nil
|
|
65
|
+
@origin = nil
|
|
66
|
+
@world_id = nil
|
|
65
67
|
|
|
66
68
|
@client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
|
|
67
69
|
end
|
|
68
70
|
|
|
69
|
-
attr_reader :frame, :task_manager
|
|
71
|
+
attr_reader :frame, :task_manager, :origin, :world_id
|
|
70
72
|
|
|
71
73
|
# only used in Puppeteer::WaitTask#initialize
|
|
72
74
|
private def _bound_functions
|
|
@@ -109,6 +111,25 @@ class Puppeteer::IsolaatedWorld
|
|
|
109
111
|
@task_manager.terminate_all(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
|
|
110
112
|
end
|
|
111
113
|
|
|
114
|
+
# @rbs origin: String -- Origin for this realm
|
|
115
|
+
# @rbs return: String -- Origin
|
|
116
|
+
def origin=(origin)
|
|
117
|
+
@origin = origin
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @rbs world_id: String -- World id for this realm
|
|
121
|
+
# @rbs return: String -- World id
|
|
122
|
+
def world_id=(world_id)
|
|
123
|
+
@world_id = world_id
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @rbs return: Puppeteer::Extension? -- Owning extension for this realm
|
|
127
|
+
def extension
|
|
128
|
+
return nil unless @world_id.is_a?(String)
|
|
129
|
+
|
|
130
|
+
frame.page.browser.extensions[@world_id]
|
|
131
|
+
end
|
|
132
|
+
|
|
112
133
|
def detached?
|
|
113
134
|
@detached
|
|
114
135
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
class Puppeteer::Issue
|
|
4
|
+
# @rbs issue: Hash[String, untyped] -- CDP issue payload
|
|
5
|
+
# @rbs return: void -- No return value
|
|
6
|
+
def initialize(issue)
|
|
7
|
+
@code = issue['code']
|
|
8
|
+
@details = issue['details']
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @rbs return: String -- Issue code
|
|
12
|
+
attr_reader :code
|
|
13
|
+
|
|
14
|
+
# @rbs return: Hash[String, untyped] -- Issue details payload
|
|
15
|
+
attr_reader :details
|
|
16
|
+
end
|
|
@@ -26,6 +26,7 @@ class Puppeteer::JSCoverage
|
|
|
26
26
|
@enabled = false
|
|
27
27
|
@script_urls = {}
|
|
28
28
|
@script_sources = {}
|
|
29
|
+
@script_parsed_tasks = []
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def start(
|
|
@@ -52,9 +53,10 @@ class Puppeteer::JSCoverage
|
|
|
52
53
|
@enabled = true
|
|
53
54
|
@script_urls.clear
|
|
54
55
|
@script_sources.clear
|
|
56
|
+
@script_parsed_tasks.clear
|
|
55
57
|
@event_listeners = []
|
|
56
58
|
@event_listeners << @client.add_event_listener('Debugger.scriptParsed') do |event|
|
|
57
|
-
Async do
|
|
59
|
+
@script_parsed_tasks << Async do
|
|
58
60
|
Puppeteer::AsyncUtils.future_with_logging { on_script_parsed(event) }.call
|
|
59
61
|
end
|
|
60
62
|
end
|
|
@@ -95,11 +97,22 @@ class Puppeteer::JSCoverage
|
|
|
95
97
|
response = @client.send_message('Debugger.getScriptSource', scriptId: event['scriptId'])
|
|
96
98
|
@script_urls[event['scriptId']] = url
|
|
97
99
|
@script_sources[event['scriptId']] = response['scriptSource']
|
|
100
|
+
rescue Puppeteer::Connection::ProtocolError
|
|
101
|
+
# The page can navigate while we are fetching sources for coverage.
|
|
102
|
+
# This matches upstream behavior that ignores these transient failures.
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private def drain_script_parsed_tasks
|
|
107
|
+
pending_tasks = @script_parsed_tasks
|
|
108
|
+
@script_parsed_tasks = []
|
|
109
|
+
pending_tasks.each(&:wait)
|
|
98
110
|
end
|
|
99
111
|
|
|
100
112
|
def stop
|
|
101
113
|
raise 'JSCoverage is not enabled' unless @enabled
|
|
102
114
|
@enabled = false
|
|
115
|
+
drain_script_parsed_tasks
|
|
103
116
|
|
|
104
117
|
results = Puppeteer::AsyncUtils.await_promise_all(
|
|
105
118
|
@client.async_send_message('Profiler.takePreciseCoverage'),
|
|
@@ -32,7 +32,12 @@ module Puppeteer::Launcher
|
|
|
32
32
|
@default_viewport = options.key?(:default_viewport) ? options[:default_viewport] : Puppeteer::Viewport.new(width: 800, height: 600)
|
|
33
33
|
@slow_mo = options[:slow_mo] || 0
|
|
34
34
|
@network_enabled = options.fetch(:network_enabled, true)
|
|
35
|
+
@issues_enabled = options.fetch(:issues_enabled, true)
|
|
35
36
|
@protocol_timeout = options[:protocol_timeout]
|
|
37
|
+
@block_list = options[:block_list]
|
|
38
|
+
if @block_list && !@block_list.is_a?(Array)
|
|
39
|
+
raise ArgumentError.new('block_list must be an Array of URL patterns')
|
|
40
|
+
end
|
|
36
41
|
|
|
37
42
|
# only for Puppeteer.connect
|
|
38
43
|
@target_filter = options[:target_filter]
|
|
@@ -46,7 +51,7 @@ module Puppeteer::Launcher
|
|
|
46
51
|
end
|
|
47
52
|
end
|
|
48
53
|
|
|
49
|
-
attr_reader :default_viewport, :slow_mo, :target_filter, :is_page_target, :network_enabled, :protocol_timeout
|
|
54
|
+
attr_reader :default_viewport, :slow_mo, :target_filter, :is_page_target, :network_enabled, :issues_enabled, :protocol_timeout, :block_list
|
|
50
55
|
|
|
51
56
|
def ignore_https_errors?
|
|
52
57
|
@ignore_https_errors
|
|
@@ -87,6 +87,8 @@ module Puppeteer::Launcher
|
|
|
87
87
|
ignore_https_errors: @browser_options.ignore_https_errors?,
|
|
88
88
|
default_viewport: @browser_options.default_viewport,
|
|
89
89
|
network_enabled: @browser_options.network_enabled,
|
|
90
|
+
issues_enabled: @browser_options.issues_enabled,
|
|
91
|
+
block_list: @browser_options.block_list,
|
|
90
92
|
process: runner.proc,
|
|
91
93
|
close_callback: -> { runner.close },
|
|
92
94
|
target_filter_callback: nil,
|
|
@@ -129,7 +131,6 @@ module Puppeteer::Launcher
|
|
|
129
131
|
'--disable-component-update',
|
|
130
132
|
'--disable-default-apps',
|
|
131
133
|
'--disable-dev-shm-usage',
|
|
132
|
-
'--disable-extensions',
|
|
133
134
|
# AcceptCHFrame disabled because of crbug.com/1348106.
|
|
134
135
|
'--disable-features=Translate,BackForwardCache,AcceptCHFrame,MediaRouter,OptimizationHints,IPH_ReadingModePageActionLabel,ReadAnythingOmniboxChip',
|
|
135
136
|
'--disable-hang-monitor',
|
|
@@ -177,6 +178,20 @@ module Puppeteer::Launcher
|
|
|
177
178
|
end
|
|
178
179
|
end
|
|
179
180
|
|
|
181
|
+
if chrome_arg_options.enable_extensions
|
|
182
|
+
chrome_arguments << '--enable-unsafe-extension-debugging'
|
|
183
|
+
if chrome_arg_options.enable_extensions.is_a?(Array) && !chrome_arg_options.enable_extensions.empty?
|
|
184
|
+
extension_paths = chrome_arg_options.enable_extensions.map do |path|
|
|
185
|
+
File.expand_path(path)
|
|
186
|
+
end
|
|
187
|
+
joined_paths = extension_paths.join(',')
|
|
188
|
+
chrome_arguments << "--disable-extensions-except=#{joined_paths}"
|
|
189
|
+
chrome_arguments << "--load-extension=#{joined_paths}"
|
|
190
|
+
end
|
|
191
|
+
else
|
|
192
|
+
chrome_arguments << '--disable-extensions'
|
|
193
|
+
end
|
|
194
|
+
|
|
180
195
|
if chrome_arg_options.args.all? { |arg| arg.start_with?('-') }
|
|
181
196
|
chrome_arguments << 'about:blank'
|
|
182
197
|
end
|
|
@@ -31,13 +31,14 @@ module Puppeteer::Launcher
|
|
|
31
31
|
@user_data_dir = options[:user_data_dir]
|
|
32
32
|
@devtools = options[:devtools] || false
|
|
33
33
|
@headless = options[:headless]
|
|
34
|
+
@enable_extensions = options[:enable_extensions] || false
|
|
34
35
|
if @headless.nil?
|
|
35
36
|
@headless = !@devtools
|
|
36
37
|
end
|
|
37
38
|
@debugging_port = options[:debugging_port] || 0
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
attr_reader :args, :user_data_dir, :debugging_port
|
|
41
|
+
attr_reader :args, :user_data_dir, :debugging_port, :enable_extensions
|
|
41
42
|
|
|
42
43
|
def headless?
|
|
43
44
|
@headless
|
|
@@ -54,6 +54,10 @@ class Puppeteer::NetworkManager
|
|
|
54
54
|
}
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
def active?
|
|
58
|
+
@offline || @latency != 0 || @download != -1 || @upload != -1
|
|
59
|
+
end
|
|
60
|
+
|
|
57
61
|
def refresh
|
|
58
62
|
update_network_conditions
|
|
59
63
|
end
|
|
@@ -102,7 +106,6 @@ class Puppeteer::NetworkManager
|
|
|
102
106
|
@user_cache_disabled = nil
|
|
103
107
|
@internal_network_condition = InternalNetworkCondition.new(method(:send_to_clients))
|
|
104
108
|
@interception_semaphore = Async::Semaphore.new(1)
|
|
105
|
-
|
|
106
109
|
add_client(@client)
|
|
107
110
|
end
|
|
108
111
|
|
|
@@ -138,10 +141,8 @@ class Puppeteer::NetworkManager
|
|
|
138
141
|
"#<Puppeteer::HTTPRequest #{values.join(' ')}>"
|
|
139
142
|
end
|
|
140
143
|
|
|
141
|
-
private def apply_to_clients
|
|
142
|
-
@clients.each
|
|
143
|
-
yield client
|
|
144
|
-
end
|
|
144
|
+
private def apply_to_clients(&block)
|
|
145
|
+
@clients.each(&block)
|
|
145
146
|
end
|
|
146
147
|
|
|
147
148
|
private def ignore_client_error?(error)
|
|
@@ -192,7 +193,7 @@ class Puppeteer::NetworkManager
|
|
|
192
193
|
if @protocol_request_interception_enabled
|
|
193
194
|
safe_send_message(client, 'Fetch.enable',
|
|
194
195
|
handleAuthRequests: true,
|
|
195
|
-
patterns: [{ urlPattern: '*' }]
|
|
196
|
+
patterns: [{ urlPattern: '*' }]
|
|
196
197
|
)
|
|
197
198
|
else
|
|
198
199
|
safe_send_message(client, 'Fetch.disable')
|
|
@@ -208,7 +209,9 @@ class Puppeteer::NetworkManager
|
|
|
208
209
|
apply_user_agent(client)
|
|
209
210
|
apply_protocol_cache_disabled(client)
|
|
210
211
|
apply_protocol_request_interception(client)
|
|
211
|
-
|
|
212
|
+
if @internal_network_condition.active?
|
|
213
|
+
safe_send_message(client, 'Network.emulateNetworkConditions', @internal_network_condition.params)
|
|
214
|
+
end
|
|
212
215
|
end
|
|
213
216
|
|
|
214
217
|
# @param username [String|NilClass]
|
|
@@ -343,11 +346,9 @@ class Puppeteer::NetworkManager
|
|
|
343
346
|
private def dispatch_intercepted_request(event, fetch_request_id, client:)
|
|
344
347
|
if Async::Task.current?
|
|
345
348
|
Async do
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
debug_puts(err)
|
|
350
|
-
end
|
|
349
|
+
handle_request(event, fetch_request_id, client: client)
|
|
350
|
+
rescue => err
|
|
351
|
+
debug_puts(err)
|
|
351
352
|
end
|
|
352
353
|
else
|
|
353
354
|
handle_request(event, fetch_request_id, client: client)
|
|
@@ -365,6 +366,7 @@ class Puppeteer::NetworkManager
|
|
|
365
366
|
if existing_request &&
|
|
366
367
|
existing_request.url == event_url &&
|
|
367
368
|
existing_request.method == event.dig('request', 'method')
|
|
369
|
+
|
|
368
370
|
if_present(@network_event_manager.request_extra_info(network_request_id).shift) do |extra_info|
|
|
369
371
|
existing_request.update_headers(extra_info['headers'])
|
|
370
372
|
end
|
|
@@ -474,12 +476,10 @@ class Puppeteer::NetworkManager
|
|
|
474
476
|
end
|
|
475
477
|
end
|
|
476
478
|
|
|
477
|
-
private def with_interception_lock
|
|
479
|
+
private def with_interception_lock(&block)
|
|
478
480
|
return yield unless Async::Task.current?
|
|
479
481
|
|
|
480
|
-
@interception_semaphore.acquire
|
|
481
|
-
yield
|
|
482
|
-
end
|
|
482
|
+
@interception_semaphore.acquire(&block)
|
|
483
483
|
end
|
|
484
484
|
|
|
485
485
|
private def handle_request_without_network_instrumentation(event, client)
|
data/lib/puppeteer/page.rb
CHANGED
|
@@ -359,6 +359,22 @@ class Puppeteer::Page
|
|
|
359
359
|
@target.browser_context
|
|
360
360
|
end
|
|
361
361
|
|
|
362
|
+
# @rbs return: bool -- Whether DevTools is attached to this page
|
|
363
|
+
def has_devtools
|
|
364
|
+
!!browser._has_devtools_target(@target.target_id)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# @rbs extension: Puppeteer::Extension -- Extension to trigger
|
|
368
|
+
# @rbs return: void -- No return value
|
|
369
|
+
def trigger_extension_action(extension)
|
|
370
|
+
extension.trigger_action(self)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# @rbs return: Array[untyped] -- Extension execution realms on the main frame
|
|
374
|
+
def extension_realms
|
|
375
|
+
main_frame.extension_realms
|
|
376
|
+
end
|
|
377
|
+
|
|
362
378
|
class TargetCrashedError < Puppeteer::Error; end
|
|
363
379
|
|
|
364
380
|
private def handle_target_crashed
|
|
@@ -854,10 +870,7 @@ class Puppeteer::Page
|
|
|
854
870
|
end
|
|
855
871
|
|
|
856
872
|
private def add_console_message(type, args, stack_trace)
|
|
857
|
-
text_tokens = args.map
|
|
858
|
-
value = arg.remote_object.value
|
|
859
|
-
value.nil? ? arg.to_s : value
|
|
860
|
-
end
|
|
873
|
+
text_tokens = args.map { |arg| console_value_from_js_handle(arg) }
|
|
861
874
|
|
|
862
875
|
stack_trace_locations =
|
|
863
876
|
if stack_trace && stack_trace['callFrames']
|
|
@@ -875,6 +888,25 @@ class Puppeteer::Page
|
|
|
875
888
|
emit_event(PageEmittedEvents::Console, console_message)
|
|
876
889
|
end
|
|
877
890
|
|
|
891
|
+
private def console_value_from_js_handle(handle)
|
|
892
|
+
remote_object = handle.remote_object
|
|
893
|
+
return remote_object.value unless remote_object.object_id?
|
|
894
|
+
|
|
895
|
+
value_from_remote_object_reference(remote_object)
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
private def value_from_remote_object_reference(remote_object)
|
|
899
|
+
description = remote_object.description.to_s
|
|
900
|
+
if remote_object.sub_type == 'error' && !description.empty?
|
|
901
|
+
newline_index = description.index("\n")
|
|
902
|
+
return newline_index ? description[0...newline_index] : description
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
type = remote_object.sub_type || remote_object.type
|
|
906
|
+
class_name = remote_object.class_name || remote_object.description || 'Object'
|
|
907
|
+
"[#{type} #{class_name}]"
|
|
908
|
+
end
|
|
909
|
+
|
|
878
910
|
private def handle_dialog_opening(event)
|
|
879
911
|
dialog_type = event['type']
|
|
880
912
|
unless %w(alert confirm prompt beforeunload).include?(dialog_type)
|
data/lib/puppeteer/puppeteer.rb
CHANGED
|
@@ -31,6 +31,9 @@ class Puppeteer::Puppeteer
|
|
|
31
31
|
# @rbs headless: bool? -- Run browser in headless mode
|
|
32
32
|
# @rbs ignore_https_errors: bool? -- Ignore HTTPS errors
|
|
33
33
|
# @rbs network_enabled: bool? -- Enable network domain
|
|
34
|
+
# @rbs issues_enabled: bool? -- Enable issues domain
|
|
35
|
+
# @rbs block_list: Array[String]? -- URL block list patterns
|
|
36
|
+
# @rbs enable_extensions: (bool | Array[String])? -- Enable extensions or load unpacked extensions
|
|
34
37
|
# @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
|
|
35
38
|
# @rbs slow_mo: Integer? -- Delay between operations (ms)
|
|
36
39
|
# @rbs protocol_timeout: Integer? -- CDP protocol timeout in milliseconds
|
|
@@ -56,6 +59,9 @@ class Puppeteer::Puppeteer
|
|
|
56
59
|
headless: nil,
|
|
57
60
|
ignore_https_errors: nil,
|
|
58
61
|
network_enabled: true,
|
|
62
|
+
issues_enabled: true,
|
|
63
|
+
block_list: nil,
|
|
64
|
+
enable_extensions: false,
|
|
59
65
|
default_viewport: NoViewport.new,
|
|
60
66
|
slow_mo: nil,
|
|
61
67
|
protocol_timeout: nil,
|
|
@@ -85,6 +91,9 @@ class Puppeteer::Puppeteer
|
|
|
85
91
|
headless: headless,
|
|
86
92
|
ignore_https_errors: ignore_https_errors,
|
|
87
93
|
network_enabled: network_enabled,
|
|
94
|
+
issues_enabled: issues_enabled,
|
|
95
|
+
block_list: block_list,
|
|
96
|
+
enable_extensions: enable_extensions,
|
|
88
97
|
default_viewport: default_viewport,
|
|
89
98
|
slow_mo: slow_mo,
|
|
90
99
|
protocol_timeout: protocol_timeout,
|
|
@@ -131,8 +140,11 @@ class Puppeteer::Puppeteer
|
|
|
131
140
|
# @rbs browser_ws_endpoint: String? -- Browser WebSocket endpoint
|
|
132
141
|
# @rbs browser_url: String? -- Browser HTTP URL for WebSocket discovery
|
|
133
142
|
# @rbs transport: Puppeteer::WebSocketTransport? -- Pre-connected transport
|
|
143
|
+
# @rbs channel: (String | Symbol)? -- Browser channel
|
|
134
144
|
# @rbs ignore_https_errors: bool? -- Ignore HTTPS errors
|
|
135
145
|
# @rbs network_enabled: bool? -- Enable network domain
|
|
146
|
+
# @rbs issues_enabled: bool? -- Enable issues domain
|
|
147
|
+
# @rbs block_list: Array[String]? -- URL block list patterns
|
|
136
148
|
# @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
|
|
137
149
|
# @rbs slow_mo: Integer? -- Delay between operations (ms)
|
|
138
150
|
# @rbs protocol_timeout: Integer? -- CDP protocol timeout in milliseconds
|
|
@@ -142,9 +154,12 @@ class Puppeteer::Puppeteer
|
|
|
142
154
|
browser_ws_endpoint: nil,
|
|
143
155
|
browser_url: nil,
|
|
144
156
|
transport: nil,
|
|
157
|
+
channel: nil,
|
|
145
158
|
ignore_https_errors: nil,
|
|
146
159
|
network_enabled: true,
|
|
147
|
-
|
|
160
|
+
issues_enabled: true,
|
|
161
|
+
block_list: nil,
|
|
162
|
+
default_viewport: NoViewport.new,
|
|
148
163
|
slow_mo: nil,
|
|
149
164
|
protocol_timeout: nil,
|
|
150
165
|
&block
|
|
@@ -153,12 +168,17 @@ class Puppeteer::Puppeteer
|
|
|
153
168
|
browser_ws_endpoint: browser_ws_endpoint,
|
|
154
169
|
browser_url: browser_url,
|
|
155
170
|
transport: transport,
|
|
171
|
+
channel: channel&.to_s,
|
|
156
172
|
ignore_https_errors: ignore_https_errors,
|
|
157
173
|
network_enabled: network_enabled,
|
|
158
|
-
|
|
174
|
+
issues_enabled: issues_enabled,
|
|
175
|
+
block_list: block_list,
|
|
159
176
|
slow_mo: slow_mo,
|
|
160
177
|
protocol_timeout: protocol_timeout,
|
|
161
178
|
}.compact
|
|
179
|
+
unless default_viewport.is_a?(NoViewport)
|
|
180
|
+
options[:default_viewport] = default_viewport
|
|
181
|
+
end
|
|
162
182
|
if async_context?
|
|
163
183
|
browser = Puppeteer::BrowserConnector.new(options).connect_to_browser
|
|
164
184
|
if block
|
|
@@ -16,12 +16,13 @@ class Puppeteer::RemoteObject
|
|
|
16
16
|
@object_id = payload['objectId']
|
|
17
17
|
@type = payload['type']
|
|
18
18
|
@sub_type = payload['subtype']
|
|
19
|
+
@class_name = payload['className']
|
|
19
20
|
@unserializable_value = payload['unserializableValue']
|
|
20
21
|
@value = payload['value']
|
|
21
22
|
@description = payload['description']
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
attr_reader :sub_type, :type, :description
|
|
25
|
+
attr_reader :sub_type, :type, :class_name, :description
|
|
25
26
|
|
|
26
27
|
# @rbs return: bool
|
|
27
28
|
def object_id?
|
data/lib/puppeteer/target.rb
CHANGED
|
@@ -142,6 +142,23 @@ class Puppeteer::Target
|
|
|
142
142
|
@page
|
|
143
143
|
end
|
|
144
144
|
|
|
145
|
+
# @return [Puppeteer::Page]
|
|
146
|
+
def as_page
|
|
147
|
+
existing_page = page
|
|
148
|
+
return existing_page if existing_page
|
|
149
|
+
return @as_page if @as_page
|
|
150
|
+
|
|
151
|
+
client = @session || @session_factory.call(false)
|
|
152
|
+
client.wait_for_ready if client.respond_to?(:wait_for_ready)
|
|
153
|
+
@as_page = Puppeteer::Page.create(
|
|
154
|
+
client,
|
|
155
|
+
self,
|
|
156
|
+
@ignore_https_errors,
|
|
157
|
+
nil,
|
|
158
|
+
network_enabled: @network_enabled,
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
145
162
|
# @return [Puppeteer::CdpWebWorker|nil]
|
|
146
163
|
def worker
|
|
147
164
|
return nil unless ['service_worker', 'shared_worker'].include?(@target_info.type)
|
data/lib/puppeteer/version.rb
CHANGED