puppeteer-ruby 0.50.1 → 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 +4 -4
- data/docs/api_coverage.md +38 -6
- data/lib/puppeteer/browser.rb +82 -0
- data/lib/puppeteer/browser_connector.rb +2 -0
- data/lib/puppeteer/browser_context.rb +51 -3
- data/lib/puppeteer/chrome_target_manager.rb +53 -6
- 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/http_request.rb +15 -1
- data/lib/puppeteer/http_response.rb +6 -1
- 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 +17 -2
- data/lib/puppeteer/launcher/chrome_arg_options.rb +2 -1
- data/lib/puppeteer/locators.rb +56 -37
- data/lib/puppeteer/network_manager.rb +16 -16
- data/lib/puppeteer/page.rb +90 -7
- data/lib/puppeteer/puppeteer.rb +15 -0
- data/lib/puppeteer/remote_object.rb +2 -1
- data/lib/puppeteer/target.rb +17 -0
- data/lib/puppeteer/version.rb +2 -1
- data/lib/puppeteer.rb +2 -0
- data/puppeteer-ruby.gemspec +1 -1
- data/sig/_supplementary.rbs +7 -0
- data/sig/puppeteer/browser.rbs +34 -2
- 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/http_request.rbs +2 -0
- data/sig/puppeteer/issue.rbs +13 -0
- data/sig/puppeteer/locators.rbs +5 -2
- data/sig/puppeteer/page.rbs +20 -0
- data/sig/puppeteer/puppeteer.rbs +7 -2
- data/sig/puppeteer/remote_object.rbs +2 -0
- metadata +8 -4
|
@@ -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|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# rbs_inline: enabled
|
|
2
2
|
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
3
5
|
class Puppeteer::HTTPRequest
|
|
4
6
|
include Puppeteer::DebugPrint
|
|
5
7
|
include Puppeteer::IfPresent
|
|
@@ -101,7 +103,7 @@ class Puppeteer::HTTPRequest
|
|
|
101
103
|
resource_type = event['type'] || event['resourceType'] || 'other'
|
|
102
104
|
@resource_type = resource_type.downcase
|
|
103
105
|
@method = event['request']['method']
|
|
104
|
-
@post_data = event
|
|
106
|
+
@post_data = parse_post_data(event)
|
|
105
107
|
has_post_data = event.dig('request', 'hasPostData')
|
|
106
108
|
@has_post_data = has_post_data.nil? ? !@post_data.nil? : has_post_data
|
|
107
109
|
@frame = frame
|
|
@@ -160,6 +162,18 @@ class Puppeteer::HTTPRequest
|
|
|
160
162
|
end
|
|
161
163
|
end
|
|
162
164
|
|
|
165
|
+
private def parse_post_data(event)
|
|
166
|
+
post_data_entries = event.dig('request', 'postDataEntries')
|
|
167
|
+
if post_data_entries && !post_data_entries.empty?
|
|
168
|
+
post_data_entries
|
|
169
|
+
.filter_map { |entry| entry['bytes'] ? Base64.decode64(entry['bytes']) : nil }
|
|
170
|
+
.join
|
|
171
|
+
.force_encoding('UTF-8')
|
|
172
|
+
else
|
|
173
|
+
event.dig('request', 'postData')
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
163
177
|
private def assert_interception_not_handled
|
|
164
178
|
if @interception_handled
|
|
165
179
|
raise AlreadyHandledError.new
|
|
@@ -113,7 +113,12 @@ class Puppeteer::HTTPResponse
|
|
|
113
113
|
# @param text [String]
|
|
114
114
|
# @rbs return: String -- Response body as text
|
|
115
115
|
def text
|
|
116
|
-
buffer
|
|
116
|
+
content = buffer
|
|
117
|
+
content = content.dup.force_encoding('UTF-8')
|
|
118
|
+
unless content.valid_encoding?
|
|
119
|
+
raise Puppeteer::Error.new('Could not decode response body as UTF-8')
|
|
120
|
+
end
|
|
121
|
+
content
|
|
117
122
|
end
|
|
118
123
|
|
|
119
124
|
# @param json [Hash]
|
|
@@ -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,9 +131,8 @@ 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
|
-
'--disable-features=Translate,BackForwardCache,AcceptCHFrame,MediaRouter,OptimizationHints',
|
|
135
|
+
'--disable-features=Translate,BackForwardCache,AcceptCHFrame,MediaRouter,OptimizationHints,IPH_ReadingModePageActionLabel,ReadAnythingOmniboxChip',
|
|
135
136
|
'--disable-hang-monitor',
|
|
136
137
|
'--disable-ipc-flooding-protection',
|
|
137
138
|
'--disable-popup-blocking',
|
|
@@ -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
|
data/lib/puppeteer/locators.rb
CHANGED
|
@@ -213,15 +213,16 @@ class Puppeteer::Locator
|
|
|
213
213
|
end
|
|
214
214
|
|
|
215
215
|
# @rbs value: String -- Value to fill
|
|
216
|
+
# @rbs typing_threshold: Integer -- Minimum length to switch to direct assignment
|
|
216
217
|
# @rbs return: void -- No return value
|
|
217
|
-
def fill(value)
|
|
218
|
+
def fill(value, typing_threshold: 100)
|
|
218
219
|
perform_action('Locator.fill',
|
|
219
220
|
conditions: [
|
|
220
221
|
method(:ensure_element_is_in_viewport_if_needed),
|
|
221
222
|
method(:wait_for_stable_bounding_box_if_needed),
|
|
222
223
|
method(:wait_for_enabled_if_needed),
|
|
223
224
|
]) do |handle, _options|
|
|
224
|
-
fill_element(handle, value)
|
|
225
|
+
fill_element(handle, value, typing_threshold: typing_threshold)
|
|
225
226
|
end
|
|
226
227
|
end
|
|
227
228
|
|
|
@@ -422,7 +423,7 @@ class Puppeteer::Locator
|
|
|
422
423
|
end
|
|
423
424
|
end
|
|
424
425
|
|
|
425
|
-
private def fill_element(handle, value)
|
|
426
|
+
private def fill_element(handle, value, typing_threshold: 100)
|
|
426
427
|
input_type = handle.evaluate(<<~JAVASCRIPT)
|
|
427
428
|
el => {
|
|
428
429
|
if (el instanceof HTMLSelectElement) {
|
|
@@ -461,52 +462,70 @@ class Puppeteer::Locator
|
|
|
461
462
|
when 'select'
|
|
462
463
|
handle.select(value)
|
|
463
464
|
when 'contenteditable', 'typeable-input'
|
|
464
|
-
|
|
465
|
-
(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
465
|
+
if value.length < typing_threshold
|
|
466
|
+
text_to_type = handle.evaluate(<<~JAVASCRIPT, value)
|
|
467
|
+
(input, newValue) => {
|
|
468
|
+
const currentValue = input.isContentEditable
|
|
469
|
+
? input.innerText
|
|
470
|
+
: input.value;
|
|
471
|
+
|
|
472
|
+
if (
|
|
473
|
+
newValue.length <= currentValue.length ||
|
|
474
|
+
!newValue.startsWith(input.value)
|
|
475
|
+
) {
|
|
476
|
+
if (input.isContentEditable) {
|
|
477
|
+
input.innerText = '';
|
|
478
|
+
} else {
|
|
479
|
+
input.value = '';
|
|
480
|
+
}
|
|
481
|
+
return newValue;
|
|
482
|
+
}
|
|
483
|
+
const originalValue = input.isContentEditable
|
|
484
|
+
? input.innerText
|
|
485
|
+
: input.value;
|
|
469
486
|
|
|
470
|
-
if (
|
|
471
|
-
newValue.length <= currentValue.length ||
|
|
472
|
-
!newValue.startsWith(input.value)
|
|
473
|
-
) {
|
|
474
487
|
if (input.isContentEditable) {
|
|
475
488
|
input.innerText = '';
|
|
489
|
+
input.innerText = originalValue;
|
|
476
490
|
} else {
|
|
477
491
|
input.value = '';
|
|
492
|
+
input.value = originalValue;
|
|
478
493
|
}
|
|
479
|
-
return newValue;
|
|
494
|
+
return newValue.substring(originalValue.length);
|
|
480
495
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
input.innerText = originalValue;
|
|
488
|
-
} else {
|
|
489
|
-
input.value = '';
|
|
490
|
-
input.value = originalValue;
|
|
491
|
-
}
|
|
492
|
-
return newValue.substring(originalValue.length);
|
|
493
|
-
}
|
|
494
|
-
JAVASCRIPT
|
|
495
|
-
text_to_type = text_to_type.to_s
|
|
496
|
-
handle.type_text(text_to_type)
|
|
496
|
+
JAVASCRIPT
|
|
497
|
+
text_to_type = text_to_type.to_s
|
|
498
|
+
handle.type_text(text_to_type) unless text_to_type.empty?
|
|
499
|
+
else
|
|
500
|
+
fill_directly(handle, value)
|
|
501
|
+
end
|
|
497
502
|
when 'other-input'
|
|
498
|
-
handle
|
|
499
|
-
handle.evaluate(<<~JAVASCRIPT, value)
|
|
500
|
-
(input, newValue) => {
|
|
501
|
-
input.value = newValue;
|
|
502
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
503
|
-
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
504
|
-
}
|
|
505
|
-
JAVASCRIPT
|
|
503
|
+
fill_directly(handle, value)
|
|
506
504
|
else
|
|
507
505
|
raise Puppeteer::Error.new('Element cannot be filled out.')
|
|
508
506
|
end
|
|
509
507
|
end
|
|
508
|
+
|
|
509
|
+
private def fill_directly(handle, value)
|
|
510
|
+
handle.focus
|
|
511
|
+
handle.evaluate(<<~JAVASCRIPT, value)
|
|
512
|
+
(input, newValue) => {
|
|
513
|
+
const currentValue = input.isContentEditable
|
|
514
|
+
? input.innerText
|
|
515
|
+
: input.value;
|
|
516
|
+
if (currentValue === newValue) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (input.isContentEditable) {
|
|
520
|
+
input.innerText = newValue;
|
|
521
|
+
} else {
|
|
522
|
+
input.value = newValue;
|
|
523
|
+
}
|
|
524
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
525
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
526
|
+
}
|
|
527
|
+
JAVASCRIPT
|
|
528
|
+
end
|
|
510
529
|
end
|
|
511
530
|
|
|
512
531
|
class Puppeteer::FunctionLocator < Puppeteer::Locator
|
|
@@ -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)
|