Skip to content
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
### Fixed
- Headers can be added on a remote browser whose `/json/version` has no `User-Agent`, such as browserless v2, instead of
failing with `Invalid parameters` [rubycdp/cuprite#300]
- A `Ferrum::Node` found before a navigation or reload kept its cached CDP node id, which Chrome reuses for the new
document, so `focus`, `click`, `scroll_into_view` and friends silently hit another element. Node ids are now dropped
on `DOM.documentUpdated` and such nodes raise `Ferrum::NodeNotFoundError` [#86]

### Removed
- `Browser::Process#default_user_agent`, `#browser_version`, `#protocol_version`, `#v8_version` and `#webkit_version`,
Expand Down
12 changes: 9 additions & 3 deletions lib/ferrum/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,26 @@ def initialize(frame, target_id, description, object_id: nil, node_id: nil)
@tag_name = description["nodeName"].downcase
@object_id = object_id
@node_id = node_id
@document_version = @page.document_version
end

# Frontend node id is resolved lazily, on first actual need (focus, click, scroll_into_view, etc.)
# We can try to subscribe to `DOM.childNodeRemoved` and `DOM.childNodeInserted` in the future
# to keep track of nodes.
def node_id
@node_id ||= begin
return @node_id if @node_id && @document_version == page.document_version

document_version = page.document_version
@node_id = begin
id = page.command("DOM.requestNode", objectId: @object_id)["nodeId"]
raise NodeNotFoundError, "node is not trackable" if id.zero?
raise NodeNotFoundError, { "message" => "node is not trackable" } if id.zero?

id
rescue NoExecutionContextError
raise NodeNotFoundError, "node is not trackable"
raise NodeNotFoundError, { "message" => "node is not trackable" }
end
@document_version = document_version
@node_id
end

# Whether this is an element node, as opposed to e.g. a text node.
Expand Down
9 changes: 9 additions & 0 deletions lib/ferrum/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ class Page
# @return [Downloads]
attr_reader :downloads

# Bumped every time Chrome reports `DOM.documentUpdated`, which
# invalidates every node id handed out before it.
#
# @return [Integer]
attr_reader :document_version

def initialize(client, context_id:, target_id:, proxy: nil)
@client = client
@context_id = context_id
Expand All @@ -96,6 +102,7 @@ def initialize(client, context_id:, target_id:, proxy: nil)
@frames = Concurrent::Map.new
@main_frame = Frame.new(nil, self)
@event = Utils::Event.new.tap(&:set)
@document_version = 0
self.proxy = proxy

@mouse = Mouse.new(self)
Expand Down Expand Up @@ -526,6 +533,8 @@ def subscribe
network.subscribe
downloads.subscribe

on("DOM.documentUpdated") { @document_version += 1 }

if @options.logger
on("Runtime.consoleAPICalled") do |params|
log_console_api(params)
Expand Down
1 change: 1 addition & 0 deletions sig/ferrum/node.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Ferrum

@node_id: untyped
@object_id: untyped
@document_version: ::Integer

attr_reader page: untyped

Expand Down
2 changes: 2 additions & 0 deletions sig/ferrum/page.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module Ferrum
attr_reader headers: Headers
attr_reader cookies: Cookies
attr_reader downloads: Downloads
attr_reader document_version: ::Integer
attr_reader client: (Client | SessionClient)

@client: (Client | SessionClient)
Expand All @@ -40,6 +41,7 @@ module Ferrum
@tracing: Page::Tracing
@downloads: Downloads
@referrer: String?
@document_version: ::Integer

def initialize: ((Client | SessionClient) client, context_id: String?, target_id: String, ?proxy: { host: String, port: ::Integer, user: String?, password: String? }?) -> void

Expand Down
14 changes: 14 additions & 0 deletions spec/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@
expect { node.text }.to raise_error(Ferrum::NodeNotFoundError)
end

it "raises an error instead of focusing a node from the new document after reload" do
browser.go_to("/form")
node = browser.at_css("#customer_email")
node.focus

browser.refresh
browser.at_css("#customer_email").focus
browser.at_css("#customer_email").blur

expect { node.focus }.to raise_error(Ferrum::NodeNotFoundError, "node is not trackable")
expect { node.focus }.to raise_error(Ferrum::NodeNotFoundError, "node is not trackable")
expect(browser.evaluate("document.activeElement.id")).not_to eq("customer_email")
end

it "raises an error if the element is not visible" do
browser.go_to("/index")

Expand Down
Loading