ferrum 0.12 → 0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -22
- data/lib/ferrum/browser/client.rb +6 -5
- data/lib/ferrum/browser/command.rb +9 -6
- data/lib/ferrum/browser/options/base.rb +1 -4
- data/lib/ferrum/browser/options/chrome.rb +22 -10
- data/lib/ferrum/browser/options/firefox.rb +3 -6
- data/lib/ferrum/browser/options.rb +84 -0
- data/lib/ferrum/browser/process.rb +6 -7
- data/lib/ferrum/browser/version_info.rb +71 -0
- data/lib/ferrum/browser/web_socket.rb +1 -1
- data/lib/ferrum/browser/xvfb.rb +1 -1
- data/lib/ferrum/browser.rb +184 -64
- data/lib/ferrum/context.rb +3 -2
- data/lib/ferrum/contexts.rb +2 -2
- data/lib/ferrum/cookies/cookie.rb +183 -0
- data/lib/ferrum/cookies.rb +122 -49
- data/lib/ferrum/dialog.rb +30 -0
- data/lib/ferrum/frame/dom.rb +177 -0
- data/lib/ferrum/frame/runtime.rb +41 -61
- data/lib/ferrum/frame.rb +91 -3
- data/lib/ferrum/headers.rb +28 -0
- data/lib/ferrum/keyboard.rb +45 -2
- data/lib/ferrum/mouse.rb +84 -0
- data/lib/ferrum/network/exchange.rb +104 -5
- data/lib/ferrum/network/intercepted_request.rb +3 -12
- data/lib/ferrum/network/request.rb +58 -19
- data/lib/ferrum/network/request_params.rb +57 -0
- data/lib/ferrum/network/response.rb +106 -4
- data/lib/ferrum/network.rb +193 -8
- data/lib/ferrum/node.rb +21 -1
- data/lib/ferrum/page/animation.rb +16 -0
- data/lib/ferrum/page/frames.rb +66 -11
- data/lib/ferrum/page/screenshot.rb +97 -0
- data/lib/ferrum/page/tracing.rb +26 -0
- data/lib/ferrum/page.rb +158 -45
- data/lib/ferrum/proxy.rb +91 -2
- data/lib/ferrum/target.rb +6 -4
- data/lib/ferrum/version.rb +1 -1
- metadata +7 -101
data/lib/ferrum/page/frames.rb
CHANGED
@@ -5,12 +5,54 @@ require "ferrum/frame"
|
|
5
5
|
module Ferrum
|
6
6
|
class Page
|
7
7
|
module Frames
|
8
|
+
# The page's main frame, the top of the tree and the parent of all frames.
|
9
|
+
#
|
10
|
+
# @return [Frame]
|
8
11
|
attr_reader :main_frame
|
9
12
|
|
13
|
+
#
|
14
|
+
# Returns all the frames current page have.
|
15
|
+
#
|
16
|
+
# @return [Array<Frame>]
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# browser.go_to("https://www.w3schools.com/tags/tag_frame.asp")
|
20
|
+
# browser.frames # =>
|
21
|
+
# # [
|
22
|
+
# # #<Ferrum::Frame
|
23
|
+
# # @id="C6D104CE454A025FBCF22B98DE612B12"
|
24
|
+
# # @parent_id=nil @name=nil @state=:stopped_loading @execution_id=1>,
|
25
|
+
# # #<Ferrum::Frame
|
26
|
+
# # @id="C09C4E4404314AAEAE85928EAC109A93"
|
27
|
+
# # @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=2>,
|
28
|
+
# # #<Ferrum::Frame
|
29
|
+
# # @id="2E9C7F476ED09D87A42F2FEE3C6FBC3C"
|
30
|
+
# # @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=3>,
|
31
|
+
# # ...
|
32
|
+
# # ]
|
33
|
+
#
|
10
34
|
def frames
|
11
35
|
@frames.values
|
12
36
|
end
|
13
37
|
|
38
|
+
#
|
39
|
+
# Find frame by given options.
|
40
|
+
#
|
41
|
+
# @param [String] id
|
42
|
+
# Unique frame's id that browser provides.
|
43
|
+
#
|
44
|
+
# @param [String] name
|
45
|
+
# Frame's name if there's one.
|
46
|
+
#
|
47
|
+
# @param [String] execution_id
|
48
|
+
# Frame's context execution id.
|
49
|
+
#
|
50
|
+
# @return [Frame, nil]
|
51
|
+
# The matching frame.
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# browser.frame_by(id: "C6D104CE454A025FBCF22B98DE612B12")
|
55
|
+
#
|
14
56
|
def frame_by(id: nil, name: nil, execution_id: nil)
|
15
57
|
if id
|
16
58
|
@frames[id]
|
@@ -25,6 +67,7 @@ module Ferrum
|
|
25
67
|
|
26
68
|
def frames_subscribe
|
27
69
|
subscribe_frame_attached
|
70
|
+
subscribe_frame_detached
|
28
71
|
subscribe_frame_started_loading
|
29
72
|
subscribe_frame_navigated
|
30
73
|
subscribe_frame_stopped_loading
|
@@ -43,14 +86,26 @@ module Ferrum
|
|
43
86
|
def subscribe_frame_attached
|
44
87
|
on("Page.frameAttached") do |params|
|
45
88
|
parent_frame_id, frame_id = params.values_at("parentFrameId", "frameId")
|
46
|
-
@frames
|
89
|
+
@frames.put_if_absent(frame_id, Frame.new(frame_id, self, parent_frame_id))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def subscribe_frame_detached
|
94
|
+
on("Page.frameDetached") do |params|
|
95
|
+
frame = @frames[params["frameId"]]
|
96
|
+
|
97
|
+
if frame&.main?
|
98
|
+
frame.execution_id = nil
|
99
|
+
else
|
100
|
+
@frames.delete(params["frameId"])
|
101
|
+
end
|
47
102
|
end
|
48
103
|
end
|
49
104
|
|
50
105
|
def subscribe_frame_started_loading
|
51
106
|
on("Page.frameStartedLoading") do |params|
|
52
107
|
frame = @frames[params["frameId"]]
|
53
|
-
frame.state = :started_loading
|
108
|
+
frame.state = :started_loading if frame
|
54
109
|
@event.reset
|
55
110
|
end
|
56
111
|
end
|
@@ -59,8 +114,11 @@ module Ferrum
|
|
59
114
|
on("Page.frameNavigated") do |params|
|
60
115
|
frame_id, name = params["frame"]&.values_at("id", "name")
|
61
116
|
frame = @frames[frame_id]
|
62
|
-
|
63
|
-
frame
|
117
|
+
|
118
|
+
if frame
|
119
|
+
frame.state = :navigated
|
120
|
+
frame.name = name
|
121
|
+
end
|
64
122
|
end
|
65
123
|
end
|
66
124
|
|
@@ -107,14 +165,12 @@ module Ferrum
|
|
107
165
|
root_frame = command("Page.getFrameTree").dig("frameTree", "frame", "id")
|
108
166
|
if frame_id == root_frame
|
109
167
|
@main_frame.id = frame_id
|
110
|
-
@frames
|
168
|
+
@frames.put_if_absent(frame_id, @main_frame)
|
111
169
|
end
|
112
170
|
end
|
113
171
|
|
114
|
-
frame = @frames
|
172
|
+
frame = @frames.fetch_or_store(frame_id, Frame.new(frame_id, self))
|
115
173
|
frame.execution_id = context_id
|
116
|
-
|
117
|
-
@frames[frame_id] ||= frame
|
118
174
|
end
|
119
175
|
end
|
120
176
|
|
@@ -128,13 +184,12 @@ module Ferrum
|
|
128
184
|
|
129
185
|
def subscribe_execution_contexts_cleared
|
130
186
|
on("Runtime.executionContextsCleared") do
|
131
|
-
@frames.
|
132
|
-
@main_frame.execution_id = nil
|
187
|
+
@frames.each_value { |f| f.execution_id = nil }
|
133
188
|
end
|
134
189
|
end
|
135
190
|
|
136
191
|
def idling?
|
137
|
-
@frames.all? { |
|
192
|
+
@frames.values.all? { |f| f.state == :stopped_loading }
|
138
193
|
end
|
139
194
|
end
|
140
195
|
end
|
@@ -26,6 +26,51 @@ module Ferrum
|
|
26
26
|
A6: { width: 4.13, height: 5.83 }
|
27
27
|
}.freeze
|
28
28
|
|
29
|
+
#
|
30
|
+
# Saves screenshot on a disk or returns it as base64.
|
31
|
+
#
|
32
|
+
# @param [Hash{Symbol => Object}] opts
|
33
|
+
#
|
34
|
+
# @option opts [String] :path
|
35
|
+
# The path to save a screenshot on the disk. `:encoding` will be set to
|
36
|
+
# `:binary` automatically.
|
37
|
+
#
|
38
|
+
# @option opts [:base64, :binary] :encoding
|
39
|
+
# The encoding the image should be returned in.
|
40
|
+
#
|
41
|
+
# @option opts ["jpeg", "png"] :format
|
42
|
+
# The format the image should be returned in.
|
43
|
+
#
|
44
|
+
# @option opts [Integer] :quality
|
45
|
+
# The image quality. **Note:** 0-100 works for jpeg only.
|
46
|
+
#
|
47
|
+
# @option opts [Boolean] :full
|
48
|
+
# Whether you need full page screenshot or a viewport.
|
49
|
+
#
|
50
|
+
# @option opts [String] :selector
|
51
|
+
# CSS selector for the given element.
|
52
|
+
#
|
53
|
+
# @option opts [Float] :scale
|
54
|
+
# Zoom in/out.
|
55
|
+
#
|
56
|
+
# @option opts [Ferrum::RGBA] :background_color
|
57
|
+
# Sets the background color.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# browser.go_to("https://google.com/")
|
61
|
+
#
|
62
|
+
# @example Save on the disk in PNG:
|
63
|
+
# browser.screenshot(path: "google.png") # => 134660
|
64
|
+
#
|
65
|
+
# @example Save on the disk in JPG:
|
66
|
+
# browser.screenshot(path: "google.jpg") # => 30902
|
67
|
+
#
|
68
|
+
# @example Save to Base64 the whole page not only viewport and reduce quality:
|
69
|
+
# browser.screenshot(full: true, quality: 60) # "iVBORw0KGgoAAAANS...
|
70
|
+
#
|
71
|
+
# @example Save with specific background color:
|
72
|
+
# browser.screenshot(background_color: Ferrum::RGBA.new(0, 0, 0, 0.0))
|
73
|
+
#
|
29
74
|
def screenshot(**opts)
|
30
75
|
path, encoding = common_options(**opts)
|
31
76
|
options = screenshot_options(path, **opts)
|
@@ -36,6 +81,42 @@ module Ferrum
|
|
36
81
|
save_file(path, bin)
|
37
82
|
end
|
38
83
|
|
84
|
+
#
|
85
|
+
# Saves PDF on a disk or returns it as Base64.
|
86
|
+
#
|
87
|
+
# @param [Hash{Symbol => Object}] opts
|
88
|
+
#
|
89
|
+
# @option opts [String] :path
|
90
|
+
# The path to save a screenshot on the disk. `:encoding` will be set to
|
91
|
+
# `:binary` automatically.
|
92
|
+
#
|
93
|
+
# @option opts [:base64, :binary] :encoding
|
94
|
+
# The encoding the image should be returned in.
|
95
|
+
#
|
96
|
+
# @option opts [Boolean] :landscape (false)
|
97
|
+
# Page orientation.
|
98
|
+
#
|
99
|
+
# @option opts [Float] :scale
|
100
|
+
# Zoom in/out.
|
101
|
+
#
|
102
|
+
# @option opts [:letter, :legal, :tabloid, :ledger, :A0, :A1, :A2, :A3, :A4, :A5, :A6] :format
|
103
|
+
# The standard paper size.
|
104
|
+
#
|
105
|
+
# @option opts [Float] :paper_width
|
106
|
+
# Sets the paper's width.
|
107
|
+
#
|
108
|
+
# @option opts [Float] :paper_height
|
109
|
+
# Sets the paper's height.
|
110
|
+
#
|
111
|
+
# @note
|
112
|
+
# See other [native options](https://chromedevtools.github.io/devtools-protocol/tot/Page#method-printToPDF) you
|
113
|
+
# can pass.
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# browser.go_to("https://google.com/")
|
117
|
+
# # Save to disk as a PDF
|
118
|
+
# browser.pdf(path: "google.pdf", paper_width: 1.0, paper_height: 1.0) # => true
|
119
|
+
#
|
39
120
|
def pdf(**opts)
|
40
121
|
path, encoding = common_options(**opts)
|
41
122
|
options = pdf_options(**opts).merge(transferMode: "ReturnAsStream")
|
@@ -43,6 +124,16 @@ module Ferrum
|
|
43
124
|
stream_to(path: path, encoding: encoding, handle: handle)
|
44
125
|
end
|
45
126
|
|
127
|
+
#
|
128
|
+
# Saves MHTML on a disk or returns it as a string.
|
129
|
+
#
|
130
|
+
# @param [String, nil] path
|
131
|
+
# The path to save a file on the disk.
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# browser.go_to("https://google.com/")
|
135
|
+
# browser.mhtml(path: "google.mhtml") # => 87742
|
136
|
+
#
|
46
137
|
def mhtml(path: nil)
|
47
138
|
data = command("Page.captureSnapshot", format: :mhtml).fetch("data")
|
48
139
|
return data if path.nil?
|
@@ -56,6 +147,12 @@ module Ferrum
|
|
56
147
|
JS
|
57
148
|
end
|
58
149
|
|
150
|
+
def device_pixel_ratio
|
151
|
+
evaluate <<~JS
|
152
|
+
window.devicePixelRatio
|
153
|
+
JS
|
154
|
+
end
|
155
|
+
|
59
156
|
def document_size
|
60
157
|
evaluate <<~JS
|
61
158
|
[document.documentElement.scrollWidth,
|
data/lib/ferrum/page/tracing.rb
CHANGED
@@ -19,6 +19,32 @@ module Ferrum
|
|
19
19
|
@subscribed_tracing_complete = false
|
20
20
|
end
|
21
21
|
|
22
|
+
#
|
23
|
+
# Accepts block, records trace and by default returns trace data from `Tracing.tracingComplete` event as output.
|
24
|
+
#
|
25
|
+
# @param [String, nil] path
|
26
|
+
# Save data on the disk.
|
27
|
+
#
|
28
|
+
# @param [:binary, :base64] encoding
|
29
|
+
# Encode output as Base64 or plain text.
|
30
|
+
#
|
31
|
+
# @param [Float, nil] timeout
|
32
|
+
# Wait until file streaming finishes in the specified time or raise
|
33
|
+
# error.
|
34
|
+
#
|
35
|
+
# @param [Boolean] screenshots
|
36
|
+
# capture screenshots in the trace.
|
37
|
+
#
|
38
|
+
# @param [Hash{String => Object}] trace_config
|
39
|
+
# config for [trace](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#type-TraceConfig),
|
40
|
+
# for categories see [getCategories](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#method-getCategories),
|
41
|
+
# only one trace config can be active at a time per browser.
|
42
|
+
#
|
43
|
+
# @return [String, true]
|
44
|
+
# The trace data from the `Tracing.tracingComplete` event.
|
45
|
+
# When `path` is specified returns `true` and stores trace data into
|
46
|
+
# file.
|
47
|
+
#
|
22
48
|
def record(path: nil, encoding: :binary, timeout: nil, trace_config: nil, screenshots: false)
|
23
49
|
@path = path
|
24
50
|
@encoding = encoding
|
data/lib/ferrum/page.rb
CHANGED
@@ -35,7 +35,7 @@ module Ferrum
|
|
35
35
|
extend Forwardable
|
36
36
|
delegate %i[at_css at_xpath css xpath
|
37
37
|
current_url current_title url title body doctype content=
|
38
|
-
execution_id evaluate evaluate_on evaluate_async execute evaluate_func
|
38
|
+
execution_id execution_id! evaluate evaluate_on evaluate_async execute evaluate_func
|
39
39
|
add_script_tag add_style_tag] => :main_frame
|
40
40
|
|
41
41
|
include Animation
|
@@ -43,23 +43,47 @@ module Ferrum
|
|
43
43
|
include Frames
|
44
44
|
include Stream
|
45
45
|
|
46
|
-
attr_accessor :referrer
|
47
|
-
attr_reader :target_id, :browser,
|
48
|
-
:headers, :cookies, :network,
|
49
|
-
:mouse, :keyboard, :event,
|
50
|
-
:tracing
|
46
|
+
attr_accessor :referrer, :timeout
|
47
|
+
attr_reader :target_id, :browser, :event, :tracing
|
51
48
|
|
52
|
-
|
53
|
-
|
49
|
+
# Mouse object.
|
50
|
+
#
|
51
|
+
# @return [Mouse]
|
52
|
+
attr_reader :mouse
|
53
|
+
|
54
|
+
# Keyboard object.
|
55
|
+
#
|
56
|
+
# @return [Keyboard]
|
57
|
+
attr_reader :keyboard
|
58
|
+
|
59
|
+
# Network object.
|
60
|
+
#
|
61
|
+
# @return [Network]
|
62
|
+
attr_reader :network
|
63
|
+
|
64
|
+
# Headers object.
|
65
|
+
#
|
66
|
+
# @return [Headers]
|
67
|
+
attr_reader :headers
|
68
|
+
|
69
|
+
# Cookie store.
|
70
|
+
#
|
71
|
+
# @return [Cookies]
|
72
|
+
attr_reader :cookies
|
73
|
+
|
74
|
+
def initialize(target_id, browser, proxy: nil)
|
75
|
+
@frames = Concurrent::Map.new
|
54
76
|
@main_frame = Frame.new(nil, self)
|
55
77
|
@browser = browser
|
56
78
|
@target_id = target_id
|
79
|
+
@timeout = @browser.timeout
|
57
80
|
@event = Event.new.tap(&:set)
|
81
|
+
self.proxy = proxy
|
58
82
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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)
|
63
87
|
|
64
88
|
@mouse = Mouse.new(self)
|
65
89
|
@keyboard = Keyboard.new(self)
|
@@ -72,30 +96,31 @@ module Ferrum
|
|
72
96
|
prepare_page
|
73
97
|
end
|
74
98
|
|
75
|
-
def timeout
|
76
|
-
@browser.timeout
|
77
|
-
end
|
78
|
-
|
79
99
|
def context
|
80
100
|
@browser.contexts.find_by(target_id: target_id)
|
81
101
|
end
|
82
102
|
|
103
|
+
#
|
104
|
+
# Navigates the page to a URL.
|
105
|
+
#
|
106
|
+
# @param [String, nil] url
|
107
|
+
# The URL to navigate to. The url should include scheme unless you set
|
108
|
+
# `{Browser#base_url = url}` when configuring driver.
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# browser.go_to("https://github.com/")
|
112
|
+
#
|
83
113
|
def go_to(url = nil)
|
84
114
|
options = { url: combine_url!(url) }
|
85
115
|
options.merge!(referrer: referrer) if referrer
|
86
116
|
response = command("Page.navigate", wait: GOTO_WAIT, **options)
|
87
|
-
|
88
|
-
if
|
89
|
-
net::ERR_NAME_RESOLUTION_FAILED
|
90
|
-
net::ERR_INTERNET_DISCONNECTED
|
91
|
-
net::ERR_CONNECTION_TIMED_OUT].include?(response["errorText"])
|
92
|
-
raise StatusError, options[:url]
|
93
|
-
end
|
117
|
+
error_text = response["errorText"]
|
118
|
+
raise StatusError.new(options[:url], "Request to #{options[:url]} failed (#{error_text})") if error_text
|
94
119
|
|
95
120
|
response["frameId"]
|
96
121
|
rescue TimeoutError
|
97
|
-
if @browser.pending_connection_errors
|
98
|
-
pendings = network.traffic.select(&:pending?).map
|
122
|
+
if @browser.options.pending_connection_errors
|
123
|
+
pendings = network.traffic.select(&:pending?).map(&:url).compact
|
99
124
|
raise PendingConnectionsError.new(options[:url], pendings) unless pendings.empty?
|
100
125
|
end
|
101
126
|
end
|
@@ -120,34 +145,87 @@ module Ferrum
|
|
120
145
|
command("Emulation.setDeviceMetricsOverride", slowmoable: true,
|
121
146
|
width: width,
|
122
147
|
height: height,
|
123
|
-
deviceScaleFactor:
|
124
|
-
mobile: false
|
125
|
-
fitWindow: false)
|
148
|
+
deviceScaleFactor: 0,
|
149
|
+
mobile: false)
|
126
150
|
end
|
127
151
|
|
152
|
+
#
|
153
|
+
# The current position of the browser window.
|
154
|
+
#
|
155
|
+
# @return [(Integer, Integer)]
|
156
|
+
# The left, top coordinates of the browser window.
|
157
|
+
#
|
158
|
+
# @example
|
159
|
+
# browser.position # => [10, 20]
|
160
|
+
#
|
128
161
|
def position
|
129
162
|
@browser.command("Browser.getWindowBounds", windowId: window_id).fetch("bounds").values_at("left", "top")
|
130
163
|
end
|
131
164
|
|
165
|
+
#
|
166
|
+
# Sets the position of the browser window.
|
167
|
+
#
|
168
|
+
# @param [Hash{Symbol => Object}] options
|
169
|
+
#
|
170
|
+
# @option options [Integer] :left
|
171
|
+
# The number of pixels from the left-hand side of the screen.
|
172
|
+
#
|
173
|
+
# @option options [Integer] :top
|
174
|
+
# The number of pixels from the top of the screen.
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# browser.position = { left: 10, top: 20 }
|
178
|
+
#
|
132
179
|
def position=(options)
|
133
180
|
@browser.command("Browser.setWindowBounds",
|
134
181
|
windowId: window_id,
|
135
182
|
bounds: { left: options[:left], top: options[:top] })
|
136
183
|
end
|
137
184
|
|
185
|
+
#
|
186
|
+
# Reloads the current page.
|
187
|
+
#
|
188
|
+
# @example
|
189
|
+
# browser.go_to("https://github.com/")
|
190
|
+
# browser.refresh
|
191
|
+
#
|
138
192
|
def refresh
|
139
193
|
command("Page.reload", wait: timeout, slowmoable: true)
|
140
194
|
end
|
141
195
|
alias reload refresh
|
142
196
|
|
197
|
+
#
|
198
|
+
# Stop all navigations and loading pending resources on the page.
|
199
|
+
#
|
200
|
+
# @example
|
201
|
+
# browser.go_to("https://github.com/")
|
202
|
+
# browser.stop
|
203
|
+
#
|
143
204
|
def stop
|
144
205
|
command("Page.stopLoading", slowmoable: true)
|
145
206
|
end
|
146
207
|
|
208
|
+
#
|
209
|
+
# Navigates to the previous URL in the browser's history.
|
210
|
+
#
|
211
|
+
# @example
|
212
|
+
# browser.go_to("https://github.com/")
|
213
|
+
# browser.at_xpath("//a").click
|
214
|
+
# browser.back
|
215
|
+
#
|
147
216
|
def back
|
148
217
|
history_navigate(delta: -1)
|
149
218
|
end
|
150
219
|
|
220
|
+
#
|
221
|
+
# Navigates to the next URL in the browser's history.
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
# browser.go_to("https://github.com/")
|
225
|
+
# browser.at_xpath("//a").click
|
226
|
+
# browser.back
|
227
|
+
# browser.forward
|
228
|
+
#
|
151
229
|
def forward
|
152
230
|
history_navigate(delta: 1)
|
153
231
|
end
|
@@ -158,6 +236,20 @@ module Ferrum
|
|
158
236
|
@event.set
|
159
237
|
end
|
160
238
|
|
239
|
+
#
|
240
|
+
# Enables/disables CSP bypass.
|
241
|
+
#
|
242
|
+
# @param [Boolean] enabled
|
243
|
+
#
|
244
|
+
# @return [Boolean]
|
245
|
+
#
|
246
|
+
# @example
|
247
|
+
# browser.bypass_csp # => true
|
248
|
+
# browser.go_to("https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md")
|
249
|
+
# browser.refresh
|
250
|
+
# browser.add_script_tag(content: "window.__injected = 42")
|
251
|
+
# browser.evaluate("window.__injected") # => 42
|
252
|
+
#
|
161
253
|
def bypass_csp(enabled: true)
|
162
254
|
command("Page.setBypassCSP", enabled: enabled)
|
163
255
|
enabled
|
@@ -173,16 +265,16 @@ module Ferrum
|
|
173
265
|
|
174
266
|
def command(method, wait: 0, slowmoable: false, **params)
|
175
267
|
iteration = @event.reset if wait.positive?
|
176
|
-
sleep(@browser.slowmo) if slowmoable && @browser.slowmo.positive?
|
268
|
+
sleep(@browser.options.slowmo) if slowmoable && @browser.options.slowmo.positive?
|
177
269
|
result = @client.command(method, params)
|
178
270
|
|
179
271
|
if wait.positive?
|
180
|
-
@event.wait(wait)
|
181
272
|
# Wait a bit after command and check if iteration has
|
182
273
|
# changed which means there was some network event for
|
183
274
|
# the main frame and it started to load new content.
|
275
|
+
@event.wait(wait)
|
184
276
|
if iteration != @event.iteration
|
185
|
-
set = @event.wait(
|
277
|
+
set = @event.wait(timeout)
|
186
278
|
raise TimeoutError unless set
|
187
279
|
end
|
188
280
|
end
|
@@ -218,19 +310,31 @@ module Ferrum
|
|
218
310
|
@client.subscribed?(event)
|
219
311
|
end
|
220
312
|
|
313
|
+
def use_proxy?
|
314
|
+
@proxy_host && @proxy_port
|
315
|
+
end
|
316
|
+
|
317
|
+
def use_authorized_proxy?
|
318
|
+
use_proxy? && @proxy_user && @proxy_password
|
319
|
+
end
|
320
|
+
|
321
|
+
def document_node_id
|
322
|
+
command("DOM.getDocument", depth: 0).dig("root", "nodeId")
|
323
|
+
end
|
324
|
+
|
221
325
|
private
|
222
326
|
|
223
327
|
def subscribe
|
224
328
|
frames_subscribe
|
225
329
|
network.subscribe
|
226
330
|
|
227
|
-
if @browser.logger
|
331
|
+
if @browser.options.logger
|
228
332
|
on("Runtime.consoleAPICalled") do |params|
|
229
|
-
params["args"].each { |r| @browser.logger.puts(r["value"]) }
|
333
|
+
params["args"].each { |r| @browser.options.logger.puts(r["value"]) }
|
230
334
|
end
|
231
335
|
end
|
232
336
|
|
233
|
-
if @browser.js_errors
|
337
|
+
if @browser.options.js_errors
|
234
338
|
on("Runtime.exceptionThrown") do |params|
|
235
339
|
# FIXME: https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
|
236
340
|
Thread.main.raise JavaScriptError.new(
|
@@ -243,7 +347,7 @@ module Ferrum
|
|
243
347
|
on(:dialog) do |dialog, _index, total|
|
244
348
|
if total == 1
|
245
349
|
warn "Dialog was shown but you didn't provide `on(:dialog)` callback, accepting it by default. " \
|
246
|
-
"Please take a look at https://github.com/rubycdp/ferrum#
|
350
|
+
"Please take a look at https://github.com/rubycdp/ferrum#dialogs"
|
247
351
|
dialog.accept
|
248
352
|
end
|
249
353
|
end
|
@@ -257,21 +361,22 @@ module Ferrum
|
|
257
361
|
command("Log.enable")
|
258
362
|
command("Network.enable")
|
259
363
|
|
260
|
-
if
|
261
|
-
|
262
|
-
|
364
|
+
if use_authorized_proxy?
|
365
|
+
network.authorize(user: @proxy_user,
|
366
|
+
password: @proxy_password,
|
367
|
+
type: :proxy) do |request, _index, _total|
|
263
368
|
request.continue
|
264
369
|
end
|
265
370
|
end
|
266
371
|
|
267
|
-
if @browser.options
|
268
|
-
unless Pathname.new(@browser.options
|
372
|
+
if @browser.options.save_path
|
373
|
+
unless Pathname.new(@browser.options.save_path).absolute?
|
269
374
|
raise Error, "supply absolute path for `:save_path` option"
|
270
375
|
end
|
271
376
|
|
272
377
|
@browser.command("Browser.setDownloadBehavior",
|
273
378
|
browserContextId: context.id,
|
274
|
-
downloadPath: browser.options
|
379
|
+
downloadPath: @browser.options.save_path,
|
275
380
|
behavior: "allow", eventsEnabled: true)
|
276
381
|
end
|
277
382
|
|
@@ -285,7 +390,8 @@ module Ferrum
|
|
285
390
|
resize(width: width, height: height)
|
286
391
|
|
287
392
|
response = command("Page.getNavigationHistory")
|
288
|
-
|
393
|
+
transition_type = response.dig("entries", 0, "transitionType")
|
394
|
+
return if transition_type == "auto_toplevel"
|
289
395
|
|
290
396
|
# If we create page by clicking links, submitting forms and so on it
|
291
397
|
# opens a new window for which `frameStoppedLoading` event never
|
@@ -303,7 +409,7 @@ module Ferrum
|
|
303
409
|
# We also evaluate script just in case because
|
304
410
|
# `Page.addScriptToEvaluateOnNewDocument` doesn't work in popups.
|
305
411
|
command("Runtime.evaluate", expression: extension,
|
306
|
-
|
412
|
+
executionContextId: execution_id!,
|
307
413
|
returnByValue: true)
|
308
414
|
end
|
309
415
|
end
|
@@ -333,8 +439,15 @@ module Ferrum
|
|
333
439
|
(nil_or_relative ? @browser.base_url.join(url.to_s) : url).to_s
|
334
440
|
end
|
335
441
|
|
336
|
-
def
|
337
|
-
|
442
|
+
def ws_url
|
443
|
+
"ws://#{@browser.process.host}:#{@browser.process.port}/devtools/page/#{@target_id}"
|
444
|
+
end
|
445
|
+
|
446
|
+
def proxy=(options)
|
447
|
+
@proxy_host = options&.[](:host) || @browser.options.proxy&.[](:host)
|
448
|
+
@proxy_port = options&.[](:port) || @browser.options.proxy&.[](:port)
|
449
|
+
@proxy_user = options&.[](:user) || @browser.options.proxy&.[](:user)
|
450
|
+
@proxy_password = options&.[](:password) || @browser.options.proxy&.[](:password)
|
338
451
|
end
|
339
452
|
end
|
340
453
|
end
|