ferrum 0.13 → 0.15
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 +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
|