cuprite 0.6.0 → 0.7.1

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.
@@ -1,200 +0,0 @@
1
- module Capybara::Cuprite
2
- class Browser
3
- module Input
4
- KEYS = JSON.parse(File.read(File.expand_path("../input.json", __FILE__)))
5
- MODIFIERS = { "alt" => 1, "ctrl" => 2, "control" => 2, "meta" => 4, "command" => 4, "shift" => 8 }
6
- KEYS_MAPPING = {
7
- cancel: "Cancel", help: "Help", backspace: "Backspace", tab: "Tab",
8
- clear: "Clear", return: "Enter", enter: "Enter", shift: "Shift",
9
- ctrl: "Control", control: "Control", alt: "Alt", pause: "Pause",
10
- escape: "Escape", space: "Space", pageup: "PageUp", page_up: "PageUp",
11
- pagedown: "PageDown", page_down: "PageDown", end: "End", home: "Home",
12
- left: "ArrowLeft", up: "ArrowUp", right: "ArrowRight",
13
- down: "ArrowDown", insert: "Insert", delete: "Delete",
14
- semicolon: "Semicolon", equals: "Equal", numpad0: "Numpad0",
15
- numpad1: "Numpad1", numpad2: "Numpad2", numpad3: "Numpad3",
16
- numpad4: "Numpad4", numpad5: "Numpad5", numpad6: "Numpad6",
17
- numpad7: "Numpad7", numpad8: "Numpad8", numpad9: "Numpad9",
18
- multiply: "NumpadMultiply", add: "NumpadAdd",
19
- separator: "NumpadDecimal", subtract: "NumpadSubtract",
20
- decimal: "NumpadDecimal", divide: "NumpadDivide", f1: "F1", f2: "F2",
21
- f3: "F3", f4: "F4", f5: "F5", f6: "F6", f7: "F7", f8: "F8", f9: "F9",
22
- f10: "F10", f11: "F11", f12: "F12", meta: "Meta", command: "Meta",
23
- }
24
-
25
- def click(node, keys = [], offset = {})
26
- x, y, modifiers = prepare_before_click(__method__, node, keys, offset)
27
- command("Input.dispatchMouseEvent", type: "mousePressed", modifiers: modifiers, button: "left", x: x, y: y, clickCount: 1)
28
- @wait = 0.05 # Potential wait because if network event is triggered then we have to wait until it's over.
29
- command("Input.dispatchMouseEvent", type: "mouseReleased", modifiers: modifiers, button: "left", x: x, y: y, clickCount: 1)
30
- end
31
-
32
- def right_click(node, keys = [], offset = {})
33
- x, y, modifiers = prepare_before_click(__method__, node, keys, offset)
34
- command("Input.dispatchMouseEvent", type: "mousePressed", modifiers: modifiers, button: "right", x: x, y: y, clickCount: 1)
35
- command("Input.dispatchMouseEvent", type: "mouseReleased", modifiers: modifiers, button: "right", x: x, y: y, clickCount: 1)
36
- end
37
-
38
- def double_click(node, keys = [], offset = {})
39
- x, y, modifiers = prepare_before_click(__method__, node, keys, offset)
40
- command("Input.dispatchMouseEvent", type: "mousePressed", modifiers: modifiers, button: "left", x: x, y: y, clickCount: 2)
41
- command("Input.dispatchMouseEvent", type: "mouseReleased", modifiers: modifiers, button: "left", x: x, y: y, clickCount: 2)
42
- end
43
-
44
- def click_coordinates(x, y)
45
- command("Input.dispatchMouseEvent", type: "mousePressed", button: "left", x: x, y: y, clickCount: 1)
46
- @wait = 0.05 # Potential wait because if network event is triggered then we have to wait until it's over.
47
- command("Input.dispatchMouseEvent", type: "mouseReleased", button: "left", x: x, y: y, clickCount: 1)
48
- end
49
-
50
- def hover(node)
51
- evaluate_on(node: node, expr: "_cuprite.scrollIntoViewport(this)")
52
- x, y = calculate_quads(node)
53
- command("Input.dispatchMouseEvent", type: "mouseMoved", x: x, y: y)
54
- end
55
-
56
- def set(node, value)
57
- object_id = command("DOM.resolveNode", nodeId: node["nodeId"]).dig("object", "objectId")
58
- evaluate("_cuprite.set(arguments[0], arguments[1])", { "objectId" => object_id }, value)
59
- end
60
-
61
- def drag(node, other)
62
- raise NotImplementedError
63
- end
64
-
65
- def drag_by(node, x, y)
66
- raise NotImplementedError
67
- end
68
-
69
- def select(node, value)
70
- evaluate_on(node: node, expr: "_cuprite.select(this, #{value})")
71
- end
72
-
73
- def trigger(node, event)
74
- options = event.to_s == "click" ? { wait: 0.1 } : {}
75
- evaluate_on(node: node, expr: %(_cuprite.trigger(this, "#{event}")), **options)
76
- end
77
-
78
- def scroll_to(top, left)
79
- execute("window.scrollTo(#{top}, #{left})")
80
- end
81
-
82
- def send_keys(node, keys)
83
- keys = normalize_keys(Array(keys))
84
-
85
- click(node) if !evaluate_on(node: node, expr: %(_cuprite.containsSelection(this)))
86
-
87
- keys.each do |key|
88
- type = key[:text] ? "keyDown" : "rawKeyDown"
89
- command("Input.dispatchKeyEvent", type: type, **key)
90
- command("Input.dispatchKeyEvent", type: "keyUp", **key)
91
- end
92
- end
93
-
94
- def normalize_keys(keys, pressed_keys = [], memo = [])
95
- case keys
96
- when Array
97
- pressed_keys.push([])
98
- memo += combine_strings(keys).map { |k| normalize_keys(k, pressed_keys, memo) }
99
- pressed_keys.pop
100
- memo.flatten.compact
101
- when Symbol
102
- key = keys.to_s.downcase
103
-
104
- if MODIFIERS.keys.include?(key)
105
- pressed_keys.last.push(key)
106
- nil
107
- else
108
- _key = KEYS.fetch(KEYS_MAPPING[key.to_sym] || key.to_sym)
109
- _key[:modifiers] = pressed_keys.flatten.map { |k| MODIFIERS[k] }.reduce(0, :|)
110
- to_options(_key)
111
- end
112
- when String
113
- pressed = pressed_keys.flatten
114
- keys.each_char.map do |char|
115
- if pressed.empty?
116
- key = KEYS[char] || {}
117
- key = key.merge(text: char, unmodifiedText: char)
118
- [to_options(key)]
119
- else
120
- key = KEYS[char] || {}
121
- text = pressed == ["shift"] ? char.upcase : char
122
- key = key.merge(
123
- text: text,
124
- unmodifiedText: text,
125
- isKeypad: key["location"] == 3,
126
- modifiers: pressed.map { |k| MODIFIERS[k] }.reduce(0, :|),
127
- )
128
-
129
- modifiers = pressed.map { |k| to_options(KEYS.fetch(KEYS_MAPPING[k.to_sym])) }
130
- modifiers + [to_options(key)]
131
- end.flatten
132
- end
133
- end
134
- end
135
-
136
- def combine_strings(keys)
137
- keys
138
- .chunk { |k| k.is_a?(String) }
139
- .map { |s, k| s ? [k.reduce(&:+)] : k }
140
- .reduce(&:+)
141
- end
142
-
143
- private
144
-
145
- def prepare_before_click(name, node, keys, offset)
146
- evaluate_on(node: node, expr: "_cuprite.scrollIntoViewport(this)")
147
- x, y = calculate_quads(node, offset[:x], offset[:y])
148
- evaluate_on(node: node, expr: "_cuprite.mouseEventTest(this, '#{name}', #{x}, #{y})")
149
-
150
- modifiers = keys.map { |k| MODIFIERS[k.to_s] }.compact.reduce(0, :|)
151
-
152
- command("Input.dispatchMouseEvent", type: "mouseMoved", x: x, y: y)
153
-
154
- [x, y, modifiers]
155
- end
156
-
157
- def calculate_quads(node, offset_x = nil, offset_y = nil)
158
- quads = get_content_quads(node)
159
- offset_x, offset_y = offset_x.to_i, offset_y.to_i
160
-
161
- if offset_x > 0 || offset_y > 0
162
- point = quads.first
163
- [point[:x] + offset_x, point[:y] + offset_y]
164
- else
165
- x, y = quads.inject([0, 0]) do |memo, point|
166
- [memo[0] + point[:x],
167
- memo[1] + point[:y]]
168
- end
169
- [x / 4, y / 4]
170
- end
171
- end
172
-
173
- def get_content_quads(node)
174
- begin
175
- result = command("DOM.getContentQuads", nodeId: node["nodeId"])
176
- rescue BrowserError => e
177
- if e.message == "Could not compute content quads."
178
- raise MouseEventFailed.new("MouseEventFailed: click, none, 0, 0")
179
- else
180
- raise
181
- end
182
- end
183
-
184
- raise "Node is either not visible or not an HTMLElement" if result["quads"].size == 0
185
-
186
- # FIXME: Case when a few quads returned
187
- result["quads"].map do |quad|
188
- [{x: quad[0], y: quad[1]},
189
- {x: quad[2], y: quad[3]},
190
- {x: quad[4], y: quad[5]},
191
- {x: quad[6], y: quad[7]}]
192
- end.first
193
- end
194
-
195
- def to_options(hash)
196
- hash.inject({}) { |memo, (k, v)| memo.merge(k.to_sym => v) }
197
- end
198
- end
199
- end
200
- end
@@ -1,90 +0,0 @@
1
- module Capybara::Cuprite
2
- class Browser
3
- module Net
4
- def proxy_authorize(user, password)
5
- if user && password
6
- @proxy_username, @proxy_password = user, password
7
- intercept_request("*")
8
- end
9
- end
10
-
11
- def authorize(user, password)
12
- @username, @password = user, password
13
- intercept_request("*")
14
- end
15
-
16
- def intercept_request(patterns)
17
- patterns = Array(patterns).map { |p| { urlPattern: p } }
18
- @client.command("Network.setRequestInterception", patterns: patterns)
19
- end
20
-
21
- def continue_request(interception_id, options = nil)
22
- options ||= {}
23
- options = options.merge(interceptionId: interception_id)
24
- @client.command("Network.continueInterceptedRequest", **options)
25
- end
26
-
27
- private
28
-
29
- def subscribe_events
30
- super if defined?(super)
31
-
32
- @client.subscribe("Network.loadingFailed") do |params|
33
- # Free mutex as we aborted main request we are waiting for
34
- if params["requestId"] == @request_id && params["canceled"] == true
35
- signal
36
- @client.command("DOM.getDocument", depth: 0)
37
- end
38
- end
39
-
40
- @client.subscribe("Network.requestIntercepted") do |params|
41
- @authorized_ids ||= []
42
- @proxy_authorized_ids ||= []
43
- url = params.dig("request", "url")
44
- interception_id = params["interceptionId"]
45
-
46
- if params["authChallenge"]
47
- response = if params.dig("authChallenge", "source") == "Proxy"
48
- if @proxy_authorized_ids.include?(interception_id)
49
- { response: "CancelAuth" }
50
- elsif @proxy_username && @proxy_password
51
- { response: "ProvideCredentials",
52
- username: @proxy_username,
53
- password: @proxy_password }
54
- else
55
- { response: "CancelAuth" }
56
- end
57
- else
58
- if @authorized_ids.include?(interception_id)
59
- { response: "CancelAuth" }
60
- elsif @username && @password
61
- { response: "ProvideCredentials",
62
- username: @username,
63
- password: @password }
64
- else
65
- { response: "CancelAuth" }
66
- end
67
- end
68
-
69
- @authorized_ids << interception_id
70
- continue_request(interception_id, authChallengeResponse: response)
71
- elsif @browser.url_blacklist && !@browser.url_blacklist.empty?
72
- if @browser.url_blacklist.any? { |r| r.match(url) }
73
- continue_request(interception_id, errorReason: "Aborted")
74
- else
75
- continue_request(interception_id)
76
- end
77
- elsif @browser.url_whitelist && !@browser.url_whitelist.empty?
78
- if @browser.url_whitelist.any? { |r| r.match(url) }
79
- continue_request(interception_id)
80
- else
81
- continue_request(interception_id, errorReason: "Aborted")
82
- end
83
- else
84
- continue_request(interception_id)
85
- end
86
- end
87
- end
88
- end
89
- end
90
- end
@@ -1,378 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "capybara/cuprite/browser/dom"
4
- require "capybara/cuprite/browser/input"
5
- require "capybara/cuprite/browser/runtime"
6
- require "capybara/cuprite/browser/frame"
7
- require "capybara/cuprite/browser/client"
8
- require "capybara/cuprite/browser/net"
9
- require "capybara/cuprite/network/error"
10
- require "capybara/cuprite/network/request"
11
- require "capybara/cuprite/network/response"
12
-
13
- # RemoteObjectId is from a JavaScript world, and corresponds to any JavaScript
14
- # object, including JS wrappers for DOM nodes. There is a way to convert between
15
- # node ids and remote object ids (DOM.requestNode and DOM.resolveNode).
16
- #
17
- # NodeId is used for inspection, when backend tracks the node and sends updates to
18
- # the frontend. If you somehow got NodeId over protocol, backend should have
19
- # pushed to the frontend all of it's ancestors up to the Document node via
20
- # DOM.setChildNodes. After that, frontend is always kept up-to-date about anything
21
- # happening to the node.
22
- #
23
- # BackendNodeId is just a unique identifier for a node. Obtaining it does not send
24
- # any updates, for example, the node may be destroyed without any notification.
25
- # This is a way to keep a reference to the Node, when you don't necessarily want
26
- # to keep track of it. One example would be linking to the node from performance
27
- # data (e.g. relayout root node). BackendNodeId may be either resolved to
28
- # inspected node (DOM.pushNodesByBackendIdsToFrontend) or described in more
29
- # details (DOM.describeNode).
30
- module Capybara::Cuprite
31
- class Browser
32
- class Page
33
- include Input, DOM, Runtime, Frame, Net
34
-
35
- attr_accessor :referrer
36
- attr_reader :target_id, :status_code, :response_headers
37
-
38
- def initialize(target_id, browser)
39
- @wait = 0
40
- @target_id, @browser = target_id, browser
41
- @mutex, @resource = Mutex.new, ConditionVariable.new
42
- @network_traffic = []
43
-
44
- @frames = {}
45
- @waiting_frames ||= Set.new
46
- @frame_stack = []
47
- @accept_modal = []
48
- @modal_messages = []
49
-
50
- begin
51
- @session_id = @browser.command("Target.attachToTarget", targetId: @target_id)["sessionId"]
52
- rescue BrowserError => e
53
- if e.message == "No target with given id found"
54
- raise NoSuchWindowError
55
- else
56
- raise
57
- end
58
- end
59
-
60
- host = @browser.process.host
61
- port = @browser.process.port
62
- ws_url = "ws://#{host}:#{port}/devtools/page/#{@target_id}"
63
- @client = Client.new(browser, ws_url)
64
-
65
- subscribe_events
66
- prepare_page
67
- end
68
-
69
- def timeout
70
- @browser.timeout
71
- end
72
-
73
- def visit(url)
74
- @wait = timeout
75
- options = { url: url }
76
- options.merge!(referrer: referrer) if referrer
77
- response = command("Page.navigate", **options)
78
- # https://cs.chromium.org/chromium/src/net/base/net_error_list.h
79
- if %w[net::ERR_NAME_NOT_RESOLVED
80
- net::ERR_NAME_RESOLUTION_FAILED
81
- net::ERR_INTERNET_DISCONNECTED
82
- net::ERR_CONNECTION_TIMED_OUT].include?(response["errorText"])
83
- raise StatusFailError, "url" => url
84
- end
85
- response["frameId"]
86
- end
87
-
88
- def close
89
- @browser.command("Target.detachFromTarget", sessionId: @session_id)
90
- @browser.command("Target.closeTarget", targetId: @target_id)
91
- close_connection
92
- end
93
-
94
- def close_connection
95
- @client.close
96
- end
97
-
98
- def resize(width: nil, height: nil, fullscreen: false)
99
- result = @browser.command("Browser.getWindowForTarget", targetId: @target_id)
100
- @window_id, @bounds = result.values_at("windowId", "bounds")
101
-
102
- if fullscreen
103
- @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { windowState: "fullscreen" })
104
- else
105
- @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { windowState: "normal" })
106
- @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { width: width, height: height, windowState: "normal" })
107
- command("Emulation.setDeviceMetricsOverride", width: width, height: height, deviceScaleFactor: 1, mobile: false)
108
- end
109
- end
110
-
111
- def refresh
112
- @wait = timeout
113
- command("Page.reload")
114
- end
115
-
116
- def network_traffic(type = nil)
117
- case type.to_s
118
- when "all"
119
- @network_traffic
120
- when "blocked"
121
- @network_traffic.select { |r| r.response.nil? } # when request blocked
122
- else
123
- @network_traffic.select { |r| r.response } # when request isn't blocked
124
- end
125
- end
126
-
127
- def clear_network_traffic
128
- @network_traffic = []
129
- end
130
-
131
- def go_back
132
- go(-1)
133
- end
134
-
135
- def go_forward
136
- go(1)
137
- end
138
-
139
- def accept_confirm
140
- @accept_modal << true
141
- end
142
-
143
- def dismiss_confirm
144
- @accept_modal << false
145
- end
146
-
147
- def accept_prompt(modal_response)
148
- @accept_modal << true
149
- @modal_response = modal_response
150
- end
151
-
152
- def dismiss_prompt
153
- @accept_modal << false
154
- end
155
-
156
- def find_modal(options)
157
- start_time = Capybara::Helpers.monotonic_time
158
- timeout_sec = options.fetch(:wait) { session_wait_time }
159
- expect_text = options[:text]
160
- expect_regexp = expect_text.is_a?(Regexp) ? expect_text : Regexp.escape(expect_text.to_s)
161
- not_found_msg = "Unable to find modal dialog"
162
- not_found_msg += " with #{expect_text}" if expect_text
163
-
164
- begin
165
- modal_text = @modal_messages.shift
166
- raise Capybara::ModalNotFound if modal_text.nil? || (expect_text && !modal_text.match(expect_regexp))
167
- rescue Capybara::ModalNotFound => e
168
- raise e, not_found_msg if (Capybara::Helpers.monotonic_time - start_time) >= timeout_sec
169
- sleep(0.05)
170
- retry
171
- end
172
-
173
- modal_text
174
- end
175
-
176
- def reset_modals
177
- @accept_modal = []
178
- @modal_response = nil
179
- @modal_messages = []
180
- end
181
-
182
- def command(*args)
183
- id = nil
184
-
185
- @mutex.synchronize do
186
- id = @client.command(*args)
187
- stop_at = Capybara::Helpers.monotonic_time + @wait
188
-
189
- while @wait > 0 && (remain = stop_at - Capybara::Helpers.monotonic_time) > 0
190
- @resource.wait(@mutex, remain)
191
- end
192
-
193
- @wait = 0
194
- end
195
-
196
- @client.wait(id: id)
197
- end
198
-
199
- private
200
-
201
- def subscribe_events
202
- super
203
-
204
- if @browser.logger
205
- @client.subscribe("Runtime.consoleAPICalled") do |params|
206
- params["args"].each { |r| @browser.logger.puts(r["value"]) }
207
- end
208
- end
209
-
210
- if @browser.js_errors
211
- @client.subscribe("Runtime.exceptionThrown") do |params|
212
- Thread.main.raise JavaScriptError.new(params.dig("exceptionDetails", "exception"))
213
- end
214
- end
215
-
216
- @client.subscribe("Page.javascriptDialogOpening") do |params|
217
- accept_modal = @accept_modal.last
218
- if accept_modal == true || accept_modal == false
219
- @accept_modal.pop
220
- @modal_messages << params["message"]
221
- options = { accept: accept_modal }
222
- response = @modal_response || params["defaultPrompt"]
223
- options.merge!(promptText: response) if response
224
- @client.command("Page.handleJavaScriptDialog", **options)
225
- else
226
- warn "Modal window has been opened, but you didn't wrap your code into (`accept_prompt` | `dismiss_prompt` | `accept_confirm` | `dismiss_confirm` | `accept_alert`), accepting by default"
227
- options = { accept: true }
228
- response = params["defaultPrompt"]
229
- options.merge!(promptText: response) if response
230
- @client.command("Page.handleJavaScriptDialog", **options)
231
- end
232
- end
233
-
234
- @client.subscribe("Page.windowOpen") do
235
- @browser.targets.refresh
236
- @mutex.try_lock
237
- sleep 0.3 # Dirty hack because new window doesn't have events at all
238
- @mutex.unlock if @mutex.locked? && @mutex.owned?
239
- end
240
-
241
- @client.subscribe("Page.navigatedWithinDocument") do
242
- signal if @waiting_frames.empty?
243
- end
244
-
245
- @client.subscribe("Page.domContentEventFired") do |params|
246
- # `frameStoppedLoading` doesn't occur if status isn't success
247
- if @status_code != 200
248
- signal
249
- @client.command("DOM.getDocument", depth: 0)
250
- end
251
- end
252
-
253
- @client.subscribe("Network.requestWillBeSent") do |params|
254
- if params["frameId"] == @frame_id
255
- # Possible types:
256
- # Document, Stylesheet, Image, Media, Font, Script, TextTrack, XHR,
257
- # Fetch, EventSource, WebSocket, Manifest, SignedExchange, Ping,
258
- # CSPViolationReport, Other
259
- if params["type"] == "Document"
260
- @mutex.try_lock
261
- @request_id = params["requestId"]
262
- end
263
- end
264
-
265
- id, time = params.values_at("requestId", "wallTime")
266
- params = params["request"].merge("id" => id, "time" => time)
267
- @network_traffic << Network::Request.new(params)
268
- end
269
-
270
- @client.subscribe("Network.responseReceived") do |params|
271
- if params["requestId"] == @request_id
272
- @response_headers = params.dig("response", "headers")
273
- @status_code = params.dig("response", "status")
274
- end
275
-
276
- if request = @network_traffic.find { |r| r.id == params["requestId"] }
277
- params = params["response"].merge("id" => params["requestId"])
278
- request.response = Network::Response.new(params)
279
- end
280
- end
281
-
282
- @client.subscribe("Network.loadingFinished") do |params|
283
- if request = @network_traffic.find { |r| r.id == params["requestId"] }
284
- # Sometimes we never get the Network.responseReceived event.
285
- # See https://crbug.com/883475
286
- #
287
- # Network.loadingFinished's encodedDataLength contains both body and headers
288
- # sizes received by wire. See https://crbug.com/764946
289
- if response = request.response
290
- response.body_size = params["encodedDataLength"] - response.headers_size
291
- end
292
- end
293
- end
294
-
295
- @client.subscribe("Log.entryAdded") do |params|
296
- source = params.dig("entry", "source")
297
- level = params.dig("entry", "level")
298
- if source == "network" && level == "error"
299
- id = params.dig("entry", "networkRequestId")
300
- if request = @network_traffic.find { |r| r.id == id }
301
- request.error = Network::Error.new(params["entry"])
302
- end
303
- end
304
- end
305
- end
306
-
307
- def prepare_page
308
- command("Page.enable")
309
- command("DOM.enable")
310
- command("CSS.enable")
311
- command("Runtime.enable")
312
- command("Log.enable")
313
- command("Network.enable")
314
-
315
- if Capybara.save_path
316
- command("Page.setDownloadBehavior", behavior: "allow", downloadPath: Capybara.save_path.to_s)
317
- end
318
-
319
- @browser.extensions.each do |extension|
320
- @client.command("Page.addScriptToEvaluateOnNewDocument", source: extension)
321
- end
322
-
323
- inject_extensions
324
-
325
- width, height = @browser.window_size
326
- resize(width: width, height: height)
327
-
328
- url_whitelist = Array(@browser.url_whitelist)
329
- url_blacklist = Array(@browser.url_blacklist)
330
- intercept_request("*") if !url_whitelist.empty? || !url_blacklist.empty?
331
-
332
- response = command("Page.getNavigationHistory")
333
- if response.dig("entries", 0, "transitionType") != "typed"
334
- # If we create page by clicking links, submiting forms and so on it
335
- # opens a new window for which `frameStoppedLoading` event never
336
- # occurs and thus search for nodes cannot be completed. Here we check
337
- # the history and if the transitionType for example `link` then
338
- # content is already loaded and we can try to get the document.
339
- @client.command("DOM.getDocument", depth: 0)
340
- end
341
- end
342
-
343
- def inject_extensions
344
- @browser.extensions.each do |extension|
345
- # https://github.com/GoogleChrome/puppeteer/issues/1443
346
- # https://github.com/ChromeDevTools/devtools-protocol/issues/77
347
- # https://github.com/cyrus-and/chrome-remote-interface/issues/319
348
- # We also evaluate script just in case because
349
- # `Page.addScriptToEvaluateOnNewDocument` doesn't work in popups.
350
- @client.command("Runtime.evaluate", expression: extension,
351
- contextId: execution_context_id,
352
- returnByValue: true)
353
- end
354
- end
355
-
356
- def signal
357
- @wait = 0
358
-
359
- if @mutex.locked? && @mutex.owned?
360
- @resource.signal
361
- @mutex.unlock
362
- else
363
- @mutex.synchronize { @resource.signal }
364
- end
365
- end
366
-
367
- def go(delta)
368
- history = command("Page.getNavigationHistory")
369
- index, entries = history.values_at("currentIndex", "entries")
370
-
371
- if entry = entries[index + delta]
372
- @wait = 0.05 # Potential wait because of network event
373
- command("Page.navigateToHistoryEntry", entryId: entry["id"])
374
- end
375
- end
376
- end
377
- end
378
- end