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
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async"
|
|
4
|
+
require "async/promise"
|
|
5
|
+
require "delegate"
|
|
6
|
+
require "thread"
|
|
7
|
+
|
|
8
|
+
module Puppeteer
|
|
9
|
+
# Runs a dedicated Async reactor in a background thread and proxies calls into it.
|
|
10
|
+
class ReactorRunner
|
|
11
|
+
class Finalizer
|
|
12
|
+
def initialize(queue, thread)
|
|
13
|
+
@queue = queue
|
|
14
|
+
@thread = thread
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(_id)
|
|
18
|
+
@queue.close
|
|
19
|
+
@thread.join unless ::Thread.current == @thread
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Proxy < SimpleDelegator
|
|
24
|
+
# @param runner [ReactorRunner]
|
|
25
|
+
# @param target [Object]
|
|
26
|
+
# @param owns_runner [Boolean]
|
|
27
|
+
def initialize(runner, target, owns_runner: false)
|
|
28
|
+
super(target)
|
|
29
|
+
@runner = runner
|
|
30
|
+
@owns_runner = owns_runner
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Override tap to distinguish between Ruby's Object#tap and Puppeteer's tap method.
|
|
34
|
+
# When called with a block only (Ruby's tap), delegate to super.
|
|
35
|
+
# When called with args (Puppeteer's tap), route through the reactor.
|
|
36
|
+
def tap(*args, **kwargs, &block)
|
|
37
|
+
if args.empty? && kwargs.empty? && block
|
|
38
|
+
super(&block)
|
|
39
|
+
else
|
|
40
|
+
@runner.sync do
|
|
41
|
+
args = args.map { |arg| @runner.unwrap(arg) }
|
|
42
|
+
kwargs = kwargs.transform_values { |value| @runner.unwrap(value) }
|
|
43
|
+
result = __getobj__.public_send(:tap, *args, **kwargs, &block)
|
|
44
|
+
@runner.wrap(result)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
50
|
+
if @runner.closed?
|
|
51
|
+
return false if name == :connected?
|
|
52
|
+
return nil if @owns_runner && close_like?(name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
@runner.sync do
|
|
57
|
+
args = args.map { |arg| @runner.unwrap(arg) }
|
|
58
|
+
kwargs = kwargs.transform_values { |value| @runner.unwrap(value) }
|
|
59
|
+
result = __getobj__.public_send(name, *args, **kwargs, &block)
|
|
60
|
+
@runner.wrap(result)
|
|
61
|
+
end
|
|
62
|
+
ensure
|
|
63
|
+
if @owns_runner && close_like?(name)
|
|
64
|
+
@runner.wait_until_idle
|
|
65
|
+
@runner.close
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def respond_to_missing?(name, include_private = false)
|
|
71
|
+
__getobj__.respond_to?(name, include_private) || super
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def class
|
|
75
|
+
__getobj__.class
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def is_a?(klass)
|
|
79
|
+
return true if klass == Proxy || klass == self.class
|
|
80
|
+
|
|
81
|
+
__getobj__.is_a?(klass)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
alias kind_of? is_a?
|
|
85
|
+
|
|
86
|
+
def instance_of?(klass)
|
|
87
|
+
return true if klass == Proxy || klass == self.class
|
|
88
|
+
|
|
89
|
+
__getobj__.instance_of?(klass)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def ==(other)
|
|
93
|
+
__getobj__ == @runner.unwrap(other)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def eql?(other)
|
|
97
|
+
__getobj__.eql?(@runner.unwrap(other))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def hash
|
|
101
|
+
__getobj__.hash
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private def close_like?(name)
|
|
105
|
+
name == :close || name == :disconnect
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def initialize
|
|
110
|
+
@queue = Thread::Queue.new
|
|
111
|
+
@ready = Queue.new
|
|
112
|
+
@closed = false
|
|
113
|
+
@thread = Thread.new do
|
|
114
|
+
Sync do |task|
|
|
115
|
+
barrier = Async::Barrier.new(parent: task)
|
|
116
|
+
@barrier = barrier
|
|
117
|
+
@ready << true
|
|
118
|
+
begin
|
|
119
|
+
while (job = @queue.pop)
|
|
120
|
+
barrier.async do
|
|
121
|
+
job.call
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
rescue ClosedQueueError
|
|
125
|
+
# Queue closed; exit the reactor loop.
|
|
126
|
+
ensure
|
|
127
|
+
barrier.stop
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
ensure
|
|
131
|
+
@barrier = nil
|
|
132
|
+
@closed = true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
ObjectSpace.define_finalizer(self, Finalizer.new(@queue, @thread))
|
|
136
|
+
@ready.pop
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def sync(&block)
|
|
140
|
+
return block.call if runner_thread?
|
|
141
|
+
raise ::Puppeteer::Error.new("ReactorRunner is closed") if closed?
|
|
142
|
+
|
|
143
|
+
promise = Async::Promise.new
|
|
144
|
+
job = lambda do
|
|
145
|
+
begin
|
|
146
|
+
promise.resolve(block.call)
|
|
147
|
+
rescue Exception => err
|
|
148
|
+
promise.reject(err)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
begin
|
|
153
|
+
@queue << job
|
|
154
|
+
rescue ClosedQueueError
|
|
155
|
+
raise ::Puppeteer::Error.new("ReactorRunner is closed")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
promise.wait
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def close
|
|
162
|
+
return if closed?
|
|
163
|
+
|
|
164
|
+
@closed = true
|
|
165
|
+
@queue.close
|
|
166
|
+
@thread.join unless runner_thread?
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def wait_until_idle(timeout: 1.0)
|
|
170
|
+
return if closed?
|
|
171
|
+
|
|
172
|
+
sync do
|
|
173
|
+
return unless @barrier
|
|
174
|
+
|
|
175
|
+
deadline = timeout ? Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout : nil
|
|
176
|
+
loop do
|
|
177
|
+
break if @barrier.empty?
|
|
178
|
+
break if deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
179
|
+
|
|
180
|
+
Async::Task.current.sleep(0.01)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
rescue Puppeteer::Error
|
|
184
|
+
# Runner closed while waiting; ignore.
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def closed?
|
|
188
|
+
@closed
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def wrap(value)
|
|
192
|
+
return value if value.nil? || value.is_a?(Proxy)
|
|
193
|
+
|
|
194
|
+
if value.is_a?(Array)
|
|
195
|
+
return value.map { |item| wrap(item) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
return Proxy.new(self, value) if proxyable?(value)
|
|
199
|
+
|
|
200
|
+
value
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def unwrap(value, seen = nil)
|
|
204
|
+
seen ||= {}
|
|
205
|
+
|
|
206
|
+
case value
|
|
207
|
+
when Proxy
|
|
208
|
+
value.__getobj__
|
|
209
|
+
when Array
|
|
210
|
+
object_id = value.object_id
|
|
211
|
+
return seen[object_id] if seen.key?(object_id)
|
|
212
|
+
|
|
213
|
+
result = []
|
|
214
|
+
seen[object_id] = result
|
|
215
|
+
value.each { |item| result << unwrap(item, seen) }
|
|
216
|
+
result
|
|
217
|
+
when Hash
|
|
218
|
+
object_id = value.object_id
|
|
219
|
+
return seen[object_id] if seen.key?(object_id)
|
|
220
|
+
|
|
221
|
+
result = {}
|
|
222
|
+
seen[object_id] = result
|
|
223
|
+
value.each do |key, item|
|
|
224
|
+
result[unwrap(key, seen)] = unwrap(item, seen)
|
|
225
|
+
end
|
|
226
|
+
result
|
|
227
|
+
else
|
|
228
|
+
value
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
private def runner_thread?
|
|
233
|
+
Thread.current == @thread
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
private def proxyable?(value)
|
|
237
|
+
return false if value.is_a?(Module) || value.is_a?(Class)
|
|
238
|
+
|
|
239
|
+
name = value.class.name
|
|
240
|
+
return false unless name&.start_with?("Puppeteer")
|
|
241
|
+
return false if name.start_with?("Puppeteer::Bidi")
|
|
242
|
+
return false if value.is_a?(ReactorRunner) || value.is_a?(Proxy)
|
|
243
|
+
|
|
244
|
+
true
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
1
5
|
# providing #valueFromRemoteObject, #releaseObject
|
|
2
6
|
class Puppeteer::RemoteObject
|
|
3
7
|
include Puppeteer::DebugPrint
|
|
4
8
|
using Puppeteer::DefineAsyncMethod
|
|
5
9
|
|
|
10
|
+
UNSERIALIZABLE_SENTINEL_KEY = '__puppeteer_unserializable__'
|
|
11
|
+
NUMBER_SENTINEL_KEY = '__puppeteer_number__'
|
|
12
|
+
REGEXP_SENTINEL_KEY = '__puppeteer_regexp__'
|
|
13
|
+
|
|
6
14
|
# @param payload [Hash]
|
|
7
15
|
def initialize(payload)
|
|
8
16
|
@object_id = payload['objectId']
|
|
@@ -10,37 +18,19 @@ class Puppeteer::RemoteObject
|
|
|
10
18
|
@sub_type = payload['subtype']
|
|
11
19
|
@unserializable_value = payload['unserializableValue']
|
|
12
20
|
@value = payload['value']
|
|
21
|
+
@description = payload['description']
|
|
13
22
|
end
|
|
14
23
|
|
|
15
|
-
attr_reader :sub_type
|
|
24
|
+
attr_reader :sub_type, :type, :description
|
|
16
25
|
|
|
17
|
-
# @return
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# original logic:
|
|
22
|
-
# if (this._remoteObject.objectId) {
|
|
23
|
-
# const response = await this._client.send('Runtime.callFunctionOn', {
|
|
24
|
-
# functionDeclaration: 'function() { return this; }',
|
|
25
|
-
# objectId: this._remoteObject.objectId,
|
|
26
|
-
# returnByValue: true,
|
|
27
|
-
# awaitPromise: true,
|
|
28
|
-
# });
|
|
29
|
-
# return helper.valueFromRemoteObject(response.result);
|
|
30
|
-
# }
|
|
26
|
+
# @rbs return: bool
|
|
27
|
+
def object_id?
|
|
28
|
+
!@object_id.nil?
|
|
29
|
+
end
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'objectId': @object_id,
|
|
36
|
-
'returnByValue': true,
|
|
37
|
-
'awaitPromise': true,
|
|
38
|
-
}
|
|
39
|
-
response = client.send_message('Runtime.callFunctionOn', params)
|
|
40
|
-
Puppeteer::RemoteObject.new(response['result'])
|
|
41
|
-
else
|
|
42
|
-
nil
|
|
43
|
-
end
|
|
31
|
+
# @rbs return: String?
|
|
32
|
+
def object_id_value
|
|
33
|
+
@object_id
|
|
44
34
|
end
|
|
45
35
|
|
|
46
36
|
# @return [String]
|
|
@@ -52,7 +42,10 @@ class Puppeteer::RemoteObject
|
|
|
52
42
|
# return 'JSHandle@' + type;
|
|
53
43
|
# }
|
|
54
44
|
if @object_id
|
|
55
|
-
@sub_type
|
|
45
|
+
return @sub_type if @sub_type
|
|
46
|
+
return 'window' if @type == 'object' && @description == 'Window'
|
|
47
|
+
|
|
48
|
+
@type
|
|
56
49
|
else
|
|
57
50
|
nil
|
|
58
51
|
end
|
|
@@ -82,13 +75,12 @@ class Puppeteer::RemoteObject
|
|
|
82
75
|
def box_model(client)
|
|
83
76
|
result = client.send_message('DOM.getBoxModel', objectId: @object_id)
|
|
84
77
|
|
|
85
|
-
#
|
|
86
|
-
# while Chrome throws Error(Could not compute box model)
|
|
78
|
+
# Some browsers return zeroed box model data instead of throwing errors.
|
|
87
79
|
model = result['model']
|
|
88
80
|
if model['width'] == 0 && model['height'] == 0 &&
|
|
89
81
|
%w(content padding border margin).all? { |key| model[key].all?(&:nil?) }
|
|
90
82
|
|
|
91
|
-
debug_puts('Could not compute box model
|
|
83
|
+
debug_puts('Could not compute box model.')
|
|
92
84
|
return nil
|
|
93
85
|
end
|
|
94
86
|
result
|
|
@@ -113,24 +105,112 @@ class Puppeteer::RemoteObject
|
|
|
113
105
|
# helper#valueFromRemoteObject
|
|
114
106
|
def value
|
|
115
107
|
if @unserializable_value
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
108
|
+
if @type == 'bigint' || @unserializable_value.end_with?('n')
|
|
109
|
+
return Integer(@unserializable_value.delete_suffix('n'))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
case @unserializable_value
|
|
113
|
+
when '-0'
|
|
114
|
+
-0.0
|
|
115
|
+
when 'NaN'
|
|
116
|
+
Float::NAN
|
|
117
|
+
when 'Infinity'
|
|
118
|
+
Float::INFINITY
|
|
119
|
+
when '-Infinity'
|
|
120
|
+
-Float::INFINITY
|
|
121
|
+
else
|
|
122
|
+
raise NotImplementedError.new("Unsupported unserializable value: #{@unserializable_value}")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if @sub_type == 'date'
|
|
127
|
+
return parse_date_value(@value)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if @sub_type == 'regexp' && @description
|
|
131
|
+
source, flags = parse_regexp(@description)
|
|
132
|
+
return Regexp.new(source, regexp_options(flags))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
normalize_serialized_value(@value)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @rbs value: untyped -- Serialized value from JavaScript
|
|
139
|
+
# @rbs return: untyped -- Normalized Ruby value
|
|
140
|
+
private def normalize_serialized_value(value)
|
|
141
|
+
case value
|
|
142
|
+
when Array
|
|
143
|
+
value.map { |item| normalize_serialized_value(item) }
|
|
144
|
+
when Hash
|
|
145
|
+
if value.keys == [UNSERIALIZABLE_SENTINEL_KEY] && value[UNSERIALIZABLE_SENTINEL_KEY] == true
|
|
146
|
+
nil
|
|
147
|
+
elsif value.keys == [NUMBER_SENTINEL_KEY]
|
|
148
|
+
unserializable_number(value[NUMBER_SENTINEL_KEY])
|
|
149
|
+
elsif value.keys == [REGEXP_SENTINEL_KEY] && value[REGEXP_SENTINEL_KEY].is_a?(Hash)
|
|
150
|
+
regexp_value = value[REGEXP_SENTINEL_KEY]
|
|
151
|
+
source = regexp_value['source'].to_s
|
|
152
|
+
flags = regexp_value['flags'].to_s
|
|
153
|
+
Regexp.new(source, regexp_options(flags))
|
|
154
|
+
else
|
|
155
|
+
value.transform_values { |item| normalize_serialized_value(item) }
|
|
156
|
+
end
|
|
157
|
+
else
|
|
158
|
+
value
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @rbs value: String -- Special number string
|
|
163
|
+
# @rbs return: Float | String -- Parsed number or original value
|
|
164
|
+
private def unserializable_number(value)
|
|
165
|
+
case value
|
|
166
|
+
when '-0'
|
|
167
|
+
-0.0
|
|
168
|
+
when 'NaN'
|
|
169
|
+
Float::NAN
|
|
170
|
+
when 'Infinity'
|
|
171
|
+
Float::INFINITY
|
|
172
|
+
when '-Infinity'
|
|
173
|
+
-Float::INFINITY
|
|
174
|
+
else
|
|
175
|
+
value
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# @rbs description: String -- Regexp description string
|
|
180
|
+
# @rbs return: [String, String] -- Source and flags tuple
|
|
181
|
+
private def parse_regexp(description)
|
|
182
|
+
return [description, ''] unless description.start_with?('/')
|
|
183
|
+
|
|
184
|
+
last_slash = description.rindex('/')
|
|
185
|
+
return [description, ''] unless last_slash && last_slash > 0
|
|
186
|
+
|
|
187
|
+
source = description[1...last_slash] || ''
|
|
188
|
+
flags = description[(last_slash + 1)..] || ''
|
|
189
|
+
[source, flags]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @rbs value: untyped -- Date value from CDP
|
|
193
|
+
# @rbs return: Time? -- Parsed Ruby time
|
|
194
|
+
private def parse_date_value(value)
|
|
195
|
+
case value
|
|
196
|
+
when String
|
|
197
|
+
Time.iso8601(value)
|
|
198
|
+
when Numeric
|
|
199
|
+
Time.at(value / 1000.0)
|
|
131
200
|
else
|
|
132
|
-
|
|
201
|
+
nil
|
|
133
202
|
end
|
|
203
|
+
rescue ArgumentError
|
|
204
|
+
value
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @rbs flags: String -- Regexp flags string
|
|
208
|
+
# @rbs return: Integer -- Ruby Regexp options
|
|
209
|
+
private def regexp_options(flags)
|
|
210
|
+
options = 0
|
|
211
|
+
options |= Regexp::IGNORECASE if flags.include?('i')
|
|
212
|
+
options |= Regexp::MULTILINE if flags.include?('m')
|
|
213
|
+
options
|
|
134
214
|
end
|
|
135
215
|
|
|
136
216
|
# @param client [Puppeteer::CDPSession]
|
data/lib/puppeteer/target.rb
CHANGED
|
@@ -18,6 +18,7 @@ class Puppeteer::Target
|
|
|
18
18
|
# @param {!function():!Promise<!Puppeteer.CDPSession>} sessionFactory
|
|
19
19
|
# @param {boolean} ignoreHTTPSErrors
|
|
20
20
|
# @param {?Puppeteer.Viewport} defaultViewport
|
|
21
|
+
# @param {boolean} networkEnabled
|
|
21
22
|
def initialize(target_info:,
|
|
22
23
|
session:,
|
|
23
24
|
browser_context:,
|
|
@@ -25,6 +26,7 @@ class Puppeteer::Target
|
|
|
25
26
|
session_factory:,
|
|
26
27
|
ignore_https_errors:,
|
|
27
28
|
default_viewport:,
|
|
29
|
+
network_enabled:,
|
|
28
30
|
is_page_target_callback:)
|
|
29
31
|
@session = session
|
|
30
32
|
@target_manager = target_manager
|
|
@@ -34,36 +36,59 @@ class Puppeteer::Target
|
|
|
34
36
|
@session_factory = session_factory
|
|
35
37
|
@ignore_https_errors = ignore_https_errors
|
|
36
38
|
@default_viewport = default_viewport
|
|
39
|
+
@network_enabled = network_enabled
|
|
37
40
|
@is_page_target_callback = is_page_target_callback
|
|
41
|
+
@worker = nil
|
|
38
42
|
|
|
39
43
|
# /** @type {?Promise<!Puppeteer.Page>} */
|
|
40
44
|
# this._pagePromise = null;
|
|
41
45
|
# /** @type {?Promise<!Worker>} */
|
|
42
46
|
# this._workerPromise = null;
|
|
43
|
-
@initialize_callback_promise =
|
|
44
|
-
@initialized_promise =
|
|
45
|
-
|
|
47
|
+
@initialize_callback_promise = Async::Promise.new
|
|
48
|
+
@initialized_promise = Async::Promise.new
|
|
49
|
+
@is_closed_promise = Async::Promise.new
|
|
50
|
+
|
|
51
|
+
Async do
|
|
52
|
+
@initialized_promise.resolve(handle_initialized(@initialize_callback_promise.wait))
|
|
53
|
+
rescue => err
|
|
54
|
+
@initialized_promise.reject(err)
|
|
46
55
|
end
|
|
47
|
-
@is_closed_promise = resolvable_future
|
|
48
56
|
|
|
49
57
|
@is_initialized = !@is_page_target_callback.call(@target_info) || !@target_info.url.empty?
|
|
50
58
|
|
|
51
59
|
if @is_initialized
|
|
52
|
-
@initialize_callback_promise.
|
|
60
|
+
@initialize_callback_promise.resolve(true)
|
|
53
61
|
end
|
|
54
62
|
end
|
|
55
63
|
|
|
56
64
|
attr_reader :target_id, :target_info, :initialized_promise, :is_closed_promise
|
|
57
65
|
|
|
66
|
+
def ==(other)
|
|
67
|
+
other = other.__getobj__ if other.is_a?(Puppeteer::ReactorRunner::Proxy)
|
|
68
|
+
return true if equal?(other)
|
|
69
|
+
return false unless other.is_a?(Puppeteer::Target)
|
|
70
|
+
return false if target_id.nil? || other.target_id.nil?
|
|
71
|
+
|
|
72
|
+
target_id == other.target_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def eql?(other)
|
|
76
|
+
self == other
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def hash
|
|
80
|
+
target_id ? target_id.hash : super
|
|
81
|
+
end
|
|
82
|
+
|
|
58
83
|
def closed_callback
|
|
59
|
-
@is_closed_promise.
|
|
84
|
+
@is_closed_promise.resolve(true) unless @is_closed_promise.resolved?
|
|
60
85
|
end
|
|
61
86
|
|
|
62
|
-
class InitializeFailure <
|
|
87
|
+
class InitializeFailure < Puppeteer::Error; end
|
|
63
88
|
|
|
64
89
|
def ignore_initialize_callback_promise
|
|
65
|
-
unless @initialize_callback_promise.
|
|
66
|
-
@initialize_callback_promise.
|
|
90
|
+
unless @initialize_callback_promise.resolved?
|
|
91
|
+
@initialize_callback_promise.resolve(false)
|
|
67
92
|
end
|
|
68
93
|
end
|
|
69
94
|
|
|
@@ -93,7 +118,9 @@ class Puppeteer::Target
|
|
|
93
118
|
end
|
|
94
119
|
|
|
95
120
|
def create_cdp_session
|
|
96
|
-
@session_factory.call(false)
|
|
121
|
+
session = @session_factory.call(false)
|
|
122
|
+
session.target = self if session.respond_to?(:target=)
|
|
123
|
+
session
|
|
97
124
|
end
|
|
98
125
|
|
|
99
126
|
def target_manager
|
|
@@ -103,34 +130,54 @@ class Puppeteer::Target
|
|
|
103
130
|
def page
|
|
104
131
|
if @is_page_target_callback.call(@target_info) && @page.nil?
|
|
105
132
|
client = @session || @session_factory.call(true)
|
|
106
|
-
|
|
133
|
+
client.wait_for_ready if client.respond_to?(:wait_for_ready)
|
|
134
|
+
@page = Puppeteer::Page.create(
|
|
135
|
+
client,
|
|
136
|
+
self,
|
|
137
|
+
@ignore_https_errors,
|
|
138
|
+
@default_viewport,
|
|
139
|
+
network_enabled: @network_enabled,
|
|
140
|
+
)
|
|
107
141
|
end
|
|
108
142
|
@page
|
|
109
143
|
end
|
|
110
144
|
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
# @return [Puppeteer::CdpWebWorker|nil]
|
|
146
|
+
def worker
|
|
147
|
+
return nil unless ['service_worker', 'shared_worker'].include?(@target_info.type)
|
|
148
|
+
return @worker if @worker
|
|
149
|
+
|
|
150
|
+
if @target_info.type == 'service_worker'
|
|
151
|
+
@target_manager&.wait_for_service_worker_detach(@target_id)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
client =
|
|
155
|
+
if @target_info.type == 'service_worker'
|
|
156
|
+
@session_factory.call(false)
|
|
157
|
+
else
|
|
158
|
+
@session || @session_factory.call(false)
|
|
159
|
+
end
|
|
160
|
+
client.target = self if client.respond_to?(:target=)
|
|
161
|
+
client.wait_for_ready if client.respond_to?(:wait_for_ready)
|
|
162
|
+
@worker = Puppeteer::CdpWebWorker.new(
|
|
163
|
+
client,
|
|
164
|
+
@target_info.url,
|
|
165
|
+
@target_id,
|
|
166
|
+
@target_info.type,
|
|
167
|
+
nil,
|
|
168
|
+
nil,
|
|
169
|
+
)
|
|
170
|
+
end
|
|
124
171
|
|
|
125
172
|
# @return {string}
|
|
126
173
|
def url
|
|
127
174
|
@target_info.url
|
|
128
175
|
end
|
|
129
176
|
|
|
130
|
-
# @return {"page"|"background_page"|"service_worker"|"shared_worker"|"other"|"browser"}
|
|
177
|
+
# @return {"page"|"background_page"|"service_worker"|"shared_worker"|"webview"|"tab"|"other"|"browser"}
|
|
131
178
|
def type
|
|
132
179
|
type = @target_info.type
|
|
133
|
-
if ['page', 'background_page', 'service_worker', 'shared_worker', 'browser'].include?(type)
|
|
180
|
+
if ['page', 'background_page', 'service_worker', 'shared_worker', 'webview', 'tab', 'browser'].include?(type)
|
|
134
181
|
type
|
|
135
182
|
else
|
|
136
183
|
'other'
|
|
@@ -165,7 +212,7 @@ class Puppeteer::Target
|
|
|
165
212
|
|
|
166
213
|
if !@is_initialized && (!@is_page_target_callback.call(@target_info) || !@target_info.url.empty?)
|
|
167
214
|
@is_initialized = true
|
|
168
|
-
@initialize_callback_promise.
|
|
215
|
+
@initialize_callback_promise.resolve(true)
|
|
169
216
|
end
|
|
170
217
|
end
|
|
171
218
|
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require 'timeout'
|
|
2
|
-
|
|
3
1
|
class Puppeteer::TimeoutHelper
|
|
4
2
|
# @param timeout_ms [String|Integer|nil]
|
|
5
3
|
# @param default_timeout_ms [Integer]
|
|
@@ -9,14 +7,12 @@ class Puppeteer::TimeoutHelper
|
|
|
9
7
|
end
|
|
10
8
|
|
|
11
9
|
def with_timeout(&block)
|
|
12
|
-
if @timeout_ms
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
else
|
|
19
|
-
block.call
|
|
10
|
+
return block.call if @timeout_ms <= 0
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
Puppeteer::AsyncUtils.async_timeout(@timeout_ms, &block).wait
|
|
14
|
+
rescue Async::TimeoutError
|
|
15
|
+
raise Puppeteer::TimeoutError.new("waiting for #{@task_name} failed: timeout #{@timeout_ms}ms exceeded")
|
|
20
16
|
end
|
|
21
17
|
end
|
|
22
18
|
end
|