Skip to content

Commit

Permalink
refactor: don't pass browser instance everywhere
Browse files Browse the repository at this point in the history
Pass a client instead of browser. This also helps for a preparation to switch to a flatten mode and one socket for all pages and browser.
  • Loading branch information
route committed Jan 3, 2024
1 parent 17bea6b commit 352e142
Show file tree
Hide file tree
Showing 17 changed files with 346 additions and 351 deletions.
297 changes: 151 additions & 146 deletions README.md

Large diffs are not rendered by default.

51 changes: 10 additions & 41 deletions lib/ferrum/browser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ class Browser
on position position=
playback_rate playback_rate=
disable_javascript set_viewport resize] => :page
delegate %i[default_user_agent] => :process

attr_reader :client, :process, :contexts, :options, :base_url
attr_accessor :timeout
attr_reader :client, :process, :contexts, :options

delegate %i[timeout timeout= base_url base_url= default_user_agent default_user_agent= extensions] => :options
delegate %i[command] => :client

#
# Initializes the browser.
Expand Down Expand Up @@ -125,27 +126,9 @@ def initialize(options = nil)
@options = Options.new(options)
@client = @process = @contexts = nil

@timeout = @options.timeout
@base_url = @options.base_url if @options.base_url

start
end

#
# Sets the base URL.
#
# @param [String] value
# The new base URL value.
#
# @raise [ArgumentError] when path is not absolute or doesn't include schema
#
# @return [Addressable::URI]
# The parsed base URI value.
#
def base_url=(value)
@base_url = options.parse_base_url(value)
end

#
# Creates a new page.
#
Expand All @@ -163,7 +146,7 @@ def create_page(new_context: false, proxy: nil)
params = {}

if proxy
options.parse_proxy(proxy)
options.validate_proxy(proxy)
params.merge!(proxyServer: "#{proxy[:host]}:#{proxy[:port]}")
params.merge!(proxyBypassList: proxy[:bypass]) if proxy[:bypass]
end
Expand All @@ -182,12 +165,6 @@ def create_page(new_context: false, proxy: nil)
end
end

def extensions
@extensions ||= Array(options.extensions).map do |ext|
(ext.is_a?(Hash) && ext[:source]) || File.read(ext)
end
end

#
# Evaluate JavaScript to modify things before a page load.
#
Expand All @@ -205,13 +182,6 @@ def evaluate_on_new_document(expression)
extensions << expression
end

def command(*args)
@client.command(*args)
rescue DeadBrowserError
restart
raise
end

#
# Closes browser tabs opened by the `Browser` instance.
#
Expand Down Expand Up @@ -270,12 +240,11 @@ def start

begin
@process.start
@client = Client.new(
@process.ws_url, self,
logger: options.logger,
ws_max_receive_size: options.ws_max_receive_size
)
@contexts = Contexts.new(self)
@options.ws_url = @process.ws_url&.merge(path: "/")
@options.default_user_agent = @process.default_user_agent

@client = Client.new(@process.ws_url, options)
@contexts = Contexts.new(@client)
rescue StandardError
@process.stop
raise
Expand Down
16 changes: 11 additions & 5 deletions lib/ferrum/browser/client.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require "forwardable"
require "ferrum/browser/subscriber"
require "ferrum/browser/web_socket"

Expand All @@ -8,11 +9,16 @@ class Browser
class Client
INTERRUPTIONS = %w[Fetch.requestPaused Fetch.authRequired].freeze

def initialize(ws_url, connectable, logger: nil, ws_max_receive_size: nil, id_starts_with: 0)
@connectable = connectable
@command_id = id_starts_with
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, ws_max_receive_size, logger)
@ws = WebSocket.new(ws_url, options.ws_max_receive_size, options.logger)
@subscriber, @interrupter = Subscriber.build(2)

@thread = Thread.new do
Expand All @@ -39,7 +45,7 @@ def command(method, params = {})
message = build_message(method, params)
@pendings[message[:id]] = pending
@ws.send_message(message)
data = pending.value!(@connectable.timeout)
data = pending.value!(timeout)
@pendings.delete(message[:id])

raise DeadBrowserError if data.nil? && @ws.messages.closed?
Expand Down
40 changes: 24 additions & 16 deletions lib/ferrum/browser/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ class Options
PROCESS_TIMEOUT = ENV.fetch("FERRUM_PROCESS_TIMEOUT", 10).to_i
DEBUG_MODE = !ENV.fetch("FERRUM_DEBUG", nil).nil?

attr_reader :window_size, :timeout, :logger, :ws_max_receive_size,
attr_reader :window_size, :logger, :ws_max_receive_size,
:js_errors, :base_url, :slowmo, :pending_connection_errors,
:url, :env, :process_timeout, :browser_name, :browser_path,
:save_path, :extensions, :proxy, :port, :host, :headless,
:ignore_default_browser_options, :browser_options, :xvfb
attr_accessor :timeout, :ws_url, :default_user_agent

def initialize(options = nil)
@options = Hash(options&.dup)
Expand All @@ -32,35 +33,29 @@ def initialize(options = nil)
@slowmo = @options[:slowmo].to_f

@ws_max_receive_size, @env, @browser_name, @browser_path,
@save_path, @extensions, @ignore_default_browser_options, @xvfb = @options.values_at(
:ws_max_receive_size, :env, :browser_name, :browser_path, :save_path, :extensions,
@save_path, @ignore_default_browser_options, @xvfb = @options.values_at(
:ws_max_receive_size, :env, :browser_name, :browser_path, :save_path,
:ignore_default_browser_options, :xvfb
)

@options[:window_size] = @window_size
@proxy = parse_proxy(@options[:proxy])
@proxy = validate_proxy(@options[:proxy])
@logger = parse_logger(@options[:logger])
@base_url = parse_base_url(@options[:base_url]) if @options[:base_url]
@url = @options[:url].to_s if @options[:url]
@extensions = Array(@options[:extensions]).map do |extension|
(extension.is_a?(Hash) && extension[:source]) || File.read(extension)
end

@options.freeze
@browser_options.freeze
end

def to_h
@options
def base_url=(value)
@base_url = parse_base_url(value)
end

def parse_base_url(value)
parsed = Addressable::URI.parse(value)
unless BASE_URL_SCHEMA.include?(parsed&.normalized_scheme)
raise ArgumentError, "`base_url` should be absolute and include schema: #{BASE_URL_SCHEMA.join(' | ')}"
end

parsed
end

def parse_proxy(options)
def validate_proxy(options)
return unless options

raise ArgumentError, "proxy options must be a Hash" unless options.is_a?(Hash)
Expand All @@ -72,13 +67,26 @@ def parse_proxy(options)
options
end

def to_h
@options
end

private

def parse_logger(logger)
return logger if logger

!logger && DEBUG_MODE ? $stdout.tap { |s| s.sync = true } : logger
end

def parse_base_url(value)
parsed = Addressable::URI.parse(value)
unless BASE_URL_SCHEMA.include?(parsed&.normalized_scheme)
raise ArgumentError, "`base_url` should be absolute and include schema: #{BASE_URL_SCHEMA.join(' | ')}"
end

parsed
end
end
end
end
14 changes: 6 additions & 8 deletions lib/ferrum/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ class Context

attr_reader :id, :targets

def initialize(browser, contexts, id)
def initialize(client, contexts, id)
@id = id
@browser = browser
@client = client
@contexts = contexts
@targets = Concurrent::Map.new
@pendings = Concurrent::MVar.new
Expand Down Expand Up @@ -46,22 +46,20 @@ def create_page(**options)
end

def create_target
@browser.command("Target.createTarget",
browserContextId: @id,
url: "about:blank")
target = @pendings.take(@browser.timeout)
@client.command("Target.createTarget", browserContextId: @id, url: "about:blank")
target = @pendings.take(@client.timeout)
raise NoSuchTargetError unless target.is_a?(Target)

@targets.put_if_absent(target.id, target)
target
end

def add_target(params)
target = Target.new(@browser, params)
target = Target.new(@client, params)
if target.window?
@targets.put_if_absent(target.id, target)
else
@pendings.put(target, @browser.timeout)
@pendings.put(target, @client.timeout)
end
end

Expand Down
33 changes: 22 additions & 11 deletions lib/ferrum/contexts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

module Ferrum
class Contexts
include Enumerable

attr_reader :contexts

def initialize(browser)
def initialize(client)
@contexts = Concurrent::Map.new
@browser = browser
@client = client
subscribe
discover
end
Expand All @@ -17,24 +19,33 @@ def default_context
@default_context ||= create
end

def each(&block)
return enum_for(__method__) unless block_given?

@contexts.each(&block)
end

def [](id)
@contexts[id]
end

def find_by(target_id:)
context = nil
@contexts.each_value { |c| context = c if c.target?(target_id) }
context
end

def create(**options)
response = @browser.command("Target.createBrowserContext", **options)
response = @client.command("Target.createBrowserContext", **options)
context_id = response["browserContextId"]
context = Context.new(@browser, self, context_id)
context = Context.new(@client, self, context_id)
@contexts[context_id] = context
context
end

def dispose(context_id)
context = @contexts[context_id]
@browser.command("Target.disposeBrowserContext",
browserContextId: context.id)
@client.command("Target.disposeBrowserContext", browserContextId: context.id)
@contexts.delete(context_id)
true
end
Expand All @@ -51,35 +62,35 @@ def size
private

def subscribe
@browser.client.on("Target.targetCreated") do |params|
@client.on("Target.targetCreated") do |params|
info = params["targetInfo"]
next unless info["type"] == "page"

context_id = info["browserContextId"]
@contexts[context_id]&.add_target(info)
end

@browser.client.on("Target.targetInfoChanged") do |params|
@client.on("Target.targetInfoChanged") do |params|
info = params["targetInfo"]
next unless info["type"] == "page"

context_id, target_id = info.values_at("browserContextId", "targetId")
@contexts[context_id]&.update_target(target_id, info)
end

@browser.client.on("Target.targetDestroyed") do |params|
@client.on("Target.targetDestroyed") do |params|
context = find_by(target_id: params["targetId"])
context&.delete_target(params["targetId"])
end

@browser.client.on("Target.targetCrashed") do |params|
@client.on("Target.targetCrashed") do |params|
context = find_by(target_id: params["targetId"])
context&.delete_target(params["targetId"])
end
end

def discover
@browser.command("Target.setDiscoverTargets", discover: true)
@client.command("Target.setDiscoverTargets", discover: true)
end
end
end
2 changes: 1 addition & 1 deletion lib/ferrum/cookies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def clear
private

def default_domain
URI.parse(@page.browser.base_url).host if @page.browser.base_url
URI.parse(@page.base_url).host if @page.base_url
end
end
end
2 changes: 1 addition & 1 deletion lib/ferrum/downloads.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def set_behavior(save_path:, behavior: :allow)
raise Error, "supply absolute path for `:save_path` option" unless Pathname.new(save_path.to_s).absolute?

@page.command("Browser.setDownloadBehavior",
browserContextId: @page.context.id,
browserContextId: @page.context_id,
downloadPath: save_path,
behavior: behavior,
eventsEnabled: true)
Expand Down
2 changes: 1 addition & 1 deletion lib/ferrum/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def add(headers, permanent: true)

def set_overrides(user_agent: nil, accept_language: nil, platform: nil)
options = {}
options[:userAgent] = user_agent || @page.browser.default_user_agent
options[:userAgent] = user_agent || @page.default_user_agent
options[:acceptLanguage] = accept_language if accept_language
options[:platform] if platform

Expand Down
2 changes: 1 addition & 1 deletion lib/ferrum/network.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def initialize(page)
# browser.at_xpath("//a[text() = 'No UI changes button']").click
# browser.network.wait_for_idle
#
def wait_for_idle(connections: 0, duration: 0.05, timeout: @page.browser.timeout)
def wait_for_idle(connections: 0, duration: 0.05, timeout: @page.timeout)
start = Utils::ElapsedTime.monotonic_time

until idle?(connections)
Expand Down
Loading

0 comments on commit 352e142

Please sign in to comment.