puppeteer-ruby 0.45.6 → 0.50.0.alpha5
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/.rubocop.yml +1 -3
- data/AGENTS.md +169 -0
- data/CLAUDE/README.md +41 -0
- data/CLAUDE/architecture.md +253 -0
- data/CLAUDE/cdp_protocol.md +230 -0
- data/CLAUDE/concurrency.md +216 -0
- data/CLAUDE/porting_puppeteer.md +575 -0
- data/CLAUDE/rbs_type_checking.md +101 -0
- data/CLAUDE/spec_migration_plans.md +1041 -0
- data/CLAUDE/testing.md +278 -0
- data/CLAUDE.md +242 -0
- data/README.md +8 -0
- data/Rakefile +7 -0
- data/Steepfile +28 -0
- data/docs/api_coverage.md +105 -56
- data/lib/puppeteer/aria_query_handler.rb +3 -2
- data/lib/puppeteer/async_utils.rb +214 -0
- data/lib/puppeteer/browser.rb +98 -56
- data/lib/puppeteer/browser_connector.rb +18 -3
- data/lib/puppeteer/browser_context.rb +196 -3
- data/lib/puppeteer/browser_runner.rb +18 -10
- data/lib/puppeteer/cdp_session.rb +67 -23
- data/lib/puppeteer/chrome_target_manager.rb +65 -40
- data/lib/puppeteer/connection.rb +55 -36
- data/lib/puppeteer/console_message.rb +9 -1
- data/lib/puppeteer/console_patch.rb +47 -0
- data/lib/puppeteer/css_coverage.rb +5 -3
- data/lib/puppeteer/custom_query_handler.rb +80 -33
- data/lib/puppeteer/define_async_method.rb +31 -37
- data/lib/puppeteer/dialog.rb +47 -14
- data/lib/puppeteer/element_handle.rb +231 -62
- data/lib/puppeteer/emulation_manager.rb +1 -1
- data/lib/puppeteer/env.rb +1 -1
- data/lib/puppeteer/errors.rb +25 -2
- data/lib/puppeteer/event_callbackable.rb +15 -0
- data/lib/puppeteer/events.rb +4 -0
- data/lib/puppeteer/execution_context.rb +148 -3
- data/lib/puppeteer/file_chooser.rb +6 -0
- data/lib/puppeteer/frame.rb +162 -91
- data/lib/puppeteer/frame_manager.rb +69 -48
- data/lib/puppeteer/http_request.rb +114 -38
- data/lib/puppeteer/http_response.rb +24 -7
- data/lib/puppeteer/isolated_world.rb +64 -41
- data/lib/puppeteer/js_coverage.rb +5 -3
- data/lib/puppeteer/js_handle.rb +58 -16
- data/lib/puppeteer/keyboard.rb +30 -17
- data/lib/puppeteer/launcher/browser_options.rb +3 -1
- data/lib/puppeteer/launcher/chrome.rb +8 -5
- data/lib/puppeteer/launcher/launch_options.rb +7 -2
- data/lib/puppeteer/launcher.rb +4 -8
- data/lib/puppeteer/lifecycle_watcher.rb +38 -22
- data/lib/puppeteer/mouse.rb +273 -64
- data/lib/puppeteer/network_event_manager.rb +7 -0
- data/lib/puppeteer/network_manager.rb +393 -112
- data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
- data/lib/puppeteer/page.rb +568 -226
- data/lib/puppeteer/puppeteer.rb +171 -64
- data/lib/puppeteer/query_handler_manager.rb +112 -16
- data/lib/puppeteer/reactor_runner.rb +247 -0
- data/lib/puppeteer/remote_object.rb +127 -47
- data/lib/puppeteer/target.rb +74 -27
- data/lib/puppeteer/task_manager.rb +3 -1
- data/lib/puppeteer/timeout_helper.rb +6 -10
- data/lib/puppeteer/touch_handle.rb +39 -0
- data/lib/puppeteer/touch_screen.rb +72 -22
- data/lib/puppeteer/tracing.rb +3 -3
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +264 -101
- data/lib/puppeteer/web_socket.rb +2 -2
- data/lib/puppeteer/web_socket_transport.rb +91 -27
- data/lib/puppeteer/web_worker.rb +175 -0
- data/lib/puppeteer.rb +20 -4
- data/puppeteer-ruby.gemspec +15 -11
- data/sig/_external.rbs +8 -0
- data/sig/_supplementary.rbs +314 -0
- data/sig/puppeteer/browser.rbs +166 -0
- data/sig/puppeteer/cdp_session.rbs +64 -0
- data/sig/puppeteer/dialog.rbs +41 -0
- data/sig/puppeteer/element_handle.rbs +305 -0
- data/sig/puppeteer/execution_context.rbs +87 -0
- data/sig/puppeteer/frame.rbs +226 -0
- data/sig/puppeteer/http_request.rbs +214 -0
- data/sig/puppeteer/http_response.rbs +89 -0
- data/sig/puppeteer/js_handle.rbs +64 -0
- data/sig/puppeteer/keyboard.rbs +40 -0
- data/sig/puppeteer/mouse.rbs +113 -0
- data/sig/puppeteer/page.rbs +515 -0
- data/sig/puppeteer/puppeteer.rbs +98 -0
- data/sig/puppeteer/remote_object.rbs +78 -0
- data/sig/puppeteer/touch_handle.rbs +21 -0
- data/sig/puppeteer/touch_screen.rbs +35 -0
- data/sig/puppeteer/web_worker.rbs +83 -0
- metadata +116 -45
- data/CHANGELOG.md +0 -397
- data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
- data/lib/puppeteer/firefox_target_manager.rb +0 -157
- data/lib/puppeteer/launcher/firefox.rb +0 -453
data/lib/puppeteer/launcher.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require_relative './launcher/browser_options'
|
|
2
2
|
require_relative './launcher/chrome'
|
|
3
3
|
require_relative './launcher/chrome_arg_options'
|
|
4
|
-
require_relative './launcher/firefox'
|
|
5
4
|
require_relative './launcher/launch_options'
|
|
6
5
|
|
|
7
6
|
# https://github.com/puppeteer/puppeteer/blob/main/src/node/Launcher.ts
|
|
@@ -9,19 +8,16 @@ module Puppeteer::Launcher
|
|
|
9
8
|
# @param project_root [String]
|
|
10
9
|
# @param prefereed_revision [String]
|
|
11
10
|
# @param is_puppeteer_core [String]
|
|
12
|
-
# @param product [String] 'chrome'
|
|
11
|
+
# @param product [String] 'chrome'
|
|
13
12
|
# @return [Puppeteer::Launcher::Chrome]
|
|
14
13
|
module_function def new(project_root:, preferred_revision:, is_puppeteer_core:, product:)
|
|
15
14
|
unless is_puppeteer_core
|
|
16
15
|
product ||= ENV['PUPPETEER_PRODUCT']
|
|
17
16
|
end
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
preferred_revision: preferred_revision,
|
|
23
|
-
is_puppeteer_core: is_puppeteer_core,
|
|
24
|
-
)
|
|
18
|
+
product = product.to_s if product
|
|
19
|
+
if product && product != 'chrome'
|
|
20
|
+
raise ArgumentError.new("Unsupported product: #{product}. Only 'chrome' is supported.")
|
|
25
21
|
end
|
|
26
22
|
|
|
27
23
|
Chrome.new(
|
|
@@ -50,12 +50,12 @@ class Puppeteer::LifecycleWatcher
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
class FrameDetachedError <
|
|
53
|
+
class FrameDetachedError < Puppeteer::Error
|
|
54
54
|
def initialize
|
|
55
55
|
super('Navigating frame was detached')
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
|
-
class TerminatedError <
|
|
58
|
+
class TerminatedError < Puppeteer::Error; end
|
|
59
59
|
|
|
60
60
|
# * @param {!Puppeteer.FrameManager} frameManager
|
|
61
61
|
# * @param {!Puppeteer.Frame} frame
|
|
@@ -72,6 +72,12 @@ class Puppeteer::LifecycleWatcher
|
|
|
72
72
|
@listener_ids['client'] = @frame_manager.client.add_event_listener(CDPSessionEmittedEvents::Disconnected) do
|
|
73
73
|
terminate(TerminatedError.new('Navigation failed because browser has disconnected!'))
|
|
74
74
|
end
|
|
75
|
+
connection = @frame_manager.client.respond_to?(:connection) ? @frame_manager.client.connection : nil
|
|
76
|
+
if connection
|
|
77
|
+
@listener_ids['connection'] = connection.add_event_listener(ConnectionEmittedEvents::Disconnected) do
|
|
78
|
+
terminate(TerminatedError.new('Navigation failed because browser has disconnected!'))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
75
81
|
@listener_ids['frame_manager'] = [
|
|
76
82
|
@frame_manager.add_event_listener(FrameManagerEmittedEvents::LifecycleEvent) do |_|
|
|
77
83
|
check_lifecycle_complete
|
|
@@ -87,24 +93,28 @@ class Puppeteer::LifecycleWatcher
|
|
|
87
93
|
@frame_manager.network_manager.add_event_listener(NetworkManagerEmittedEvents::RequestFailed, &method(:handle_request_failed)),
|
|
88
94
|
]
|
|
89
95
|
|
|
90
|
-
@same_document_navigation_promise =
|
|
91
|
-
@lifecycle_promise =
|
|
92
|
-
@new_document_navigation_promise =
|
|
93
|
-
@termination_promise =
|
|
96
|
+
@same_document_navigation_promise = Async::Promise.new
|
|
97
|
+
@lifecycle_promise = Async::Promise.new
|
|
98
|
+
@new_document_navigation_promise = Async::Promise.new
|
|
99
|
+
@termination_promise = Async::Promise.new
|
|
100
|
+
@navigation_response_received = Async::Promise.new.tap { |promise| promise.resolve(nil) }
|
|
94
101
|
check_lifecycle_complete
|
|
95
102
|
end
|
|
96
103
|
|
|
97
104
|
# @param [Puppeteer::HTTPRequest] request
|
|
98
105
|
def handle_request(request)
|
|
99
106
|
return if request.frame != @frame || !request.navigation_request?
|
|
107
|
+
if @navigation_request && request.redirect_chain.empty?
|
|
108
|
+
return
|
|
109
|
+
end
|
|
100
110
|
@navigation_request = request
|
|
101
111
|
# Resolve previous navigation response in case there are multiple
|
|
102
112
|
# navigation requests reported by the backend. This generally should not
|
|
103
113
|
# happen by it looks like it's possible.
|
|
104
|
-
@navigation_response_received.
|
|
105
|
-
@navigation_response_received =
|
|
114
|
+
@navigation_response_received.resolve(nil) if @navigation_response_received && !@navigation_response_received.resolved?
|
|
115
|
+
@navigation_response_received = Async::Promise.new
|
|
106
116
|
if request.response && !@navigation_response_received.resolved?
|
|
107
|
-
@navigation_response_received.
|
|
117
|
+
@navigation_response_received.resolve(nil)
|
|
108
118
|
end
|
|
109
119
|
end
|
|
110
120
|
|
|
@@ -112,14 +122,14 @@ class Puppeteer::LifecycleWatcher
|
|
|
112
122
|
def handle_request_failed(request)
|
|
113
123
|
return if @navigation_request&.internal&.request_id != request.internal.request_id
|
|
114
124
|
|
|
115
|
-
@navigation_response_received.
|
|
125
|
+
@navigation_response_received.resolve(nil) unless @navigation_response_received.resolved?
|
|
116
126
|
end
|
|
117
127
|
|
|
118
128
|
# @param [Puppeteer::HTTPResponse] response
|
|
119
129
|
def handle_response(response)
|
|
120
130
|
return if @navigation_request&.internal&.request_id != response.request.internal.request_id
|
|
121
131
|
|
|
122
|
-
@navigation_response_received.
|
|
132
|
+
@navigation_response_received.resolve(nil) unless @navigation_response_received.resolved?
|
|
123
133
|
end
|
|
124
134
|
|
|
125
135
|
# @param frame [Puppeteer::Frame]
|
|
@@ -134,7 +144,7 @@ class Puppeteer::LifecycleWatcher
|
|
|
134
144
|
# @return [Puppeteer::HTTPResponse]
|
|
135
145
|
def navigation_response
|
|
136
146
|
# Continue with a possibly null response.
|
|
137
|
-
@navigation_response_received.
|
|
147
|
+
@navigation_response_received.wait rescue nil
|
|
138
148
|
if_present(@navigation_request) do |request|
|
|
139
149
|
request.response
|
|
140
150
|
end
|
|
@@ -142,6 +152,8 @@ class Puppeteer::LifecycleWatcher
|
|
|
142
152
|
|
|
143
153
|
# @param error [TerminatedError]
|
|
144
154
|
private def terminate(error)
|
|
155
|
+
return if @termination_promise.resolved?
|
|
156
|
+
|
|
145
157
|
@termination_promise.reject(error)
|
|
146
158
|
end
|
|
147
159
|
|
|
@@ -153,12 +165,12 @@ class Puppeteer::LifecycleWatcher
|
|
|
153
165
|
|
|
154
166
|
def timeout_or_termination_promise
|
|
155
167
|
if @timeout > 0
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
@termination_promise.
|
|
168
|
+
-> do
|
|
169
|
+
begin
|
|
170
|
+
Puppeteer::AsyncUtils.async_timeout(@timeout, @termination_promise).wait
|
|
171
|
+
rescue Async::TimeoutError
|
|
172
|
+
raise Puppeteer::TimeoutError.new("Navigation timeout of #{@timeout} ms exceeded")
|
|
159
173
|
end
|
|
160
|
-
rescue Timeout::Error
|
|
161
|
-
raise Puppeteer::TimeoutError.new("Navigation timeout of #{@timeout}ms exceeded")
|
|
162
174
|
end
|
|
163
175
|
else
|
|
164
176
|
@termination_promise
|
|
@@ -187,12 +199,12 @@ class Puppeteer::LifecycleWatcher
|
|
|
187
199
|
private def check_lifecycle_complete
|
|
188
200
|
# We expect navigation to commit.
|
|
189
201
|
return unless @expected_lifecycle.completed?(@frame)
|
|
190
|
-
@lifecycle_promise.
|
|
191
|
-
if @has_same_document_navigation &&
|
|
192
|
-
@same_document_navigation_promise.
|
|
202
|
+
@lifecycle_promise.resolve(true) unless @lifecycle_promise.resolved?
|
|
203
|
+
if @has_same_document_navigation && !@same_document_navigation_promise.resolved?
|
|
204
|
+
@same_document_navigation_promise.resolve(true)
|
|
193
205
|
end
|
|
194
|
-
if (@swapped || @frame.loader_id != @initial_loader_id) &&
|
|
195
|
-
@new_document_navigation_promise.
|
|
206
|
+
if (@swapped || @frame.loader_id != @initial_loader_id) && !@new_document_navigation_promise.resolved?
|
|
207
|
+
@new_document_navigation_promise.resolve(true)
|
|
196
208
|
end
|
|
197
209
|
end
|
|
198
210
|
|
|
@@ -203,6 +215,10 @@ class Puppeteer::LifecycleWatcher
|
|
|
203
215
|
if_present(@listener_ids['frame_manager']) do |ids|
|
|
204
216
|
@frame_manager.remove_event_listener(*ids)
|
|
205
217
|
end
|
|
218
|
+
if_present(@listener_ids['connection']) do |id|
|
|
219
|
+
connection = @frame_manager.client.respond_to?(:connection) ? @frame_manager.client.connection : nil
|
|
220
|
+
connection&.remove_event_listener(id)
|
|
221
|
+
end
|
|
206
222
|
if_present(@listener_ids['network_manager']) do |ids|
|
|
207
223
|
@frame_manager.network_manager.remove_event_listener(*ids)
|
|
208
224
|
end
|
data/lib/puppeteer/mouse.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
1
3
|
class Puppeteer::Mouse
|
|
2
4
|
using Puppeteer::DefineAsyncMethod
|
|
3
5
|
|
|
@@ -6,124 +8,232 @@ class Puppeteer::Mouse
|
|
|
6
8
|
LEFT = 'left'
|
|
7
9
|
RIGHT = 'right'
|
|
8
10
|
MIDDLE = 'middle'
|
|
11
|
+
BACK = 'back'
|
|
12
|
+
FORWARD = 'forward'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ButtonFlag
|
|
16
|
+
NONE = 0
|
|
17
|
+
LEFT = 1
|
|
18
|
+
RIGHT = 1 << 1
|
|
19
|
+
MIDDLE = 1 << 2
|
|
20
|
+
BACK = 1 << 3
|
|
21
|
+
FORWARD = 1 << 4
|
|
9
22
|
end
|
|
10
23
|
|
|
11
|
-
# @
|
|
12
|
-
# @
|
|
24
|
+
# @rbs client: Puppeteer::CDPSession -- CDP session
|
|
25
|
+
# @rbs keyboard: Puppeteer::Keyboard -- Keyboard instance
|
|
26
|
+
# @rbs return: void -- No return value
|
|
13
27
|
def initialize(client, keyboard)
|
|
14
28
|
@client = client
|
|
15
29
|
@keyboard = keyboard
|
|
16
30
|
|
|
17
|
-
@
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
@base_state = {
|
|
32
|
+
position: {
|
|
33
|
+
x: 0,
|
|
34
|
+
y: 0,
|
|
35
|
+
},
|
|
36
|
+
buttons: ButtonFlag::NONE,
|
|
37
|
+
}
|
|
38
|
+
@transactions = []
|
|
39
|
+
@state_mutex = Mutex.new
|
|
40
|
+
@dispatch_mutex = Mutex.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @rbs return: void -- No return value
|
|
44
|
+
def reset
|
|
45
|
+
[
|
|
46
|
+
[ButtonFlag::RIGHT, Button::RIGHT],
|
|
47
|
+
[ButtonFlag::MIDDLE, Button::MIDDLE],
|
|
48
|
+
[ButtonFlag::LEFT, Button::LEFT],
|
|
49
|
+
[ButtonFlag::FORWARD, Button::FORWARD],
|
|
50
|
+
[ButtonFlag::BACK, Button::BACK],
|
|
51
|
+
].each do |flag, button|
|
|
52
|
+
up(button: button) if (state[:buttons] & flag) != 0
|
|
53
|
+
end
|
|
54
|
+
if state[:position][:x] != 0 || state[:position][:y] != 0
|
|
55
|
+
move(0, 0)
|
|
56
|
+
end
|
|
20
57
|
end
|
|
21
58
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# @
|
|
59
|
+
define_async_method :async_reset
|
|
60
|
+
|
|
61
|
+
# @rbs x: Numeric -- X coordinate
|
|
62
|
+
# @rbs y: Numeric -- Y coordinate
|
|
63
|
+
# @rbs steps: Integer? -- Number of intermediate steps
|
|
64
|
+
# @rbs return: void -- No return value
|
|
25
65
|
def move(x, y, steps: nil)
|
|
26
66
|
move_steps = (steps || 1).to_i
|
|
27
67
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
68
|
+
from = state[:position]
|
|
69
|
+
to = {
|
|
70
|
+
x: x,
|
|
71
|
+
y: y,
|
|
72
|
+
}
|
|
32
73
|
|
|
33
74
|
return if move_steps <= 0
|
|
34
75
|
|
|
35
|
-
move_steps
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
76
|
+
1.upto(move_steps) do |i|
|
|
77
|
+
with_transaction do |update_state|
|
|
78
|
+
update_state.call(
|
|
79
|
+
position: {
|
|
80
|
+
x: from[:x] + (to[:x] - from[:x]) * i / move_steps.to_f,
|
|
81
|
+
y: from[:y] + (to[:y] - from[:y]) * i / move_steps.to_f,
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
current_state = state
|
|
85
|
+
buttons = current_state[:buttons]
|
|
86
|
+
position = current_state[:position]
|
|
87
|
+
@client.send_message('Input.dispatchMouseEvent',
|
|
88
|
+
type: 'mouseMoved',
|
|
89
|
+
modifiers: @keyboard.modifiers,
|
|
90
|
+
buttons: buttons,
|
|
91
|
+
button: button_from_pressed_buttons(buttons),
|
|
92
|
+
x: position[:x],
|
|
93
|
+
y: position[:y],
|
|
94
|
+
)
|
|
95
|
+
end
|
|
44
96
|
end
|
|
45
97
|
end
|
|
46
98
|
|
|
47
99
|
define_async_method :async_move
|
|
48
100
|
|
|
49
|
-
# @
|
|
50
|
-
# @
|
|
51
|
-
# @
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
101
|
+
# @rbs x: Numeric -- X coordinate
|
|
102
|
+
# @rbs y: Numeric -- Y coordinate
|
|
103
|
+
# @rbs delay: Numeric? -- Delay between down and up (ms)
|
|
104
|
+
# @rbs button: String? -- Mouse button
|
|
105
|
+
# @rbs click_count: Integer? -- Deprecated: use count (click_count only sets clickCount)
|
|
106
|
+
# @rbs count: Integer? -- Number of click repetitions
|
|
107
|
+
# @rbs return: void -- No return value
|
|
108
|
+
def click(x, y, delay: nil, button: nil, click_count: nil, count: nil)
|
|
109
|
+
warn_deprecated_click_count if !click_count.nil?
|
|
110
|
+
count ||= 1
|
|
111
|
+
click_count ||= count
|
|
112
|
+
if count < 1
|
|
113
|
+
raise Puppeteer::Error.new('Click must occur a positive number of times.')
|
|
114
|
+
end
|
|
115
|
+
# Serialize click sequences to keep event ordering stable under thread-based concurrency.
|
|
116
|
+
@dispatch_mutex.synchronize do
|
|
117
|
+
move(x, y)
|
|
118
|
+
if click_count == count
|
|
119
|
+
1.upto(count - 1) do |i|
|
|
120
|
+
down(button: button, click_count: i)
|
|
121
|
+
up(button: button, click_count: i)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
down(button: button, click_count: click_count)
|
|
125
|
+
if !delay.nil?
|
|
126
|
+
Puppeteer::AsyncUtils.sleep_seconds(delay / 1000.0)
|
|
127
|
+
end
|
|
128
|
+
up(button: button, click_count: click_count)
|
|
63
129
|
end
|
|
64
|
-
up(button: button, click_count: click_count)
|
|
65
130
|
end
|
|
66
131
|
|
|
67
132
|
define_async_method :async_click
|
|
68
133
|
|
|
69
|
-
|
|
134
|
+
private def warn_deprecated_click_count
|
|
135
|
+
return if self.class.deprecated_click_count_warned
|
|
136
|
+
|
|
137
|
+
self.class.deprecated_click_count_warned = true
|
|
138
|
+
warn('DEPRECATED: `click_count` is deprecated; use `count` instead.')
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class << self
|
|
142
|
+
attr_accessor :deprecated_click_count_warned
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
self.deprecated_click_count_warned = false
|
|
146
|
+
|
|
147
|
+
# @rbs button: String? -- Mouse button
|
|
148
|
+
# @rbs click_count: Integer? -- Click count to report
|
|
149
|
+
# @rbs return: void -- No return value
|
|
70
150
|
def down(button: nil, click_count: nil)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
151
|
+
button ||= Button::LEFT
|
|
152
|
+
flag = button_flag(button)
|
|
153
|
+
with_transaction do |update_state|
|
|
154
|
+
update_state.call(
|
|
155
|
+
buttons: state[:buttons] | flag,
|
|
156
|
+
)
|
|
157
|
+
current_state = state
|
|
158
|
+
position = current_state[:position]
|
|
159
|
+
@client.send_message('Input.dispatchMouseEvent',
|
|
160
|
+
type: 'mousePressed',
|
|
161
|
+
modifiers: @keyboard.modifiers,
|
|
162
|
+
clickCount: click_count || 1,
|
|
163
|
+
buttons: current_state[:buttons],
|
|
164
|
+
button: button,
|
|
165
|
+
x: position[:x],
|
|
166
|
+
y: position[:y],
|
|
167
|
+
)
|
|
168
|
+
end
|
|
80
169
|
end
|
|
81
170
|
|
|
82
171
|
define_async_method :async_down
|
|
83
172
|
|
|
84
|
-
# @
|
|
173
|
+
# @rbs button: String? -- Mouse button
|
|
174
|
+
# @rbs click_count: Integer? -- Click count to report
|
|
175
|
+
# @rbs return: void -- No return value
|
|
85
176
|
def up(button: nil, click_count: nil)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
177
|
+
button ||= Button::LEFT
|
|
178
|
+
flag = button_flag(button)
|
|
179
|
+
with_transaction do |update_state|
|
|
180
|
+
update_state.call(
|
|
181
|
+
buttons: state[:buttons] & ~flag,
|
|
182
|
+
)
|
|
183
|
+
current_state = state
|
|
184
|
+
position = current_state[:position]
|
|
185
|
+
@client.send_message('Input.dispatchMouseEvent',
|
|
186
|
+
type: 'mouseReleased',
|
|
187
|
+
modifiers: @keyboard.modifiers,
|
|
188
|
+
clickCount: click_count || 1,
|
|
189
|
+
buttons: current_state[:buttons],
|
|
190
|
+
button: button,
|
|
191
|
+
x: position[:x],
|
|
192
|
+
y: position[:y],
|
|
193
|
+
)
|
|
194
|
+
end
|
|
95
195
|
end
|
|
96
196
|
|
|
97
197
|
define_async_method :async_up
|
|
98
198
|
|
|
99
199
|
# Dispatches a `mousewheel` event.
|
|
100
200
|
#
|
|
101
|
-
# @
|
|
102
|
-
# @
|
|
201
|
+
# @rbs delta_x: Numeric -- Scroll delta X
|
|
202
|
+
# @rbs delta_y: Numeric -- Scroll delta Y
|
|
203
|
+
# @rbs return: void -- No return value
|
|
103
204
|
def wheel(delta_x: 0, delta_y: 0)
|
|
205
|
+
current_state = state
|
|
206
|
+
position = current_state[:position]
|
|
104
207
|
@client.send_message('Input.dispatchMouseEvent',
|
|
105
208
|
type: 'mouseWheel',
|
|
106
|
-
x:
|
|
107
|
-
y:
|
|
209
|
+
x: position[:x],
|
|
210
|
+
y: position[:y],
|
|
108
211
|
deltaX: delta_x,
|
|
109
212
|
deltaY: delta_y,
|
|
110
213
|
modifiers: @keyboard.modifiers,
|
|
111
214
|
pointerType: 'mouse',
|
|
215
|
+
buttons: current_state[:buttons],
|
|
112
216
|
)
|
|
113
217
|
end
|
|
114
218
|
|
|
219
|
+
# @rbs start: Puppeteer::ElementHandle::Point -- Drag start point
|
|
220
|
+
# @rbs target: Puppeteer::ElementHandle::Point -- Drag end point
|
|
221
|
+
# @rbs return: Hash[String, untyped] -- Drag data payload
|
|
115
222
|
def drag(start, target)
|
|
116
|
-
promise =
|
|
223
|
+
promise = Async::Promise.new.tap do |future|
|
|
117
224
|
@client.once('Input.dragIntercepted') do |event|
|
|
118
|
-
|
|
225
|
+
future.resolve(event['data'])
|
|
119
226
|
end
|
|
120
227
|
end
|
|
121
228
|
move(start.x, start.y)
|
|
122
229
|
down
|
|
123
230
|
move(target.x, target.y)
|
|
124
|
-
promise.
|
|
231
|
+
promise.wait
|
|
125
232
|
end
|
|
126
233
|
|
|
234
|
+
# @rbs target: Puppeteer::ElementHandle::Point -- Drag target point
|
|
235
|
+
# @rbs data: Hash[String, untyped] -- Drag data payload
|
|
236
|
+
# @rbs return: void -- No return value
|
|
127
237
|
def drag_enter(target, data)
|
|
128
238
|
@client.send_message('Input.dispatchDragEvent',
|
|
129
239
|
type: 'dragEnter',
|
|
@@ -134,6 +244,9 @@ class Puppeteer::Mouse
|
|
|
134
244
|
)
|
|
135
245
|
end
|
|
136
246
|
|
|
247
|
+
# @rbs target: Puppeteer::ElementHandle::Point -- Drag target point
|
|
248
|
+
# @rbs data: Hash[String, untyped] -- Drag data payload
|
|
249
|
+
# @rbs return: void -- No return value
|
|
137
250
|
def drag_over(target, data)
|
|
138
251
|
@client.send_message('Input.dispatchDragEvent',
|
|
139
252
|
type: 'dragOver',
|
|
@@ -144,6 +257,9 @@ class Puppeteer::Mouse
|
|
|
144
257
|
)
|
|
145
258
|
end
|
|
146
259
|
|
|
260
|
+
# @rbs target: Puppeteer::ElementHandle::Point -- Drag target point
|
|
261
|
+
# @rbs data: Hash[String, untyped] -- Drag data payload
|
|
262
|
+
# @rbs return: void -- No return value
|
|
147
263
|
def drop(target, data)
|
|
148
264
|
@client.send_message('Input.dispatchDragEvent',
|
|
149
265
|
type: 'drop',
|
|
@@ -154,14 +270,107 @@ class Puppeteer::Mouse
|
|
|
154
270
|
)
|
|
155
271
|
end
|
|
156
272
|
|
|
273
|
+
# @rbs start: Puppeteer::ElementHandle::Point -- Drag start point
|
|
274
|
+
# @rbs target: Puppeteer::ElementHandle::Point -- Drag end point
|
|
275
|
+
# @rbs delay: Numeric? -- Delay before drop (ms)
|
|
276
|
+
# @rbs return: void -- No return value
|
|
157
277
|
def drag_and_drop(start, target, delay: nil)
|
|
158
278
|
data = drag(start, target)
|
|
159
279
|
drag_enter(target, data)
|
|
160
280
|
drag_over(target, data)
|
|
161
281
|
if delay
|
|
162
|
-
|
|
282
|
+
Puppeteer::AsyncUtils.sleep_seconds(delay / 1000.0)
|
|
163
283
|
end
|
|
164
284
|
drop(target, data)
|
|
165
285
|
up
|
|
166
286
|
end
|
|
287
|
+
|
|
288
|
+
private def state
|
|
289
|
+
@state_mutex.synchronize do
|
|
290
|
+
merged = {
|
|
291
|
+
position: {
|
|
292
|
+
x: @base_state[:position][:x],
|
|
293
|
+
y: @base_state[:position][:y],
|
|
294
|
+
},
|
|
295
|
+
buttons: @base_state[:buttons],
|
|
296
|
+
}
|
|
297
|
+
@transactions.each do |transaction|
|
|
298
|
+
if transaction.key?(:position)
|
|
299
|
+
merged[:position] = transaction[:position]
|
|
300
|
+
end
|
|
301
|
+
if transaction.key?(:buttons)
|
|
302
|
+
merged[:buttons] = transaction[:buttons]
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
merged
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# @rbs block: Proc -- Block receiving state update callback
|
|
310
|
+
# @rbs return: untyped -- Block result
|
|
311
|
+
private def with_transaction(&block)
|
|
312
|
+
transaction = {}
|
|
313
|
+
@state_mutex.synchronize do
|
|
314
|
+
@transactions << transaction
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
begin
|
|
318
|
+
update_state = lambda do |updates|
|
|
319
|
+
@state_mutex.synchronize do
|
|
320
|
+
transaction.merge!(updates)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
block.call(update_state)
|
|
324
|
+
|
|
325
|
+
@state_mutex.synchronize do
|
|
326
|
+
@base_state = merge_state(@base_state, transaction)
|
|
327
|
+
@transactions.delete(transaction)
|
|
328
|
+
end
|
|
329
|
+
rescue
|
|
330
|
+
@state_mutex.synchronize do
|
|
331
|
+
@transactions.delete(transaction)
|
|
332
|
+
end
|
|
333
|
+
raise
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
private def merge_state(base_state, transaction)
|
|
338
|
+
merged = base_state.dup
|
|
339
|
+
merged[:position] = transaction[:position] if transaction.key?(:position)
|
|
340
|
+
merged[:buttons] = transaction[:buttons] if transaction.key?(:buttons)
|
|
341
|
+
merged
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
private def button_flag(button)
|
|
345
|
+
case button
|
|
346
|
+
when Button::LEFT
|
|
347
|
+
ButtonFlag::LEFT
|
|
348
|
+
when Button::RIGHT
|
|
349
|
+
ButtonFlag::RIGHT
|
|
350
|
+
when Button::MIDDLE
|
|
351
|
+
ButtonFlag::MIDDLE
|
|
352
|
+
when Button::BACK
|
|
353
|
+
ButtonFlag::BACK
|
|
354
|
+
when Button::FORWARD
|
|
355
|
+
ButtonFlag::FORWARD
|
|
356
|
+
else
|
|
357
|
+
raise Puppeteer::Error.new("Unsupported mouse button: #{button}")
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
private def button_from_pressed_buttons(buttons)
|
|
362
|
+
if (buttons & ButtonFlag::LEFT) != 0
|
|
363
|
+
Button::LEFT
|
|
364
|
+
elsif (buttons & ButtonFlag::RIGHT) != 0
|
|
365
|
+
Button::RIGHT
|
|
366
|
+
elsif (buttons & ButtonFlag::MIDDLE) != 0
|
|
367
|
+
Button::MIDDLE
|
|
368
|
+
elsif (buttons & ButtonFlag::BACK) != 0
|
|
369
|
+
Button::BACK
|
|
370
|
+
elsif (buttons & ButtonFlag::FORWARD) != 0
|
|
371
|
+
Button::FORWARD
|
|
372
|
+
else
|
|
373
|
+
Button::NONE
|
|
374
|
+
end
|
|
375
|
+
end
|
|
167
376
|
end
|
|
@@ -44,6 +44,7 @@ class Puppeteer::NetworkEventManager
|
|
|
44
44
|
# handle redirects, we have to make them Arrays to represent the chain of
|
|
45
45
|
# events.
|
|
46
46
|
@response_received_extra_info_map = {}
|
|
47
|
+
@request_will_be_sent_extra_info_map = {}
|
|
47
48
|
@queued_redirect_info_map = {}
|
|
48
49
|
@queued_event_group_map = {}
|
|
49
50
|
end
|
|
@@ -51,11 +52,16 @@ class Puppeteer::NetworkEventManager
|
|
|
51
52
|
def forget(network_request_id)
|
|
52
53
|
@request_will_be_sent_map.delete(network_request_id)
|
|
53
54
|
@request_paused_map.delete(network_request_id)
|
|
55
|
+
@request_will_be_sent_extra_info_map.delete(network_request_id)
|
|
54
56
|
@queued_event_group_map.delete(network_request_id)
|
|
55
57
|
@queued_redirect_info_map.delete(network_request_id)
|
|
56
58
|
@response_received_extra_info_map.delete(network_request_id)
|
|
57
59
|
end
|
|
58
60
|
|
|
61
|
+
def request_extra_info(network_request_id)
|
|
62
|
+
@request_will_be_sent_extra_info_map[network_request_id] ||= []
|
|
63
|
+
end
|
|
64
|
+
|
|
59
65
|
def response_extra_info(network_request_id)
|
|
60
66
|
@response_received_extra_info_map[network_request_id] ||= []
|
|
61
67
|
end
|
|
@@ -108,6 +114,7 @@ class Puppeteer::NetworkEventManager
|
|
|
108
114
|
@http_requests_map[network_request_id]
|
|
109
115
|
end
|
|
110
116
|
|
|
117
|
+
|
|
111
118
|
def forget_request(network_request_id)
|
|
112
119
|
@http_requests_map.delete(network_request_id)
|
|
113
120
|
end
|