lib/puppeteer/page.rb in puppeteer-ruby-0.35.1 vs lib/puppeteer/page.rb in puppeteer-ruby-0.36.0
- old
+ new
@@ -1,14 +1,16 @@
require 'base64'
require 'json'
require "stringio"
+require_relative './page/metrics'
require_relative './page/pdf_options'
require_relative './page/screenshot_options'
require_relative './page/screenshot_task_queue'
class Puppeteer::Page
+ include Puppeteer::DebugPrint
include Puppeteer::EventCallbackable
include Puppeteer::IfPresent
using Puppeteer::DefineAsyncMethod
# @param {!Puppeteer.CDPSession} client
@@ -44,10 +46,12 @@
@coverage = Puppeteer::Coverage.new(client)
@javascript_enabled = true
@screenshot_task_queue = ScreenshotTaskQueue.new
@workers = {}
+ @user_drag_interception_enabled = false
+
@client.on_event('Target.attachedToTarget') do |event|
if event['targetInfo']['type'] != 'worker'
# If we don't detach from service workers, they will never die.
@client.async_send_message('Target.detachFromTarget', sessionId: event['sessionId'])
next
@@ -100,21 +104,25 @@
emit_event(PageEmittedEvents::Load)
end
@client.on('Runtime.consoleAPICalled') do |event|
handle_console_api(event)
end
- # client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
+ @client.on('Runtime.bindingCalled') do |event|
+ handle_binding_called(event)
+ end
@client.on_event('Page.javascriptDialogOpening') do |event|
handle_dialog_opening(event)
end
@client.on_event('Runtime.exceptionThrown') do |exception|
handle_exception(exception['exceptionDetails'])
end
@client.on_event('Inspector.targetCrashed') do |event|
handle_target_crashed
end
- # client.on('Performance.metrics', event => this._emitMetrics(event));
+ @client.on_event('Performance.metrics') do |event|
+ emit_event(PageEmittedEvents::Metrics, MetricsEvent.new(event))
+ end
@client.on_event('Log.entryAdded') do |event|
handle_log_entry_added(event)
end
@client.on_event('Page.fileChooserOpened') do |event|
handle_file_chooser(event)
@@ -132,10 +140,15 @@
@client.async_send_message('Performance.enable'),
@client.async_send_message('Log.enable'),
)
end
+ def drag_interception_enabled?
+ @user_drag_interception_enabled
+ end
+ alias_method :drag_interception_enabled, :drag_interception_enabled?
+
# @param event_name [Symbol]
def on(event_name, &block)
unless PageEmittedEvents.values.include?(event_name.to_s)
raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{PageEmittedEvents.values.to_a.join(", ")}")
end
@@ -264,14 +277,24 @@
# @param value [Bool]
def request_interception=(value)
@frame_manager.network_manager.request_interception = value
end
+ def drag_interception_enabled=(enabled)
+ @user_drag_interception_enabled = enabled
+ @client.send_message('Input.setInterceptDrags', enabled: enabled)
+ end
+
def offline_mode=(enabled)
@frame_manager.network_manager.offline_mode = enabled
end
+ # @param network_condition [Puppeteer::NetworkCondition|nil]
+ def emulate_network_conditions(network_condition)
+ @frame_manager.network_manager.emulate_network_conditions(network_condition)
+ end
+
# @param {number} timeout
def default_navigation_timeout=(timeout)
@timeout_settings.default_navigation_timeout = timeout
end
@@ -390,10 +413,55 @@
# @param content [String?]
def add_style_tag(url: nil, path: nil, content: nil)
main_frame.add_style_tag(url: url, path: path, content: content)
end
+ # @param name [String]
+ # @param puppeteer_function [Proc]
+ def expose_function(name, puppeteer_function)
+ if @page_bindings[name]
+ raise ArgumentError.new("Failed to add page binding with name `#{name}` already exists!")
+ end
+ @page_bindings[name] = puppeteer_function
+
+ add_page_binding = <<~JAVASCRIPT
+ function (type, bindingName) {
+ /* Cast window to any here as we're about to add properties to it
+ * via win[bindingName] which TypeScript doesn't like.
+ */
+ const win = window;
+ const binding = win[bindingName];
+
+ win[bindingName] = (...args) => {
+ const me = window[bindingName];
+ let callbacks = me.callbacks;
+ if (!callbacks) {
+ callbacks = new Map();
+ me.callbacks = callbacks;
+ }
+ const seq = (me.lastSeq || 0) + 1;
+ me.lastSeq = seq;
+ const promise = new Promise((resolve, reject) =>
+ callbacks.set(seq, { resolve, reject })
+ );
+ binding(JSON.stringify({ type, name: bindingName, seq, args }));
+ return promise;
+ };
+ }
+ JAVASCRIPT
+
+ source = JavaScriptFunction.new(add_page_binding, ['exposedFun', name]).source
+ @client.send_message('Runtime.addBinding', name: name)
+ @client.send_message('Page.addScriptToEvaluateOnNewDocument', source: source)
+
+ promises = @frame_manager.frames.map do |frame|
+ frame.async_evaluate("() => #{source}")
+ end
+ await_all(*promises)
+
+ nil
+ end
# /**
# * @param {string} name
# * @param {Function} puppeteerFunction
# */
# async exposeFunction(name, puppeteerFunction) {
@@ -438,41 +506,15 @@
# @param user_agent [String]
def user_agent=(user_agent)
@frame_manager.network_manager.user_agent = user_agent
end
- # /**
- # * @return {!Promise<!Metrics>}
- # */
- # async metrics() {
- # const response = await this._client.send('Performance.getMetrics');
- # return this._buildMetricsObject(response.metrics);
- # }
+ def metrics
+ response = @client.send_message('Performance.getMetrics')
+ Metrics.new(response['metrics'])
+ end
- # /**
- # * @param {!Protocol.Performance.metricsPayload} event
- # */
- # _emitMetrics(event) {
- # this.emit(PageEmittedEvents::Metrics, {
- # title: event.title,
- # metrics: this._buildMetricsObject(event.metrics)
- # });
- # }
-
- # /**
- # * @param {?Array<!Protocol.Performance.Metric>} metrics
- # * @return {!Metrics}
- # */
- # _buildMetricsObject(metrics) {
- # const result = {};
- # for (const metric of metrics || []) {
- # if (supportedMetrics.has(metric.name))
- # result[metric.name] = metric.value;
- # }
- # return result;
- # }
-
class PageError < StandardError ; end
private def handle_exception(exception_details)
message = Puppeteer::ExceptionDetails.new(exception_details).message
err = PageError.new(message)
@@ -504,61 +546,56 @@
Puppeteer::JSHandle.create(context: context, remote_object: remote_object)
end
add_console_message(event['type'], values, event['stackTrace'])
end
- # /**
- # * @param {!Protocol.Runtime.bindingCalledPayload} event
- # */
- # async _onBindingCalled(event) {
- # const {name, seq, args} = JSON.parse(event.payload);
- # let expression = null;
- # try {
- # const result = await this._pageBindings.get(name)(...args);
- # expression = helper.evaluationString(deliverResult, name, seq, result);
- # } catch (error) {
- # if (error instanceof Error)
- # expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack);
- # else
- # expression = helper.evaluationString(deliverErrorValue, name, seq, error);
- # }
- # this._client.send('Runtime.evaluate', { expression, contextId: event.executionContextId }).catch(debugError);
+ def handle_binding_called(event)
+ execution_context_id = event['executionContextId']
+ payload =
+ begin
+ JSON.parse(event['payload'])
+ rescue
+ # The binding was either called by something in the page or it was
+ # called before our wrapper was initialized.
+ return
+ end
+ name = payload['name']
+ seq = payload['seq']
+ args = payload['args']
- # /**
- # * @param {string} name
- # * @param {number} seq
- # * @param {*} result
- # */
- # function deliverResult(name, seq, result) {
- # window[name]['callbacks'].get(seq).resolve(result);
- # window[name]['callbacks'].delete(seq);
- # }
+ if payload['type'] != 'exposedFun' || !@page_bindings[name]
+ return
+ end
- # /**
- # * @param {string} name
- # * @param {number} seq
- # * @param {string} message
- # * @param {string} stack
- # */
- # function deliverError(name, seq, message, stack) {
- # const error = new Error(message);
- # error.stack = stack;
- # window[name]['callbacks'].get(seq).reject(error);
- # window[name]['callbacks'].delete(seq);
- # }
+ expression =
+ begin
+ result = @page_bindings[name].call(*args)
- # /**
- # * @param {string} name
- # * @param {number} seq
- # * @param {*} value
- # */
- # function deliverErrorValue(name, seq, value) {
- # window[name]['callbacks'].get(seq).reject(value);
- # window[name]['callbacks'].delete(seq);
- # }
- # }
+ deliver_result = <<~JAVASCRIPT
+ function (name, seq, result) {
+ window[name].callbacks.get(seq).resolve(result);
+ window[name].callbacks.delete(seq);
+ }
+ JAVASCRIPT
+ JavaScriptFunction.new(deliver_result, [name, seq, result]).source
+ rescue => err
+ deliver_error = <<~JAVASCRIPT
+ function (name, seq, message) {
+ const error = new Error(message);
+ window[name].callbacks.get(seq).reject(error);
+ window[name].callbacks.delete(seq);
+ }
+ JAVASCRIPT
+ JavaScriptFunction.new(deliver_error, [name, seq, err.message]).source
+ end
+
+ @client.async_send_message('Runtime.evaluate', expression: expression, contextId: execution_context_id).rescue do |error|
+ debug_puts(error)
+ end
+ end
+
private def add_console_message(type, args, stack_trace)
text_tokens = args.map { |arg| arg.remote_object.value }
call_frame = stack_trace['callFrames']&.first
location =
@@ -629,14 +666,13 @@
# @param timeout [number|nil]
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
# @return [Puppeteer::Response]
def reload(timeout: nil, wait_until: nil)
- await_all(
- async_wait_for_navigation(timeout: timeout, wait_until: wait_until),
- @client.async_send_message('Page.reload'),
- ).first
+ wait_for_navigation(timeout: timeout, wait_until: wait_until) do
+ @client.send_message('Page.reload')
+ end
end
def wait_for_navigation(timeout: nil, wait_until: nil)
main_frame.send(:wait_for_navigation, timeout: timeout, wait_until: wait_until)
end
@@ -758,14 +794,13 @@
private def go(delta, timeout: nil, wait_until: nil)
history = @client.send_message('Page.getNavigationHistory')
entries = history['entries']
index = history['currentIndex'] + delta
if_present(entries[index]) do |entry|
- await_all(
- async_wait_for_navigation(timeout: timeout, wait_until: wait_until),
- @client.async_send_message('Page.navigateToHistoryEntry', entryId: entry['id']),
- )
+ wait_for_navigation(timeout: timeout, wait_until: wait_until) do
+ @client.send_message('Page.navigateToHistoryEntry', entryId: entry['id'])
+ end
end
end
# Brings page to front (activates tab).
def bring_to_front
@@ -795,9 +830,18 @@
media_type_str = media_type.to_s
unless ['screen', 'print', ''].include?(media_type_str)
raise ArgumentError.new("Unsupported media type: #{media_type}")
end
@client.send_message('Emulation.setEmulatedMedia', media: media_type_str)
+ end
+
+ # @param factor [Number|nil] Factor at which the CPU will be throttled (2x, 2.5x. 3x, ...). Passing `nil` disables cpu throttling.
+ def emulate_cpu_throttling(factor)
+ if factor.nil? || factor >= 1
+ @client.send_message('Emulation.setCPUThrottlingRate', rate: factor || 1)
+ else
+ raise ArgumentError.new('Throttling rate should be greater or equal to 1')
+ end
end
# @param features [Array]
def emulate_media_features(features)
if features.nil?