puppeteer-ruby 0.0.21 → 0.0.27

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.
@@ -14,16 +14,24 @@ class Puppeteer::BrowserRunner
14
14
  @proc = nil
15
15
  @connection = nil
16
16
  @closed = true
17
- @listeners = []
18
17
  end
19
18
 
20
19
  attr_reader :proc, :connection
21
20
 
22
21
  class BrowserProcess
23
22
  def initialize(env, executable_path, args)
23
+ @spawnargs =
24
+ if args && !args.empty?
25
+ [executable_path] + args
26
+ else
27
+ [executable_path]
28
+ end
29
+
24
30
  stdin, @stdout, @stderr, @thread = Open3.popen3(env, executable_path, *args)
25
31
  stdin.close
26
32
  @pid = @thread.pid
33
+ rescue Errno::ENOENT => err
34
+ raise LaunchError.new(err.message)
27
35
  end
28
36
 
29
37
  def kill
@@ -37,7 +45,13 @@ class Puppeteer::BrowserRunner
37
45
  @thread.join
38
46
  end
39
47
 
40
- attr_reader :stdout, :stderr
48
+ attr_reader :stdout, :stderr, :spawnargs
49
+ end
50
+
51
+ class LaunchError < StandardError
52
+ def initialize(reason)
53
+ super("Failed to launch browser! #{reason}")
54
+ end
41
55
  end
42
56
 
43
57
  # @param {!(Launcher.LaunchOptions)=} options
@@ -123,12 +137,12 @@ class Puppeteer::BrowserRunner
123
137
 
124
138
  # @return {Promise}
125
139
  def kill
126
- unless @closed
127
- @proc.kill
128
- end
129
140
  if @temp_directory
130
141
  FileUtils.rm_rf(@temp_directory)
131
142
  end
143
+ unless @closed
144
+ @proc.kill
145
+ end
132
146
  end
133
147
 
134
148
 
@@ -9,7 +9,7 @@ class Puppeteer::CDPSession
9
9
  # @param {string} targetType
10
10
  # @param {string} sessionId
11
11
  def initialize(connection, target_type, session_id)
12
- @callbacks = {}
12
+ @callbacks = Concurrent::Hash.new
13
13
  @connection = connection
14
14
  @target_type = target_type
15
15
  @session_id = session_id
@@ -31,10 +31,14 @@ class Puppeteer::CDPSession
31
31
  if !@connection
32
32
  raise Error.new("Protocol error (#{method}): Session closed. Most likely the #{@target_type} has been closed.")
33
33
  end
34
- id = @connection.raw_send(message: { sessionId: @session_id, method: method, params: params })
34
+
35
35
  promise = resolvable_future
36
- callback = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
37
- @callbacks[id] = callback
36
+
37
+ @connection.generate_id do |id|
38
+ @callbacks[id] = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
39
+ @connection.raw_send(id: id, message: { sessionId: @session_id, method: method, params: params })
40
+ end
41
+
38
42
  promise
39
43
  end
40
44
 
@@ -44,10 +48,10 @@ class Puppeteer::CDPSession
44
48
  if callback = @callbacks.delete(message['id'])
45
49
  callback_with_message(callback, message)
46
50
  else
47
- raise Error.new("unknown id: #{id}")
51
+ raise Error.new("unknown id: #{message['id']}")
48
52
  end
49
53
  else
50
- emit_event message['method'], message['params']
54
+ emit_event(message['method'], message['params'])
51
55
  end
52
56
  end
53
57
 
@@ -79,7 +83,7 @@ class Puppeteer::CDPSession
79
83
  end
80
84
  @callbacks.clear
81
85
  @connection = nil
82
- emit_event 'Events.CDPSession.Disconnected'
86
+ emit_event(CDPSessionEmittedEvents::Disconnected)
83
87
  end
84
88
 
85
89
  # @param event_name [String]
@@ -4,9 +4,13 @@ module Puppeteer::ConcurrentRubyUtils
4
4
  # REMARK: This method doesn't assure the order of calling.
5
5
  # for example, await_all(async1, async2) calls calls2 -> calls1 often.
6
6
  def await_all(*args)
7
- if args.length == 1 && args[0].is_a?(Enumerable)
8
- Concurrent::Promises.zip(*(args[0])).value!
7
+ if args.length == 1 && args.first.is_a?(Enumerable)
8
+ await_all(*args.first)
9
9
  else
10
+ if args.any? { |arg| !arg.is_a?(Concurrent::Promises::Future) }
11
+ raise ArgumentError.new("All argument must be a Future: #{args}")
12
+ end
13
+
10
14
  Concurrent::Promises.zip(*args).value!
11
15
  end
12
16
  end
@@ -15,9 +19,13 @@ module Puppeteer::ConcurrentRubyUtils
15
19
  # REMARK: This method doesn't assure the order of calling.
16
20
  # for example, await_all(async1, async2) calls calls2 -> calls1 often.
17
21
  def await_any(*args)
18
- if args.length == 1 && args[0].is_a?(Enumerable)
19
- Concurrent::Promises.any(*(args[0])).value!
22
+ if args.length == 1 && args.first.is_a?(Enumerable)
23
+ await_any(*args.first)
20
24
  else
25
+ if args.any? { |arg| !arg.is_a?(Concurrent::Promises::Future) }
26
+ raise ArgumentError.new("All argument must be a Future: #{args}")
27
+ end
28
+
21
29
  Concurrent::Promises.any(*args).value!
22
30
  end
23
31
  end
@@ -31,8 +39,16 @@ module Puppeteer::ConcurrentRubyUtils
31
39
  end
32
40
  end
33
41
 
34
- def future(&block)
35
- Concurrent::Promises.future(&block)
42
+ def future(*args, &block)
43
+ Concurrent::Promises.future(*args) do |*block_args|
44
+ block.call(*block_args)
45
+ rescue Puppeteer::TimeoutError
46
+ # suppress error logging
47
+ raise
48
+ rescue => err
49
+ Logger.new($stderr).warn(err)
50
+ raise err
51
+ end
36
52
  end
37
53
 
38
54
  def resolvable_future(&block)
@@ -39,7 +39,7 @@ class Puppeteer::Connection
39
39
  def initialize(url, transport, delay = 0)
40
40
  @url = url
41
41
  @last_id = 0
42
- @callbacks = {}
42
+ @callbacks = Concurrent::Hash.new
43
43
  @delay = delay
44
44
 
45
45
  @transport = transport
@@ -52,7 +52,7 @@ class Puppeteer::Connection
52
52
  handle_close
53
53
  end
54
54
 
55
- @sessions = {}
55
+ @sessions = Concurrent::Hash.new
56
56
  @closed = false
57
57
  end
58
58
 
@@ -92,22 +92,41 @@ class Puppeteer::Connection
92
92
  end
93
93
 
94
94
  def async_send_message(method, params = {})
95
- id = raw_send(message: { method: method, params: params })
96
95
  promise = resolvable_future
97
- @callbacks[id] = MessageCallback.new(method: method, promise: promise)
96
+
97
+ generate_id do |id|
98
+ @callbacks[id] = MessageCallback.new(method: method, promise: promise)
99
+ raw_send(id: id, message: { method: method, params: params })
100
+ end
101
+
98
102
  promise
99
103
  end
100
104
 
101
- private def generate_id
102
- @last_id += 1
105
+ # package private. not intended to use externally.
106
+ #
107
+ # ```usage
108
+ # connection.generate_id do |generated_id|
109
+ # # play with generated_id
110
+ # end
111
+ # ````
112
+ #
113
+ def generate_id(&block)
114
+ block.call(@last_id += 1)
103
115
  end
104
116
 
105
- def raw_send(message:)
106
- id = generate_id
117
+ # package private. not intended to use externally.
118
+ def raw_send(id:, message:)
119
+ # In original puppeteer (JS) implementation,
120
+ # id is generated here using #generate_id and the id argument is not passed to #raw_send.
121
+ #
122
+ # However with concurrent-ruby, '#handle_message' is sometimes called
123
+ # just soon after @transport.send_text and **before returning the id.**
124
+ #
125
+ # So we have to know the message id in advance before send_text.
126
+ #
107
127
  payload = JSON.fast_generate(message.compact.merge(id: id))
108
128
  @transport.send_text(payload)
109
129
  request_debug_printer.handle_payload(payload)
110
- id
111
130
  end
112
131
 
113
132
  # Just for effective debugging :)
@@ -211,7 +230,7 @@ class Puppeteer::Connection
211
230
  end
212
231
  end
213
232
  else
214
- emit_event message['method'], message['params']
233
+ emit_event(message['method'], message['params'])
215
234
  end
216
235
  end
217
236
 
@@ -233,7 +252,7 @@ class Puppeteer::Connection
233
252
  session.handle_closed
234
253
  end
235
254
  @sessions.clear
236
- emit_event 'Events.Connection.Disconnected'
255
+ emit_event(ConnectionEmittedEvents::Disconnected)
237
256
  end
238
257
 
239
258
  def on_close(&block)
@@ -10,12 +10,21 @@ module Puppeteer::DefineAsyncMethod
10
10
  end
11
11
 
12
12
  original_method = instance_method(async_method_name[6..-1])
13
- define_method(async_method_name) do |*args|
14
- Concurrent::Promises.future do
15
- original_method.bind(self).call(*args)
16
- rescue => err
17
- Logger.new($stderr).warn(err)
18
- raise err
13
+ define_method(async_method_name) do |*args, **kwargs|
14
+ if kwargs.empty? # for Ruby < 2.7
15
+ Concurrent::Promises.future do
16
+ original_method.bind(self).call(*args)
17
+ rescue => err
18
+ Logger.new($stderr).warn(err)
19
+ raise err
20
+ end
21
+ else
22
+ Concurrent::Promises.future do
23
+ original_method.bind(self).call(*args, **kwargs)
24
+ rescue => err
25
+ Logger.new($stderr).warn(err)
26
+ raise err
27
+ end
19
28
  end
20
29
  end
21
30
  end
@@ -134,7 +134,7 @@ class Puppeteer::DOMWorld
134
134
 
135
135
  # @return [String]
136
136
  def content
137
- evaluate <<-JAVASCRIPT
137
+ evaluate(<<-JAVASCRIPT)
138
138
  () => {
139
139
  let retVal = '';
140
140
  if (document.doctype)
@@ -151,7 +151,7 @@ class Puppeteer::DOMWorld
151
151
  # @param wait_until [String|Array<String>]
152
152
  def set_content(html, timeout: nil, wait_until: nil)
153
153
  option_wait_until = [wait_until || 'load'].flatten
154
- option_timeout = @timeout_settings.navigation_timeout
154
+ option_timeout = timeout || @timeout_settings.navigation_timeout
155
155
 
156
156
  # We rely upon the fact that document.open() will reset frame lifecycle with "init"
157
157
  # lifecycle event. @see https://crrev.com/608658
@@ -31,6 +31,8 @@ module Puppeteer::EventCallbackable
31
31
  (@event_listeners[event_name] ||= EventListeners.new).add(&block)
32
32
  end
33
33
 
34
+ alias_method :on, :add_event_listener
35
+
34
36
  def remove_event_listener(*id_args)
35
37
  (@event_listeners ||= {}).each do |event_name, listeners|
36
38
  id_args.each do |id|
@@ -50,6 +52,8 @@ module Puppeteer::EventCallbackable
50
52
  end
51
53
  end
52
54
 
55
+ alias_method :once, :observe_first
56
+
53
57
  def on_event(event_name, &block)
54
58
  @event_callbackable_handlers ||= {}
55
59
  @event_callbackable_handlers[event_name] = block
@@ -0,0 +1,184 @@
1
+ require 'digest/md5'
2
+
3
+ module EventsDefinitionUtils
4
+ refine Kernel do
5
+ # Symbol is used to prevent external parties listening to these events
6
+ def Symbol(str)
7
+ Digest::MD5.hexdigest(str)
8
+ end
9
+ end
10
+
11
+ refine Hash do
12
+ def define_const_into(target_module)
13
+ each do |key, value|
14
+ target_module.const_set(key, value)
15
+ target_module.define_singleton_method(key) { value }
16
+ end
17
+ keyset = Set.new(keys)
18
+ valueset = Set.new(values)
19
+ target_module.define_singleton_method(:keys) { keyset }
20
+ target_module.define_singleton_method(:values) { valueset }
21
+ end
22
+ end
23
+ end
24
+
25
+ using EventsDefinitionUtils
26
+
27
+ # Internal events that the Connection class emits.
28
+ module ConnectionEmittedEvents ; end
29
+
30
+ {
31
+ Disconnected: Symbol('Connection.Disconnected'),
32
+ }.define_const_into(ConnectionEmittedEvents)
33
+
34
+ # Internal events that the CDPSession class emits.
35
+ module CDPSessionEmittedEvents ; end
36
+
37
+ {
38
+ Disconnected: Symbol('CDPSession.Disconnected'),
39
+ }.define_const_into(CDPSessionEmittedEvents)
40
+
41
+ # All the events a Browser may emit.
42
+ module BrowserEmittedEvents ; end
43
+
44
+ {
45
+ # Emitted when Puppeteer gets disconnected from the Chromium instance. This might happen because of one of the following:
46
+ # - Chromium is closed or crashed
47
+ # - The Browser#disconnect method was called.
48
+ Disconnected: 'disconnected',
49
+
50
+ # Emitted when the url of a target changes. Contains a {@link Target} instance.
51
+ TargetChanged: 'targetchanged',
52
+
53
+ # Emitted when a target is created, for example when a new page is opened by
54
+ # window.open or by Browser#newPage
55
+ # Contains a Target instance.
56
+ TargetCreated: 'targetcreated',
57
+
58
+ # Emitted when a target is destroyed, for example when a page is closed.
59
+ # Contains a Target instance.
60
+ TargetDestroyed: 'targetdestroyed',
61
+ }.define_const_into(BrowserEmittedEvents)
62
+
63
+ module BrowserContextEmittedEvents ; end
64
+
65
+ {
66
+ # Emitted when the url of a target inside the browser context changes.
67
+ # Contains a Target instance.
68
+ TargetChanged: 'targetchanged',
69
+
70
+ # Emitted when a target is created, for example when a new page is opened by
71
+ # window.open or by BrowserContext#newPage
72
+ # Contains a Target instance.
73
+ TargetCreated: 'targetcreated',
74
+
75
+ # Emitted when a target is destroyed within the browser context, for example when a page is closed.
76
+ # Contains a Target instance.
77
+ TargetDestroyed: 'targetdestroyed',
78
+ }.define_const_into(BrowserContextEmittedEvents)
79
+
80
+ # We use symbols to prevent any external parties listening to these events.
81
+ # They are internal to Puppeteer.
82
+ module NetworkManagerEmittedEvents ; end
83
+
84
+ {
85
+ Request: Symbol('NetworkManager.Request'),
86
+ Response: Symbol('NetworkManager.Response'),
87
+ RequestFailed: Symbol('NetworkManager.RequestFailed'),
88
+ RequestFinished: Symbol('NetworkManager.RequestFinished'),
89
+ }.define_const_into(NetworkManagerEmittedEvents)
90
+
91
+
92
+ # We use symbols to prevent external parties listening to these events.
93
+ # They are internal to Puppeteer.
94
+ module FrameManagerEmittedEvents ; end
95
+
96
+ {
97
+ FrameAttached: Symbol('FrameManager.FrameAttached'),
98
+ FrameNavigated: Symbol('FrameManager.FrameNavigated'),
99
+ FrameDetached: Symbol('FrameManager.FrameDetached'),
100
+ LifecycleEvent: Symbol('FrameManager.LifecycleEvent'),
101
+ FrameNavigatedWithinDocument: Symbol('FrameManager.FrameNavigatedWithinDocument'),
102
+ ExecutionContextCreated: Symbol('FrameManager.ExecutionContextCreated'),
103
+ ExecutionContextDestroyed: Symbol('FrameManager.ExecutionContextDestroyed'),
104
+ }.define_const_into(FrameManagerEmittedEvents)
105
+
106
+ # All the events that a page instance may emit.
107
+ module PageEmittedEvents ; end
108
+
109
+ {
110
+ # Emitted when the page closes.
111
+ Close: 'close',
112
+
113
+ # Emitted when JavaScript within the page calls one of console API methods,
114
+ # e.g. `console.log` or `console.dir`. Also emitted if the page throws an
115
+ # error or a warning.
116
+ Console: 'console',
117
+
118
+ # Emitted when a JavaScript dialog appears, such as `alert`, `prompt`,
119
+ # `confirm` or `beforeunload`. Puppeteer can respond to the dialog via
120
+ # Dialog#accept or Dialog#dismiss.
121
+ Dialog: 'dialog',
122
+
123
+ # Emitted when the JavaScript
124
+ # {https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded DOMContentLoaded} event is dispatched.
125
+ DOMContentLoaded: 'domcontentloaded',
126
+
127
+ # Emitted when the page crashes. Will contain an `Error`.
128
+ Error: 'error',
129
+
130
+ # Emitted when a frame is attached. Will contain a Frame.
131
+ FrameAttached: 'frameattached',
132
+ # Emitted when a frame is detached. Will contain a Frame.
133
+ FrameDetached: 'framedetached',
134
+ # Emitted when a frame is navigated to a new URL. Will contain a {@link Frame}.
135
+ FrameNavigated: 'framenavigated',
136
+
137
+ # Emitted when the JavaScript
138
+ # {https://developer.mozilla.org/en-US/docs/Web/Events/load | load} event is dispatched.
139
+ Load: 'load',
140
+
141
+ # Emitted when the JavaScript code makes a call to `console.timeStamp`. For
142
+ # the list of metrics see {@link Page.metrics | page.metrics}.
143
+ #
144
+ # Contains an object with two properties:
145
+ # - `title`: the title passed to `console.timeStamp`
146
+ # - `metrics`: objec containing metrics as key/value pairs. The values will be `number`s.
147
+ Metrics: 'metrics',
148
+
149
+ # Emitted when an uncaught exception happens within the page.
150
+ # Contains an `Error`.
151
+ PageError: 'pageerror',
152
+
153
+ # Emitted when the page opens a new tab or window.
154
+ # Contains a Page corresponding to the popup window.
155
+ Popup: 'popup',
156
+
157
+ # Emitted when a page issues a request and contains a HTTPRequest.
158
+ #
159
+ # The object is readonly. See Page#setRequestInterception for intercepting and mutating requests.
160
+ Request: 'request',
161
+
162
+ # Emitted when a request fails, for example by timing out.
163
+ #
164
+ # Contains a HTTPRequest.
165
+ #
166
+ # NOTE: HTTP Error responses, such as 404 or 503, are still successful
167
+ # responses from HTTP standpoint, so request will complete with
168
+ # `requestfinished` event and not with `requestfailed`.
169
+ RequestFailed: 'requestfailed',
170
+
171
+ # Emitted when a request finishes successfully. Contains a HTTPRequest.
172
+ RequestFinished: 'requestfinished',
173
+
174
+ # Emitted when a response is received. Contains a HTTPResponse.
175
+ Response: 'response',
176
+
177
+ # Emitted when a dedicated
178
+ # {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API WebWorker} is spawned by the page.
179
+ WorkerCreated: 'workercreated',
180
+
181
+ # Emitted when a dedicated
182
+ # {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API WebWorker} is destroyed by the page.
183
+ WorkerDestroyed: 'workerdestroyed',
184
+ }.define_const_into(PageEmittedEvents)