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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -3
  3. data/AGENTS.md +169 -0
  4. data/CLAUDE/README.md +41 -0
  5. data/CLAUDE/architecture.md +253 -0
  6. data/CLAUDE/cdp_protocol.md +230 -0
  7. data/CLAUDE/concurrency.md +216 -0
  8. data/CLAUDE/porting_puppeteer.md +575 -0
  9. data/CLAUDE/rbs_type_checking.md +101 -0
  10. data/CLAUDE/spec_migration_plans.md +1041 -0
  11. data/CLAUDE/testing.md +278 -0
  12. data/CLAUDE.md +242 -0
  13. data/README.md +8 -0
  14. data/Rakefile +7 -0
  15. data/Steepfile +28 -0
  16. data/docs/api_coverage.md +105 -56
  17. data/lib/puppeteer/aria_query_handler.rb +3 -2
  18. data/lib/puppeteer/async_utils.rb +214 -0
  19. data/lib/puppeteer/browser.rb +98 -56
  20. data/lib/puppeteer/browser_connector.rb +18 -3
  21. data/lib/puppeteer/browser_context.rb +196 -3
  22. data/lib/puppeteer/browser_runner.rb +18 -10
  23. data/lib/puppeteer/cdp_session.rb +67 -23
  24. data/lib/puppeteer/chrome_target_manager.rb +65 -40
  25. data/lib/puppeteer/connection.rb +55 -36
  26. data/lib/puppeteer/console_message.rb +9 -1
  27. data/lib/puppeteer/console_patch.rb +47 -0
  28. data/lib/puppeteer/css_coverage.rb +5 -3
  29. data/lib/puppeteer/custom_query_handler.rb +80 -33
  30. data/lib/puppeteer/define_async_method.rb +31 -37
  31. data/lib/puppeteer/dialog.rb +47 -14
  32. data/lib/puppeteer/element_handle.rb +231 -62
  33. data/lib/puppeteer/emulation_manager.rb +1 -1
  34. data/lib/puppeteer/env.rb +1 -1
  35. data/lib/puppeteer/errors.rb +25 -2
  36. data/lib/puppeteer/event_callbackable.rb +15 -0
  37. data/lib/puppeteer/events.rb +4 -0
  38. data/lib/puppeteer/execution_context.rb +148 -3
  39. data/lib/puppeteer/file_chooser.rb +6 -0
  40. data/lib/puppeteer/frame.rb +162 -91
  41. data/lib/puppeteer/frame_manager.rb +69 -48
  42. data/lib/puppeteer/http_request.rb +114 -38
  43. data/lib/puppeteer/http_response.rb +24 -7
  44. data/lib/puppeteer/isolated_world.rb +64 -41
  45. data/lib/puppeteer/js_coverage.rb +5 -3
  46. data/lib/puppeteer/js_handle.rb +58 -16
  47. data/lib/puppeteer/keyboard.rb +30 -17
  48. data/lib/puppeteer/launcher/browser_options.rb +3 -1
  49. data/lib/puppeteer/launcher/chrome.rb +8 -5
  50. data/lib/puppeteer/launcher/launch_options.rb +7 -2
  51. data/lib/puppeteer/launcher.rb +4 -8
  52. data/lib/puppeteer/lifecycle_watcher.rb +38 -22
  53. data/lib/puppeteer/mouse.rb +273 -64
  54. data/lib/puppeteer/network_event_manager.rb +7 -0
  55. data/lib/puppeteer/network_manager.rb +393 -112
  56. data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
  57. data/lib/puppeteer/page.rb +568 -226
  58. data/lib/puppeteer/puppeteer.rb +171 -64
  59. data/lib/puppeteer/query_handler_manager.rb +112 -16
  60. data/lib/puppeteer/reactor_runner.rb +247 -0
  61. data/lib/puppeteer/remote_object.rb +127 -47
  62. data/lib/puppeteer/target.rb +74 -27
  63. data/lib/puppeteer/task_manager.rb +3 -1
  64. data/lib/puppeteer/timeout_helper.rb +6 -10
  65. data/lib/puppeteer/touch_handle.rb +39 -0
  66. data/lib/puppeteer/touch_screen.rb +72 -22
  67. data/lib/puppeteer/tracing.rb +3 -3
  68. data/lib/puppeteer/version.rb +1 -1
  69. data/lib/puppeteer/wait_task.rb +264 -101
  70. data/lib/puppeteer/web_socket.rb +2 -2
  71. data/lib/puppeteer/web_socket_transport.rb +91 -27
  72. data/lib/puppeteer/web_worker.rb +175 -0
  73. data/lib/puppeteer.rb +20 -4
  74. data/puppeteer-ruby.gemspec +15 -11
  75. data/sig/_external.rbs +8 -0
  76. data/sig/_supplementary.rbs +314 -0
  77. data/sig/puppeteer/browser.rbs +166 -0
  78. data/sig/puppeteer/cdp_session.rbs +64 -0
  79. data/sig/puppeteer/dialog.rbs +41 -0
  80. data/sig/puppeteer/element_handle.rbs +305 -0
  81. data/sig/puppeteer/execution_context.rbs +87 -0
  82. data/sig/puppeteer/frame.rbs +226 -0
  83. data/sig/puppeteer/http_request.rbs +214 -0
  84. data/sig/puppeteer/http_response.rbs +89 -0
  85. data/sig/puppeteer/js_handle.rbs +64 -0
  86. data/sig/puppeteer/keyboard.rbs +40 -0
  87. data/sig/puppeteer/mouse.rbs +113 -0
  88. data/sig/puppeteer/page.rbs +515 -0
  89. data/sig/puppeteer/puppeteer.rbs +98 -0
  90. data/sig/puppeteer/remote_object.rbs +78 -0
  91. data/sig/puppeteer/touch_handle.rbs +21 -0
  92. data/sig/puppeteer/touch_screen.rbs +35 -0
  93. data/sig/puppeteer/web_worker.rbs +83 -0
  94. metadata +116 -45
  95. data/CHANGELOG.md +0 -397
  96. data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
  97. data/lib/puppeteer/firefox_target_manager.rb +0 -157
  98. 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 [Future<Puppeteer::RemoteObject|nil>]
18
- def evaluate_self(client)
19
- # ported logic from JSHandle#json_value.
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
- if @object_id
33
- params = {
34
- 'functionDeclaration': 'function() { return this; }',
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 || @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
- # Firefox returns width/height = 0, content/padding/border/margin = [nil, nil, nil, nil, nil, nil, nil, nil]
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 in Firefox.')
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
- # if (remoteObject.type === 'bigint' && typeof BigInt !== 'undefined')
117
- # return BigInt(remoteObject.unserializableValue.replace('n', ''));
118
- # switch (remoteObject.unserializableValue) {
119
- # case '-0':
120
- # return -0;
121
- # case 'NaN':
122
- # return NaN;
123
- # case 'Infinity':
124
- # return Infinity;
125
- # case '-Infinity':
126
- # return -Infinity;
127
- # default:
128
- # throw new Error('Unsupported unserializable value: ' + remoteObject.unserializableValue);
129
- # }
130
- raise NotImplementedError.new('unserializable_value is not implemented yet')
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
- @value
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]
@@ -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 = resolvable_future
44
- @initialized_promise = @initialize_callback_promise.then do |success|
45
- handle_initialized(success)
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.fulfill(true)
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.fulfill(true)
84
+ @is_closed_promise.resolve(true) unless @is_closed_promise.resolved?
60
85
  end
61
86
 
62
- class InitializeFailure < StandardError; end
87
+ class InitializeFailure < Puppeteer::Error; end
63
88
 
64
89
  def ignore_initialize_callback_promise
65
- unless @initialize_callback_promise.fulfilled?
66
- @initialize_callback_promise.fulfill(false)
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
- @page = Puppeteer::Page.create(client, self, @ignore_https_errors, @default_viewport)
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
- # * @return {!Promise<?Worker>}
113
- # */
114
- # async worker() {
115
- # if (this._targetInfo.type !== 'service_worker' && this._targetInfo.type !== 'shared_worker')
116
- # return null;
117
- # if (!this._workerPromise) {
118
- # // TODO(einbinder): Make workers send their console logs.
119
- # this._workerPromise = this._sessionFactory()
120
- # .then(client => new Worker(client, this._targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */));
121
- # }
122
- # return this._workerPromise;
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.fulfill(true)
215
+ @initialize_callback_promise.resolve(true)
169
216
  end
170
217
  end
171
218
  end
@@ -19,6 +19,8 @@ class Puppeteer::TaskManager
19
19
  end
20
20
 
21
21
  def async_rerun_all
22
- Concurrent::Promises.zip(*@tasks.map(&:async_rerun))
22
+ Async do
23
+ Puppeteer::AsyncUtils.await_promise_all(*@tasks.map(&:async_rerun))
24
+ end
23
25
  end
24
26
  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 > 0
13
- begin
14
- Timeout.timeout(@timeout_ms / 1000.0, &block)
15
- rescue Timeout::Error
16
- raise Puppeteer::TimeoutError.new("waiting for #{@task_name} failed: timeout #{@timeout_ms}ms exceeded")
17
- end
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