ferrum 0.13 → 0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +288 -154
- data/lib/ferrum/browser/command.rb +8 -0
- data/lib/ferrum/browser/options/chrome.rb +17 -5
- data/lib/ferrum/browser/options.rb +38 -25
- data/lib/ferrum/browser/process.rb +44 -17
- data/lib/ferrum/browser.rb +34 -52
- data/lib/ferrum/client/subscriber.rb +76 -0
- data/lib/ferrum/{browser → client}/web_socket.rb +36 -22
- data/lib/ferrum/client.rb +169 -0
- data/lib/ferrum/context.rb +19 -15
- data/lib/ferrum/contexts.rb +46 -12
- data/lib/ferrum/cookies/cookie.rb +57 -0
- data/lib/ferrum/cookies.rb +40 -4
- data/lib/ferrum/downloads.rb +60 -0
- data/lib/ferrum/errors.rb +2 -1
- data/lib/ferrum/frame.rb +1 -0
- data/lib/ferrum/headers.rb +1 -1
- data/lib/ferrum/network/exchange.rb +29 -2
- data/lib/ferrum/network/intercepted_request.rb +8 -17
- data/lib/ferrum/network/request.rb +23 -39
- data/lib/ferrum/network/request_params.rb +57 -0
- data/lib/ferrum/network/response.rb +25 -5
- data/lib/ferrum/network.rb +43 -16
- data/lib/ferrum/node.rb +21 -1
- data/lib/ferrum/page/frames.rb +5 -5
- data/lib/ferrum/page/screenshot.rb +42 -24
- data/lib/ferrum/page.rb +183 -131
- data/lib/ferrum/proxy.rb +1 -1
- data/lib/ferrum/target.rb +25 -5
- data/lib/ferrum/utils/elapsed_time.rb +0 -2
- data/lib/ferrum/utils/event.rb +19 -0
- data/lib/ferrum/utils/platform.rb +4 -0
- data/lib/ferrum/utils/thread.rb +18 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum.rb +3 -0
- metadata +14 -114
- data/lib/ferrum/browser/client.rb +0 -102
- data/lib/ferrum/browser/subscriber.rb +0 -36
data/lib/ferrum/page.rb
CHANGED
@@ -1,50 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "forwardable"
|
4
|
+
require "pathname"
|
4
5
|
require "ferrum/mouse"
|
5
6
|
require "ferrum/keyboard"
|
6
7
|
require "ferrum/headers"
|
7
8
|
require "ferrum/cookies"
|
8
9
|
require "ferrum/dialog"
|
9
10
|
require "ferrum/network"
|
11
|
+
require "ferrum/downloads"
|
10
12
|
require "ferrum/page/frames"
|
11
13
|
require "ferrum/page/screenshot"
|
12
14
|
require "ferrum/page/animation"
|
13
15
|
require "ferrum/page/tracing"
|
14
16
|
require "ferrum/page/stream"
|
15
|
-
require "ferrum/browser/client"
|
16
17
|
|
17
18
|
module Ferrum
|
18
19
|
class Page
|
19
20
|
GOTO_WAIT = ENV.fetch("FERRUM_GOTO_WAIT", 0.1).to_f
|
20
21
|
|
21
|
-
class Event < Concurrent::Event
|
22
|
-
def iteration
|
23
|
-
synchronize { @iteration }
|
24
|
-
end
|
25
|
-
|
26
|
-
def reset
|
27
|
-
synchronize do
|
28
|
-
@iteration += 1
|
29
|
-
@set = false if @set
|
30
|
-
@iteration
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
22
|
extend Forwardable
|
36
23
|
delegate %i[at_css at_xpath css xpath
|
37
24
|
current_url current_title url title body doctype content=
|
38
25
|
execution_id execution_id! evaluate evaluate_on evaluate_async execute evaluate_func
|
39
26
|
add_script_tag add_style_tag] => :main_frame
|
27
|
+
delegate %i[base_url default_user_agent timeout timeout=] => :@options
|
40
28
|
|
41
29
|
include Animation
|
42
30
|
include Screenshot
|
43
31
|
include Frames
|
44
32
|
include Stream
|
45
33
|
|
46
|
-
attr_accessor :referrer
|
47
|
-
attr_reader :
|
34
|
+
attr_accessor :referrer
|
35
|
+
attr_reader :context_id, :target_id, :event, :tracing
|
36
|
+
|
37
|
+
# Client connection.
|
38
|
+
#
|
39
|
+
# @return [Client]
|
40
|
+
attr_reader :client
|
48
41
|
|
49
42
|
# Mouse object.
|
50
43
|
#
|
@@ -71,61 +64,56 @@ module Ferrum
|
|
71
64
|
# @return [Cookies]
|
72
65
|
attr_reader :cookies
|
73
66
|
|
74
|
-
|
67
|
+
# Downloads object.
|
68
|
+
#
|
69
|
+
# @return [Downloads]
|
70
|
+
attr_reader :downloads
|
71
|
+
|
72
|
+
def initialize(client, context_id:, target_id:, proxy: nil)
|
73
|
+
@client = client
|
74
|
+
@context_id = context_id
|
75
|
+
@target_id = target_id
|
76
|
+
@options = client.options
|
77
|
+
|
75
78
|
@frames = Concurrent::Map.new
|
76
79
|
@main_frame = Frame.new(nil, self)
|
77
|
-
@
|
78
|
-
@target_id = target_id
|
79
|
-
@timeout = @browser.timeout
|
80
|
-
@event = Event.new.tap(&:set)
|
80
|
+
@event = Utils::Event.new.tap(&:set)
|
81
81
|
self.proxy = proxy
|
82
82
|
|
83
|
-
@client = Browser::Client.new(ws_url, self,
|
84
|
-
logger: @browser.options.logger,
|
85
|
-
ws_max_receive_size: @browser.options.ws_max_receive_size,
|
86
|
-
id_starts_with: 1000)
|
87
|
-
|
88
83
|
@mouse = Mouse.new(self)
|
89
84
|
@keyboard = Keyboard.new(self)
|
90
85
|
@headers = Headers.new(self)
|
91
86
|
@cookies = Cookies.new(self)
|
92
87
|
@network = Network.new(self)
|
93
88
|
@tracing = Tracing.new(self)
|
89
|
+
@downloads = Downloads.new(self)
|
94
90
|
|
95
91
|
subscribe
|
96
92
|
prepare_page
|
97
93
|
end
|
98
94
|
|
99
|
-
def context
|
100
|
-
@browser.contexts.find_by(target_id: target_id)
|
101
|
-
end
|
102
|
-
|
103
95
|
#
|
104
96
|
# Navigates the page to a URL.
|
105
97
|
#
|
106
98
|
# @param [String, nil] url
|
107
99
|
# The URL to navigate to. The url should include scheme unless you set
|
108
|
-
# `{Browser#base_url = url}` when configuring
|
100
|
+
# `{Browser#base_url = url}` when configuring.
|
109
101
|
#
|
110
102
|
# @example
|
111
|
-
#
|
103
|
+
# page.go_to("https://github.com/")
|
112
104
|
#
|
113
105
|
def go_to(url = nil)
|
114
106
|
options = { url: combine_url!(url) }
|
115
107
|
options.merge!(referrer: referrer) if referrer
|
116
108
|
response = command("Page.navigate", wait: GOTO_WAIT, **options)
|
117
|
-
# https://cs.chromium.org/chromium/src/net/base/net_error_list.h
|
118
|
-
if
|
119
|
-
|
120
|
-
net::ERR_INTERNET_DISCONNECTED
|
121
|
-
net::ERR_CONNECTION_TIMED_OUT
|
122
|
-
net::ERR_FILE_NOT_FOUND].include?(response["errorText"])
|
123
|
-
raise StatusError, options[:url]
|
109
|
+
error_text = response["errorText"] # https://cs.chromium.org/chromium/src/net/base/net_error_list.h
|
110
|
+
if error_text && error_text != "net::ERR_ABORTED" # Request aborted due to user action or download
|
111
|
+
raise StatusError.new(options[:url], "Request to #{options[:url]} failed (#{error_text})")
|
124
112
|
end
|
125
113
|
|
126
114
|
response["frameId"]
|
127
115
|
rescue TimeoutError
|
128
|
-
if @
|
116
|
+
if @options.pending_connection_errors
|
129
117
|
pendings = network.traffic.select(&:pending?).map(&:url).compact
|
130
118
|
raise PendingConnectionsError.new(options[:url], pendings) unless pendings.empty?
|
131
119
|
end
|
@@ -135,42 +123,76 @@ module Ferrum
|
|
135
123
|
|
136
124
|
def close
|
137
125
|
@headers.clear
|
138
|
-
|
139
|
-
|
126
|
+
client.command("Target.closeTarget", async: true, targetId: @target_id)
|
127
|
+
close_connection
|
128
|
+
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def close_connection
|
133
|
+
client&.close
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Overrides device screen dimensions and emulates viewport according to parameters
|
138
|
+
#
|
139
|
+
# Read more [here](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride).
|
140
|
+
#
|
141
|
+
# @param [Integer] width width value in pixels. 0 disables the override
|
142
|
+
#
|
143
|
+
# @param [Integer] height height value in pixels. 0 disables the override
|
144
|
+
#
|
145
|
+
# @param [Float] scale_factor device scale factor value. 0 disables the override
|
146
|
+
#
|
147
|
+
# @param [Boolean] mobile whether to emulate mobile device
|
148
|
+
#
|
149
|
+
def set_viewport(width:, height:, scale_factor: 0, mobile: false)
|
150
|
+
command(
|
151
|
+
"Emulation.setDeviceMetricsOverride",
|
152
|
+
slowmoable: true,
|
153
|
+
width: width,
|
154
|
+
height: height,
|
155
|
+
deviceScaleFactor: scale_factor,
|
156
|
+
mobile: mobile
|
157
|
+
)
|
140
158
|
end
|
141
159
|
|
142
160
|
def resize(width: nil, height: nil, fullscreen: false)
|
143
161
|
if fullscreen
|
144
162
|
width, height = document_size
|
145
|
-
|
163
|
+
self.window_bounds = { window_state: "fullscreen" }
|
146
164
|
else
|
147
|
-
|
148
|
-
|
165
|
+
self.window_bounds = { window_state: "normal" }
|
166
|
+
self.window_bounds = { width: width, height: height }
|
149
167
|
end
|
150
168
|
|
151
|
-
|
152
|
-
width: width,
|
153
|
-
height: height,
|
154
|
-
deviceScaleFactor: 1,
|
155
|
-
mobile: false,
|
156
|
-
fitWindow: false)
|
169
|
+
set_viewport(width: width, height: height)
|
157
170
|
end
|
158
171
|
|
159
172
|
#
|
160
|
-
#
|
173
|
+
# Disables JavaScript execution from the HTML source for the page.
|
174
|
+
#
|
175
|
+
# This doesn't prevent users evaluate JavaScript with Ferrum.
|
176
|
+
#
|
177
|
+
def disable_javascript
|
178
|
+
command("Emulation.setScriptExecutionDisabled", value: true)
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
# The current position of the window.
|
161
183
|
#
|
162
184
|
# @return [(Integer, Integer)]
|
163
|
-
# The left, top coordinates of the
|
185
|
+
# The left, top coordinates of the window.
|
164
186
|
#
|
165
187
|
# @example
|
166
|
-
#
|
188
|
+
# page.position # => [10, 20]
|
167
189
|
#
|
168
190
|
def position
|
169
|
-
|
191
|
+
window_bounds.values_at("left", "top")
|
170
192
|
end
|
171
193
|
|
172
194
|
#
|
173
|
-
# Sets the position of the
|
195
|
+
# Sets the position of the window.
|
174
196
|
#
|
175
197
|
# @param [Hash{Symbol => Object}] options
|
176
198
|
#
|
@@ -181,20 +203,72 @@ module Ferrum
|
|
181
203
|
# The number of pixels from the top of the screen.
|
182
204
|
#
|
183
205
|
# @example
|
184
|
-
#
|
206
|
+
# page.position = { left: 10, top: 20 }
|
185
207
|
#
|
186
208
|
def position=(options)
|
187
|
-
|
188
|
-
|
189
|
-
|
209
|
+
self.window_bounds = { left: options[:left], top: options[:top] }
|
210
|
+
end
|
211
|
+
|
212
|
+
# Sets the position of the window.
|
213
|
+
#
|
214
|
+
# @param [Hash{Symbol => Object}] bounds
|
215
|
+
#
|
216
|
+
# @option options [Integer] :left
|
217
|
+
# The number of pixels from the left-hand side of the screen.
|
218
|
+
#
|
219
|
+
# @option options [Integer] :top
|
220
|
+
# The number of pixels from the top of the screen.
|
221
|
+
#
|
222
|
+
# @option options [Integer] :width
|
223
|
+
# The window width in pixels.
|
224
|
+
#
|
225
|
+
# @option options [Integer] :height
|
226
|
+
# The window height in pixels.
|
227
|
+
#
|
228
|
+
# @option options [String] :window_state
|
229
|
+
# The window state. Default to normal. Allowed Values: normal, minimized, maximized, fullscreen
|
230
|
+
#
|
231
|
+
# @example
|
232
|
+
# page.window_bounds = { left: 10, top: 20, width: 1024, height: 768, window_state: "normal" }
|
233
|
+
#
|
234
|
+
def window_bounds=(bounds)
|
235
|
+
options = bounds.dup
|
236
|
+
window_state = options.delete(:window_state)
|
237
|
+
bounds = { windowState: window_state, **options }.compact
|
238
|
+
|
239
|
+
client.command("Browser.setWindowBounds", windowId: window_id, bounds: bounds)
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# Current window bounds.
|
244
|
+
#
|
245
|
+
# @return [Hash{String => (Integer, String)}]
|
246
|
+
#
|
247
|
+
# @example
|
248
|
+
# page.window_bounds # => { "left": 0, "top": 1286, "width": 10, "height": 10, "windowState": "normal" }
|
249
|
+
#
|
250
|
+
def window_bounds
|
251
|
+
client.command("Browser.getWindowBounds", windowId: window_id).fetch("bounds")
|
252
|
+
end
|
253
|
+
|
254
|
+
#
|
255
|
+
# Current window id.
|
256
|
+
#
|
257
|
+
# @return [Integer]
|
258
|
+
#
|
259
|
+
# @example
|
260
|
+
# page.window_id # => 1
|
261
|
+
#
|
262
|
+
def window_id
|
263
|
+
client.command("Browser.getWindowForTarget", targetId: target_id)["windowId"]
|
190
264
|
end
|
191
265
|
|
192
266
|
#
|
193
267
|
# Reloads the current page.
|
194
268
|
#
|
195
269
|
# @example
|
196
|
-
#
|
197
|
-
#
|
270
|
+
# page.go_to("https://github.com/")
|
271
|
+
# page.refresh
|
198
272
|
#
|
199
273
|
def refresh
|
200
274
|
command("Page.reload", wait: timeout, slowmoable: true)
|
@@ -205,41 +279,41 @@ module Ferrum
|
|
205
279
|
# Stop all navigations and loading pending resources on the page.
|
206
280
|
#
|
207
281
|
# @example
|
208
|
-
#
|
209
|
-
#
|
282
|
+
# page.go_to("https://github.com/")
|
283
|
+
# page.stop
|
210
284
|
#
|
211
285
|
def stop
|
212
286
|
command("Page.stopLoading", slowmoable: true)
|
213
287
|
end
|
214
288
|
|
215
289
|
#
|
216
|
-
# Navigates to the previous URL in the
|
290
|
+
# Navigates to the previous URL in the history.
|
217
291
|
#
|
218
292
|
# @example
|
219
|
-
#
|
220
|
-
#
|
221
|
-
#
|
293
|
+
# page.go_to("https://github.com/")
|
294
|
+
# page.at_xpath("//a").click
|
295
|
+
# page.back
|
222
296
|
#
|
223
297
|
def back
|
224
298
|
history_navigate(delta: -1)
|
225
299
|
end
|
226
300
|
|
227
301
|
#
|
228
|
-
# Navigates to the next URL in the
|
302
|
+
# Navigates to the next URL in the history.
|
229
303
|
#
|
230
304
|
# @example
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
305
|
+
# page.go_to("https://github.com/")
|
306
|
+
# page.at_xpath("//a").click
|
307
|
+
# page.back
|
308
|
+
# page.forward
|
235
309
|
#
|
236
310
|
def forward
|
237
311
|
history_navigate(delta: 1)
|
238
312
|
end
|
239
313
|
|
240
|
-
def wait_for_reload(
|
314
|
+
def wait_for_reload(timeout = 1)
|
241
315
|
@event.reset if @event.set?
|
242
|
-
@event.wait(
|
316
|
+
@event.wait(timeout)
|
243
317
|
@event.set
|
244
318
|
end
|
245
319
|
|
@@ -251,29 +325,21 @@ module Ferrum
|
|
251
325
|
# @return [Boolean]
|
252
326
|
#
|
253
327
|
# @example
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
328
|
+
# page.bypass_csp # => true
|
329
|
+
# page.go_to("https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md")
|
330
|
+
# page.refresh
|
331
|
+
# page.add_script_tag(content: "window.__injected = 42")
|
332
|
+
# page.evaluate("window.__injected") # => 42
|
259
333
|
#
|
260
334
|
def bypass_csp(enabled: true)
|
261
335
|
command("Page.setBypassCSP", enabled: enabled)
|
262
336
|
enabled
|
263
337
|
end
|
264
338
|
|
265
|
-
def window_id
|
266
|
-
@browser.command("Browser.getWindowForTarget", targetId: @target_id)["windowId"]
|
267
|
-
end
|
268
|
-
|
269
|
-
def set_window_bounds(bounds = {})
|
270
|
-
@browser.command("Browser.setWindowBounds", windowId: window_id, bounds: bounds)
|
271
|
-
end
|
272
|
-
|
273
339
|
def command(method, wait: 0, slowmoable: false, **params)
|
274
340
|
iteration = @event.reset if wait.positive?
|
275
|
-
sleep(@
|
276
|
-
result =
|
341
|
+
sleep(@options.slowmo) if slowmoable && @options.slowmo.positive?
|
342
|
+
result = client.command(method, **params)
|
277
343
|
|
278
344
|
if wait.positive?
|
279
345
|
# Wait a bit after command and check if iteration has
|
@@ -291,30 +357,30 @@ module Ferrum
|
|
291
357
|
def on(name, &block)
|
292
358
|
case name
|
293
359
|
when :dialog
|
294
|
-
|
360
|
+
client.on("Page.javascriptDialogOpening") do |params, index, total|
|
295
361
|
dialog = Dialog.new(self, params)
|
296
362
|
block.call(dialog, index, total)
|
297
363
|
end
|
298
364
|
when :request
|
299
|
-
|
300
|
-
request = Network::InterceptedRequest.new(
|
365
|
+
client.on("Fetch.requestPaused") do |params, index, total|
|
366
|
+
request = Network::InterceptedRequest.new(client, params)
|
301
367
|
exchange = network.select(request.network_id).last
|
302
368
|
exchange ||= network.build_exchange(request.network_id)
|
303
369
|
exchange.intercepted_request = request
|
304
370
|
block.call(request, index, total)
|
305
371
|
end
|
306
372
|
when :auth
|
307
|
-
|
373
|
+
client.on("Fetch.authRequired") do |params, index, total|
|
308
374
|
request = Network::AuthRequest.new(self, params)
|
309
375
|
block.call(request, index, total)
|
310
376
|
end
|
311
377
|
else
|
312
|
-
|
378
|
+
client.on(name, &block)
|
313
379
|
end
|
314
380
|
end
|
315
381
|
|
316
382
|
def subscribed?(event)
|
317
|
-
|
383
|
+
client.subscribed?(event)
|
318
384
|
end
|
319
385
|
|
320
386
|
def use_proxy?
|
@@ -325,19 +391,24 @@ module Ferrum
|
|
325
391
|
use_proxy? && @proxy_user && @proxy_password
|
326
392
|
end
|
327
393
|
|
394
|
+
def document_node_id
|
395
|
+
command("DOM.getDocument", depth: 0).dig("root", "nodeId")
|
396
|
+
end
|
397
|
+
|
328
398
|
private
|
329
399
|
|
330
400
|
def subscribe
|
331
401
|
frames_subscribe
|
332
402
|
network.subscribe
|
403
|
+
downloads.subscribe
|
333
404
|
|
334
|
-
if @
|
405
|
+
if @options.logger
|
335
406
|
on("Runtime.consoleAPICalled") do |params|
|
336
|
-
params["args"].each { |r| @
|
407
|
+
params["args"].each { |r| @options.logger.puts(r["value"]) }
|
337
408
|
end
|
338
409
|
end
|
339
410
|
|
340
|
-
if @
|
411
|
+
if @options.js_errors
|
341
412
|
on("Runtime.exceptionThrown") do |params|
|
342
413
|
# FIXME: https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
|
343
414
|
Thread.main.raise JavaScriptError.new(
|
@@ -350,7 +421,7 @@ module Ferrum
|
|
350
421
|
on(:dialog) do |dialog, _index, total|
|
351
422
|
if total == 1
|
352
423
|
warn "Dialog was shown but you didn't provide `on(:dialog)` callback, accepting it by default. " \
|
353
|
-
"Please take a look at https://github.com/rubycdp/ferrum#
|
424
|
+
"Please take a look at https://github.com/rubycdp/ferrum#dialogs"
|
354
425
|
dialog.accept
|
355
426
|
end
|
356
427
|
end
|
@@ -372,28 +443,17 @@ module Ferrum
|
|
372
443
|
end
|
373
444
|
end
|
374
445
|
|
375
|
-
if @
|
376
|
-
unless Pathname.new(@browser.options.save_path).absolute?
|
377
|
-
raise Error, "supply absolute path for `:save_path` option"
|
378
|
-
end
|
379
|
-
|
380
|
-
@browser.command("Browser.setDownloadBehavior",
|
381
|
-
browserContextId: context.id,
|
382
|
-
downloadPath: @browser.options.save_path,
|
383
|
-
behavior: "allow", eventsEnabled: true)
|
384
|
-
end
|
446
|
+
downloads.set_behavior(save_path: @options.save_path) if @options.save_path
|
385
447
|
|
386
|
-
@
|
448
|
+
@options.extensions.each do |extension|
|
387
449
|
command("Page.addScriptToEvaluateOnNewDocument", source: extension)
|
388
450
|
end
|
389
451
|
|
390
452
|
inject_extensions
|
391
453
|
|
392
|
-
width, height = @browser.window_size
|
393
|
-
resize(width: width, height: height)
|
394
|
-
|
395
454
|
response = command("Page.getNavigationHistory")
|
396
|
-
|
455
|
+
transition_type = response.dig("entries", 0, "transitionType")
|
456
|
+
return if transition_type == "auto_toplevel"
|
397
457
|
|
398
458
|
# If we create page by clicking links, submitting forms and so on it
|
399
459
|
# opens a new window for which `frameStoppedLoading` event never
|
@@ -404,7 +464,7 @@ module Ferrum
|
|
404
464
|
end
|
405
465
|
|
406
466
|
def inject_extensions
|
407
|
-
@
|
467
|
+
@options.extensions.each do |extension|
|
408
468
|
# https://github.com/GoogleChrome/puppeteer/issues/1443
|
409
469
|
# https://github.com/ChromeDevTools/devtools-protocol/issues/77
|
410
470
|
# https://github.com/cyrus-and/chrome-remote-interface/issues/319
|
@@ -434,26 +494,18 @@ module Ferrum
|
|
434
494
|
url = Addressable::URI.parse(url_or_path)
|
435
495
|
nil_or_relative = url.nil? || url.relative?
|
436
496
|
|
437
|
-
if nil_or_relative && !@
|
497
|
+
if nil_or_relative && !@options.base_url
|
438
498
|
raise "Set :base_url browser's option or use absolute url in `go_to`, you passed: #{url_or_path}"
|
439
499
|
end
|
440
500
|
|
441
|
-
(nil_or_relative ? @
|
442
|
-
end
|
443
|
-
|
444
|
-
def document_node_id
|
445
|
-
command("DOM.getDocument", depth: 0).dig("root", "nodeId")
|
446
|
-
end
|
447
|
-
|
448
|
-
def ws_url
|
449
|
-
"ws://#{@browser.process.host}:#{@browser.process.port}/devtools/page/#{@target_id}"
|
501
|
+
(nil_or_relative ? @options.base_url.join(url.to_s) : url).to_s
|
450
502
|
end
|
451
503
|
|
452
504
|
def proxy=(options)
|
453
|
-
@proxy_host = options&.[](:host) || @
|
454
|
-
@proxy_port = options&.[](:port) || @
|
455
|
-
@proxy_user = options&.[](:user) || @
|
456
|
-
@proxy_password = options&.[](:password) || @
|
505
|
+
@proxy_host = options&.[](:host) || @options.proxy&.[](:host)
|
506
|
+
@proxy_port = options&.[](:port) || @options.proxy&.[](:port)
|
507
|
+
@proxy_user = options&.[](:user) || @options.proxy&.[](:user)
|
508
|
+
@proxy_password = options&.[](:password) || @options.proxy&.[](:password)
|
457
509
|
end
|
458
510
|
end
|
459
511
|
end
|
data/lib/ferrum/proxy.rb
CHANGED
data/lib/ferrum/target.rb
CHANGED
@@ -8,17 +8,21 @@ module Ferrum
|
|
8
8
|
# where we enhance page class and build page ourselves.
|
9
9
|
attr_writer :page
|
10
10
|
|
11
|
-
|
11
|
+
attr_reader :session_id, :options
|
12
|
+
|
13
|
+
def initialize(browser_client, session_id = nil, params = nil)
|
12
14
|
@page = nil
|
13
|
-
@
|
15
|
+
@session_id = session_id
|
14
16
|
@params = params
|
17
|
+
@browser_client = browser_client
|
18
|
+
@options = browser_client.options
|
15
19
|
end
|
16
20
|
|
17
21
|
def update(params)
|
18
|
-
@params
|
22
|
+
@params.merge!(params)
|
19
23
|
end
|
20
24
|
|
21
|
-
def
|
25
|
+
def connected?
|
22
26
|
!!@page
|
23
27
|
end
|
24
28
|
|
@@ -26,9 +30,13 @@ module Ferrum
|
|
26
30
|
@page ||= build_page
|
27
31
|
end
|
28
32
|
|
33
|
+
def client
|
34
|
+
@client ||= build_client
|
35
|
+
end
|
36
|
+
|
29
37
|
def build_page(**options)
|
30
38
|
maybe_sleep_if_new_window
|
31
|
-
Page.new(
|
39
|
+
Page.new(client, context_id: context_id, target_id: id, **options)
|
32
40
|
end
|
33
41
|
|
34
42
|
def id
|
@@ -63,5 +71,17 @@ module Ferrum
|
|
63
71
|
# Dirty hack because new window doesn't have events at all
|
64
72
|
sleep(NEW_WINDOW_WAIT) if window?
|
65
73
|
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def build_client
|
78
|
+
return @browser_client.session(session_id) if options.flatten
|
79
|
+
|
80
|
+
Client.new(ws_url, options)
|
81
|
+
end
|
82
|
+
|
83
|
+
def ws_url
|
84
|
+
@browser_client.ws_url.merge(path: "/devtools/page/#{id}")
|
85
|
+
end
|
66
86
|
end
|
67
87
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
module Utils
|
5
|
+
class Event < Concurrent::Event
|
6
|
+
def iteration
|
7
|
+
synchronize { @iteration }
|
8
|
+
end
|
9
|
+
|
10
|
+
def reset
|
11
|
+
synchronize do
|
12
|
+
@iteration += 1
|
13
|
+
@set = false if @set
|
14
|
+
@iteration
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
module Utils
|
5
|
+
module Thread
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def spawn(abort_on_exception: true)
|
9
|
+
::Thread.new(abort_on_exception) do |whether_abort_on_exception|
|
10
|
+
::Thread.current.abort_on_exception = whether_abort_on_exception
|
11
|
+
::Thread.current.report_on_exception = true if ::Thread.current.respond_to?(:report_on_exception=)
|
12
|
+
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/ferrum/version.rb
CHANGED