Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

executionContextId is 0 when using multiples connections #3865

Closed
kblok opened this issue Jan 29, 2019 · 6 comments
Closed

executionContextId is 0 when using multiples connections #3865

kblok opened this issue Jan 29, 2019 · 6 comments
Labels
bug chromium Issues with Puppeteer-Chromium

Comments

@kblok
Copy link
Contributor

kblok commented Jan 29, 2019

Steps to reproduce

Tell us about your environment:

  • Puppeteer version: Master branch
  • Platform / OS version: MacOS 10.14.2
  • Node.js version: v8.10

What steps will reproduce the problem?

(async() => {
    var options =  { headless : true, };
    var browser = await puppeteer.launch(options);
    var browserWSEndpoint = browser.wsEndpoint();

    var browser2 = await puppeteer.connect({ browserWSEndpoint });
    var page = (await browser2.pages())[0];
    await page.evaluateOnNewDocument(() => {console.log('Loading js helper1');});
    await page.goto("https://www.google.com");
    browser2.disconnect();

    var browser3 = await puppeteer.connect({ browserWSEndpoint });
    var page2 = (await browser3.pages())[0];
    await page2.goto("https://www.google.com");
    await page2.evaluateOnNewDocument(() => {console.log('Loading js helper1');});

    browser3.disconnect();
    browser.close();
})();

What is the expected result?
It shouldn't fail.

What happens instead?
I'm getting a

(node:28860) UnhandledPromiseRejectionWarning: Error: INTERNAL ERROR: missing context with id = 0
warning.js:18
    at assert (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/helper.js:278:11)
    at FrameManager.executionContextById (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/FrameManager.js:308:5)
    at Page._onConsoleAPI (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:504:40)
    at CDPSession.Page.client.on.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:133:57)
    at emitOne (events.js:116:13)
    at CDPSession.emit (events.js:211:7)
    at CDPSession._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:216:12)
    at Connection._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:99:19)
    at WebSocketTransport._ws.addEventListener.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/WebSocketTransport.js:41:24)
    at WebSocket.onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/node_modules/ws/lib/event-target.js:120:16)
(node:28860) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
warning.js:18
```

This was reported on Puppeteer-Sharp https://github.com/kblok/puppeteer-sharp/issues/856
@aslushnikov
Copy link
Contributor

@kblok Weird, I can't reproduce this on OSX or Linux. Maybe there's something else special with your setup? Could it be you have PUPPETEER_EXECUTABLE_PATH defined that points to a custom chromium?

@kblok
Copy link
Contributor Author

kblok commented Jan 29, 2019

I'm using the one in `.local-chromium. But it's true, it doesn't happen every time. Let me try to get a more solid test.

@kblok
Copy link
Contributor Author

kblok commented Jan 29, 2019

@aslushnikov I know it sounds stupid. But, could you try running it a few times?

@aslushnikov
Copy link
Contributor

@kblok I have the following:

const puppeteer = require('.');

(async() => {
    const theBrowser = await puppeteer.launch({headless: true});
    const browserWSEndpoint = theBrowser.wsEndpoint();

    for (let i = 0; i < 100; ++i) {
      console.log('Iteration #' + i);
      const  browser = await puppeteer.connect({ browserWSEndpoint });
      const page = (await browser.pages())[0];
      await page.evaluateOnNewDocument(() => {console.log('Loading js helper1');});
      await page.goto("https://www.google.com");
      browser.disconnect();
    }
    theBrowser.close();
})();

works flawlessly for me. Can you please confirm this script fails for you?

@kblok
Copy link
Contributor Author

kblok commented Jan 29, 2019

@aslushnikov could you try that script with https://www.facebook.com?

Darios-MBP:puppeteer neo$ node test/playground.js
Iteration #0
Iteration #1
(node:34022) UnhandledPromiseRejectionWarning: Error: INTERNAL ERROR: missing context with id = 0
    at assert (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/helper.js:229:11)
    at FrameManager.executionContextById (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/FrameManager.js:333:5)
    at Page._onConsoleAPI (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:520:40)
    at CDPSession.Page.client.on.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:135:57)
    at emitOne (events.js:116:13)
    at CDPSession.emit (events.js:211:7)
    at CDPSession._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:200:12)
    at Connection._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:112:17)
    at WebSocketTransport._ws.addEventListener.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/WebSocketTransport.js:41:24)
    at WebSocket.onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/node_modules/ws/lib/event-target.js:120:16)
(node:34022) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handledwith .catch(). (rejection id: 1)
(node:34022) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Iteration #2
(node:34022) UnhandledPromiseRejectionWarning: Error: INTERNAL ERROR: missing context with id = 0
    at assert (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/helper.js:229:11)
    at FrameManager.executionContextById (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/FrameManager.js:333:5)
    at Page._onConsoleAPI (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:520:40)
    at CDPSession.Page.client.on.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:135:57)
    at emitOne (events.js:116:13)
    at CDPSession.emit (events.js:211:7)
    at CDPSession._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:200:12)
    at Connection._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:112:17)
    at WebSocketTransport._ws.addEventListener.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/WebSocketTransport.js:41:24)
    at WebSocket.onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/node_modules/ws/lib/event-target.js:120:16)
(node:34022) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handledwith .catch(). (rejection id: 2)
Iteration #3
(node:34022) UnhandledPromiseRejectionWarning: Error: INTERNAL ERROR: missing context with id = 0
    at assert (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/helper.js:229:11)
    at FrameManager.executionContextById (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/FrameManager.js:333:5)
    at Page._onConsoleAPI (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:520:40)
    at CDPSession.Page.client.on.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Page.js:135:57)
    at emitOne (events.js:116:13)
    at CDPSession.emit (events.js:211:7)
    at CDPSession._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:200:12)
    at Connection._onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/Connection.js:112:17)
    at WebSocketTransport._ws.addEventListener.event (/Users/neo/Documents/Coding/Open Source/puppeteer/lib/WebSocketTransport.js:41:24)
    at WebSocket.onMessage (/Users/neo/Documents/Coding/Open Source/puppeteer/node_modules/ws/lib/event-target.js:120:16)
(node:34022) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handledwith .catch(). (rejection id: 3)
Iteration #4

@aslushnikov
Copy link
Contributor

@kblok yep manager to reproduce it!

@aslushnikov aslushnikov added bug chromium Issues with Puppeteer-Chromium labels Jan 29, 2019
aslushnikov added a commit to aslushnikov/puppeteer that referenced this issue Jan 30, 2019
YusukeIwaki pushed a commit to YusukeIwaki/puppeteer-ruby that referenced this issue Jan 3, 2020
diff --git a/lib/puppeteer/page.rb b/lib/puppeteer/page.rb
index 1ebea72..7c37959 100644
--- a/lib/puppeteer/page.rb
+++ b/lib/puppeteer/page.rb
@@ -35,10 +35,56 @@ class Puppeteer::Page
     @page_bindings = {}
     #@coverage = Coverage.new(client)
     @javascript_enabled = true
-    @Viewport = nil
     @screenshot_task_queue = screenshot_task_queue
-    @Workers = {}

+    @Workers = {}
+    # client.on('Target.attachedToTarget', event => {
+    #   if (event.targetInfo.type !== 'worker') {
+    #     // If we don't detach from service workers, they will never die.
+    #     client.send('Target.detachFromTarget', {
+    #       sessionId: event.sessionId
+    #     }).catch(debugError);
+    #     return;
+    #   }
+    #   const session = Connection.fromSession(client).session(event.sessionId);
+    #   const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
+    #   this._workers.set(event.sessionId, worker);
+    #   this.emit(Events.Page.WorkerCreated, worker);
+    # });
+    # client.on('Target.detachedFromTarget', event => {
+    #   const worker = this._workers.get(event.sessionId);
+    #   if (!worker)
+    #     return;
+    #   this.emit(Events.Page.WorkerDestroyed, worker);
+    #   this._workers.delete(event.sessionId);
+    # });
+
+    # this._frameManager.on(Events.FrameManager.FrameAttached, event => this.emit(Events.Page.FrameAttached, event));
+    # this._frameManager.on(Events.FrameManager.FrameDetached, event => this.emit(Events.Page.FrameDetached, event));
+    # this._frameManager.on(Events.FrameManager.FrameNavigated, event => this.emit(Events.Page.FrameNavigated, event));
+
+    # const networkManager = this._frameManager.networkManager();
+    # networkManager.on(Events.NetworkManager.Request, event => this.emit(Events.Page.Request, event));
+    # networkManager.on(Events.NetworkManager.Response, event => this.emit(Events.Page.Response, event));
+    # networkManager.on(Events.NetworkManager.RequestFailed, event => this.emit(Events.Page.RequestFailed, event));
+    # networkManager.on(Events.NetworkManager.RequestFinished, event => this.emit(Events.Page.RequestFinished, event));
+    # this._fileChooserInterceptionIsDisabled = false;
+    # this._fileChooserInterceptors = new Set();
+
+    # client.on('Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded));
+    # client.on('Page.loadEventFired', event => this.emit(Events.Page.Load));
+    # client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
+    # client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
+    # client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
+    # client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
+    # client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
+    # client.on('Performance.metrics', event => this._emitMetrics(event));
+    # client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
+    # client.on('Page.fileChooserOpened', event => this._onFileChooser(event));
+    # this._target._isClosedPromise.then(() => {
+    #   this.emit(Events.Page.Close);
+    #   this._closed = true;
+    # });
   end

   def init
@@ -53,9 +99,55 @@ class Puppeteer::Page
     end
   end

-  def target
-    @target
-  end
+  # /**
+  #  * @param {!Protocol.Page.fileChooserOpenedPayload} event
+  #  */
+  # _onFileChooser(event) {
+  #   if (!this._fileChooserInterceptors.size) {
+  #     this._client.send('Page.handleFileChooser', { action: 'fallback' }).catch(debugError);
+  #     return;
+  #   }
+  #   const interceptors = Array.from(this._fileChooserInterceptors);
+  #   this._fileChooserInterceptors.clear();
+  #   const fileChooser = new FileChooser(this._client, event);
+  #   for (const interceptor of interceptors)
+  #     interceptor.call(null, fileChooser);
+  # }
+
+  # /**
+  #  * @param {!{timeout?: number}=} options
+  #  * @return !Promise<!FileChooser>}
+  #  */
+  # async waitForFileChooser(options = {}) {
+  #   if (this._fileChooserInterceptionIsDisabled)
+  #     throw new Error('File chooser handling does not work with multiple connections to the same page');
+  #   const {
+  #     timeout = this._timeoutSettings.timeout(),
+  #   } = options;
+  #   let callback;
+  #   const promise = new Promise(x => callback = x);
+  #   this._fileChooserInterceptors.add(callback);
+  #   return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(e => {
+  #     this._fileChooserInterceptors.delete(callback);
+  #     throw e;
+  #   });
+  # }
+
+  # /**
+  #  * @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options
+  #  */
+  # async setGeolocation(options) {
+  #   const { longitude, latitude, accuracy = 0} = options;
+  #   if (longitude < -180 || longitude > 180)
+  #     throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
+  #   if (latitude < -90 || latitude > 90)
+  #     throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
+  #   if (accuracy < 0)
+  #     throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
+  #   await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy});
+  # }
+
+  attr_reader :target

   def browser
     @target.browser
@@ -65,25 +157,28 @@ class Puppeteer::Page
     @target.browser_context
   end

-  def main_frame
-    @frame_manager.main_frame
-  end
+  class TargetCrashedError < StandardError ; end

-  def keyboard
-    @keyboard
+  private def handle_target_crashed
+    emit_event 'error', TargetCrashedError.new('Page crashed!')
   end

-  def touchscreen
-    @touchscreen
-  end
+  # /**
+  #  * @param {!Protocol.Log.entryAddedPayload} event
+  #  */
+  # _onLogEntryAdded(event) {
+  #   const {level, text, args, source, url, lineNumber} = event.entry;
+  #   if (args)
+  #     args.map(arg => helper.releaseObject(this._client, arg));
+  #   if (source !== 'worker')
+  #     this.emit(Events.Page.Console, new ConsoleMessage(level, text, [], {url, lineNumber}));
+  # }

-  def coverage
-    @coverage
+  def main_frame
+    @frame_manager.main_frame
   end

-  def accessibility
-    @accessibility
-  end
+  attr_reader :keyboard, :touch_screen, :coverage, :accessibility

   def frames
     @frame_manager.frames
@@ -93,6 +188,15 @@ class Puppeteer::Page
     @workers.values
   end

+  # @param value [Bool]
+  def request_interception=(value)
+    @frame_manager.network_manager.request_interception = value
+  end
+
+  def offline_mode=(enabled)
+    @frame_manager.network_manager.offline_mode = enabled
+  end
+
   # @param {number} timeout
   def default_navigation_timeout=(timeout)
     @timeout_settings.default_navigation_timeout = timeout
@@ -157,9 +261,47 @@ class Puppeteer::Page
     main_frame.Sx(expression)
   end

-  def evaluate(page_function, *args)
-    @frame_manager.main_frame.evaluate(page_function, *args)
-  end
+  # /**
+  #  * @param {!Array<string>} urls
+  #  * @return {!Promise<!Array<Network.Cookie>>}
+  #  */
+  # async cookies(...urls) {
+  #   return (await this._client.send('Network.getCookies', {
+  #     urls: urls.length ? urls : [this.url()]
+  #   })).cookies;
+  # }
+
+  # /**
+  #  * @param {Array<Protocol.Network.deleteCookiesParameters>} cookies
+  #  */
+  # async deleteCookie(...cookies) {
+  #   const pageURL = this.url();
+  #   for (const cookie of cookies) {
+  #     const item = Object.assign({}, cookie);
+  #     if (!cookie.url && pageURL.startsWith('http'))
+  #       item.url = pageURL;
+  #     await this._client.send('Network.deleteCookies', item);
+  #   }
+  # }
+
+  # /**
+  #  * @param {Array<Network.CookieParam>} cookies
+  #  */
+  # async setCookie(...cookies) {
+  #   const pageURL = this.url();
+  #   const startsWithHTTP = pageURL.startsWith('http');
+  #   const items = cookies.map(cookie => {
+  #     const item = Object.assign({}, cookie);
+  #     if (!item.url && startsWithHTTP)
+  #       item.url = pageURL;
+  #     assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
+  #     assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
+  #     return item;
+  #   });
+  #   await this.deleteCookie(...items);
+  #   if (items.length)
+  #     await this._client.send('Network.setCookies', { cookies: items });
+  # }

   class ScriptTag
     # @param {!{content?: string, path?: string, type?: string, url?: string}} options
@@ -194,6 +336,38 @@ class Puppeteer::Page
     main_frame.add_style_tag(style_tag)
   end

+  # /**
+  #  * @param {string} name
+  #  * @param {Function} puppeteerFunction
+  #  */
+  # async exposeFunction(name, puppeteerFunction) {
+  #   if (this._pageBindings.has(name))
+  #     throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
+  #   this._pageBindings.set(name, puppeteerFunction);
+
+  #   const expression = helper.evaluationString(addPageBinding, name);
+  #   await this._client.send('Runtime.addBinding', {name: name});
+  #   await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: expression});
+  #   await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError)));
+
+  #   function addPageBinding(bindingName) {
+  #     const binding = window[bindingName];
+  #     window[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({name: bindingName, seq, args}));
+  #       return promise;
+  #     };
+  #   }
+  # }
+
   # @param username [String?]
   # @param password [String?]
   def authenticate(username: nil, password: nil)
@@ -207,9 +381,170 @@ class Puppeteer::Page

   # @param user_agent [String]
   def user_agent=(user_agent)
+    puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #{user_agent}"
     @frame_manager.network_manager.user_agent = user_agent
+    puts "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
   end

+  # /**
+  #  * @return {!Promise<!Metrics>}
+  #  */
+  # async metrics() {
+  #   const response = await this._client.send('Performance.getMetrics');
+  #   return this._buildMetricsObject(response.metrics);
+  # }
+
+  # /**
+  #  * @param {!Protocol.Performance.metricsPayload} event
+  #  */
+  # _emitMetrics(event) {
+  #   this.emit(Events.Page.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;
+  # }
+
+  # /**
+  #  * @param {!Protocol.Runtime.ExceptionDetails} exceptionDetails
+  #  */
+  # _handleException(exceptionDetails) {
+  #   const message = helper.getExceptionMessage(exceptionDetails);
+  #   const err = new Error(message);
+  #   err.stack = ''; // Don't report clientside error with a node stack attached
+  #   this.emit(Events.Page.PageError, err);
+  # }
+
+  # /**
+  #  * @param {!Protocol.Runtime.consoleAPICalledPayload} event
+  #  */
+  # async _onConsoleAPI(event) {
+  #   if (event.executionContextId === 0) {
+  #     // DevTools protocol stores the last 1000 console messages. These
+  #     // messages are always reported even for removed execution contexts. In
+  #     // this case, they are marked with executionContextId = 0 and are
+  #     // reported upon enabling Runtime agent.
+  #     //
+  #     // Ignore these messages since:
+  #     // - there's no execution context we can use to operate with message
+  #     //   arguments
+  #     // - these messages are reported before Puppeteer clients can subscribe
+  #     //   to the 'console'
+  #     //   page event.
+  #     //
+  #     // @see puppeteer/puppeteer#3865
+  #     return;
+  #   }
+  #   const context = this._frameManager.executionContextById(event.executionContextId);
+  #   const values = event.args.map(arg => createJSHandle(context, arg));
+  #   this._addConsoleMessage(event.type, values, event.stackTrace);
+  # }
+
+  # /**
+  #  * @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);
+
+  #   /**
+  #    * @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);
+  #   }
+
+  #   /**
+  #    * @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);
+  #   }
+
+  #   /**
+  #    * @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);
+  #   }
+  # }
+
+  # /**
+  #  * @param {string} type
+  #  * @param {!Array<!Puppeteer.JSHandle>} args
+  #  * @param {Protocol.Runtime.StackTrace=} stackTrace
+  #  */
+  # _addConsoleMessage(type, args, stackTrace) {
+  #   if (!this.listenerCount(Events.Page.Console)) {
+  #     args.forEach(arg => arg.dispose());
+  #     return;
+  #   }
+  #   const textTokens = [];
+  #   for (const arg of args) {
+  #     const remoteObject = arg._remoteObject;
+  #     if (remoteObject.objectId)
+  #       textTokens.push(arg.toString());
+  #     else
+  #       textTokens.push(helper.valueFromRemoteObject(remoteObject));
+  #   }
+  #   const location = stackTrace && stackTrace.callFrames.length ? {
+  #     url: stackTrace.callFrames[0].url,
+  #     lineNumber: stackTrace.callFrames[0].lineNumber,
+  #     columnNumber: stackTrace.callFrames[0].columnNumber,
+  #   } : {};
+  #   const message = new ConsoleMessage(type, textTokens.join(' '), args, location);
+  #   this.emit(Events.Page.Console, message);
+  # }
+
+  # _onDialog(event) {
+  #   let dialogType = null;
+  #   if (event.type === 'alert')
+  #     dialogType = Dialog.Type.Alert;
+  #   else if (event.type === 'confirm')
+  #     dialogType = Dialog.Type.Confirm;
+  #   else if (event.type === 'prompt')
+  #     dialogType = Dialog.Type.Prompt;
+  #   else if (event.type === 'beforeunload')
+  #     dialogType = Dialog.Type.BeforeUnload;
+  #   assert(dialogType, 'Unknown javascript dialog type: ' + event.type);
+  #   const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt);
+  #   this.emit(Events.Page.Dialog, dialog);
+  # }
+
   # @return [String]
   def url
     main_frame.url
@@ -228,7 +563,7 @@ class Puppeteer::Page

   # @param {string} html
   def content=(html)
-    set_content(html)
+    main_frame.set_content(html)
   end

   # @param {string} url
@@ -254,6 +589,42 @@ class Puppeteer::Page
     main_frame.wait_for_navigation(timeout: timeout, wait_until: wait_until)
   end

+  # /**
+  #  * @param {(string|Function)} urlOrPredicate
+  #  * @param {!{timeout?: number}=} options
+  #  * @return {!Promise<!Puppeteer.Request>}
+  #  */
+  # async waitForRequest(urlOrPredicate, options = {}) {
+  #   const {
+  #     timeout = this._timeoutSettings.timeout(),
+  #   } = options;
+  #   return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Request, request => {
+  #     if (helper.isString(urlOrPredicate))
+  #       return (urlOrPredicate === request.url());
+  #     if (typeof urlOrPredicate === 'function')
+  #       return !!(urlOrPredicate(request));
+  #     return false;
+  #   }, timeout, this._sessionClosePromise());
+  # }
+
+  # /**
+  #  * @param {(string|Function)} urlOrPredicate
+  #  * @param {!{timeout?: number}=} options
+  #  * @return {!Promise<!Puppeteer.Response>}
+  #  */
+  # async waitForResponse(urlOrPredicate, options = {}) {
+  #   const {
+  #     timeout = this._timeoutSettings.timeout(),
+  #   } = options;
+  #   return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Response, response => {
+  #     if (helper.isString(urlOrPredicate))
+  #       return (urlOrPredicate === response.url());
+  #     if (typeof urlOrPredicate === 'function')
+  #       return !!(urlOrPredicate(response));
+  #     return false;
+  #   }, timeout, this._sessionClosePromise());
+  # }
+
   # @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
   # @return {!Promise<?Puppeteer.Response>}
   def go_back(timeout: nil, wait_until: nil)
@@ -280,15 +651,57 @@ class Puppeteer::Page

   # @param device [Device]
   def emulate(device)
-    viewport = device.viewport
-    user_agent = device.user_agent
+    self.viewport = device.viewport
+    self.user_agent = device.user_agent
   end

   # @param {boolean} enabled
   def javascript_enabled=(enabled)
     return if (@javascript_enabled == enabled)
     @javascript_enabled = enabled
-    # await this._client.send('Emulation.setScriptExecutionDisabled', { value: !enabled });
+    @client.send_message('Emulation.setScriptExecutionDisabled', value: !enabled);
+  end
+
+  # /**
+  #  * @param {boolean} enabled
+  #  */
+  # async setBypassCSP(enabled) {
+  #   await this._client.send('Page.setBypassCSP', { enabled });
+  # }
+
+  # /**
+  #  * @param {?string} type
+  #  */
+  # async emulateMediaType(type) {
+  #   assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type);
+  #   await this._client.send('Emulation.setEmulatedMedia', {media: type || ''});
+  # }
+
+  # /**
+  #  * @param {?Array<MediaFeature>} features
+  #  */
+  # async emulateMediaFeatures(features) {
+  #   if (features === null)
+  #     await this._client.send('Emulation.setEmulatedMedia', {features: null});
+  #   if (Array.isArray(features)) {
+  #     features.every(mediaFeature => {
+  #       const name = mediaFeature.name;
+  #       assert(/^prefers-(?:color-scheme|reduced-motion)$/.test(name), 'Unsupported media feature: ' + name);
+  #       return true;
+  #     });
+  #     await this._client.send('Emulation.setEmulatedMedia', {features: features});
+  #   }
+  # }
+
+  # @param timezone_id [String?]
+  def emulate_timezone(timezone_id)
+    @client.send_message('Emulation.setTimezoneOverride', timezoneId: timezoneId || '')
+  rescue => err
+    if err.message.include?('Invalid timezone')
+      raise ArgumentError.new("Invalid timezone ID: #{timezone_id}")
+    else
+      raise err
+    end
   end

   # @param viewport [Viewport]
@@ -298,9 +711,7 @@ class Puppeteer::Page
     reload if needs_reload
   end

-  def viewport
-    @Viewport
-  end
+  attr_reader :viewport

   # @param {Function|string} pageFunction
   # @param {!Array<*>} args
@@ -309,6 +720,15 @@ class Puppeteer::Page
     main_frame.evaluate(page_function, *args)
   end

+  # /**
+  #  * @param {Function|string} pageFunction
+  #  * @param {!Array<*>} args
+  #  */
+  # async evaluateOnNewDocument(pageFunction, ...args) {
+  #   const source = helper.evaluationString(pageFunction, ...args);
+  #   await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
+  # }
+
   # @param {boolean} enabled
   def cache_enabled=(enabled)
     @frame_manager.network_manager.cache_enabled = enabled
@@ -319,6 +739,151 @@ class Puppeteer::Page
     @title
   end

+  # /**
+  #  * @param {!ScreenshotOptions=} options
+  #  * @return {!Promise<!Buffer|!String>}
+  #  */
+  # async screenshot(options = {}) {
+  #   let screenshotType = null;
+  #   // options.type takes precedence over inferring the type from options.path
+  #   // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
+  #   if (options.type) {
+  #     assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
+  #     screenshotType = options.type;
+  #   } else if (options.path) {
+  #     const mimeType = mime.getType(options.path);
+  #     if (mimeType === 'image/png')
+  #       screenshotType = 'png';
+  #     else if (mimeType === 'image/jpeg')
+  #       screenshotType = 'jpeg';
+  #     assert(screenshotType, 'Unsupported screenshot mime type: ' + mimeType);
+  #   }
+
+  #   if (!screenshotType)
+  #     screenshotType = 'png';
+
+  #   if (options.quality) {
+  #     assert(screenshotType === 'jpeg', 'options.quality is unsupported for the ' + screenshotType + ' screenshots');
+  #     assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality));
+  #     assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
+  #     assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality);
+  #   }
+  #   assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive');
+  #   if (options.clip) {
+  #     assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x));
+  #     assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y));
+  #     assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width));
+  #     assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height));
+  #     assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.');
+  #     assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.');
+  #   }
+  #   return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, screenshotType, options));
+  # }
+
+  # /**
+  #  * @param {"png"|"jpeg"} format
+  #  * @param {!ScreenshotOptions=} options
+  #  * @return {!Promise<!Buffer|!String>}
+  #  */
+  # async _screenshotTask(format, options) {
+  #   await this._client.send('Target.activateTarget', {targetId: this._target._targetId});
+  #   let clip = options.clip ? processClip(options.clip) : undefined;
+
+  #   if (options.fullPage) {
+  #     const metrics = await this._client.send('Page.getLayoutMetrics');
+  #     const width = Math.ceil(metrics.contentSize.width);
+  #     const height = Math.ceil(metrics.contentSize.height);
+
+  #     // Overwrite clip for full page at all times.
+  #     clip = { x: 0, y: 0, width, height, scale: 1 };
+  #     const {
+  #       isMobile = false,
+  #       deviceScaleFactor = 1,
+  #       isLandscape = false
+  #     } = this._viewport || {};
+  #     /** @type {!Protocol.Emulation.ScreenOrientation} */
+  #     const screenOrientation = isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
+  #     await this._client.send('Emulation.setDeviceMetricsOverride', { mobile: isMobile, width, height, deviceScaleFactor, screenOrientation });
+  #   }
+  #   const shouldSetDefaultBackground = options.omitBackground && format === 'png';
+  #   if (shouldSetDefaultBackground)
+  #     await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color: { r: 0, g: 0, b: 0, a: 0 } });
+  #   const result = await this._client.send('Page.captureScreenshot', { format, quality: options.quality, clip });
+  #   if (shouldSetDefaultBackground)
+  #     await this._client.send('Emulation.setDefaultBackgroundColorOverride');
+
+  #   if (options.fullPage && this._viewport)
+  #     await this.setViewport(this._viewport);
+
+  #   const buffer = options.encoding === 'base64' ? result.data : Buffer.from(result.data, 'base64');
+  #   if (options.path)
+  #     await writeFileAsync(options.path, buffer);
+  #   return buffer;
+
+  #   function processClip(clip) {
+  #     const x = Math.round(clip.x);
+  #     const y = Math.round(clip.y);
+  #     const width = Math.round(clip.width + clip.x - x);
+  #     const height = Math.round(clip.height + clip.y - y);
+  #     return {x, y, width, height, scale: 1};
+  #   }
+  # }
+
+  # /**
+  #  * @param {!PDFOptions=} options
+  #  * @return {!Promise<!Buffer>}
+  #  */
+  # async pdf(options = {}) {
+  #   const {
+  #     scale = 1,
+  #     displayHeaderFooter = false,
+  #     headerTemplate = '',
+  #     footerTemplate = '',
+  #     printBackground = false,
+  #     landscape = false,
+  #     pageRanges = '',
+  #     preferCSSPageSize = false,
+  #     margin = {},
+  #     path = null
+  #   } = options;
+
+  #   let paperWidth = 8.5;
+  #   let paperHeight = 11;
+  #   if (options.format) {
+  #     const format = Page.PaperFormats[options.format.toLowerCase()];
+  #     assert(format, 'Unknown paper format: ' + options.format);
+  #     paperWidth = format.width;
+  #     paperHeight = format.height;
+  #   } else {
+  #     paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
+  #     paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
+  #   }
+
+  #   const marginTop = convertPrintParameterToInches(margin.top) || 0;
+  #   const marginLeft = convertPrintParameterToInches(margin.left) || 0;
+  #   const marginBottom = convertPrintParameterToInches(margin.bottom) || 0;
+  #   const marginRight = convertPrintParameterToInches(margin.right) || 0;
+
+  #   const result = await this._client.send('Page.printToPDF', {
+  #     transferMode: 'ReturnAsStream',
+  #     landscape,
+  #     displayHeaderFooter,
+  #     headerTemplate,
+  #     footerTemplate,
+  #     printBackground,
+  #     scale,
+  #     paperWidth,
+  #     paperHeight,
+  #     marginTop,
+  #     marginBottom,
+  #     marginLeft,
+  #     marginRight,
+  #     pageRanges,
+  #     preferCSSPageSize
+  #   });
+  #   return await helper.readProtocolStream(this._client, result.stream, path);
+  # }
+
   # @param {!{runBeforeUnload: (boolean|undefined)}=} options
   def close
     # assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
@@ -336,10 +901,7 @@ class Puppeteer::Page
     @closed
   end

-  # @return [Mouse]
-  def mouse
-    @Mouse
-  end
+  attr_reader :mouse

   # @param {string} selector
   # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
@@ -376,17 +938,37 @@ class Puppeteer::Page
     main_frame.type(selector, text, delay: delay)
   end

+  # /**
+  #  * @param {(string|number|Function)} selectorOrFunctionOrTimeout
+  #  * @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options
+  #  * @param {!Array<*>} args
+  #  * @return {!Promise<!Puppeteer.JSHandle>}
+  #  */
+  # waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
+  #   return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
+  # }
+
   # @param {string} selector
   # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
   # @return {!Promise<?Puppeteer.ElementHandle>}
-  def wait_for_selector(selector, options = {}) # TODO: あとでキーワード引数にする
+  def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
+    options = {
+      visible: visible,
+      hidden: hidden,
+      timeout: timeout
+    }.compact
     main_frame.wait_for_selector(selector, options)
   end

   # @param {string} xpath
   # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
   # @return {!Promise<?Puppeteer.ElementHandle>}
-  def wait_for_xpath(xpath, options = {}) # TODO: あとでキーワード引数にする
+  def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil) # TODO: あとでキーワード引数にする
+    options = {
+      visible: visible,
+      hidden: hidden,
+      timeout: timeout
+    }.compact
     main_frame.wait_for_xpath(xpath, options)
   end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug chromium Issues with Puppeteer-Chromium
Projects
None yet
Development

No branches or pull requests

2 participants