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
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "ferrum/network/request_params"
|
3
4
|
require "base64"
|
4
5
|
|
5
6
|
module Ferrum
|
6
7
|
class Network
|
7
8
|
class InterceptedRequest
|
9
|
+
include RequestParams
|
10
|
+
|
8
11
|
attr_accessor :request_id, :frame_id, :resource_type, :network_id, :status
|
9
12
|
|
10
|
-
def initialize(
|
13
|
+
def initialize(client, params)
|
11
14
|
@status = nil
|
12
|
-
@
|
15
|
+
@client = client
|
13
16
|
@params = params
|
14
17
|
@request_id = params["requestId"]
|
15
18
|
@frame_id = params["frameId"]
|
@@ -40,30 +43,18 @@ module Ferrum
|
|
40
43
|
options = options.merge(body: Base64.strict_encode64(options.fetch(:body, ""))) if has_body
|
41
44
|
|
42
45
|
@status = :responded
|
43
|
-
@
|
46
|
+
@client.command("Fetch.fulfillRequest", async: true, **options)
|
44
47
|
end
|
45
48
|
|
46
49
|
def continue(**options)
|
47
50
|
options = options.merge(requestId: request_id)
|
48
51
|
@status = :continued
|
49
|
-
@
|
52
|
+
@client.command("Fetch.continueRequest", async: true, **options)
|
50
53
|
end
|
51
54
|
|
52
55
|
def abort
|
53
56
|
@status = :aborted
|
54
|
-
@
|
55
|
-
end
|
56
|
-
|
57
|
-
def url
|
58
|
-
@request["url"]
|
59
|
-
end
|
60
|
-
|
61
|
-
def method
|
62
|
-
@request["method"]
|
63
|
-
end
|
64
|
-
|
65
|
-
def headers
|
66
|
-
@request["headers"]
|
57
|
+
@client.command("Fetch.failRequest", async: true, requestId: request_id, errorReason: "BlockedByClient")
|
67
58
|
end
|
68
59
|
|
69
60
|
def initial_priority
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "ferrum/network/request_params"
|
3
4
|
require "time"
|
4
5
|
|
5
6
|
module Ferrum
|
@@ -9,6 +10,8 @@ module Ferrum
|
|
9
10
|
# object.
|
10
11
|
#
|
11
12
|
class Request
|
13
|
+
include RequestParams
|
14
|
+
|
12
15
|
#
|
13
16
|
# Initializes the request object.
|
14
17
|
#
|
@@ -51,69 +54,50 @@ module Ferrum
|
|
51
54
|
end
|
52
55
|
|
53
56
|
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# @return [String]
|
57
|
-
#
|
58
|
-
def frame_id
|
59
|
-
@params["frameId"]
|
60
|
-
end
|
61
|
-
|
62
|
-
#
|
63
|
-
# The URL for the request.
|
64
|
-
#
|
65
|
-
# @return [String]
|
66
|
-
#
|
67
|
-
def url
|
68
|
-
@request["url"]
|
69
|
-
end
|
70
|
-
|
57
|
+
# Determines if the request is XHR.
|
71
58
|
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
# @return [String, nil]
|
59
|
+
# @return [Boolean]
|
75
60
|
#
|
76
|
-
def
|
77
|
-
|
61
|
+
def xhr?
|
62
|
+
type?("xhr")
|
78
63
|
end
|
79
64
|
|
80
65
|
#
|
81
|
-
# The request
|
66
|
+
# The frame ID of the request.
|
82
67
|
#
|
83
68
|
# @return [String]
|
84
69
|
#
|
85
|
-
def
|
86
|
-
@
|
70
|
+
def frame_id
|
71
|
+
@params["frameId"]
|
87
72
|
end
|
88
73
|
|
89
74
|
#
|
90
|
-
# The request
|
75
|
+
# The request timestamp.
|
91
76
|
#
|
92
|
-
# @return [
|
77
|
+
# @return [Time]
|
93
78
|
#
|
94
|
-
def
|
95
|
-
@
|
79
|
+
def time
|
80
|
+
@time ||= Time.strptime(@params["wallTime"].to_s, "%s")
|
96
81
|
end
|
97
82
|
|
98
83
|
#
|
99
|
-
#
|
84
|
+
# Determines if a request is of type ping.
|
100
85
|
#
|
101
|
-
# @return [
|
86
|
+
# @return [Boolean]
|
102
87
|
#
|
103
|
-
def
|
104
|
-
|
88
|
+
def ping?
|
89
|
+
type?("ping")
|
105
90
|
end
|
106
91
|
|
107
92
|
#
|
108
|
-
#
|
93
|
+
# Converts the request to a Hash.
|
109
94
|
#
|
110
|
-
# @return [String
|
111
|
-
# The
|
95
|
+
# @return [Hash{String => Object}]
|
96
|
+
# The params of the request.
|
112
97
|
#
|
113
|
-
def
|
114
|
-
@
|
98
|
+
def to_h
|
99
|
+
@params
|
115
100
|
end
|
116
|
-
alias body post_data
|
117
101
|
end
|
118
102
|
end
|
119
103
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Network
|
5
|
+
#
|
6
|
+
# Common methods used by both {Request} and {InterceptedRequest}.
|
7
|
+
#
|
8
|
+
module RequestParams
|
9
|
+
#
|
10
|
+
# The URL for the request.
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
def url
|
15
|
+
@request["url"]
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# The URL fragment for the request.
|
20
|
+
#
|
21
|
+
# @return [String, nil]
|
22
|
+
#
|
23
|
+
def url_fragment
|
24
|
+
@request["urlFragment"]
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# The request method.
|
29
|
+
#
|
30
|
+
# @return [String]
|
31
|
+
#
|
32
|
+
def method
|
33
|
+
@request["method"]
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# The request headers.
|
38
|
+
#
|
39
|
+
# @return [Hash{String => String}]
|
40
|
+
#
|
41
|
+
def headers
|
42
|
+
@request["headers"]
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# The optional HTTP `POST` form data.
|
47
|
+
#
|
48
|
+
# @return [String, nil]
|
49
|
+
# The HTTP `POST` form data.
|
50
|
+
#
|
51
|
+
def post_data
|
52
|
+
@request["postData"]
|
53
|
+
end
|
54
|
+
alias body post_data
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -18,8 +18,13 @@ module Ferrum
|
|
18
18
|
# @return [Hash{String => Object}]
|
19
19
|
attr_reader :params
|
20
20
|
|
21
|
+
# The response is fully loaded by the browser.
|
21
22
|
#
|
22
|
-
#
|
23
|
+
# @return [Boolean]
|
24
|
+
attr_writer :loaded
|
25
|
+
|
26
|
+
#
|
27
|
+
# Initializes the responses object.
|
23
28
|
#
|
24
29
|
# @param [Page] page
|
25
30
|
# The page associated with the network response.
|
@@ -121,9 +126,8 @@ module Ferrum
|
|
121
126
|
#
|
122
127
|
def body
|
123
128
|
@body ||= begin
|
124
|
-
body, encoded = @page
|
125
|
-
|
126
|
-
.values_at("body", "base64Encoded")
|
129
|
+
body, encoded = @page.command("Network.getResponseBody", requestId: id)
|
130
|
+
.values_at("body", "base64Encoded")
|
127
131
|
encoded ? Base64.decode64(body) : body
|
128
132
|
end
|
129
133
|
end
|
@@ -135,8 +139,22 @@ module Ferrum
|
|
135
139
|
@page.network.response == self
|
136
140
|
end
|
137
141
|
|
142
|
+
# The response is fully loaded by the browser or not.
|
143
|
+
#
|
144
|
+
# @return [Boolean]
|
145
|
+
def loaded?
|
146
|
+
@loaded
|
147
|
+
end
|
148
|
+
|
149
|
+
# Whether the response is a redirect.
|
150
|
+
#
|
151
|
+
# @return [Boolean]
|
152
|
+
def redirect?
|
153
|
+
params.key?("redirectResponse")
|
154
|
+
end
|
155
|
+
|
138
156
|
#
|
139
|
-
#
|
157
|
+
# Compares the response's ID to another response's ID.
|
140
158
|
#
|
141
159
|
# @return [Boolean]
|
142
160
|
# Indicates whether the response has the same ID as the other response
|
@@ -154,6 +172,8 @@ module Ferrum
|
|
154
172
|
def inspect
|
155
173
|
%(#<#{self.class} @params=#{@params.inspect} @response=#{@response.inspect}>)
|
156
174
|
end
|
175
|
+
|
176
|
+
alias to_h params
|
157
177
|
end
|
158
178
|
end
|
159
179
|
end
|
data/lib/ferrum/network.rb
CHANGED
@@ -11,9 +11,10 @@ module Ferrum
|
|
11
11
|
class Network
|
12
12
|
CLEAR_TYPE = %i[traffic cache].freeze
|
13
13
|
AUTHORIZE_TYPE = %i[server proxy].freeze
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
REQUEST_STAGES = %i[Request Response].freeze
|
15
|
+
RESOURCE_TYPES = %i[Document Stylesheet Image Media Font Script TextTrack
|
16
|
+
XHR Fetch Prefetch EventSource WebSocket Manifest
|
17
|
+
SignedExchange Ping CSPViolationReport Preflight Other].freeze
|
17
18
|
AUTHORIZE_BLOCK_MISSING = "Block is missing, call `authorize(...) { |r| r.continue } " \
|
18
19
|
"or subscribe to `on(:request)` events before calling it"
|
19
20
|
AUTHORIZE_TYPE_WRONG = ":type should be in #{AUTHORIZE_TYPE}"
|
@@ -58,7 +59,7 @@ module Ferrum
|
|
58
59
|
# browser.at_xpath("//a[text() = 'No UI changes button']").click
|
59
60
|
# browser.network.wait_for_idle
|
60
61
|
#
|
61
|
-
def wait_for_idle(connections: 0, duration: 0.05, timeout: @page.
|
62
|
+
def wait_for_idle(connections: 0, duration: 0.05, timeout: @page.timeout)
|
62
63
|
start = Utils::ElapsedTime.monotonic_time
|
63
64
|
|
64
65
|
until idle?(connections)
|
@@ -187,11 +188,20 @@ module Ferrum
|
|
187
188
|
# end
|
188
189
|
# browser.go_to("https://google.com")
|
189
190
|
#
|
190
|
-
def intercept(pattern: "*", resource_type: nil)
|
191
|
+
def intercept(pattern: "*", resource_type: nil, request_stage: nil, handle_auth_requests: true)
|
191
192
|
pattern = { urlPattern: pattern }
|
192
|
-
pattern[:resourceType] = resource_type if resource_type && RESOURCE_TYPES.include?(resource_type.to_s)
|
193
193
|
|
194
|
-
|
194
|
+
if resource_type && RESOURCE_TYPES.none?(resource_type.to_sym)
|
195
|
+
raise ArgumentError, "Unknown resource type '#{resource_type}' must be #{RESOURCE_TYPES.join(' | ')}"
|
196
|
+
end
|
197
|
+
|
198
|
+
if request_stage && REQUEST_STAGES.none?(request_stage.to_sym)
|
199
|
+
raise ArgumentError, "Unknown request stage '#{request_stage}' must be #{REQUEST_STAGES.join(' | ')}"
|
200
|
+
end
|
201
|
+
|
202
|
+
pattern[:resourceType] = resource_type if resource_type
|
203
|
+
pattern[:requestStage] = request_stage if request_stage
|
204
|
+
@page.command("Fetch.enable", patterns: [pattern], handleAuthRequests: handle_auth_requests)
|
195
205
|
end
|
196
206
|
|
197
207
|
#
|
@@ -323,13 +333,23 @@ module Ferrum
|
|
323
333
|
#
|
324
334
|
# @example
|
325
335
|
# browser.network.offline_mode
|
326
|
-
# browser.go_to("https://github.com/")
|
327
|
-
#
|
336
|
+
# browser.go_to("https://github.com/")
|
337
|
+
# # => Request to https://github.com/ failed (net::ERR_INTERNET_DISCONNECTED) (Ferrum::StatusError)
|
328
338
|
#
|
329
339
|
def offline_mode
|
330
340
|
emulate_network_conditions(offline: true, latency: 0, download_throughput: 0, upload_throughput: 0)
|
331
341
|
end
|
332
342
|
|
343
|
+
#
|
344
|
+
# Toggles ignoring cache for each request. If true, cache will not be used.
|
345
|
+
#
|
346
|
+
# @example
|
347
|
+
# browser.network.cache(disable: true)
|
348
|
+
#
|
349
|
+
def cache(disable:)
|
350
|
+
@page.command("Network.setCacheDisabled", cacheDisabled: disable)
|
351
|
+
end
|
352
|
+
|
333
353
|
private
|
334
354
|
|
335
355
|
def subscribe_request_will_be_sent
|
@@ -352,6 +372,7 @@ module Ferrum
|
|
352
372
|
if params["redirectResponse"]
|
353
373
|
previous_exchange = select(request.id)[-2]
|
354
374
|
response = Network::Response.new(@page, params)
|
375
|
+
response.loaded = true
|
355
376
|
previous_exchange.response = response
|
356
377
|
end
|
357
378
|
|
@@ -364,26 +385,31 @@ module Ferrum
|
|
364
385
|
def subscribe_response_received
|
365
386
|
@page.on("Network.responseReceived") do |params|
|
366
387
|
exchange = select(params["requestId"]).last
|
388
|
+
next unless exchange
|
367
389
|
|
368
|
-
|
369
|
-
|
370
|
-
exchange.response = response
|
371
|
-
end
|
390
|
+
response = Network::Response.new(@page, params)
|
391
|
+
exchange.response = response
|
372
392
|
end
|
373
393
|
end
|
374
394
|
|
375
395
|
def subscribe_loading_finished
|
376
396
|
@page.on("Network.loadingFinished") do |params|
|
377
397
|
exchange = select(params["requestId"]).last
|
378
|
-
|
398
|
+
next unless exchange
|
399
|
+
|
400
|
+
if (response = exchange.response)
|
401
|
+
response.loaded = true
|
402
|
+
response.body_size = params["encodedDataLength"]
|
403
|
+
end
|
379
404
|
end
|
380
405
|
end
|
381
406
|
|
382
407
|
def subscribe_loading_failed
|
383
408
|
@page.on("Network.loadingFailed") do |params|
|
384
409
|
exchange = select(params["requestId"]).last
|
385
|
-
|
410
|
+
next unless exchange
|
386
411
|
|
412
|
+
exchange.error ||= Network::Error.new
|
387
413
|
exchange.error.id = params["requestId"]
|
388
414
|
exchange.error.type = params["type"]
|
389
415
|
exchange.error.error_text = params["errorText"]
|
@@ -397,8 +423,9 @@ module Ferrum
|
|
397
423
|
entry = params["entry"] || {}
|
398
424
|
if entry["source"] == "network" && entry["level"] == "error"
|
399
425
|
exchange = select(entry["networkRequestId"]).last
|
400
|
-
|
426
|
+
next unless exchange
|
401
427
|
|
428
|
+
exchange.error ||= Network::Error.new
|
402
429
|
exchange.error.id = entry["networkRequestId"]
|
403
430
|
exchange.error.url = entry["url"]
|
404
431
|
exchange.error.description = entry["text"]
|
data/lib/ferrum/node.rb
CHANGED
@@ -88,6 +88,26 @@ module Ferrum
|
|
88
88
|
raise NotImplementedError
|
89
89
|
end
|
90
90
|
|
91
|
+
def scroll_into_view
|
92
|
+
tap { page.command("DOM.scrollIntoViewIfNeeded", nodeId: node_id) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def in_viewport?(of: nil)
|
96
|
+
function = <<~JS
|
97
|
+
function(element, scope) {
|
98
|
+
const rect = element.getBoundingClientRect();
|
99
|
+
const [height, width] = scope
|
100
|
+
? [scope.offsetHeight, scope.offsetWidth]
|
101
|
+
: [window.innerHeight, window.innerWidth];
|
102
|
+
return rect.top >= 0 &&
|
103
|
+
rect.left >= 0 &&
|
104
|
+
rect.bottom <= height &&
|
105
|
+
rect.right <= width;
|
106
|
+
}
|
107
|
+
JS
|
108
|
+
page.evaluate_func(function, self, of)
|
109
|
+
end
|
110
|
+
|
91
111
|
def select_file(value)
|
92
112
|
page.command("DOM.setFileInputFiles", slowmoable: true, nodeId: node_id, files: Array(value))
|
93
113
|
end
|
@@ -208,7 +228,7 @@ module Ferrum
|
|
208
228
|
|
209
229
|
def content_quads
|
210
230
|
quads = page.command("DOM.getContentQuads", nodeId: node_id)["quads"]
|
211
|
-
raise CoordinatesNotFoundError, "Node is either not visible or not an HTMLElement" if quads.
|
231
|
+
raise CoordinatesNotFoundError, "Node is either not visible or not an HTMLElement" if quads.empty?
|
212
232
|
|
213
233
|
quads
|
214
234
|
end
|
data/lib/ferrum/page/frames.rb
CHANGED
@@ -16,8 +16,8 @@ module Ferrum
|
|
16
16
|
# @return [Array<Frame>]
|
17
17
|
#
|
18
18
|
# @example
|
19
|
-
#
|
20
|
-
#
|
19
|
+
# page.go_to("https://www.w3schools.com/tags/tag_frame.asp")
|
20
|
+
# page.frames # =>
|
21
21
|
# # [
|
22
22
|
# # #<Ferrum::Frame
|
23
23
|
# # @id="C6D104CE454A025FBCF22B98DE612B12"
|
@@ -39,7 +39,7 @@ module Ferrum
|
|
39
39
|
# Find frame by given options.
|
40
40
|
#
|
41
41
|
# @param [String] id
|
42
|
-
# Unique frame's id that
|
42
|
+
# Unique frame's id that page provides.
|
43
43
|
#
|
44
44
|
# @param [String] name
|
45
45
|
# Frame's name if there's one.
|
@@ -51,7 +51,7 @@ module Ferrum
|
|
51
51
|
# The matching frame.
|
52
52
|
#
|
53
53
|
# @example
|
54
|
-
#
|
54
|
+
# page.frame_by(id: "C6D104CE454A025FBCF22B98DE612B12")
|
55
55
|
#
|
56
56
|
def frame_by(id: nil, name: nil, execution_id: nil)
|
57
57
|
if id
|
@@ -134,7 +134,7 @@ module Ferrum
|
|
134
134
|
end
|
135
135
|
|
136
136
|
frame = @frames[params["frameId"]]
|
137
|
-
frame
|
137
|
+
frame&.state = :stopped_loading
|
138
138
|
|
139
139
|
@event.set if idling?
|
140
140
|
end
|
@@ -5,6 +5,9 @@ require "ferrum/rgba"
|
|
5
5
|
module Ferrum
|
6
6
|
class Page
|
7
7
|
module Screenshot
|
8
|
+
FULL_WARNING = "Ignoring :selector or :area in #screenshot since full: true was given at %s"
|
9
|
+
AREA_WARNING = "Ignoring :area in #screenshot since selector: was given at %s"
|
10
|
+
|
8
11
|
DEFAULT_PDF_OPTIONS = {
|
9
12
|
landscape: false,
|
10
13
|
paper_width: 8.5,
|
@@ -50,6 +53,9 @@ module Ferrum
|
|
50
53
|
# @option opts [String] :selector
|
51
54
|
# CSS selector for the given element.
|
52
55
|
#
|
56
|
+
# @option opts [Hash] :area
|
57
|
+
# x, y, width, height to screenshot an area.
|
58
|
+
#
|
53
59
|
# @option opts [Float] :scale
|
54
60
|
# Zoom in/out.
|
55
61
|
#
|
@@ -57,19 +63,19 @@ module Ferrum
|
|
57
63
|
# Sets the background color.
|
58
64
|
#
|
59
65
|
# @example
|
60
|
-
#
|
66
|
+
# page.go_to("https://google.com/")
|
61
67
|
#
|
62
68
|
# @example Save on the disk in PNG:
|
63
|
-
#
|
69
|
+
# page.screenshot(path: "google.png") # => 134660
|
64
70
|
#
|
65
71
|
# @example Save on the disk in JPG:
|
66
|
-
#
|
72
|
+
# page.screenshot(path: "google.jpg") # => 30902
|
67
73
|
#
|
68
74
|
# @example Save to Base64 the whole page not only viewport and reduce quality:
|
69
|
-
#
|
75
|
+
# page.screenshot(full: true, quality: 60) # "iVBORw0KGgoAAAANS...
|
70
76
|
#
|
71
77
|
# @example Save with specific background color:
|
72
|
-
#
|
78
|
+
# page.screenshot(background_color: Ferrum::RGBA.new(0, 0, 0, 0.0))
|
73
79
|
#
|
74
80
|
def screenshot(**opts)
|
75
81
|
path, encoding = common_options(**opts)
|
@@ -113,9 +119,9 @@ module Ferrum
|
|
113
119
|
# can pass.
|
114
120
|
#
|
115
121
|
# @example
|
116
|
-
#
|
122
|
+
# page.go_to("https://google.com/")
|
117
123
|
# # Save to disk as a PDF
|
118
|
-
#
|
124
|
+
# page.pdf(path: "google.pdf", paper_width: 1.0, paper_height: 1.0) # => true
|
119
125
|
#
|
120
126
|
def pdf(**opts)
|
121
127
|
path, encoding = common_options(**opts)
|
@@ -131,8 +137,8 @@ module Ferrum
|
|
131
137
|
# The path to save a file on the disk.
|
132
138
|
#
|
133
139
|
# @example
|
134
|
-
#
|
135
|
-
#
|
140
|
+
# page.go_to("https://google.com/")
|
141
|
+
# page.mhtml(path: "google.mhtml") # => 87742
|
136
142
|
#
|
137
143
|
def mhtml(path: nil)
|
138
144
|
data = command("Page.captureSnapshot", format: :mhtml).fetch("data")
|
@@ -147,6 +153,12 @@ module Ferrum
|
|
147
153
|
JS
|
148
154
|
end
|
149
155
|
|
156
|
+
def device_pixel_ratio
|
157
|
+
evaluate <<~JS
|
158
|
+
window.devicePixelRatio
|
159
|
+
JS
|
160
|
+
end
|
161
|
+
|
150
162
|
def document_size
|
151
163
|
evaluate <<~JS
|
152
164
|
[document.documentElement.scrollWidth,
|
@@ -192,7 +204,7 @@ module Ferrum
|
|
192
204
|
screenshot_options.merge!(quality: quality) if quality
|
193
205
|
screenshot_options.merge!(format: format)
|
194
206
|
|
195
|
-
clip = area_options(options[:full], options[:selector], scale)
|
207
|
+
clip = area_options(options[:full], options[:selector], scale, options[:area])
|
196
208
|
screenshot_options.merge!(clip: clip) if clip
|
197
209
|
|
198
210
|
screenshot_options
|
@@ -208,29 +220,35 @@ module Ferrum
|
|
208
220
|
[format, quality]
|
209
221
|
end
|
210
222
|
|
211
|
-
def area_options(full, selector, scale)
|
212
|
-
|
213
|
-
warn(
|
223
|
+
def area_options(full, selector, scale, area = nil)
|
224
|
+
warn(FULL_WARNING % caller(1..1).first) if full && (selector || area)
|
225
|
+
warn(AREA_WARNING % caller(1..1).first) if selector && area
|
214
226
|
|
215
227
|
clip = if full
|
216
|
-
|
217
|
-
{ x: 0, y: 0, width: width, height: height, scale: scale } if width.positive? && height.positive?
|
228
|
+
full_window_area || viewport_area
|
218
229
|
elsif selector
|
219
|
-
bounding_rect(selector)
|
230
|
+
bounding_rect(selector)
|
231
|
+
elsif area
|
232
|
+
area
|
233
|
+
else
|
234
|
+
viewport_area
|
220
235
|
end
|
221
236
|
|
222
|
-
|
223
|
-
unless clip
|
224
|
-
width, height = viewport_size
|
225
|
-
clip = { x: 0, y: 0, width: width, height: height }
|
226
|
-
end
|
227
|
-
|
228
|
-
clip.merge!(scale: scale)
|
229
|
-
end
|
237
|
+
clip.merge!(scale: scale)
|
230
238
|
|
231
239
|
clip
|
232
240
|
end
|
233
241
|
|
242
|
+
def full_window_area
|
243
|
+
width, height = document_size
|
244
|
+
{ x: 0, y: 0, width: width, height: height } if width.positive? && height.positive?
|
245
|
+
end
|
246
|
+
|
247
|
+
def viewport_area
|
248
|
+
width, height = viewport_size
|
249
|
+
{ x: 0, y: 0, width: width, height: height }
|
250
|
+
end
|
251
|
+
|
234
252
|
def bounding_rect(selector)
|
235
253
|
rect = evaluate_async(%(
|
236
254
|
const rect = document
|