Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add_target putting wrong target to pendings sometimes #433

Merged
merged 3 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- `#files` information about downloaded files
- `#wait` wait for file download to be completed
- `#set_behavior` where and whether to store file
- `Browser::Client#command` accepts :async parameter [#433]

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

### Fixed

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

### Removed

Expand Down
3 changes: 2 additions & 1 deletion lib/ferrum/browser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
require "forwardable"
require "ferrum/page"
require "ferrum/proxy"
require "ferrum/client"
require "ferrum/contexts"
require "ferrum/browser/xvfb"
require "ferrum/browser/options"
require "ferrum/browser/process"
require "ferrum/browser/client"
require "ferrum/browser/binary"
require "ferrum/browser/version_info"

Expand Down Expand Up @@ -209,6 +209,7 @@ def quit
return unless @client

contexts.close_connections

@client.close
@process.stop
@client = @process = @contexts = nil
Expand Down
109 changes: 0 additions & 109 deletions lib/ferrum/browser/client.rb

This file was deleted.

113 changes: 113 additions & 0 deletions lib/ferrum/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# frozen_string_literal: true

require "forwardable"
require "ferrum/client/subscriber"
require "ferrum/client/web_socket"

module Ferrum
class Client
extend Forwardable
delegate %i[timeout timeout=] => :options

attr_reader :options

def initialize(ws_url, options)
@command_id = 0
@options = options
@pendings = Concurrent::Hash.new
@ws = WebSocket.new(ws_url, options.ws_max_receive_size, options.logger)
@subscriber = Subscriber.new

start
end

def command(method, async: false, **params)
message = build_message(method, params)

if async
@ws.send_message(message)
true
else
pending = Concurrent::IVar.new
@pendings[message[:id]] = pending
@ws.send_message(message)
data = pending.value!(timeout)
@pendings.delete(message[:id])

raise DeadBrowserError if data.nil? && @ws.messages.closed?
raise TimeoutError unless data

error, response = data.values_at("error", "result")
raise_browser_error(error) if error
response
end
end

def on(event, &block)
@subscriber.on(event, &block)
end

def subscribed?(event)
@subscriber.subscribed?(event)
end

def close
@ws.close
# Give a thread some time to handle a tail of messages
@pendings.clear
@thread.kill unless @thread.join(1)
@subscriber.close
end

def inspect
"#<#{self.class} " \
"@command_id=#{@command_id.inspect} " \
"@pendings=#{@pendings.inspect} " \
"@ws=#{@ws.inspect}>"
end

private

def start
@thread = Utils::Thread.spawn do
loop do
message = @ws.messages.pop
break unless message

if message.key?("method")
@subscriber << message
else
@pendings[message["id"]]&.set(message)
end
end
end
end

def build_message(method, params)
{ method: method, params: params }.merge(id: next_command_id)
end

def next_command_id
@command_id += 1
end

def raise_browser_error(error)
case error["message"]
# Node has disappeared while we were trying to get it
when "No node with given id found",
"Could not find node with given id",
"Inspected target navigated or closed"
raise NodeNotFoundError, error
# Context is lost, page is reloading
when "Cannot find context with specified id"
raise NoExecutionContextError, error
when "No target with given id found"
raise NoSuchPageError
when /Could not compute content quads/
raise CoordinatesNotFoundError
else
raise BrowserError, error
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Ferrum
class Browser
class Client
class Subscriber
INTERRUPTIONS = %w[Fetch.requestPaused Fetch.authRequired].freeze

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
require "websocket/driver"

module Ferrum
class Browser
class Client
class WebSocket
WEBSOCKET_BUG_SLEEP = 0.05
SKIP_LOGGING_SCREENSHOTS = !ENV["FERRUM_LOGGING_SCREENSHOTS"]
Expand Down
7 changes: 4 additions & 3 deletions lib/ferrum/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ def create_target
end

def add_target(params)
target = Target.new(@client, params)
@targets.put_if_absent(target.id, target)

new_target = Target.new(@client, params)
target = @targets.put_if_absent(new_target.id, new_target)
target ||= new_target # `put_if_absent` returns nil if added a new value or existing if there was one already
@pendings.put(target, @client.timeout) if @pendings.empty?
target
end

def update_target(target_id, params)
Expand Down
10 changes: 5 additions & 5 deletions lib/ferrum/network/intercepted_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ class InterceptedRequest

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

def initialize(page, params)
def initialize(client, params)
@status = nil
@page = page
@client = client
@params = params
@request_id = params["requestId"]
@frame_id = params["frameId"]
Expand Down Expand Up @@ -43,18 +43,18 @@ def respond(**options)
options = options.merge(body: Base64.strict_encode64(options.fetch(:body, ""))) if has_body

@status = :responded
@page.command("Fetch.fulfillRequest", **options)
@client.command("Fetch.fulfillRequest", **options)
end

def continue(**options)
options = options.merge(requestId: request_id)
@status = :continued
@page.command("Fetch.continueRequest", **options)
@client.command("Fetch.continueRequest", **options)
end

def abort
@status = :aborted
@page.command("Fetch.failRequest", requestId: request_id, errorReason: "BlockedByClient")
@client.command("Fetch.failRequest", requestId: request_id, errorReason: "BlockedByClient")
end

def initial_priority
Expand Down
Loading