fix(page): Page.goto should properly handle historyAPI in beforeunload (#3198)

Fixes #2764.
This commit is contained in:
Andrey Lushnikov 2018-09-05 22:59:29 +01:00 committed by GitHub
parent 28d92116b7
commit d54c7edeae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 29 deletions

View File

@ -51,16 +51,36 @@ class NavigatorWatcher {
helper.addEventListener(this._frameManager, FrameManager.Events.FrameDetached, this._checkLifecycleComplete.bind(this)) helper.addEventListener(this._frameManager, FrameManager.Events.FrameDetached, this._checkLifecycleComplete.bind(this))
]; ];
const lifecycleCompletePromise = new Promise(fulfill => { this._sameDocumentNavigationPromise = new Promise(fulfill => {
this._lifecycleCompleteCallback = fulfill; this._sameDocumentNavigationCompleteCallback = fulfill;
}); });
this._navigationPromise = Promise.race([
this._createTimeoutPromise(), this._newDocumentNavigationPromise = new Promise(fulfill => {
lifecycleCompletePromise this._newDocumentNavigationCompleteCallback = fulfill;
]).then(error => {
this._cleanup();
return error;
}); });
this._timeoutPromise = this._createTimeoutPromise();
}
/**
* @return {!Promise<?Error>}
*/
sameDocumentNavigationPromise() {
return this._sameDocumentNavigationPromise;
}
/**
* @return {!Promise<?Error>}
*/
newDocumentNavigationPromise() {
return this._newDocumentNavigationPromise;
}
/**
* @return {!Promise<?Error>}
*/
timeoutPromise() {
return this._timeoutPromise;
} }
/** /**
@ -74,13 +94,6 @@ class NavigatorWatcher {
.then(() => new TimeoutError(errorMessage)); .then(() => new TimeoutError(errorMessage));
} }
/**
* @return {!Promise<?Error>}
*/
async navigationPromise() {
return this._navigationPromise;
}
/** /**
* @param {!Puppeteer.Frame} frame * @param {!Puppeteer.Frame} frame
*/ */
@ -97,7 +110,10 @@ class NavigatorWatcher {
return; return;
if (!checkLifecycle(this._frame, this._expectedLifecycle)) if (!checkLifecycle(this._frame, this._expectedLifecycle))
return; return;
this._lifecycleCompleteCallback(); if (this._hasSameDocumentNavigation)
this._sameDocumentNavigationCompleteCallback();
if (this._frame._loaderId !== this._initialLoaderId)
this._newDocumentNavigationCompleteCallback();
/** /**
* @param {!Puppeteer.Frame} frame * @param {!Puppeteer.Frame} frame
@ -117,13 +133,8 @@ class NavigatorWatcher {
} }
} }
cancel() { dispose() {
this._cleanup();
}
_cleanup() {
helper.removeEventListeners(this._eventListeners); helper.removeEventListeners(this._eventListeners);
this._lifecycleCompleteCallback(new Error('Navigation failed'));
clearTimeout(this._maximumTimer); clearTimeout(this._maximumTimer);
} }
} }

View File

@ -591,14 +591,18 @@ class Page extends EventEmitter {
const mainFrame = this._frameManager.mainFrame(); const mainFrame = this._frameManager.mainFrame();
const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout; const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
const watcher = new NavigatorWatcher(this._frameManager, mainFrame, timeout, options); const watcher = new NavigatorWatcher(this._frameManager, mainFrame, timeout, options);
const navigationPromise = watcher.navigationPromise(); let ensureNewDocumentNavigation = false;
let error = await Promise.race([ let error = await Promise.race([
navigate(this._client, url, referrer), navigate(this._client, url, referrer),
navigationPromise, watcher.timeoutPromise(),
]); ]);
if (!error) if (!error) {
error = await navigationPromise; error = await Promise.race([
watcher.cancel(); watcher.timeoutPromise(),
ensureNewDocumentNavigation ? watcher.newDocumentNavigationPromise() : watcher.sameDocumentNavigationPromise(),
]);
}
watcher.dispose();
helper.removeEventListeners(eventListeners); helper.removeEventListeners(eventListeners);
if (error) if (error)
throw error; throw error;
@ -614,6 +618,7 @@ class Page extends EventEmitter {
async function navigate(client, url, referrer) { async function navigate(client, url, referrer) {
try { try {
const response = await client.send('Page.navigate', {url, referrer}); const response = await client.send('Page.navigate', {url, referrer});
ensureNewDocumentNavigation = !!response.loaderId;
return response.errorText ? new Error(`${response.errorText} at ${url}`) : null; return response.errorText ? new Error(`${response.errorText} at ${url}`) : null;
} catch (error) { } catch (error) {
return error; return error;
@ -644,7 +649,12 @@ class Page extends EventEmitter {
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));
const error = await watcher.navigationPromise(); const error = await Promise.race([
watcher.timeoutPromise(),
watcher.sameDocumentNavigationPromise(),
watcher.newDocumentNavigationPromise()
]);
watcher.dispose();
helper.removeEventListeners([listener]); helper.removeEventListeners([listener]);
if (error) if (error)
throw error; throw error;

View File

@ -552,7 +552,7 @@ module.exports.addTests = function({testRunner, expect, headless}) {
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.securityDetails()).toBe(null); expect(response.securityDetails()).toBe(null);
}); });
xit('should work when page calls history API in beforeunload', async({page, server}) => { it('should work when page calls history API in beforeunload', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => { await page.evaluate(() => {
window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false);