Class: Puppeteer::Bidi::ExposedFunction
- Inherits:
-
Object
- Object
- Puppeteer::Bidi::ExposedFunction
- Defined in:
- lib/puppeteer/bidi/exposed_function.rb,
sig/puppeteer/bidi/exposed_function.rbs
Overview
ExposedFunction manages the lifecycle of a function exposed to the page. It uses script.message to receive calls from the page.
Defined Under Namespace
Classes: ThrownValue
Instance Attribute Summary collapse
-
#name ⇒ String
readonly
: String.
Class Method Summary collapse
-
.from(frame, name, apply = nil, isolate: false, &block) ⇒ void
Create and initialize an exposed function.
Instance Method Summary collapse
-
#build_function_declaration ⇒ String
Build the JavaScript function declaration for exposeFunction.
-
#channel_argument ⇒ Hash[Symbol, untyped]
Build the channel argument for script.addPreloadScript/callFunction.
- #debug_error(error) ⇒ Object
-
#dispose ⇒ void
Dispose this exposed function, removing it from the page.
-
#dispose_call_handles(args_handle, handles) ⇒ void
Dispose call handles after processing.
-
#disposed? ⇒ Boolean
Check if this exposed function is disposed.
-
#find_frame(context_id) ⇒ Frame?
Find frame by browsing context ID.
-
#find_realm(frame, realm_id) ⇒ FrameRealm?
Find frame realm by realm ID.
-
#frame_in_scope?(frame) ⇒ Boolean
Check if a frame belongs to the current frame subtree.
-
#handle_message(params) ⇒ void
Handle script.message events from the page.
-
#initialize(frame, name, apply, isolate: false) ⇒ ExposedFunction
constructor
A new instance of ExposedFunction.
-
#inject_into_frame(frame) ⇒ void
Inject the function into a single frame.
-
#inject_into_frames ⇒ void
Inject the function into existing frames.
-
#process_call(data_handle) ⇒ void
Process a function call from the page.
-
#remove_binding_from_frame(frame) ⇒ void
Remove the exposed binding from a frame subtree.
-
#send_error(data_handle, error) ⇒ void
Send an error response back to the page.
-
#send_result(data_handle, result) ⇒ void
Send a successful response back to the page.
-
#send_thrown_value(data_handle, value) ⇒ void
Send a non-Error rejection value back to the page.
-
#session ⇒ Core::Session
Get the BiDi session.
-
#setup ⇒ void
Set up the exposed function by injecting it into the page.
Constructor Details
#initialize(frame, name, apply, isolate: false) ⇒ ExposedFunction
Returns a new instance of ExposedFunction.
47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 47 def initialize(frame, name, apply, isolate: false) @frame = frame @name = name @apply = apply @isolate = isolate @channel = "__puppeteer__#{@frame._id}_page_exposeFunction_#{@name}" @scripts = [] @disposed = false @listener = nil @frame_listener = nil @injected_contexts = {} end |
Instance Attribute Details
#name ⇒ String (readonly)
: String
40 41 42 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 40 def name @name end |
Class Method Details
.from(frame, name, apply = nil, isolate: false, &block) ⇒ void
This method returns an undefined value.
Create and initialize an exposed function.
29 30 31 32 33 34 35 36 37 38 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 29 def self.from(frame, name, apply = nil, isolate: false, &block) handler = apply || block unless handler&.respond_to?(:call) raise ArgumentError, "expose_function requires a callable" end func = new(frame, name, handler, isolate: isolate) func.send(:setup) func end |
Instance Method Details
#build_function_declaration ⇒ String
Build the JavaScript function declaration for exposeFunction.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 111 def build_function_declaration name_literal = JSON.generate(@name) <<~JS (callback) => { Object.assign(globalThis, { [#{name_literal}]: function (...args) { return new Promise((resolve, reject) => { callback([resolve, reject, args]); }); }, }); } JS end |
#channel_argument ⇒ Hash[Symbol, untyped]
Build the channel argument for script.addPreloadScript/callFunction.
128 129 130 131 132 133 134 135 136 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 128 def channel_argument { type: "channel", value: { channel: @channel, ownership: "root" } } end |
#debug_error(error) ⇒ Object
383 384 385 386 387 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 383 def debug_error(error) return unless ENV["DEBUG_BIDI_COMMAND"] warn(error.) end |
#dispose ⇒ void
This method returns an undefined value.
Dispose this exposed function, removing it from the page.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 68 def dispose return if @disposed @disposed = true session.off("script.message", &@listener) if @listener @listener = nil if @frame_listener @frame.page.off(:frameattached, &@frame_listener) @frame_listener = nil end remove_binding_from_frame(@frame) @scripts.each do |frame, script_id| begin frame.browsing_context.remove_preload_script(script_id).wait rescue StandardError => e debug_error(e) end end end |
#dispose_call_handles(args_handle, handles) ⇒ void
This method returns an undefined value.
Dispose call handles after processing.
312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 312 def dispose_call_handles(args_handle, handles) begin args_handle.dispose rescue StandardError nil end handles.each do |handle| begin handle.dispose rescue StandardError nil end end end |
#disposed? ⇒ Boolean
Check if this exposed function is disposed.
62 63 64 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 62 def disposed? @disposed end |
#find_frame(context_id) ⇒ Frame?
Find frame by browsing context ID.
330 331 332 333 334 335 336 337 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 330 def find_frame(context_id) frames = [@frame] frames.each do |frame| return frame if frame.browsing_context.id == context_id frames.concat(frame.child_frames) end nil end |
#find_realm(frame, realm_id) ⇒ FrameRealm?
Find frame realm by realm ID.
343 344 345 346 347 348 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 343 def find_realm(frame, realm_id) return frame.main_realm if frame.main_realm.core_realm.id == realm_id return frame.isolated_realm if frame.isolated_realm.core_realm.id == realm_id nil end |
#frame_in_scope?(frame) ⇒ Boolean
Check if a frame belongs to the current frame subtree.
353 354 355 356 357 358 359 360 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 353 def frame_in_scope?(frame) current = frame while current return true if current == @frame current = current.parent_frame end false end |
#handle_message(params) ⇒ void
This method returns an undefined value.
Handle script.message events from the page.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 189 def (params) return if @disposed return unless params.is_a?(Hash) return unless params["channel"] == @channel source = params["source"] || {} frame = find_frame(source["context"]) return unless frame realm = find_realm(frame, source["realm"]) return unless realm data_handle = JSHandle.from(params["data"], realm.core_realm) begin process_call(data_handle) ensure begin data_handle.dispose rescue StandardError nil end end rescue StandardError => e debug_error(e) end |
#inject_into_frame(frame) ⇒ void
This method returns an undefined value.
Inject the function into a single frame.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 151 def inject_into_frame(frame) return if @injected_contexts[frame.browsing_context.id] function_declaration = build_function_declaration channel = channel_argument realm = @isolate ? frame.isolated_realm : frame.main_realm script_id = nil if frame.browsing_context.parent.nil? begin script_id = frame.browsing_context.add_preload_script( function_declaration, arguments: [channel], sandbox: realm.core_realm.sandbox ).wait rescue StandardError => e debug_error(e) end end begin realm.core_realm.call_function( function_declaration, false, arguments: [channel] ).wait rescue StandardError => e debug_error(e) return end @scripts << [frame, script_id] if script_id @injected_contexts[frame.browsing_context.id] = true end |
#inject_into_frames ⇒ void
This method returns an undefined value.
Inject the function into existing frames.
140 141 142 143 144 145 146 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 140 def inject_into_frames frames = [@frame] frames.each do |frame| frames.concat(frame.child_frames) inject_into_frame(frame) end end |
#process_call(data_handle) ⇒ void
This method returns an undefined value.
Process a function call from the page.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 218 def process_call(data_handle) args = [] handles = [] args_handle = data_handle.evaluate_handle("([, , args]) => args") begin args_handle.get_properties.each do |index, handle| index_int = begin Integer(index, 10) rescue ArgumentError, TypeError nil end next unless index_int handles << handle if handle.is_a?(ElementHandle) args[index_int] = handle else args[index_int] = handle.json_value end end result = @apply.call(*args) result = AsyncUtils.await(result) rescue StandardError => e if e.is_a?(ThrownValue) send_thrown_value(data_handle, e.value) elsif e.is_a?(TypeError) && e..include?("exception class/object expected") send_thrown_value(data_handle, nil) else send_error(data_handle, e) end dispose_call_handles(args_handle, handles) return end send_result(data_handle, result) dispose_call_handles(args_handle, handles) end |
#remove_binding_from_frame(frame) ⇒ void
This method returns an undefined value.
Remove the exposed binding from a frame subtree.
365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 365 def remove_binding_from_frame(frame) begin frame.evaluate("(name) => { delete globalThis[name]; }", @name) rescue StandardError => e debug_error(e) end frame.child_frames.each do |child| remove_binding_from_frame(child) end end |
#send_error(data_handle, error) ⇒ void
This method returns an undefined value.
Send an error response back to the page.
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 276 def send_error(data_handle, error) name = error.class.name = error. stack = error.backtrace&.join("\n") data_handle.evaluate(<<~JS, name, , stack) ([, reject], name, message, stack) => { const error = new Error(message); error.name = name; if (stack) { error.stack = stack; } reject(error); } JS rescue StandardError => e debug_error(e) end |
#send_result(data_handle, result) ⇒ void
This method returns an undefined value.
Send a successful response back to the page.
262 263 264 265 266 267 268 269 270 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 262 def send_result(data_handle, result) data_handle.evaluate(<<~JS, result) ([resolve], result) => { resolve(result); } JS rescue StandardError => e debug_error(e) end |
#send_thrown_value(data_handle, value) ⇒ void
This method returns an undefined value.
Send a non-Error rejection value back to the page.
298 299 300 301 302 303 304 305 306 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 298 def send_thrown_value(data_handle, value) data_handle.evaluate(<<~JS, value) ([, reject], value) => { reject(value); } JS rescue StandardError => e debug_error(e) end |
#session ⇒ Core::Session
Get the BiDi session.
379 380 381 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 379 def session @frame.browsing_context.user_context.browser.session end |
#setup ⇒ void
This method returns an undefined value.
Set up the exposed function by injecting it into the page.
95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/puppeteer/bidi/exposed_function.rb', line 95 def setup @listener = proc do |params| (params) end session.on("script.message", &@listener) @frame_listener = proc do |frame| inject_into_frame(frame) if frame_in_scope?(frame) end @frame.page.on(:frameattached, &@frame_listener) inject_into_frames end |