Skip to content

Commit af88f87

Browse files
authored
fix: add_target putting wrong target to pendings sometimes (#433)
* fix: add_target putting wrong target to pendings sometimes * chore: add CHANGELOG entry * ref: rename Client
1 parent 1c429dc commit af88f87

File tree

14 files changed

+180
-139
lines changed

14 files changed

+180
-139
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- `#files` information about downloaded files
88
- `#wait` wait for file download to be completed
99
- `#set_behavior` where and whether to store file
10+
- `Browser::Client#command` accepts :async parameter [#433]
1011

1112
### Changed
1213
- `Ferrum::Page#screeshot` accepts :area option [#410]
@@ -17,10 +18,15 @@ instead of passing browser and making cyclic dependency on the browser instance,
1718
- Bump `websocket-driver` to `~> 0.7` [#432]
1819
- Got rid of `Concurrent::Async` in `Ferrum::Browser::Subscriber` [#432]
1920
- `Ferrum::Page#set_window_bounds` is renamed to `Ferrum::Page#window_bounds=`
21+
- `Ferrum::Page` get right client from the Target and passes it down everywhere [#433]
22+
- `Ferrum::Network::InterceptedRequest` accepts `Ferrum::Browser::Client` instead of `Ferrum::Page` [#433]
23+
- `Ferrum::Browser::Client` -> `Ferrum::Client` [#433]
2024

2125
### Fixed
2226

2327
- Exceptions within `.on()` were swallowed by a thread pool of `Concurrent::Async` [#432]
28+
- `Ferrum::Context#add_target` puts wrong target to pendings sometimes [#433]
29+
- Leaking connection descriptors in tests and after browser quit [#433]
2430

2531
### Removed
2632

lib/ferrum/browser.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
require "forwardable"
55
require "ferrum/page"
66
require "ferrum/proxy"
7+
require "ferrum/client"
78
require "ferrum/contexts"
89
require "ferrum/browser/xvfb"
910
require "ferrum/browser/options"
1011
require "ferrum/browser/process"
11-
require "ferrum/browser/client"
1212
require "ferrum/browser/binary"
1313
require "ferrum/browser/version_info"
1414

@@ -209,6 +209,7 @@ def quit
209209
return unless @client
210210

211211
contexts.close_connections
212+
212213
@client.close
213214
@process.stop
214215
@client = @process = @contexts = nil

lib/ferrum/browser/client.rb

Lines changed: 0 additions & 109 deletions
This file was deleted.

lib/ferrum/client.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# frozen_string_literal: true
2+
3+
require "forwardable"
4+
require "ferrum/client/subscriber"
5+
require "ferrum/client/web_socket"
6+
7+
module Ferrum
8+
class Client
9+
extend Forwardable
10+
delegate %i[timeout timeout=] => :options
11+
12+
attr_reader :options
13+
14+
def initialize(ws_url, options)
15+
@command_id = 0
16+
@options = options
17+
@pendings = Concurrent::Hash.new
18+
@ws = WebSocket.new(ws_url, options.ws_max_receive_size, options.logger)
19+
@subscriber = Subscriber.new
20+
21+
start
22+
end
23+
24+
def command(method, async: false, **params)
25+
message = build_message(method, params)
26+
27+
if async
28+
@ws.send_message(message)
29+
true
30+
else
31+
pending = Concurrent::IVar.new
32+
@pendings[message[:id]] = pending
33+
@ws.send_message(message)
34+
data = pending.value!(timeout)
35+
@pendings.delete(message[:id])
36+
37+
raise DeadBrowserError if data.nil? && @ws.messages.closed?
38+
raise TimeoutError unless data
39+
40+
error, response = data.values_at("error", "result")
41+
raise_browser_error(error) if error
42+
response
43+
end
44+
end
45+
46+
def on(event, &block)
47+
@subscriber.on(event, &block)
48+
end
49+
50+
def subscribed?(event)
51+
@subscriber.subscribed?(event)
52+
end
53+
54+
def close
55+
@ws.close
56+
# Give a thread some time to handle a tail of messages
57+
@pendings.clear
58+
@thread.kill unless @thread.join(1)
59+
@subscriber.close
60+
end
61+
62+
def inspect
63+
"#<#{self.class} " \
64+
"@command_id=#{@command_id.inspect} " \
65+
"@pendings=#{@pendings.inspect} " \
66+
"@ws=#{@ws.inspect}>"
67+
end
68+
69+
private
70+
71+
def start
72+
@thread = Utils::Thread.spawn do
73+
loop do
74+
message = @ws.messages.pop
75+
break unless message
76+
77+
if message.key?("method")
78+
@subscriber << message
79+
else
80+
@pendings[message["id"]]&.set(message)
81+
end
82+
end
83+
end
84+
end
85+
86+
def build_message(method, params)
87+
{ method: method, params: params }.merge(id: next_command_id)
88+
end
89+
90+
def next_command_id
91+
@command_id += 1
92+
end
93+
94+
def raise_browser_error(error)
95+
case error["message"]
96+
# Node has disappeared while we were trying to get it
97+
when "No node with given id found",
98+
"Could not find node with given id",
99+
"Inspected target navigated or closed"
100+
raise NodeNotFoundError, error
101+
# Context is lost, page is reloading
102+
when "Cannot find context with specified id"
103+
raise NoExecutionContextError, error
104+
when "No target with given id found"
105+
raise NoSuchPageError
106+
when /Could not compute content quads/
107+
raise CoordinatesNotFoundError
108+
else
109+
raise BrowserError, error
110+
end
111+
end
112+
end
113+
end
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
module Ferrum
4-
class Browser
4+
class Client
55
class Subscriber
66
INTERRUPTIONS = %w[Fetch.requestPaused Fetch.authRequired].freeze
77

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
require "websocket/driver"
66

77
module Ferrum
8-
class Browser
8+
class Client
99
class WebSocket
1010
WEBSOCKET_BUG_SLEEP = 0.05
1111
SKIP_LOGGING_SCREENSHOTS = !ENV["FERRUM_LOGGING_SCREENSHOTS"]

lib/ferrum/context.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,11 @@ def create_target
5454
end
5555

5656
def add_target(params)
57-
target = Target.new(@client, params)
58-
@targets.put_if_absent(target.id, target)
59-
57+
new_target = Target.new(@client, params)
58+
target = @targets.put_if_absent(new_target.id, new_target)
59+
target ||= new_target # `put_if_absent` returns nil if added a new value or existing if there was one already
6060
@pendings.put(target, @client.timeout) if @pendings.empty?
61+
target
6162
end
6263

6364
def update_target(target_id, params)

lib/ferrum/network/intercepted_request.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class InterceptedRequest
1010

1111
attr_accessor :request_id, :frame_id, :resource_type, :network_id, :status
1212

13-
def initialize(page, params)
13+
def initialize(client, params)
1414
@status = nil
15-
@page = page
15+
@client = client
1616
@params = params
1717
@request_id = params["requestId"]
1818
@frame_id = params["frameId"]
@@ -43,18 +43,18 @@ def respond(**options)
4343
options = options.merge(body: Base64.strict_encode64(options.fetch(:body, ""))) if has_body
4444

4545
@status = :responded
46-
@page.command("Fetch.fulfillRequest", **options)
46+
@client.command("Fetch.fulfillRequest", **options)
4747
end
4848

4949
def continue(**options)
5050
options = options.merge(requestId: request_id)
5151
@status = :continued
52-
@page.command("Fetch.continueRequest", **options)
52+
@client.command("Fetch.continueRequest", **options)
5353
end
5454

5555
def abort
5656
@status = :aborted
57-
@page.command("Fetch.failRequest", requestId: request_id, errorReason: "BlockedByClient")
57+
@client.command("Fetch.failRequest", requestId: request_id, errorReason: "BlockedByClient")
5858
end
5959

6060
def initial_priority

0 commit comments

Comments
 (0)