mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
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
This commit is contained in:
parent
e70f98ddb9
commit
9f071bf411
@ -173,10 +173,10 @@ class FrameManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {boolean}
|
* @return {?string}
|
||||||
*/
|
*/
|
||||||
isMainFrameLoadingFailed() {
|
unreachableMainFrameURL() {
|
||||||
return !!this._mainFrame._loadingFailed;
|
return this._mainFrame._unreachableURL || null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,7 +477,7 @@ class Frame {
|
|||||||
_navigated(framePayload) {
|
_navigated(framePayload) {
|
||||||
this._name = framePayload.name;
|
this._name = framePayload.name;
|
||||||
this._url = framePayload.url;
|
this._url = framePayload.url;
|
||||||
this._loadingFailed = !!framePayload.unreachableUrl;
|
this._unreachableURL = framePayload.unreachableUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
_detach() {
|
_detach() {
|
||||||
|
@ -20,16 +20,14 @@ class NavigatorWatcher {
|
|||||||
/**
|
/**
|
||||||
* @param {!Puppeteer.Session} client
|
* @param {!Puppeteer.Session} client
|
||||||
* @param {string} frameId
|
* @param {string} frameId
|
||||||
* @param {boolean} ignoreHTTPSErrors
|
|
||||||
* @param {!Object=} options
|
* @param {!Object=} options
|
||||||
*/
|
*/
|
||||||
constructor(client, frameId, ignoreHTTPSErrors, options = {}) {
|
constructor(client, frameId, options = {}) {
|
||||||
console.assert(options.networkIdleTimeout === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
|
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.networkIdleInflight === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
|
||||||
console.assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
|
console.assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this._frameId = frameId;
|
this._frameId = frameId;
|
||||||
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
|
|
||||||
this._timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
|
this._timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
|
||||||
let waitUntil = ['load'];
|
let waitUntil = ['load'];
|
||||||
if (Array.isArray(options.waitUntil))
|
if (Array.isArray(options.waitUntil))
|
||||||
@ -56,13 +54,6 @@ class NavigatorWatcher {
|
|||||||
navigationPromises.push(watchdog);
|
navigationPromises.push(watchdog);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._ignoreHTTPSErrors) {
|
|
||||||
const certificateError = new Promise(fulfill => {
|
|
||||||
this._eventListeners.push(helper.addEventListener(this._client, 'Security.certificateError', fulfill));
|
|
||||||
}).then(error => 'SSL Certificate error: ' + error.errorType);
|
|
||||||
navigationPromises.push(certificateError);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._eventListeners.push(helper.addEventListener(this._client, 'Page.lifecycleEvent', this._onLifecycleEvent.bind(this)));
|
this._eventListeners.push(helper.addEventListener(this._client, 'Page.lifecycleEvent', this._onLifecycleEvent.bind(this)));
|
||||||
const pendingEventsFired = new Promise(fulfill => this._pendingEventsCallback = fulfill);
|
const pendingEventsFired = new Promise(fulfill => this._pendingEventsCallback = fulfill);
|
||||||
navigationPromises.push(pendingEventsFired);
|
navigationPromises.push(pendingEventsFired);
|
||||||
|
19
lib/Page.js
19
lib/Page.js
@ -457,9 +457,9 @@ class Page extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
async goto(url, options) {
|
async goto(url, options) {
|
||||||
const mainFrame = this._frameManager.mainFrame();
|
const mainFrame = this._frameManager.mainFrame();
|
||||||
const watcher = new NavigatorWatcher(this._client, mainFrame._id, this._ignoreHTTPSErrors, options);
|
const watcher = new NavigatorWatcher(this._client, mainFrame._id, options);
|
||||||
const responses = new Map();
|
const requests = new Map();
|
||||||
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url, response));
|
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Request, request => requests.set(request.url, request));
|
||||||
const navigationPromise = watcher.waitForNavigation();
|
const navigationPromise = watcher.waitForNavigation();
|
||||||
|
|
||||||
const referrer = this._networkManager.extraHTTPHeaders()['referer'];
|
const referrer = this._networkManager.extraHTTPHeaders()['referer'];
|
||||||
@ -475,9 +475,14 @@ class Page extends EventEmitter {
|
|||||||
helper.removeEventListeners([listener]);
|
helper.removeEventListeners([listener]);
|
||||||
if (error)
|
if (error)
|
||||||
throw error;
|
throw error;
|
||||||
if (this._frameManager.isMainFrameLoadingFailed())
|
const unreachableURL = this._frameManager.unreachableMainFrameURL();
|
||||||
throw new Error('Failed to navigate: ' + url);
|
if (unreachableURL) {
|
||||||
return responses.get(this.mainFrame().url()) || null;
|
const request = requests.get(unreachableURL) || null;
|
||||||
|
const failure = request ? request.failure() : null;
|
||||||
|
throw new Error('Failed to navigate: ' + url + (failure ? ' ' + failure.errorText : ''));
|
||||||
|
}
|
||||||
|
const request = requests.get(this.mainFrame().url());
|
||||||
|
return request ? request.response() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -498,7 +503,7 @@ class Page extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
async waitForNavigation(options) {
|
async waitForNavigation(options) {
|
||||||
const mainFrame = this._frameManager.mainFrame();
|
const mainFrame = this._frameManager.mainFrame();
|
||||||
const watcher = new NavigatorWatcher(this._client, mainFrame._id, this._ignoreHTTPSErrors, options);
|
const watcher = new NavigatorWatcher(this._client, mainFrame._id, options);
|
||||||
|
|
||||||
const responses = new Map();
|
const responses = new Map();
|
||||||
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url, response));
|
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url, response));
|
||||||
|
11
test/test.js
11
test/test.js
@ -89,7 +89,7 @@ afterAll(SX(async function() {
|
|||||||
|
|
||||||
describe('Puppeteer', function() {
|
describe('Puppeteer', function() {
|
||||||
describe('Puppeteer.launch', function() {
|
describe('Puppeteer.launch', function() {
|
||||||
xit('should support ignoreHTTPSErrors option', SX(async function() {
|
it('should support ignoreHTTPSErrors option', SX(async function() {
|
||||||
const options = Object.assign({ignoreHTTPSErrors: true}, defaultBrowserOptions);
|
const options = Object.assign({ignoreHTTPSErrors: true}, defaultBrowserOptions);
|
||||||
const browser = await puppeteer.launch(options);
|
const browser = await puppeteer.launch(options);
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
@ -909,7 +909,14 @@ describe('Page', function() {
|
|||||||
page.on('requestfailed', request => expect(request).toBeTruthy());
|
page.on('requestfailed', request => expect(request).toBeTruthy());
|
||||||
let error = null;
|
let error = null;
|
||||||
await page.goto(HTTPS_PREFIX + '/empty.html').catch(e => error = e);
|
await page.goto(HTTPS_PREFIX + '/empty.html').catch(e => error = e);
|
||||||
expect(error.message).toContain('SSL Certificate error');
|
expect(error.message).toContain('net::ERR_INSECURE_RESPONSE');
|
||||||
|
}));
|
||||||
|
it('should fail when navigating to bad SSL after redirects', SX(async function() {
|
||||||
|
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
||||||
|
server.setRedirect('/redirect/2.html', '/empty.html');
|
||||||
|
let error = null;
|
||||||
|
await page.goto(HTTPS_PREFIX + '/redirect/1.html').catch(e => error = e);
|
||||||
|
expect(error.message).toContain('net::ERR_INSECURE_RESPONSE');
|
||||||
}));
|
}));
|
||||||
it('should throw if networkidle is passed as an option', SX(async function() {
|
it('should throw if networkidle is passed as an option', SX(async function() {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
Loading…
Reference in New Issue
Block a user