ferrum 0.6.1 → 0.10
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 +370 -78
- data/lib/ferrum.rb +38 -4
- data/lib/ferrum/browser.rb +19 -12
- data/lib/ferrum/browser/client.rb +23 -10
- data/lib/ferrum/browser/command.rb +57 -0
- data/lib/ferrum/browser/options/base.rb +46 -0
- data/lib/ferrum/browser/options/chrome.rb +73 -0
- data/lib/ferrum/browser/options/firefox.rb +34 -0
- data/lib/ferrum/browser/process.rb +56 -108
- data/lib/ferrum/browser/subscriber.rb +9 -1
- data/lib/ferrum/browser/web_socket.rb +23 -4
- data/lib/ferrum/browser/xvfb.rb +37 -0
- data/lib/ferrum/context.rb +3 -3
- data/lib/ferrum/cookies.rb +7 -0
- data/lib/ferrum/dialog.rb +2 -2
- data/lib/ferrum/frame.rb +20 -5
- data/lib/ferrum/frame/dom.rb +34 -37
- data/lib/ferrum/frame/runtime.rb +89 -85
- data/lib/ferrum/headers.rb +1 -1
- data/lib/ferrum/keyboard.rb +3 -3
- data/lib/ferrum/mouse.rb +14 -3
- data/lib/ferrum/network.rb +81 -20
- data/lib/ferrum/network/error.rb +8 -15
- data/lib/ferrum/network/exchange.rb +24 -21
- data/lib/ferrum/network/intercepted_request.rb +12 -3
- data/lib/ferrum/network/response.rb +4 -0
- data/lib/ferrum/node.rb +70 -26
- data/lib/ferrum/page.rb +66 -26
- data/lib/ferrum/page/frames.rb +12 -15
- data/lib/ferrum/page/screenshot.rb +64 -12
- data/lib/ferrum/rbga.rb +38 -0
- data/lib/ferrum/target.rb +10 -2
- data/lib/ferrum/version.rb +1 -1
- metadata +13 -7
data/lib/ferrum/headers.rb
CHANGED
@@ -40,7 +40,7 @@ module Ferrum
|
|
40
40
|
|
41
41
|
def set_overrides(user_agent: nil, accept_language: nil, platform: nil)
|
42
42
|
options = Hash.new
|
43
|
-
options[:userAgent] = user_agent
|
43
|
+
options[:userAgent] = user_agent || @page.browser.default_user_agent
|
44
44
|
options[:acceptLanguage] = accept_language if accept_language
|
45
45
|
options[:platform] if platform
|
46
46
|
|
data/lib/ferrum/keyboard.rb
CHANGED
@@ -33,13 +33,13 @@ module Ferrum
|
|
33
33
|
def down(key)
|
34
34
|
key = normalize_keys(Array(key))
|
35
35
|
type = key[:text] ? "keyDown" : "rawKeyDown"
|
36
|
-
@page.command("Input.dispatchKeyEvent", type: type, **key)
|
36
|
+
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: type, **key)
|
37
37
|
self
|
38
38
|
end
|
39
39
|
|
40
40
|
def up(key)
|
41
41
|
key = normalize_keys(Array(key))
|
42
|
-
@page.command("Input.dispatchKeyEvent", type: "keyUp", **key)
|
42
|
+
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: "keyUp", **key)
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
@@ -49,7 +49,7 @@ module Ferrum
|
|
49
49
|
keys.each do |key|
|
50
50
|
type = key[:text] ? "keyDown" : "rawKeyDown"
|
51
51
|
@page.command("Input.dispatchKeyEvent", type: type, **key)
|
52
|
-
@page.command("Input.dispatchKeyEvent", type: "keyUp", **key)
|
52
|
+
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: "keyUp", **key)
|
53
53
|
end
|
54
54
|
|
55
55
|
self
|
data/lib/ferrum/mouse.rb
CHANGED
@@ -32,10 +32,21 @@ module Ferrum
|
|
32
32
|
tap { mouse_event(type: "mouseReleased", **options) }
|
33
33
|
end
|
34
34
|
|
35
|
-
# FIXME: steps
|
36
35
|
def move(x:, y:, steps: 1)
|
36
|
+
from_x, from_y = @x, @y
|
37
37
|
@x, @y = x, y
|
38
|
-
|
38
|
+
|
39
|
+
steps.times do |i|
|
40
|
+
new_x = from_x + (@x - from_x) * ((i + 1) / steps.to_f)
|
41
|
+
new_y = from_y + (@y - from_y) * ((i + 1) / steps.to_f)
|
42
|
+
|
43
|
+
@page.command("Input.dispatchMouseEvent",
|
44
|
+
slowmoable: true,
|
45
|
+
type: "mouseMoved",
|
46
|
+
x: new_x.to_i,
|
47
|
+
y: new_y.to_i)
|
48
|
+
end
|
49
|
+
|
39
50
|
self
|
40
51
|
end
|
41
52
|
|
@@ -45,7 +56,7 @@ module Ferrum
|
|
45
56
|
button = validate_button(button)
|
46
57
|
options = { x: @x, y: @y, type: type, button: button, clickCount: count }
|
47
58
|
options.merge!(modifiers: modifiers) if modifiers
|
48
|
-
@page.command("Input.dispatchMouseEvent", wait: wait, **options)
|
59
|
+
@page.command("Input.dispatchMouseEvent", wait: wait, slowmoable: true, **options)
|
49
60
|
end
|
50
61
|
|
51
62
|
def validate_button(button)
|
data/lib/ferrum/network.rb
CHANGED
@@ -3,6 +3,9 @@
|
|
3
3
|
require "ferrum/network/exchange"
|
4
4
|
require "ferrum/network/intercepted_request"
|
5
5
|
require "ferrum/network/auth_request"
|
6
|
+
require "ferrum/network/error"
|
7
|
+
require "ferrum/network/request"
|
8
|
+
require "ferrum/network/response"
|
6
9
|
|
7
10
|
module Ferrum
|
8
11
|
class Network
|
@@ -20,6 +23,31 @@ module Ferrum
|
|
20
23
|
@exchange = nil
|
21
24
|
end
|
22
25
|
|
26
|
+
def wait_for_idle(connections: 0, duration: 0.05, timeout: @page.browser.timeout)
|
27
|
+
start = Ferrum.monotonic_time
|
28
|
+
|
29
|
+
until idle?(connections)
|
30
|
+
raise TimeoutError if Ferrum.timeout?(start, timeout)
|
31
|
+
sleep(duration)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def idle?(connections = 0)
|
36
|
+
pending_connections <= connections
|
37
|
+
end
|
38
|
+
|
39
|
+
def total_connections
|
40
|
+
@traffic.size
|
41
|
+
end
|
42
|
+
|
43
|
+
def finished_connections
|
44
|
+
@traffic.count(&:finished?)
|
45
|
+
end
|
46
|
+
|
47
|
+
def pending_connections
|
48
|
+
total_connections - finished_connections
|
49
|
+
end
|
50
|
+
|
23
51
|
def request
|
24
52
|
@exchange&.request
|
25
53
|
end
|
@@ -55,19 +83,21 @@ module Ferrum
|
|
55
83
|
@page.command("Fetch.enable", handleAuthRequests: true, patterns: [pattern])
|
56
84
|
end
|
57
85
|
|
58
|
-
def authorize(user:, password:, type: :server)
|
86
|
+
def authorize(user:, password:, type: :server, &block)
|
59
87
|
unless AUTHORIZE_TYPE.include?(type)
|
60
88
|
raise ArgumentError, ":type should be in #{AUTHORIZE_TYPE}"
|
61
89
|
end
|
62
90
|
|
91
|
+
if !block_given? && !@page.subscribed?("Fetch.requestPaused")
|
92
|
+
raise ArgumentError, "Block is missing, call `authorize(...) { |r| r.continue } or subscribe to `on(:request)` events before calling it"
|
93
|
+
end
|
94
|
+
|
63
95
|
@authorized_ids ||= {}
|
64
96
|
@authorized_ids[type] ||= []
|
65
97
|
|
66
98
|
intercept
|
67
99
|
|
68
|
-
@page.on(:request)
|
69
|
-
request.continue
|
70
|
-
end
|
100
|
+
@page.on(:request, &block)
|
71
101
|
|
72
102
|
@page.on(:auth) do |request, index, total|
|
73
103
|
if request.auth_challenge?(type)
|
@@ -87,38 +117,69 @@ module Ferrum
|
|
87
117
|
|
88
118
|
def subscribe
|
89
119
|
@page.on("Network.requestWillBeSent") do |params|
|
120
|
+
request = Network::Request.new(params)
|
121
|
+
|
122
|
+
# We can build exchange in two places, here on the event or when request
|
123
|
+
# is interrupted. So we have to be careful when to create new one. We
|
124
|
+
# create new exchange only if there's no with such id or there's but
|
125
|
+
# it's filled with request which means this one is new but has response
|
126
|
+
# for a redirect. So we assign response from the params to previous
|
127
|
+
# exchange and build new exchange to assign this request to it.
|
128
|
+
exchange = select(request.id).last
|
129
|
+
exchange = build_exchange(request.id) unless exchange&.blank?
|
130
|
+
|
90
131
|
# On redirects Chrome doesn't change `requestId` and there's no
|
91
132
|
# `Network.responseReceived` event for such request. If there's already
|
92
133
|
# exchange object with this id then we got redirected and params has
|
93
134
|
# `redirectResponse` key which contains the response.
|
94
|
-
if
|
95
|
-
|
135
|
+
if params["redirectResponse"]
|
136
|
+
previous_exchange = select(request.id)[-2]
|
137
|
+
response = Network::Response.new(@page, params)
|
138
|
+
previous_exchange.response = response
|
96
139
|
end
|
97
140
|
|
98
|
-
exchange =
|
99
|
-
|
100
|
-
|
141
|
+
exchange.request = request
|
142
|
+
|
143
|
+
if exchange.navigation_request?(@page.main_frame.id)
|
144
|
+
@exchange = exchange
|
145
|
+
end
|
101
146
|
end
|
102
147
|
|
103
148
|
@page.on("Network.responseReceived") do |params|
|
104
|
-
if exchange =
|
105
|
-
|
149
|
+
if exchange = select(params["requestId"]).last
|
150
|
+
response = Network::Response.new(@page, params)
|
151
|
+
exchange.response = response
|
106
152
|
end
|
107
153
|
end
|
108
154
|
|
109
155
|
@page.on("Network.loadingFinished") do |params|
|
110
|
-
exchange =
|
156
|
+
exchange = select(params["requestId"]).last
|
111
157
|
if exchange && exchange.response
|
112
158
|
exchange.response.body_size = params["encodedDataLength"]
|
113
159
|
end
|
114
160
|
end
|
115
161
|
|
162
|
+
@page.on("Network.loadingFailed") do |params|
|
163
|
+
exchange = select(params["requestId"]).last
|
164
|
+
exchange.error ||= Network::Error.new
|
165
|
+
|
166
|
+
exchange.error.id = params["requestId"]
|
167
|
+
exchange.error.type = params["type"]
|
168
|
+
exchange.error.error_text = params["errorText"]
|
169
|
+
exchange.error.monotonic_time = params["timestamp"]
|
170
|
+
exchange.error.canceled = params["canceled"]
|
171
|
+
end
|
172
|
+
|
116
173
|
@page.on("Log.entryAdded") do |params|
|
117
174
|
entry = params["entry"] || {}
|
118
|
-
if entry["source"] == "network" &&
|
119
|
-
|
120
|
-
|
121
|
-
|
175
|
+
if entry["source"] == "network" && entry["level"] == "error"
|
176
|
+
exchange = select(entry["networkRequestId"]).last
|
177
|
+
exchange.error ||= Network::Error.new
|
178
|
+
|
179
|
+
exchange.error.id = entry["networkRequestId"]
|
180
|
+
exchange.error.url = entry["url"]
|
181
|
+
exchange.error.description = entry["text"]
|
182
|
+
exchange.error.timestamp = entry["timestamp"]
|
122
183
|
end
|
123
184
|
end
|
124
185
|
end
|
@@ -135,12 +196,12 @@ module Ferrum
|
|
135
196
|
end
|
136
197
|
end
|
137
198
|
|
138
|
-
def
|
139
|
-
@traffic.
|
199
|
+
def select(request_id)
|
200
|
+
@traffic.select { |e| e.id == request_id }
|
140
201
|
end
|
141
202
|
|
142
|
-
def
|
143
|
-
@
|
203
|
+
def build_exchange(id)
|
204
|
+
Network::Exchange.new(@page, id).tap { |e| @traffic << e }
|
144
205
|
end
|
145
206
|
end
|
146
207
|
end
|
data/lib/ferrum/network/error.rb
CHANGED
@@ -3,24 +3,17 @@
|
|
3
3
|
module Ferrum
|
4
4
|
class Network
|
5
5
|
class Error
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
19
|
-
@
|
10
|
+
def canceled?
|
11
|
+
@canceled
|
20
12
|
end
|
21
13
|
|
22
|
-
def
|
23
|
-
@
|
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
|
@@ -1,39 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "ferrum/network/error"
|
4
|
-
require "ferrum/network/request"
|
5
|
-
require "ferrum/network/response"
|
6
|
-
|
7
3
|
module Ferrum
|
8
4
|
class Network
|
9
5
|
class Exchange
|
10
|
-
attr_reader :
|
6
|
+
attr_reader :id
|
7
|
+
attr_accessor :intercepted_request
|
8
|
+
attr_accessor :request, :response, :error
|
11
9
|
|
12
|
-
def initialize(page,
|
13
|
-
@page = page
|
14
|
-
@
|
15
|
-
|
10
|
+
def initialize(page, id)
|
11
|
+
@page, @id = page, id
|
12
|
+
@intercepted_request = nil
|
13
|
+
@request = @response = @error = nil
|
16
14
|
end
|
17
15
|
|
18
|
-
def
|
19
|
-
|
16
|
+
def navigation_request?(frame_id)
|
17
|
+
request.type?(:document) &&
|
18
|
+
request.frame_id == frame_id
|
20
19
|
end
|
21
20
|
|
22
|
-
def
|
23
|
-
|
21
|
+
def blank?
|
22
|
+
!request
|
24
23
|
end
|
25
24
|
|
26
|
-
def
|
27
|
-
|
25
|
+
def blocked?
|
26
|
+
intercepted_request && intercepted_request.status?(:aborted)
|
28
27
|
end
|
29
28
|
|
30
|
-
def
|
31
|
-
|
32
|
-
request.frame_id == frame_id
|
29
|
+
def finished?
|
30
|
+
blocked? || response || error
|
33
31
|
end
|
34
32
|
|
35
|
-
def
|
36
|
-
|
33
|
+
def pending?
|
34
|
+
!finished?
|
37
35
|
end
|
38
36
|
|
39
37
|
def to_a
|
@@ -41,7 +39,12 @@ module Ferrum
|
|
41
39
|
end
|
42
40
|
|
43
41
|
def inspect
|
44
|
-
|
42
|
+
"#<#{self.class} "\
|
43
|
+
"@id=#{@id.inspect} "\
|
44
|
+
"@intercepted_request=#{@intercepted_request.inspect} "\
|
45
|
+
"@request=#{@request.inspect} "\
|
46
|
+
"@response=#{@response.inspect} "\
|
47
|
+
"@error=#{@error.inspect}>"
|
45
48
|
end
|
46
49
|
end
|
47
50
|
end
|
@@ -5,14 +5,20 @@ require "base64"
|
|
5
5
|
module Ferrum
|
6
6
|
class Network
|
7
7
|
class InterceptedRequest
|
8
|
-
attr_accessor :request_id, :frame_id, :resource_type
|
8
|
+
attr_accessor :request_id, :frame_id, :resource_type, :network_id, :status
|
9
9
|
|
10
10
|
def initialize(page, params)
|
11
|
+
@status = nil
|
11
12
|
@page, @params = page, params
|
12
13
|
@request_id = params["requestId"]
|
13
14
|
@frame_id = params["frameId"]
|
14
15
|
@resource_type = params["resourceType"]
|
15
16
|
@request = params["request"]
|
17
|
+
@network_id = params["networkId"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def status?(value)
|
21
|
+
@status == value.to_sym
|
16
22
|
end
|
17
23
|
|
18
24
|
def navigation_request?
|
@@ -25,7 +31,7 @@ module Ferrum
|
|
25
31
|
|
26
32
|
def respond(**options)
|
27
33
|
has_body = options.has_key?(:body)
|
28
|
-
headers = has_body ? { "content-length" => options.fetch(:body,
|
34
|
+
headers = has_body ? { "content-length" => options.fetch(:body, "").length } : {}
|
29
35
|
headers = headers.merge(options.fetch(:responseHeaders, {}))
|
30
36
|
|
31
37
|
options = {responseCode: 200}.merge(options)
|
@@ -33,17 +39,20 @@ module Ferrum
|
|
33
39
|
requestId: request_id,
|
34
40
|
responseHeaders: header_array(headers),
|
35
41
|
})
|
36
|
-
options = options.merge(body: Base64.
|
42
|
+
options = options.merge(body: Base64.strict_encode64(options.fetch(:body, ""))) if has_body
|
37
43
|
|
44
|
+
@status = :responded
|
38
45
|
@page.command("Fetch.fulfillRequest", **options)
|
39
46
|
end
|
40
47
|
|
41
48
|
def continue(**options)
|
42
49
|
options = options.merge(requestId: request_id)
|
50
|
+
@status = :continued
|
43
51
|
@page.command("Fetch.continueRequest", **options)
|
44
52
|
end
|
45
53
|
|
46
54
|
def abort
|
55
|
+
@status = :aborted
|
47
56
|
@page.command("Fetch.failRequest", requestId: request_id, errorReason: "BlockedByClient")
|
48
57
|
end
|
49
58
|
|
data/lib/ferrum/node.rb
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
module Ferrum
|
4
4
|
class Node
|
5
|
+
MOVING_WAIT = ENV.fetch("FERRUM_NODE_MOVING_WAIT", 0.01).to_f
|
6
|
+
MOVING_ATTEMPTS = ENV.fetch("FERRUM_NODE_MOVING_ATTEMPTS", 50).to_i
|
7
|
+
|
5
8
|
attr_reader :page, :target_id, :node_id, :description, :tag_name
|
6
9
|
|
7
10
|
def initialize(frame, target_id, node_id, description)
|
@@ -24,7 +27,7 @@ module Ferrum
|
|
24
27
|
end
|
25
28
|
|
26
29
|
def focus
|
27
|
-
tap { page.command("DOM.focus", nodeId: node_id) }
|
30
|
+
tap { page.command("DOM.focus", slowmoable: true, nodeId: node_id) }
|
28
31
|
end
|
29
32
|
|
30
33
|
def blur
|
@@ -37,22 +40,23 @@ module Ferrum
|
|
37
40
|
|
38
41
|
# mode: (:left | :right | :double)
|
39
42
|
# keys: (:alt, (:ctrl | :control), (:meta | :command), :shift)
|
40
|
-
# offset: { :x, :y }
|
41
|
-
def click(mode: :left, keys: [], offset: {})
|
42
|
-
x, y = find_position(offset
|
43
|
+
# offset: { :x, :y, :position (:top | :center) }
|
44
|
+
def click(mode: :left, keys: [], offset: {}, delay: 0)
|
45
|
+
x, y = find_position(**offset)
|
43
46
|
modifiers = page.keyboard.modifiers(keys)
|
44
47
|
|
45
48
|
case mode
|
46
49
|
when :right
|
47
50
|
page.mouse.move(x: x, y: y)
|
48
51
|
page.mouse.down(button: :right, modifiers: modifiers)
|
52
|
+
sleep(delay)
|
49
53
|
page.mouse.up(button: :right, modifiers: modifiers)
|
50
54
|
when :double
|
51
55
|
page.mouse.move(x: x, y: y)
|
52
56
|
page.mouse.down(modifiers: modifiers, count: 2)
|
53
57
|
page.mouse.up(modifiers: modifiers, count: 2)
|
54
58
|
when :left
|
55
|
-
page.mouse.click(x: x, y: y, modifiers: modifiers)
|
59
|
+
page.mouse.click(x: x, y: y, modifiers: modifiers, delay: delay)
|
56
60
|
end
|
57
61
|
|
58
62
|
self
|
@@ -63,7 +67,7 @@ module Ferrum
|
|
63
67
|
end
|
64
68
|
|
65
69
|
def select_file(value)
|
66
|
-
page.command("DOM.setFileInputFiles", nodeId: node_id, files: Array(value))
|
70
|
+
page.command("DOM.setFileInputFiles", slowmoable: true, nodeId: node_id, files: Array(value))
|
67
71
|
end
|
68
72
|
|
69
73
|
def at_xpath(selector)
|
@@ -119,35 +123,75 @@ module Ferrum
|
|
119
123
|
%(#<#{self.class} @target_id=#{@target_id.inspect} @node_id=#{@node_id} @description=#{@description.inspect}>)
|
120
124
|
end
|
121
125
|
|
122
|
-
def find_position(
|
123
|
-
|
124
|
-
|
126
|
+
def find_position(x: nil, y: nil, position: :top)
|
127
|
+
prev = get_content_quads
|
128
|
+
|
129
|
+
# FIXME: Case when a few quads returned
|
130
|
+
points = Ferrum.with_attempts(errors: NodeIsMovingError, max: MOVING_ATTEMPTS, wait: 0) do
|
131
|
+
sleep(MOVING_WAIT)
|
132
|
+
current = get_content_quads
|
133
|
+
|
134
|
+
if current != prev
|
135
|
+
error = NodeIsMovingError.new(self, prev, current)
|
136
|
+
prev = current
|
137
|
+
raise(error)
|
138
|
+
end
|
139
|
+
|
140
|
+
current
|
141
|
+
end.map { |q| to_points(q) }.first
|
142
|
+
|
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
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
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
|
125
158
|
|
126
|
-
|
127
|
-
|
128
|
-
|
159
|
+
def get_content_quads
|
160
|
+
quads = page.command("DOM.getContentQuads", nodeId: node_id)["quads"]
|
161
|
+
raise "Node is either not visible or not an HTMLElement" if quads.size == 0
|
162
|
+
quads
|
163
|
+
end
|
164
|
+
|
165
|
+
def get_position(points, offset_x, offset_y, position)
|
166
|
+
x = y = nil
|
167
|
+
|
168
|
+
if offset_x && offset_y && position == :top
|
169
|
+
point = points.first
|
170
|
+
x = point[:x] + offset_x.to_i
|
171
|
+
y = point[:y] + offset_y.to_i
|
129
172
|
else
|
130
|
-
x, y =
|
173
|
+
x, y = points.inject([0, 0]) do |memo, point|
|
131
174
|
[memo[0] + point[:x],
|
132
175
|
memo[1] + point[:y]]
|
133
176
|
end
|
134
|
-
|
177
|
+
|
178
|
+
x = x / 4
|
179
|
+
y = y / 4
|
135
180
|
end
|
136
|
-
end
|
137
181
|
|
138
|
-
|
182
|
+
if offset_x && offset_y && position == :center
|
183
|
+
x = x + offset_x.to_i
|
184
|
+
y = y + offset_y.to_i
|
185
|
+
end
|
139
186
|
|
140
|
-
|
141
|
-
|
142
|
-
raise "Node is either not visible or not an HTMLElement" if result["quads"].size == 0
|
187
|
+
[x, y]
|
188
|
+
end
|
143
189
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
{x: quad[6], y: quad[7]}]
|
150
|
-
end.first
|
190
|
+
def to_points(quad)
|
191
|
+
[{x: quad[0], y: quad[1]},
|
192
|
+
{x: quad[2], y: quad[3]},
|
193
|
+
{x: quad[4], y: quad[5]},
|
194
|
+
{x: quad[6], y: quad[7]}]
|
151
195
|
end
|
152
196
|
end
|
153
197
|
end
|