module Ferrum::Frame::Runtime
Constants
- INTERMITTENT_ATTEMPTS
- INTERMITTENT_SLEEP
- LINK_TAG
- SCRIPT_SRC_TAG
- SCRIPT_TEXT_TAG
- STYLE_TAG
Public Instance Methods
add_script_tag(url: nil, path: nil, content: nil, type: "text/javascript")
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 90 def add_script_tag(url: nil, path: nil, content: nil, type: "text/javascript") expr, *args = if url [SCRIPT_SRC_TAG, url, type] elsif path || content if path content = File.read(path) content += "\n//# sourceURL=#{path}" end [SCRIPT_TEXT_TAG, content, type] end evaluate_async(expr, @page.timeout, *args) end
add_style_tag(url: nil, path: nil, content: nil)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 104 def add_style_tag(url: nil, path: nil, content: nil) expr, *args = if url [LINK_TAG, url] elsif path || content if path content = File.read(path) content += "\n//# sourceURL=#{path}" end [STYLE_TAG, content] end evaluate_async(expr, @page.timeout, *args) end
evaluate(expression, *args)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 48 def evaluate(expression, *args) expression = "function() { return %s }" % expression call(expression: expression, arguments: args) end
evaluate_async(expression, wait, *args)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 53 def evaluate_async(expression, wait, *args) template = <<~JS function() { return new Promise((__f, __r) => { try { arguments[arguments.length] = r => __f(r); arguments.length = arguments.length + 1; setTimeout(() => __r(new Error("timed out promise")), %s); %s } catch(error) { __r(error); } }); } JS expression = template % [wait * 1000, expression] call(expression: expression, arguments: args, awaitPromise: true) end
evaluate_func(expression, *args, on: nil)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 79 def evaluate_func(expression, *args, on: nil) call(expression: expression, arguments: args, on: on) end
evaluate_on(node:, expression:, by_value: true, wait: 0)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 83 def evaluate_on(node:, expression:, by_value: true, wait: 0) options = { handle: true } expression = "function() { return %s }" % expression options = { handle: false, returnByValue: true } if by_value call(expression: expression, on: node, wait: wait, **options) end
execute(expression, *args)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 73 def execute(expression, *args) expression = "function() { %s }" % expression call(expression: expression, arguments: args, handle: false, returnByValue: true) true end
Private Instance Methods
call(expression:, arguments: [], on: nil, wait: 0, handle: true, **options)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 120 def call(expression:, arguments: [], on: nil, wait: 0, handle: true, **options) errors = [NodeNotFoundError, NoExecutionContextError] attempts, sleep = INTERMITTENT_ATTEMPTS, INTERMITTENT_SLEEP Ferrum.with_attempts(errors: errors, max: attempts, wait: sleep) do params = options.dup if on response = @page.command("DOM.resolveNode", nodeId: on.node_id) object_id = response.dig("object", "objectId") params = params.merge(objectId: object_id) end if params[:executionContextId].nil? && params[:objectId].nil? params = params.merge(executionContextId: execution_id) end response = @page.command("Runtime.callFunctionOn", wait: wait, slowmoable: true, **params.merge(functionDeclaration: expression, arguments: prepare_args(arguments))) handle_error(response) response = response["result"] handle ? handle_response(response) : response.dig("value") end end
cyclic?(object_id)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 225 def cyclic?(object_id) @page.command( "Runtime.callFunctionOn", objectId: object_id, returnByValue: true, functionDeclaration: <<~JS function() { if (Array.isArray(this) && this.every(e => e instanceof Node)) { return false; } function detectCycle(obj, seen) { if (typeof obj === "object") { if (seen.indexOf(obj) !== -1) { return true; } for (let key in obj) { if (obj.hasOwnProperty(key) && detectCycle(obj[key], seen.concat([obj]))) { return true; } } } return false; } return detectCycle(this, []); } JS ) end
cyclic_object()
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 258 def cyclic_object CyclicObject.instance end
handle_error(response)
click to toggle source
FIXME: We should have a central place to handle all type of errors
# File lib/ferrum/frame/runtime.rb, line 149 def handle_error(response) result = response["result"] return if result["subtype"] != "error" case result["description"] when /\AError: timed out promise/ raise ScriptTimeoutError else raise JavaScriptError.new(result) end end
handle_response(response)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 161 def handle_response(response) case response["type"] when "boolean", "number", "string" response["value"] when "undefined" nil when "function" {} when "object" object_id = response["objectId"] case response["subtype"] when "node" # We cannot store object_id in the node because page can be reloaded # and node destroyed so we need to retrieve it each time for given id. # Though we can try to subscribe to `DOM.childNodeRemoved` and # `DOM.childNodeInserted` in the future. node_id = @page.command("DOM.requestNode", objectId: object_id)["nodeId"] description = @page.command("DOM.describeNode", nodeId: node_id)["node"] Node.new(self, @page.target_id, node_id, description) when "array" reduce_props(object_id, []) do |memo, key, value| next(memo) unless (Integer(key) rescue nil) value = value["objectId"] ? handle_response(value) : value["value"] memo.insert(key.to_i, value) end.compact when "date" response["description"] when "null" nil else reduce_props(object_id, {}) do |memo, key, value| value = value["objectId"] ? handle_response(value) : value["value"] memo.merge(key => value) end end end end
prepare_args(args)
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 200 def prepare_args(args) args.map do |arg| if arg.is_a?(Node) resolved = @page.command("DOM.resolveNode", nodeId: arg.node_id) { objectId: resolved["object"]["objectId"] } elsif arg.is_a?(Hash) && arg["objectId"] { objectId: arg["objectId"] } else { value: arg } end end end
reduce_props(object_id, to) { |memo, prop, prop| ... }
click to toggle source
# File lib/ferrum/frame/runtime.rb, line 213 def reduce_props(object_id, to) if cyclic?(object_id).dig("result", "value") return to.is_a?(Array) ? [cyclic_object] : cyclic_object else props = @page.command("Runtime.getProperties", ownProperties: true, objectId: object_id) props["result"].reduce(to) do |memo, prop| next(memo) unless prop["enumerable"] yield(memo, prop["name"], prop["value"]) end end end