ferrum 0.9 → 0.10

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.
@@ -3,24 +3,17 @@
3
3
  module Ferrum
4
4
  class Network
5
5
  class Error
6
- def initialize(data)
7
- @data = data
8
- end
9
-
10
- def id
11
- @data["networkRequestId"]
12
- end
13
-
14
- def url
15
- @data["url"]
16
- end
6
+ attr_writer :canceled
7
+ attr_reader :time, :timestamp
8
+ attr_accessor :id, :url, :type, :error_text, :monotonic_time, :description
17
9
 
18
- def description
19
- @data["text"]
10
+ def canceled?
11
+ @canceled
20
12
  end
21
13
 
22
- def time
23
- @time ||= Time.strptime(@data["timestamp"].to_s, "%s")
14
+ def timestamp=(value)
15
+ @timestamp = value
16
+ @time = Time.strptime((value / 1000).to_s, "%s")
24
17
  end
25
18
  end
26
19
  end
data/lib/ferrum/node.rb CHANGED
@@ -141,10 +141,21 @@ module Ferrum
141
141
  end.map { |q| to_points(q) }.first
142
142
 
143
143
  get_position(points, x, y, position)
144
+ rescue Ferrum::BrowserError => e
145
+ return raise unless e.message&.include?("Could not compute content quads")
146
+
147
+ find_position_via_js
144
148
  end
145
149
 
146
150
  private
147
151
 
152
+ def find_position_via_js
153
+ [
154
+ evaluate("this.getBoundingClientRect().left + window.pageXOffset + (this.offsetWidth / 2)"), # x
155
+ evaluate("this.getBoundingClientRect().top + window.pageYOffset + (this.offsetHeight / 2)") # y
156
+ ]
157
+ end
158
+
148
159
  def get_content_quads
149
160
  quads = page.command("DOM.getContentQuads", nodeId: node_id)["quads"]
150
161
  raise "Node is either not visible or not an HTMLElement" if quads.size == 0
data/lib/ferrum/page.rb CHANGED
@@ -32,7 +32,7 @@ module Ferrum
32
32
  extend Forwardable
33
33
  delegate %i[at_css at_xpath css xpath
34
34
  current_url current_title url title body doctype set_content
35
- execution_id evaluate evaluate_on evaluate_async execute
35
+ execution_id evaluate evaluate_on evaluate_async execute evaluate_func
36
36
  add_script_tag add_style_tag] => :main_frame
37
37
 
38
38
  include Frames, Screenshot
@@ -65,7 +65,7 @@ module Ferrum
65
65
  @browser.timeout
66
66
  end
67
67
 
68
- def goto(url = nil)
68
+ def go_to(url = nil)
69
69
  options = { url: combine_url!(url) }
70
70
  options.merge!(referrer: referrer) if referrer
71
71
  response = command("Page.navigate", wait: GOTO_WAIT, **options)
@@ -78,9 +78,12 @@ module Ferrum
78
78
  end
79
79
  response["frameId"]
80
80
  rescue TimeoutError
81
- pendings = network.traffic.select(&:pending?).map { |e| e.request.url }
82
- raise StatusError.new(options[:url], pendings) unless pendings.empty?
81
+ if @browser.pending_connection_errors
82
+ pendings = network.traffic.select(&:pending?).map { |e| e.request.url }
83
+ raise PendingConnectionsError.new(options[:url], pendings) unless pendings.empty?
84
+ end
83
85
  end
86
+ alias goto go_to
84
87
 
85
88
  def close
86
89
  @headers.clear
@@ -89,15 +92,12 @@ module Ferrum
89
92
  end
90
93
 
91
94
  def resize(width: nil, height: nil, fullscreen: false)
92
- result = @browser.command("Browser.getWindowForTarget", targetId: @target_id)
93
- @window_id, @bounds = result.values_at("windowId", "bounds")
94
-
95
95
  if fullscreen
96
96
  width, height = document_size
97
- @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { windowState: "fullscreen" })
97
+ set_window_bounds(windowState: "fullscreen")
98
98
  else
99
- @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { windowState: "normal" })
100
- @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { width: width, height: height, windowState: "normal" })
99
+ set_window_bounds(windowState: "normal")
100
+ set_window_bounds(width: width, height: height)
101
101
  end
102
102
 
103
103
  command("Emulation.setDeviceMetricsOverride", slowmoable: true,
@@ -137,6 +137,14 @@ module Ferrum
137
137
  enabled
138
138
  end
139
139
 
140
+ def window_id
141
+ @browser.command("Browser.getWindowForTarget", targetId: @target_id)["windowId"]
142
+ end
143
+
144
+ def set_window_bounds(bounds = {})
145
+ @browser.command("Browser.setWindowBounds", windowId: window_id, bounds: bounds)
146
+ end
147
+
140
148
  def command(method, wait: 0, slowmoable: false, **params)
141
149
  iteration = @event.reset if wait > 0
142
150
  sleep(@browser.slowmo) if slowmoable && @browser.slowmo > 0
@@ -179,6 +187,10 @@ module Ferrum
179
187
  end
180
188
  end
181
189
 
190
+ def subscribed?(event)
191
+ @client.subscribed?(event)
192
+ end
193
+
182
194
  private
183
195
 
184
196
  def subscribe
@@ -261,7 +273,7 @@ module Ferrum
261
273
  nil_or_relative = url.nil? || url.relative?
262
274
 
263
275
  if nil_or_relative && !@browser.base_url
264
- raise "Set :base_url browser's option or use absolute url in `goto`, you passed: #{url_or_path}"
276
+ raise "Set :base_url browser's option or use absolute url in `go_to`, you passed: #{url_or_path}"
265
277
  end
266
278
 
267
279
  (nil_or_relative ? @browser.base_url.join(url.to_s) : url).to_s
@@ -75,8 +75,11 @@ module Ferrum
75
75
  frame_id = params.dig("context", "auxData", "frameId")
76
76
 
77
77
  unless @main_frame.id
78
- @main_frame.id = frame_id
79
- @frames[frame_id] = @main_frame
78
+ root_frame = command("Page.getFrameTree").dig("frameTree", "frame", "id")
79
+ if frame_id == root_frame
80
+ @main_frame.id = frame_id
81
+ @frames[frame_id] = @main_frame
82
+ end
80
83
  end
81
84
 
82
85
  frame = @frames[frame_id] || Frame.new(frame_id, self)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ferrum/rbga"
4
+
3
5
  module Ferrum
4
6
  class Page
5
7
  module Screenshot
@@ -24,19 +26,33 @@ module Ferrum
24
26
  A6: { width: 4.13, height: 5.83 },
25
27
  }.freeze
26
28
 
29
+ STREAM_CHUNK = 128 * 1024
30
+
27
31
  def screenshot(**opts)
28
32
  path, encoding = common_options(**opts)
29
33
  options = screenshot_options(path, **opts)
30
- data = capture_screenshot(options, opts[:full])
34
+ data = capture_screenshot(options, opts[:full], opts[:background_color])
31
35
  return data if encoding == :base64
32
- save_file(path, data)
36
+
37
+ bin = Base64.decode64(data)
38
+ save_file(path, bin)
33
39
  end
34
40
 
35
41
  def pdf(**opts)
36
42
  path, encoding = common_options(**opts)
37
- options = pdf_options(**opts)
38
- data = command("Page.printToPDF", **options).fetch("data")
39
- return data if encoding == :base64
43
+ options = pdf_options(**opts).merge(transferMode: "ReturnAsStream")
44
+ handle = command("Page.printToPDF", **options).fetch("stream")
45
+
46
+ if path
47
+ stream_to_file(handle, path: path)
48
+ else
49
+ stream_to_memory(handle, encoding: encoding)
50
+ end
51
+ end
52
+
53
+ def mhtml(path: nil)
54
+ data = command("Page.captureSnapshot", format: :mhtml).fetch("data")
55
+ return data if path.nil?
40
56
  save_file(path, data)
41
57
  end
42
58
 
@@ -56,9 +72,31 @@ module Ferrum
56
72
  private
57
73
 
58
74
  def save_file(path, data)
59
- bin = Base64.decode64(data)
60
- return bin unless path
61
- File.open(path.to_s, "wb") { |f| f.write(bin) }
75
+ return data unless path
76
+ File.open(path.to_s, "wb") { |f| f.write(data) }
77
+ end
78
+
79
+ def stream_to_file(handle, path:)
80
+ File.open(path, "wb") { |f| stream_to(handle, f) }
81
+ true
82
+ end
83
+
84
+ def stream_to_memory(handle, encoding:)
85
+ data = String.new("") # Mutable string has << and compatible to File
86
+ stream_to(handle, data)
87
+ encoding == :base64 ? Base64.encode64(data) : data
88
+ end
89
+
90
+ def stream_to(handle, output)
91
+ loop do
92
+ result = command("IO.read", handle: handle, size: STREAM_CHUNK)
93
+
94
+ data_chunk = result["data"]
95
+ data_chunk = Base64.decode64(data_chunk) if result["base64Encoded"]
96
+ output << data_chunk
97
+
98
+ break if result["eof"]
99
+ end
62
100
  end
63
101
 
64
102
  def common_options(encoding: :base64, path: nil, **_)
@@ -134,9 +172,11 @@ module Ferrum
134
172
  option.to_s.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.to_sym
135
173
  end
136
174
 
137
- def capture_screenshot(options, full)
175
+ def capture_screenshot(options, full, background_color)
138
176
  maybe_resize_fullscreen(full) do
139
- command("Page.captureScreenshot", **options)
177
+ with_background_color(background_color) do
178
+ command("Page.captureScreenshot", **options)
179
+ end
140
180
  end.fetch("data")
141
181
  end
142
182
 
@@ -150,6 +190,18 @@ module Ferrum
150
190
  ensure
151
191
  resize(width: width, height: height) if full
152
192
  end
193
+
194
+ def with_background_color(color)
195
+ if color
196
+ raise ArgumentError, "Accept Ferrum::RGBA class only" unless color.is_a?(RGBA)
197
+
198
+ command("Emulation.setDefaultBackgroundColorOverride", color: color.to_h)
199
+ end
200
+
201
+ yield
202
+ ensure
203
+ command("Emulation.setDefaultBackgroundColorOverride") if color
204
+ end
153
205
  end
154
206
  end
155
207
  end
@@ -0,0 +1,38 @@
1
+ module Ferrum
2
+ class RGBA
3
+ def initialize(red, green, blue, alpha)
4
+ self.red = red
5
+ self.green = green
6
+ self.blue = blue
7
+ self.alpha = alpha
8
+
9
+ validate
10
+ end
11
+
12
+ def to_h
13
+ { r: red, g: green, b: blue, a: alpha }
14
+ end
15
+
16
+ private
17
+
18
+ attr_accessor :red, :green, :blue, :alpha
19
+
20
+ def validate
21
+ [red, green, blue].each(&method(:validate_color))
22
+ validate_alpha
23
+ end
24
+
25
+ def validate_color(value)
26
+ return if value && value.is_a?(Integer) && Range.new(0, 255).include?(value)
27
+
28
+ raise ArgumentError, "Wrong value of #{value} should be Integer from 0 to 255"
29
+ end
30
+
31
+ def validate_alpha
32
+ return if alpha && alpha.is_a?(Float) && Range.new(0.0, 1.0).include?(alpha)
33
+
34
+ raise ArgumentError,
35
+ "Wrong alpha value #{alpha} should be Float between 0.0 (fully transparent) and 1.0 (fully opaque)"
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ferrum
4
- VERSION = "0.9"
4
+ VERSION = "0.10"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ferrum
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.9'
4
+ version: '0.10'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Vorotilin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-24 00:00:00.000000000 Z
11
+ date: 2021-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-driver
@@ -78,14 +78,14 @@ dependencies:
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '12.3'
81
+ version: '13.0'
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '12.3'
88
+ version: '13.0'
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: rspec
91
91
  requirement: !ruby/object:Gem::Requirement
@@ -212,6 +212,7 @@ files:
212
212
  - lib/ferrum/page.rb
213
213
  - lib/ferrum/page/frames.rb
214
214
  - lib/ferrum/page/screenshot.rb
215
+ - lib/ferrum/rbga.rb
215
216
  - lib/ferrum/target.rb
216
217
  - lib/ferrum/version.rb
217
218
  homepage: https://github.com/route/ferrum
@@ -233,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
233
234
  - !ruby/object:Gem::Version
234
235
  version: '0'
235
236
  requirements: []
236
- rubygems_version: 3.1.2
237
+ rubygems_version: 3.1.4
237
238
  signing_key:
238
239
  specification_version: 4
239
240
  summary: Ruby headless Chrome driver