puppeteer-ruby 0.42.0 → 0.43.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b06e1f5f867ef4e7d648e93becc953f660bb707b29019ffe6e93f8e9f02acc4
4
- data.tar.gz: ff082c6ba661e0f2d8b0d8bc0f92f5aeecf036f9edcd7bb6f8937db4fd019947
3
+ metadata.gz: da17d477ad97a3197fa0eacf237e784204439efdca104e2a304d5cfb58c449d3
4
+ data.tar.gz: f56c151a8d7ebd8ac0ff71feafe5a90dcdd14ed997aaa9ea8e198ddd345b93dd
5
5
  SHA512:
6
- metadata.gz: 19b67fb8ef13bac90538e8755905749427a7131bba5088c16f512b51d72833d80ee296ea7537b5122bb531a28d3c95c7924cd199dc6343ab17a1b3d2c391af9b
7
- data.tar.gz: c65cb7780075ddea13c60b6c668b8a9a6d53627a8dbfa263f08694777ff10f1a3f3a98eeb2596a6497e0274a3a6d6aafc24a215ee6873c07485dc400444c0c17
6
+ metadata.gz: 3db33eea6388dd6c743395c9e3cd583d95a3f2147627ae371390e40bf6269f32d689687e8adf0fdaef8b99134b6bad2ee55061ca66c037407ec681ee62b61188
7
+ data.tar.gz: 2b169890fd8c5dde6850fff2439e5d43c03fa1b65e906ce443389c0599cf31d0e88af1ca0203c12cff90b2789c14af7a992d1c6b95caf7e78b9dadbf4d4a0f09
data/CHANGELOG.md CHANGED
@@ -1,7 +1,11 @@
1
- ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.42.0...main)]
1
+ ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.43.0...main)]
2
2
 
3
3
  - xxx
4
4
 
5
+ ### 0.43.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.42.0...0.43.0)]
6
+
7
+ - Port Puppeteer v16.0 features. Increasing stability.
8
+
5
9
  ### 0.42.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.41.0...0.42.0)]
6
10
 
7
11
  - Port Puppeteer v15.3-v15.5 features, including `Frame#page`
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
- - Puppeteer version: v15.5.0
3
- - puppeteer-ruby version: 0.42.0
2
+ - Puppeteer version: v16.2.0
3
+ - puppeteer-ruby version: 0.43.0
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -7,13 +7,15 @@ class Puppeteer::Browser
7
7
  include Puppeteer::IfPresent
8
8
  using Puppeteer::DefineAsyncMethod
9
9
 
10
+ # @param product [String|nil] 'chrome' or 'firefox'
10
11
  # @param {!Puppeteer.Connection} connection
11
12
  # @param {!Array<string>} contextIds
12
13
  # @param {boolean} ignoreHTTPSErrors
13
14
  # @param {?Puppeteer.Viewport} defaultViewport
14
15
  # @param process [Puppeteer::BrowserRunner::BrowserProcess|NilClass]
15
16
  # @param {function()=} closeCallback
16
- def self.create(connection:,
17
+ def self.create(product:,
18
+ connection:,
17
19
  context_ids:,
18
20
  ignore_https_errors:,
19
21
  default_viewport:,
@@ -22,6 +24,7 @@ class Puppeteer::Browser
22
24
  target_filter_callback:,
23
25
  is_page_target_callback:)
24
26
  browser = Puppeteer::Browser.new(
27
+ product: product,
25
28
  connection: connection,
26
29
  context_ids: context_ids,
27
30
  ignore_https_errors: ignore_https_errors,
@@ -31,17 +34,19 @@ class Puppeteer::Browser
31
34
  target_filter_callback: target_filter_callback,
32
35
  is_page_target_callback: is_page_target_callback,
33
36
  )
34
- connection.send_message('Target.setDiscoverTargets', discover: true)
37
+ browser.send(:attach)
35
38
  browser
36
39
  end
37
40
 
41
+ # @param product [String|nil] 'chrome' or 'firefox'
38
42
  # @param {!Puppeteer.Connection} connection
39
43
  # @param {!Array<string>} contextIds
40
44
  # @param {boolean} ignoreHTTPSErrors
41
45
  # @param {?Puppeteer.Viewport} defaultViewport
42
46
  # @param {?Puppeteer.ChildProcess} process
43
47
  # @param {(function():Promise)=} closeCallback
44
- def initialize(connection:,
48
+ def initialize(product:,
49
+ connection:,
45
50
  context_ids:,
46
51
  ignore_https_errors:,
47
52
  default_viewport:,
@@ -49,6 +54,7 @@ class Puppeteer::Browser
49
54
  close_callback:,
50
55
  target_filter_callback:,
51
56
  is_page_target_callback:)
57
+ @product = product || 'chrome'
52
58
  @ignore_https_errors = ignore_https_errors
53
59
  @default_viewport = default_viewport
54
60
  @process = process
@@ -56,20 +62,26 @@ class Puppeteer::Browser
56
62
  @close_callback = close_callback
57
63
  @target_filter_callback = target_filter_callback || method(:default_target_filter_callback)
58
64
  @is_page_target_callback = is_page_target_callback || method(:default_is_page_target_callback)
59
-
60
65
  @default_context = Puppeteer::BrowserContext.new(@connection, self, nil)
61
66
  @contexts = {}
67
+
62
68
  context_ids.each do |context_id|
63
69
  @contexts[context_id] = Puppeteer::BrowserContext.new(@connection, self, context_id)
64
70
  end
65
- @targets = {}
66
- @wait_for_creating_targets = {}
67
- @connection.on_event(ConnectionEmittedEvents::Disconnected) do
68
- emit_event(BrowserEmittedEvents::Disconnected)
71
+
72
+ if @product == 'firefox'
73
+ @target_manager = Puppeteer::FirefoxTargetManager.new(
74
+ connection: connection,
75
+ target_factory: method(:create_target),
76
+ target_filter_callback: @target_filter_callback,
77
+ )
78
+ else
79
+ @target_manager = Puppeteer::ChromeTargetManager.new(
80
+ connection: connection,
81
+ target_factory: method(:create_target),
82
+ target_filter_callback: @target_filter_callback,
83
+ )
69
84
  end
70
- @connection.on_event('Target.targetCreated', &method(:handle_target_created))
71
- @connection.on_event('Target.targetDestroyed', &method(:handle_target_destroyed))
72
- @connection.on_event('Target.targetInfoChanged', &method(:handle_target_info_changed))
73
85
  end
74
86
 
75
87
  private def default_target_filter_callback(target_info)
@@ -100,11 +112,45 @@ class Puppeteer::Browser
100
112
  super(event_name.to_s, &block)
101
113
  end
102
114
 
115
+ private def attach
116
+ @connection_event_listeners ||= []
117
+ @connection_event_listeners << @connection.add_event_listener(ConnectionEmittedEvents::Disconnected) do
118
+ emit_event(BrowserEmittedEvents::Disconnected)
119
+ end
120
+ @target_manager_event_listeners ||= []
121
+ @target_manager.add_event_listener(
122
+ TargetManagerEmittedEvents::TargetAvailable,
123
+ &method(:handle_attached_to_target)
124
+ )
125
+ @target_manager.add_event_listener(
126
+ TargetManagerEmittedEvents::TargetGone,
127
+ &method(:handle_detached_from_target)
128
+ )
129
+ @target_manager.add_event_listener(
130
+ TargetManagerEmittedEvents::TargetChanged,
131
+ &method(:handle_target_changed)
132
+ )
133
+ @target_manager.add_event_listener(
134
+ TargetManagerEmittedEvents::TargetDiscovered,
135
+ &method(:handle_target_discovered)
136
+ )
137
+ @target_manager.init
138
+ end
139
+
140
+ private def detach
141
+ @connection.remove_event_listener(*@connection_event_listeners)
142
+ @target_manager.remove_event_listener(*@target_manager_event_listeners)
143
+ end
144
+
103
145
  # @return [Puppeteer::BrowserRunner::BrowserProcess]
104
146
  def process
105
147
  @process
106
148
  end
107
149
 
150
+ private def target_manager
151
+ @target_manager
152
+ end
153
+
108
154
  # @return [Puppeteer::BrowserContext]
109
155
  def create_incognito_browser_context
110
156
  result = @connection.send_message('Target.createBrowserContext')
@@ -123,19 +169,16 @@ class Puppeteer::Browser
123
169
 
124
170
  # @param context_id [String]
125
171
  def dispose_context(context_id)
172
+ return unless context_id
126
173
  @connection.send_message('Target.disposeBrowserContext', browserContextId: context_id)
127
174
  @contexts.delete(context_id)
128
175
  end
129
176
 
130
- class TargetAlreadyExistError < StandardError
131
- def initialize
132
- super('Target should not exist before targetCreated')
133
- end
134
- end
177
+ class MissingBrowserContextError < StandardError ; end
135
178
 
136
- # @param {!Protocol.Target.targetCreatedPayload} event
137
- def handle_target_created(event)
138
- target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
179
+ # @param target_info [Puppeteer::Target::TargetInfo]
180
+ # @param session [CDPSession|nil]
181
+ def create_target(target_info, session)
139
182
  browser_context_id = target_info.browser_context_id
140
183
  context =
141
184
  if browser_context_id && @contexts.has_key?(browser_context_id)
@@ -144,56 +187,39 @@ class Puppeteer::Browser
144
187
  @default_context
145
188
  end
146
189
 
147
- if @targets[target_info.target_id]
148
- raise TargetAlreadyExistError.new
190
+ unless context
191
+ raise MissingBrowserContextError.new('Missing browser context')
149
192
  end
150
193
 
151
- return unless @target_filter_callback.call(target_info)
152
-
153
- target = Puppeteer::Target.new(
194
+ Puppeteer::Target.new(
154
195
  target_info: target_info,
196
+ session: session,
155
197
  browser_context: context,
198
+ target_manager: @target_manager,
156
199
  session_factory: -> { @connection.create_session(target_info) },
157
200
  ignore_https_errors: @ignore_https_errors,
158
201
  default_viewport: @default_viewport,
159
202
  is_page_target_callback: @is_page_target_callback,
160
203
  )
161
- @targets[target_info.target_id] = target
162
- if_present(@wait_for_creating_targets.delete(target_info.target_id)) do |promise|
163
- promise.fulfill(target)
164
- end
165
- if await target.initialized_promise
204
+ end
205
+
206
+ private def handle_attached_to_target(target)
207
+ if target.initialized_promise.value!
166
208
  emit_event(BrowserEmittedEvents::TargetCreated, target)
167
- context.emit_event(BrowserContextEmittedEvents::TargetCreated, target)
209
+ target.browser_context.emit_event(BrowserContextEmittedEvents::TargetCreated, target)
168
210
  end
169
211
  end
170
212
 
171
- # @param {{targetId: string}} event
172
- def handle_target_destroyed(event)
173
- target_id = event['targetId']
174
- target = @targets[target_id]
213
+ private def handle_detached_from_target(target)
175
214
  target.ignore_initialize_callback_promise
176
- @targets.delete(target_id)
177
- if_present(@wait_for_creating_targets.delete(target_id)) do |promise|
178
- promise.reject('target destroyed')
179
- end
180
215
  target.closed_callback
181
- if await target.initialized_promise
216
+ if target.initialized_promise.value!
182
217
  emit_event(BrowserEmittedEvents::TargetDestroyed, target)
183
218
  target.browser_context.emit_event(BrowserContextEmittedEvents::TargetDestroyed, target)
184
219
  end
185
220
  end
186
221
 
187
- class TargetNotExistError < StandardError
188
- def initialize
189
- super('target should exist before targetInfoChanged')
190
- end
191
- end
192
-
193
- # @param {!Protocol.Target.targetInfoChangedPayload} event
194
- def handle_target_info_changed(event)
195
- target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
196
- target = @targets[target_info.target_id] or raise TargetNotExistError.new
222
+ private def handle_target_changed(target, target_info)
197
223
  previous_url = target.url
198
224
  was_initialized = target.initialized?
199
225
  target.handle_target_info_changed(target_info)
@@ -203,6 +229,10 @@ class Puppeteer::Browser
203
229
  end
204
230
  end
205
231
 
232
+ private def handle_target_discovered(target_info)
233
+ emit_event('targetdiscovered', target_info)
234
+ end
235
+
206
236
  # @return [String]
207
237
  def ws_endpoint
208
238
  @connection.url
@@ -212,44 +242,47 @@ class Puppeteer::Browser
212
242
  @default_context.new_page
213
243
  end
214
244
 
245
+ class MissingTargetError < StandardError ; end
246
+ class CreatePageError < StandardError ; end
247
+
215
248
  # @param {?string} contextId
216
249
  # @return {!Promise<!Puppeteer.Page>}
217
250
  def create_page_in_context(context_id)
218
- create_target_params = { url: 'about:blank' }
219
- if context_id
220
- create_target_params[:browserContextId] = context_id
221
- end
251
+ create_target_params = {
252
+ url: 'about:blank',
253
+ browserContextId: context_id,
254
+ }.compact
222
255
  result = @connection.send_message('Target.createTarget', **create_target_params)
223
256
  target_id = result['targetId']
224
- target = @targets[target_id]
257
+ target = @target_manager.available_targets[target_id]
225
258
  unless target
226
- # Target.targetCreated is often notified before the response of Target.createdTarget.
227
- # https://github.com/YusukeIwaki/puppeteer-ruby/issues/91
228
- # D, [2021-04-07T03:00:10.125241 #187] DEBUG -- : SEND >> {"method":"Target.createTarget","params":{"url":"about:blank","browserContextId":"56A86FC3391B50180CF9A6450A0D8C21"},"id":3}
229
- # D, [2021-04-07T03:00:10.142396 #187] DEBUG -- : RECV << {"id"=>3, "result"=>{"targetId"=>"A518447C415A1A3E1A8979454A155632"}}
230
- # D, [2021-04-07T03:00:10.145360 #187] DEBUG -- : RECV << {"method"=>"Target.targetCreated", "params"=>{"targetInfo"=>{"targetId"=>"A518447C415A1A3E1A8979454A155632", "type"=>"page", "title"=>"", "url"=>"", "attached"=>false, "canAccessOpener"=>false, "browserContextId"=>"56A86FC3391B50180CF9A6450A0D8C21"}}}
231
- # This is just a workaround logic...
232
- @wait_for_creating_targets[target_id] = resolvable_future
233
- target = await @wait_for_creating_targets[target_id]
259
+ raise MissingTargetError.new("Missing target for page (id = #{target_id})")
260
+ end
261
+ unless target.initialized_promise.value!
262
+ raise CreatePageError.new("Failed to create target for page (id = #{target_id})")
263
+ end
264
+ page = target.page
265
+ unless page
266
+ raise CreatePageError.new("Failed to create a page for context (id = #{context_id})")
234
267
  end
235
- await target.initialized_promise
236
- await target.page
268
+ page
237
269
  end
238
270
 
239
- # @return {!Array<!Target>}
271
+ # All active targets inside the Browser. In case of multiple browser contexts, returns
272
+ # an array with all the targets in all browser contexts.
240
273
  def targets
241
- @targets.values.select { |target| target.initialized? }
274
+ @target_manager.available_targets.values.select { |target| target.initialized? }
242
275
  end
243
276
 
244
277
 
245
- # @return {!Target}
278
+ # The target associated with the browser.
246
279
  def target
247
- targets.find { |target| target.type == 'browser' }
280
+ targets.find { |target| target.type == 'browser' } or raise 'Browser target is not found'
248
281
  end
249
282
 
250
283
  # used only in Target#opener
251
284
  private def find_target_by_id(target_id)
252
- @targets[target_id]
285
+ @target_manager.available_targets[target_id]
253
286
  end
254
287
 
255
288
  # @param predicate [Proc(Puppeteer::Target -> Boolean)]
@@ -293,12 +326,12 @@ class Puppeteer::Browser
293
326
 
294
327
  # @return [String]
295
328
  def version
296
- get_version.product
329
+ Version.fetch(@connection).product
297
330
  end
298
331
 
299
332
  # @return [String]
300
333
  def user_agent
301
- get_version.user_agent
334
+ Version.fetch(@connection).user_agent
302
335
  end
303
336
 
304
337
  def close
@@ -307,6 +340,7 @@ class Puppeteer::Browser
307
340
  end
308
341
 
309
342
  def disconnect
343
+ @target_manager.dispose
310
344
  @connection.dispose
311
345
  end
312
346
 
@@ -315,6 +349,10 @@ class Puppeteer::Browser
315
349
  end
316
350
 
317
351
  class Version
352
+ def self.fetch(connection)
353
+ new(connection.send_message('Browser.getVersion'))
354
+ end
355
+
318
356
  def initialize(hash)
319
357
  @protocol_version = hash['protocolVersion']
320
358
  @product = hash['product']
@@ -325,8 +363,4 @@ class Puppeteer::Browser
325
363
 
326
364
  attr_reader :protocol_version, :product, :revision, :user_agent, :js_version
327
365
  end
328
-
329
- private def get_version
330
- Version.new(@connection.send_message('Browser.getVersion'))
331
- end
332
366
  end
@@ -0,0 +1,67 @@
1
+ require_relative './browser'
2
+ require_relative './launcher/browser_options'
3
+
4
+ class Puppeteer::BrowserConnector
5
+ def initialize(options)
6
+ @browser_options = Puppeteer::Launcher::BrowserOptions.new(options)
7
+ @browser_ws_endpoint = options[:browser_ws_endpoint]
8
+ @browser_url = options[:browser_url]
9
+ @transport = options[:transport]
10
+ end
11
+
12
+ # @return [Puppeteer::Browser]
13
+ def connect_to_browser
14
+ version = Puppeteer::Browser::Version.fetch(connection)
15
+ product = version.product.downcase.include?('firefox') ? 'firefox' : 'chrome'
16
+
17
+ result = connection.send_message('Target.getBrowserContexts')
18
+ browser_context_ids = result['browserContextIds']
19
+
20
+ Puppeteer::Browser.create(
21
+ product: product,
22
+ connection: connection,
23
+ context_ids: browser_context_ids,
24
+ ignore_https_errors: @browser_options.ignore_https_errors?,
25
+ default_viewport: @browser_options.default_viewport,
26
+ process: nil,
27
+ close_callback: -> { connection.send_message('Browser.close') },
28
+ target_filter_callback: @browser_options.target_filter,
29
+ is_page_target_callback: @browser_options.is_page_target,
30
+ )
31
+ end
32
+
33
+ private def connection
34
+ @connection ||=
35
+ if @browser_ws_endpoint && @browser_url.nil? && @transport.nil?
36
+ connect_with_browser_ws_endpoint(@browser_ws_endpoint)
37
+ elsif @browser_ws_endpoint.nil? && @browser_url && @transport.nil?
38
+ connect_with_browser_url(@browser_url)
39
+ elsif @browser_ws_endpoint.nil? && @browser_url.nil? && @transport
40
+ connect_with_transport(@transport)
41
+ else
42
+ raise ArgumentError.new("Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect")
43
+ end
44
+ end
45
+
46
+ # @return [Puppeteer::Connection]
47
+ private def connect_with_browser_ws_endpoint(browser_ws_endpoint)
48
+ transport = Puppeteer::WebSocketTransport.create(browser_ws_endpoint)
49
+ Puppeteer::Connection.new(browser_ws_endpoint, transport, @browser_options.slow_mo)
50
+ end
51
+
52
+ # @return [Puppeteer::Connection]
53
+ private def connect_with_browser_url(browser_url)
54
+ require 'net/http'
55
+ uri = URI(browser_url)
56
+ uri.path = '/json/version'
57
+ response_body = Net::HTTP.get(uri)
58
+ json = JSON.parse(response_body)
59
+ connection_url = json['webSocketDebuggerUrl']
60
+ connect_with_browser_ws_endpoint(connection_url)
61
+ end
62
+
63
+ # @return [Puppeteer::Connection]
64
+ private def connect_with_transport(transport)
65
+ Puppeteer::Connection.new('', transport, @browser_options.slow_mo)
66
+ end
67
+ end