Skip to content

Commit

Permalink
fix!: remove root from WaitForSelectorOptions
Browse files Browse the repository at this point in the history
chore: make execution context frame-independent
  • Loading branch information
YusukeIwaki committed Sep 20, 2022
1 parent 910e217 commit 3734cda
Show file tree
Hide file tree
Showing 17 changed files with 547 additions and 882 deletions.
2 changes: 1 addition & 1 deletion development/DOCS_VERSION
@@ -1 +1 @@
16.2.0
17.1.3
1,119 changes: 390 additions & 729 deletions development/puppeteer.api.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/puppeteer.rb
Expand Up @@ -32,7 +32,6 @@ module Puppeteer; end
require 'puppeteer/custom_query_handler'
require 'puppeteer/devices'
require 'puppeteer/dialog'
require 'puppeteer/dom_world'
require 'puppeteer/emulation_manager'
require 'puppeteer/exception_details'
require 'puppeteer/executable_path_finder'
Expand All @@ -43,6 +42,7 @@ module Puppeteer; end
require 'puppeteer/frame_manager'
require 'puppeteer/http_request'
require 'puppeteer/http_response'
require 'puppeteer/isolated_world'
require 'puppeteer/js_coverage'
require 'puppeteer/js_handle'
require 'puppeteer/keyboard'
Expand Down
62 changes: 51 additions & 11 deletions lib/puppeteer/aria_query_handler.rb
Expand Up @@ -25,43 +25,83 @@ class Puppeteer::AriaQueryHandler
query_options
end

def query_one(element, selector)
context = element.execution_context
# @param element [Puppeteer::ElementHandle]
# @param selector [String]
private def query_one_id(element, selector)
parse_result = parse_aria_selector(selector)
res = element.query_ax_tree(accessible_name: parse_result[:name], role: parse_result[:role])
if res.empty?

if res.first.is_a?(Hash)
res.first['backendDOMNodeId']
else
nil
end
end

def query_one(element, selector)
id = query_one_id(element, selector)

if id
element.frame.main_world.adopt_backend_node(id)
else
context.adopt_backend_node_id(res.first['backendDOMNodeId'])
nil
end
end

def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil, root: nil)
def wait_for(element_or_frame, selector, visible: nil, hidden: nil, timeout: nil)
case element_or_frame
when Puppeteer::Frame
frame = element_or_frame
element = nil
when Puppeteer::ElementHandle
frame = element_or_frame.frame
element = frame.puppeteer_world.adopt_handle(element_or_frame)
else
raise ArgumentError.new("element_or_frame must be a Frame or ElementHandle. #{element_or_frame.inspect}")
end

# addHandlerToWorld
binding_function = Puppeteer::DOMWorld::BindingFunction.new(
binding_function = Puppeteer::IsolaatedWorld::BindingFunction.new(
name: 'ariaQuerySelector',
proc: -> (sel) { query_one(root || dom_world.send(:document), sel) },
proc: -> (sel) {
id = query_one_id(element || frame.puppeteer_world.document, sel)

if id
frame.puppeteer_world.adopt_backend_node(id)
else
nil
end
},
)
dom_world.send(:wait_for_selector_in_page,
result = frame.puppeteer_world.send(:wait_for_selector_in_page,
'(_, selector) => globalThis.ariaQuerySelector(selector)',
element,
selector,
visible: visible,
hidden: hidden,
timeout: timeout,
binding_function: binding_function,
root: root,
)

element&.dispose

if result.is_a?(Puppeteer::ElementHandle)
result.frame.main_world.transfer_handle(result)
else
result&.dispose
nil
end
end

def query_all(element, selector)
context = element.execution_context
world = element.frame.main_world
parse_result = parse_aria_selector(selector)
res = element.query_ax_tree(accessible_name: parse_result[:name], role: parse_result[:role])
if res.empty?
nil
else
promises = res.map do |ax_node|
context.send(:async_adopt_backend_node_id, ax_node['backendDOMNodeId'])
world.send(:async_adopt_backend_node, ax_node['backendDOMNodeId'])
end
await_all(*promises)
end
Expand Down
31 changes: 29 additions & 2 deletions lib/puppeteer/custom_query_handler.rb
Expand Up @@ -21,12 +21,39 @@ def query_one(element, selector)
nil
end

def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil, root: nil)
def wait_for(element_or_frame, selector, visible: nil, hidden: nil, timeout: nil)
case element_or_frame
when Puppeteer::Frame
frame = element_or_frame
element = nil
when Puppeteer::ElementHandle
frame = element_or_frame.frame
element = frame.puppeteer_world.adopt_handle(element_or_frame)
else
raise ArgumentError.new("element_or_frame must be a Frame or ElementHandle. #{element_or_frame.inspect}")
end

unless @query_one
raise NotImplementedError.new("#{self.class}##{__method__} is not implemented.")
end

dom_world.send(:wait_for_selector_in_page, @query_one, selector, visible: visible, hidden: hidden, timeout: timeout, root: root)
result = frame.puppeteer_world.send(:wait_for_selector_in_page,
@query_one,
element,
selector,
visible: visible,
hidden: hidden,
timeout: timeout,
)

element&.dispose

if result.is_a?(Puppeteer::ElementHandle)
result.frame.main_world.transfer_handle(result)
else
result&.dispose
nil
end
end

def query_all(element, selector)
Expand Down
25 changes: 7 additions & 18 deletions lib/puppeteer/element_handle.rb
Expand Up @@ -12,16 +12,16 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
# @param client [Puppeteer::CDPSession]
# @param remote_object [Puppeteer::RemoteObject]
# @param frame [Puppeteer::Frame]
# @param page [Puppeteer::Page]
# @param frame_manager [Puppeteer::FrameManager]
def initialize(context:, client:, remote_object:, frame:, page:, frame_manager:)
def initialize(context:, client:, remote_object:, frame:)
super(context: context, client: client, remote_object: remote_object)
@frame = frame
@page = page
@frame_manager = frame_manager
@page = frame.page
@frame_manager = frame.frame_manager
@disposed = false
end

attr_reader :page, :frame, :frame_manager

def inspect
values = %i[context remote_object page disposed].map do |sym|
value = instance_variable_get(:"@#{sym}")
Expand Down Expand Up @@ -60,18 +60,7 @@ def inspect
# (30 seconds). Pass `0` to disable timeout. The default value can be changed
# by using the {@link Page.setDefaultTimeout} method.
def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
frame = @context.frame

secondary_world = frame.secondary_world
adopted_root = secondary_world.execution_context.adopt_element_handle(self)
handle = secondary_world.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout, root: adopted_root)
adopted_root.dispose
return nil unless handle

main_world = frame.main_world
result = main_world.execution_context.adopt_element_handle(handle)
handle.dispose
result
query_handler_manager.detect_query_handler(selector).wait_for(self, visible: visible, hidden: hidden, timeout: timeout)
end

define_async_method :async_wait_for_selector
Expand Down Expand Up @@ -653,6 +642,6 @@ def intersecting_viewport?(threshold: nil)
# used in AriaQueryHandler
def query_ax_tree(accessible_name: nil, role: nil)
@remote_object.query_ax_tree(@client,
accessible_name: accessible_name, role: role)
accessible_name: accessible_name, role: role)
end
end
63 changes: 3 additions & 60 deletions lib/puppeteer/execution_context.rb
Expand Up @@ -7,7 +7,7 @@ class Puppeteer::ExecutionContext

# @param client [Puppeteer::CDPSession]
# @param context_payload [Hash]
# @param world [Puppeteer::DOMWorld?]
# @param world [Puppeteer::IsolaatedWorld?]
def initialize(client, context_payload, world)
@client = client
@world = world
Expand All @@ -17,23 +17,16 @@ def initialize(client, context_payload, world)

attr_reader :client, :world

# only used in DOMWorld
# only used in IsolaatedWorld
private def _context_id
@context_id
end

# only used in DOMWorld::BindingFunction#add_binding_to_context
# only used in IsolaatedWorld::BindingFunction#add_binding_to_context
private def _context_name
@context_name
end

# @return [Puppeteer::Frame]
def frame
if_present(@world) do |world|
world.frame
end
end

# @param page_function [String]
# @return [Object]
def evaluate(page_function, *args)
Expand Down Expand Up @@ -208,54 +201,4 @@ class EvaluationError < StandardError; end
context_id: @context_id,
)
end

# /**
# * @param {!JSHandle} prototypeHandle
# * @return {!Promise<!JSHandle>}
# */
# async queryObjects(prototypeHandle) {
# assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!');
# assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value');
# const response = await this._client.send('Runtime.queryObjects', {
# prototypeObjectId: prototypeHandle._remoteObject.objectId
# });
# return createJSHandle(this, response.objects);
# }

# @param backend_node_id [Integer]
# @return [Puppeteer::ElementHandle]
def adopt_backend_node_id(backend_node_id)
response = @client.send_message('DOM.resolveNode',
backendNodeId: backend_node_id,
executionContextId: @context_id,
)
Puppeteer::JSHandle.create(
context: self,
remote_object: Puppeteer::RemoteObject.new(response["object"]),
)
end
private define_async_method :async_adopt_backend_node_id

# @param element_handle [Puppeteer::ElementHandle]
# @return [Puppeteer::ElementHandle]
def adopt_element_handle(element_handle)
if element_handle.execution_context == self
raise ArgumentError.new('Cannot adopt handle that already belongs to this execution context')
end

unless @world
raise 'Cannot adopt handle without DOMWorld'
end

node_info = element_handle.remote_object.node_info(@client)
response = @client.send_message('DOM.resolveNode',
backendNodeId: node_info["node"]["backendNodeId"],
executionContextId: @context_id,
)

Puppeteer::JSHandle.create(
context: self,
remote_object: Puppeteer::RemoteObject.new(response["object"]),
)
end
end

0 comments on commit 3734cda

Please sign in to comment.