mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 13:45:36 +03:00
chore: reuse navigation methods between browsers (#271)
This commit is contained in:
parent
48be99a56e
commit
5a60a96410
@ -107,79 +107,17 @@ export class FrameManager implements PageDelegate {
|
||||
this._page._didClose();
|
||||
}
|
||||
|
||||
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> {
|
||||
const {
|
||||
referer = this._networkManager.extraHTTPHeaders()['referer'],
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
|
||||
const watcher = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
let ensureNewDocumentNavigation = false;
|
||||
let error = await Promise.race([
|
||||
navigate(this._client, url, referer, frame._id),
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
]);
|
||||
if (!error) {
|
||||
error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
ensureNewDocumentNavigation ? watcher.newDocumentNavigationPromise : watcher.sameDocumentNavigationPromise,
|
||||
]);
|
||||
}
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watcher.navigationResponse();
|
||||
|
||||
async function navigate(client: CDPSession, url: string, referrer: string, frameId: string): Promise<Error | null> {
|
||||
try {
|
||||
const response = await client.send('Page.navigate', {url, referrer, frameId});
|
||||
ensureNewDocumentNavigation = !!response.loaderId;
|
||||
return response.errorText ? new Error(`${response.errorText} at ${url}`) : null;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
||||
const response = await this._client.send('Page.navigate', { url, referrer, frameId: frame._id });
|
||||
if (response.errorText)
|
||||
throw new Error(`${response.errorText} at ${url}`);
|
||||
return { newDocumentId: response.loaderId, isSameDocument: !response.loaderId };
|
||||
}
|
||||
|
||||
async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}): Promise<network.Response | null> {
|
||||
const {
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const watcher = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
const error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
watcher.sameDocumentNavigationPromise,
|
||||
watcher.newDocumentNavigationPromise,
|
||||
]);
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watcher.navigationResponse();
|
||||
}
|
||||
|
||||
async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) {
|
||||
const {
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const context = await frame._utilityContext();
|
||||
needsLifecycleResetOnSetContent(): boolean {
|
||||
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
|
||||
// lifecycle event. @see https://crrev.com/608658
|
||||
await context.evaluate(html => {
|
||||
document.open();
|
||||
document.write(html);
|
||||
document.close();
|
||||
}, html);
|
||||
const watcher = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
const error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
watcher.lifecyclePromise,
|
||||
]);
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return false;
|
||||
}
|
||||
|
||||
_onLifecycleEvent(event: Protocol.Page.lifecycleEventPayload) {
|
||||
|
@ -173,67 +173,13 @@ export class FrameManager implements PageDelegate {
|
||||
this._page._didClose();
|
||||
}
|
||||
|
||||
async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}) {
|
||||
const {
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
||||
} = options;
|
||||
|
||||
const watcher = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
const error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
watcher.newDocumentNavigationPromise,
|
||||
watcher.sameDocumentNavigationPromise,
|
||||
]);
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watcher.navigationResponse();
|
||||
async navigateFrame(frame: frames.Frame, url: string, referer: string | undefined): Promise<frames.GotoResult> {
|
||||
const response = await this._session.send('Page.navigate', { url, referer, frameId: frame._id });
|
||||
return { newDocumentId: response.navigationId, isSameDocument: !response.navigationId };
|
||||
}
|
||||
|
||||
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}) {
|
||||
const {
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
||||
referer,
|
||||
} = options;
|
||||
const watcher = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
await this._session.send('Page.navigate', {
|
||||
frameId: frame._id,
|
||||
referer,
|
||||
url,
|
||||
});
|
||||
const error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
watcher.newDocumentNavigationPromise,
|
||||
watcher.sameDocumentNavigationPromise,
|
||||
]);
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watcher.navigationResponse();
|
||||
}
|
||||
|
||||
async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) {
|
||||
const {
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const context = await frame._utilityContext();
|
||||
frame._firedLifecycleEvents.clear();
|
||||
await context.evaluate(html => {
|
||||
document.open();
|
||||
document.write(html);
|
||||
document.close();
|
||||
}, html);
|
||||
const watcher = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
const error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
watcher.lifecyclePromise,
|
||||
]);
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
needsLifecycleResetOnSetContent(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void> {
|
||||
|
@ -45,6 +45,10 @@ export type NavigateOptions = {
|
||||
export type GotoOptions = NavigateOptions & {
|
||||
referer?: string,
|
||||
};
|
||||
export type GotoResult = {
|
||||
newDocumentId?: string,
|
||||
isSameDocument?: boolean,
|
||||
};
|
||||
|
||||
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
|
||||
const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']);
|
||||
@ -280,12 +284,60 @@ export class Frame {
|
||||
this._parentFrame._childFrames.add(this);
|
||||
}
|
||||
|
||||
async goto(url: string, options?: GotoOptions): Promise<network.Response | null> {
|
||||
return this._page._delegate.navigateFrame(this, url, options);
|
||||
async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> {
|
||||
const {
|
||||
referer = (this._page._state.extraHTTPHeaders || {})['referer'],
|
||||
waitUntil = (['load'] as LifecycleEvent[]),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const watcher = new LifecycleWatcher(this, waitUntil, timeout);
|
||||
|
||||
let navigateResult: GotoResult;
|
||||
const navigate = async () => {
|
||||
try {
|
||||
navigateResult = await this._page._delegate.navigateFrame(this, url, referer);
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
let error = await Promise.race([
|
||||
navigate(),
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
]);
|
||||
if (!error) {
|
||||
const promises = [watcher.timeoutOrTerminationPromise];
|
||||
if (navigateResult.newDocumentId) {
|
||||
watcher.setExpectedDocumentId(navigateResult.newDocumentId, url);
|
||||
promises.push(watcher.newDocumentNavigationPromise);
|
||||
} else if (navigateResult.isSameDocument) {
|
||||
promises.push(watcher.sameDocumentNavigationPromise);
|
||||
} else {
|
||||
promises.push(watcher.sameDocumentNavigationPromise, watcher.newDocumentNavigationPromise);
|
||||
}
|
||||
error = await Promise.race(promises);
|
||||
}
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watcher.navigationResponse();
|
||||
}
|
||||
|
||||
async waitForNavigation(options?: NavigateOptions): Promise<network.Response | null> {
|
||||
return this._page._delegate.waitForFrameNavigation(this, options);
|
||||
async waitForNavigation(options: NavigateOptions = {}): Promise<network.Response | null> {
|
||||
const {
|
||||
waitUntil = (['load'] as LifecycleEvent[]),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const watcher = new LifecycleWatcher(this, waitUntil, timeout);
|
||||
const error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
watcher.sameDocumentNavigationPromise,
|
||||
watcher.newDocumentNavigationPromise,
|
||||
]);
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watcher.navigationResponse();
|
||||
}
|
||||
|
||||
_mainContext(): Promise<dom.FrameExecutionContext> {
|
||||
@ -351,8 +403,27 @@ export class Frame {
|
||||
});
|
||||
}
|
||||
|
||||
async setContent(html: string, options?: NavigateOptions): Promise<void> {
|
||||
return this._page._delegate.setFrameContent(this, html, options);
|
||||
async setContent(html: string, options: NavigateOptions = {}): Promise<void> {
|
||||
const {
|
||||
waitUntil = (['load'] as LifecycleEvent[]),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const context = await this._utilityContext();
|
||||
if (this._page._delegate.needsLifecycleResetOnSetContent())
|
||||
this._firedLifecycleEvents.clear();
|
||||
await context.evaluate(html => {
|
||||
document.open();
|
||||
document.write(html);
|
||||
document.close();
|
||||
}, html);
|
||||
const watcher = new LifecycleWatcher(this, waitUntil, timeout);
|
||||
const error = await Promise.race([
|
||||
watcher.timeoutOrTerminationPromise,
|
||||
watcher.lifecyclePromise,
|
||||
]);
|
||||
watcher.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
}
|
||||
|
||||
name(): string {
|
||||
@ -805,6 +876,13 @@ export class LifecycleWatcher {
|
||||
this._checkLifecycleComplete();
|
||||
}
|
||||
|
||||
setExpectedDocumentId(documentId: string, url: string) {
|
||||
this._expectedDocumentId = documentId;
|
||||
this._targetUrl = url;
|
||||
if (this._navigationRequest && this._navigationRequest._documentId !== documentId)
|
||||
this._navigationRequest = null;
|
||||
}
|
||||
|
||||
_onFrameDetached(frame: Frame) {
|
||||
if (this._frame === frame) {
|
||||
this._frameDetachedCallback.call(null, new Error('Navigating frame was detached'));
|
||||
@ -822,7 +900,9 @@ export class LifecycleWatcher {
|
||||
|
||||
_onNavigationRequest(frame: Frame, request: network.Request) {
|
||||
assert(request._documentId);
|
||||
if (frame === this._frame && this._expectedDocumentId === undefined) {
|
||||
if (frame !== this._frame)
|
||||
return;
|
||||
if (this._expectedDocumentId === undefined || this._expectedDocumentId === request._documentId) {
|
||||
this._navigationRequest = request;
|
||||
this._expectedDocumentId = request._documentId;
|
||||
this._targetUrl = request.url();
|
||||
|
@ -41,9 +41,8 @@ export interface PageDelegate {
|
||||
evaluateOnNewDocument(source: string): Promise<void>;
|
||||
closePage(runBeforeUnload: boolean): Promise<void>;
|
||||
|
||||
navigateFrame(frame: frames.Frame, url: string, options?: frames.GotoOptions): Promise<network.Response | null>;
|
||||
waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||
setFrameContent(frame: frames.Frame, html: string, options?: frames.NavigateOptions): Promise<void>;
|
||||
navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;
|
||||
needsLifecycleResetOnSetContent(): boolean;
|
||||
|
||||
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>;
|
||||
setUserAgent(userAgent: string): Promise<void>;
|
||||
|
@ -187,60 +187,13 @@ export class FrameManager implements PageDelegate {
|
||||
this._contextIdToContext.set(contextPayload.id, context);
|
||||
}
|
||||
|
||||
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> {
|
||||
const {
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[])
|
||||
} = options;
|
||||
const watchDog = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
await this._session.send('Page.navigate', {url, frameId: frame._id});
|
||||
const error = await Promise.race([
|
||||
watchDog.timeoutOrTerminationPromise,
|
||||
watchDog.newDocumentNavigationPromise,
|
||||
watchDog.sameDocumentNavigationPromise,
|
||||
]);
|
||||
watchDog.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watchDog.navigationResponse();
|
||||
async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
||||
await this._session.send('Page.navigate', { url, frameId: frame._id });
|
||||
return {}; // We cannot get loaderId of cross-process navigation in advance.
|
||||
}
|
||||
|
||||
async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}): Promise<network.Response | null> {
|
||||
const {
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[])
|
||||
} = options;
|
||||
const watchDog = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
const error = await Promise.race([
|
||||
watchDog.timeoutOrTerminationPromise,
|
||||
watchDog.newDocumentNavigationPromise,
|
||||
watchDog.sameDocumentNavigationPromise,
|
||||
]);
|
||||
watchDog.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
return watchDog.navigationResponse();
|
||||
}
|
||||
|
||||
async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) {
|
||||
// We rely upon the fact that document.open() will trigger Page.loadEventFired.
|
||||
const {
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
waitUntil = (['load'] as frames.LifecycleEvent[])
|
||||
} = options;
|
||||
const watchDog = new frames.LifecycleWatcher(frame, waitUntil, timeout);
|
||||
await frame.evaluate(html => {
|
||||
document.open();
|
||||
document.write(html);
|
||||
document.close();
|
||||
}, html);
|
||||
const error = await Promise.race([
|
||||
watchDog.timeoutOrTerminationPromise,
|
||||
watchDog.lifecyclePromise,
|
||||
]);
|
||||
watchDog.dispose();
|
||||
if (error)
|
||||
throw error;
|
||||
needsLifecycleResetOnSetContent(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) {
|
||||
|
@ -125,7 +125,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
|
||||
const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'});
|
||||
expect(response.status()).toBe(200);
|
||||
});
|
||||
it.skip(WEBKIT)('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.evaluate(() => {
|
||||
window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false);
|
||||
@ -427,6 +427,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
|
||||
expect(request1.headers['referer']).toBe('http://google.com/');
|
||||
// Make sure subresources do not inherit referer.
|
||||
expect(request2.headers['referer']).toBe(server.PREFIX + '/grid.html');
|
||||
expect(page.url()).toBe(server.PREFIX + '/grid.html');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -530,6 +530,11 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
|
||||
const result = await page.content();
|
||||
expect(result).toBe(expectedOutput);
|
||||
});
|
||||
it('should work with domcontentloaded', async({page, server}) => {
|
||||
await page.setContent('<div>hello</div>', { waitUntil: 'domcontentloaded' });
|
||||
const result = await page.content();
|
||||
expect(result).toBe(expectedOutput);
|
||||
});
|
||||
it('should not confuse with previous navigation', async({page, server}) => {
|
||||
const imgPath = '/img.png';
|
||||
let imgResponse = null;
|
||||
|
Loading…
Reference in New Issue
Block a user