puppeteer/lib/NavigatorWatcher.js
Andrey Lushnikov 9f071bf411
fix(Navigation): do not race with security error for navigation (#1237)
Currently, NavigationWatcher listens to lifecycle events from Page
domain and security events from Security domain.

However, the events are dispatched from different processes in browser:
- Page's lifecycle events are dispatched from renderer process
- Security events are dispatched from browser process

This makes for the undefined order between events and results in
NavigationWatcher reporting different failuer messages, based on
the event order.

This patch stops relying on security errors in navigation watcher and
instead switches to request failure codes for the main resource.

Fixes #1195
2017-11-01 13:28:00 -07:00

98 lines
3.3 KiB
JavaScript

/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const {helper} = require('./helper');
class NavigatorWatcher {
/**
* @param {!Puppeteer.Session} client
* @param {string} frameId
* @param {!Object=} options
*/
constructor(client, frameId, options = {}) {
console.assert(options.networkIdleTimeout === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
console.assert(options.networkIdleInflight === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
console.assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
this._client = client;
this._frameId = frameId;
this._timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
let waitUntil = ['load'];
if (Array.isArray(options.waitUntil))
waitUntil = options.waitUntil.slice();
else if (typeof options.waitUntil === 'string')
waitUntil = [options.waitUntil];
for (const value of waitUntil) {
const isAllowedValue = value === 'networkidle0' || value === 'networkidle2' || value === 'load' || value === 'domcontentloaded';
console.assert(isAllowedValue, 'Unknown value for options.waitUntil: ' + value);
}
this._pendingEvents = new Set(waitUntil);
}
/**
* @return {!Promise<?Error>}
*/
async waitForNavigation() {
this._eventListeners = [];
const navigationPromises = [];
if (this._timeout) {
const watchdog = new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
.then(() => 'Navigation Timeout Exceeded: ' + this._timeout + 'ms exceeded');
navigationPromises.push(watchdog);
}
this._eventListeners.push(helper.addEventListener(this._client, 'Page.lifecycleEvent', this._onLifecycleEvent.bind(this)));
const pendingEventsFired = new Promise(fulfill => this._pendingEventsCallback = fulfill);
navigationPromises.push(pendingEventsFired);
const error = await Promise.race(navigationPromises);
this._cleanup();
return error ? new Error(error) : null;
}
/**
* @param {!{frameId: string, name: string}} event
*/
_onLifecycleEvent(event) {
if (event.frameId !== this._frameId)
return;
const pptrName = protocolLifecycleToPuppeteer[event.name];
if (!pptrName)
return;
this._pendingEvents.delete(pptrName);
if (this._pendingEvents.size === 0)
this._pendingEventsCallback();
}
cancel() {
this._cleanup();
}
_cleanup() {
helper.removeEventListeners(this._eventListeners);
clearTimeout(this._maximumTimer);
}
}
const protocolLifecycleToPuppeteer = {
'load': 'load',
'DOMContentLoaded': 'domcontentloaded',
'networkIdle': 'networkidle0',
'networkAlmostIdle': 'networkidle2',
};
module.exports = NavigatorWatcher;