Module: Capybara::Lightpanda::Browser::Runtime
- Included in:
- Capybara::Lightpanda::Browser
- Defined in:
- lib/capybara/lightpanda/browser/runtime.rb
Overview
JS evaluation and RemoteObject plumbing: Runtime.evaluate / callFunctionOn dispatch, result serialization (Ferrum’s Frame::Runtime is the peer-gem equivalent).
Instance Method Summary collapse
-
#call_function_on(remote_object_id, function_declaration, *args, return_by_value: true) ⇒ Object
Call a function on a remote object via Runtime.callFunctionOn.
-
#evaluate(expression, *args) ⇒ Object
Evaluate JS and return a serialized value.
-
#evaluate_async(expression, *args, wait: @options.timeout) ⇒ Object
Evaluate async JS with a callback.
-
#evaluate_with_ref(expression) ⇒ Object
Evaluate JS and return a RemoteObject reference (for DOM nodes, arrays).
-
#execute(expression, *args) ⇒ Object
Execute JS without returning a value.
-
#release_object(remote_object_id) ⇒ Object
Release a remote object reference to free V8 memory.
Instance Method Details
#call_function_on(remote_object_id, function_declaration, *args, return_by_value: true) ⇒ Object
Call a function on a remote object via Runtime.callFunctionOn. Binds ‘this` to the DOM element referenced by remote_object_id.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/capybara/lightpanda/browser/runtime.rb', line 111 def call_function_on(remote_object_id, function_declaration, *args, return_by_value: true) params = { objectId: remote_object_id, functionDeclaration: function_declaration, returnByValue: return_by_value, awaitPromise: true, } params[:arguments] = args.map { |a| serialize_argument(a) } unless args.empty? response = page_command("Runtime.callFunctionOn", **params) raise_on_js_error!("call_function_on", function_declaration, response) result = response["result"] return nil if result["type"] == "undefined" return_by_value ? result["value"] : result end |
#evaluate(expression, *args) ⇒ Object
Evaluate JS and return a serialized value. No-args fast path uses Runtime.evaluate; with args we wrap as a function and dispatch via Runtime.callFunctionOn so ‘arguments` is bound. Both paths use `returnByValue: false` and unwrap so DOM-node returns come back as `{ “lightpanda_node” => … }` for the Driver to wrap.
The no-args path sends the user’s text verbatim with ‘replMode: true` (V8’s DevTools-console REPL mode — Lightpanda forwards Runtime.evaluate to the V8 inspector, which handles the flag natively). Without it, top-level ‘const`/`let` persist in the global lexical environment across classic scripts — per spec, and Chrome behaves identically —so a second `const sel = …` raises `SyntaxError: Identifier ’sel’ has already been declared`. REPL mode keeps the bindings (visible to later calls, like the DevTools console) but allows redeclaration. Completion-value semantics cover a bare expression (‘’foo’‘), a `throw` statement, and multi-statement scripts alike.
26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/capybara/lightpanda/browser/runtime.rb', line 26 def evaluate(expression, *args) if args.empty? response = page_command("Runtime.evaluate", expression: expression, returnByValue: false, awaitPromise: true, replMode: true) raise_on_js_error!("evaluate", expression, response) return unwrap_call_result(response["result"]) end wrapped = "function() { return #{expression} }" call_with_args(wrapped, args) end |
#evaluate_async(expression, *args, wait: @options.timeout) ⇒ Object
Evaluate async JS with a callback. The user’s script receives the callback as its last argument (‘arguments[arguments.length - 1]`), matching Capybara’s evaluate_async_script contract.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/capybara/lightpanda/browser/runtime.rb', line 80 def evaluate_async(expression, *args, wait: @options.timeout) timeout_ms = (wait * 1000).to_i wrapped = <<~JS function() { var __args = Array.prototype.slice.call(arguments); return new Promise(function(__resolve, __reject) { var __timer = setTimeout(function() { __reject(new Error('Async script timeout after #{timeout_ms}ms')); }, #{timeout_ms}); var __done = function(val) { clearTimeout(__timer); __resolve(val); }; __args.push(__done); (function() { #{expression} }).apply(null, __args); }); } JS call_with_args(wrapped, args) end |
#evaluate_with_ref(expression) ⇒ Object
Evaluate JS and return a RemoteObject reference (for DOM nodes, arrays).
99 100 101 102 103 104 105 106 107 |
# File 'lib/capybara/lightpanda/browser/runtime.rb', line 99 def evaluate_with_ref(expression) response = page_command("Runtime.evaluate", expression: expression, returnByValue: false, awaitPromise: true) raise_on_js_error!("evaluate_with_ref", expression, response) result = response["result"] return nil if result["type"] == "undefined" result end |
#execute(expression, *args) ⇒ Object
Execute JS without returning a value.
Like ‘evaluate`, the no-args path uses `replMode: true` so top-level `const`/`let` redeclarations across calls don’t raise. Also raises on JS exceptions so silent failures don’t mask test bugs (the previous fast path swallowed them because ‘awaitPromise: false` was checked but `exceptionDetails` was not).
46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/capybara/lightpanda/browser/runtime.rb', line 46 def execute(expression, *args) if args.empty? response = page_command("Runtime.evaluate", expression: expression, returnByValue: false, awaitPromise: false, replMode: true) raise_on_js_error!("execute", expression, response) return nil end wrapped = "function() { #{expression} }" call_with_args(wrapped, args, return_by_value: false) nil end |
#release_object(remote_object_id) ⇒ Object
Release a remote object reference to free V8 memory. Cleanup is best-effort: callers wrap their work in ‘ensure release_object(…)`, so a TimeoutError or transport hiccup here must not propagate out of the ensure block and bury the original failure.
138 139 140 141 142 143 |
# File 'lib/capybara/lightpanda/browser/runtime.rb', line 138 def release_object(remote_object_id) page_command("Runtime.releaseObject", objectId: remote_object_id) rescue Error # Object may already be released, context destroyed, or the CDP call # itself timed out / failed in transport. end |