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

chore: refactor goBack/goForward/reload #3859

Merged
merged 1 commit into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export abstract class BrowserContext extends EventEmitter {
await waitForEvent.promise;
}
const pages = this.pages();
await pages[0].mainFrame().waitForLoadState();
await pages[0].mainFrame()._waitForLoadState(progress, 'load');
if (pages.length !== 1 || pages[0].mainFrame().url() !== 'about:blank')
throw new Error(`Arguments can not specify page to be opened (first url is ${pages[0].mainFrame().url()})`);
if (this._options.isMobile || this._options.locale) {
Expand Down
4 changes: 2 additions & 2 deletions src/server/electron/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import * as types from '../types';
import { launchProcess, waitForLine, envArrayToObject } from '../processLauncher';
import { BrowserContext } from '../browserContext';
import type {BrowserWindow} from 'electron';
import { ProgressController } from '../progress';
import { ProgressController, runAbortableTask } from '../progress';
import { EventEmitter } from 'events';
import { helper } from '../helper';
import { BrowserProcess } from '../browser';
Expand Down Expand Up @@ -88,7 +88,7 @@ export class ElectronApplication extends EventEmitter {
this._windows.delete(page);
});
this._windows.add(page);
await page.mainFrame().waitForLoadState('domcontentloaded').catch(e => {}); // can happen after detach
await runAbortableTask(progress => page.mainFrame()._waitForLoadState(progress, 'domcontentloaded'), page._timeoutSettings.navigationTimeout({})).catch(e => {}); // can happen after detach
this.emit(ElectronApplication.Events.Window, page);
}

Expand Down
59 changes: 27 additions & 32 deletions src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,16 @@ export class Frame extends EventEmitter {
this._subtreeLifecycleEvents = events;
}

setupNavigationProgressController(controller: ProgressController) {
this._page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!')));
this._page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!')));
this._detachedPromise.then(() => controller.abort(new Error('Navigating frame was detached!')));
}

async goto(url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
return runNavigationTask(this, options, async progress => {
const controller = new ProgressController(this._page._timeoutSettings.navigationTimeout(options));
this.setupNavigationProgressController(controller);
return controller.run(async progress => {
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
const headers = this._page._state.extraHTTPHeaders || [];
Expand Down Expand Up @@ -471,31 +479,25 @@ export class Frame extends EventEmitter {
});
}

async waitForNavigation(options: types.NavigateOptions = {}): Promise<network.Response | null> {
return runNavigationTask(this, options, async progress => {
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
progress.log(`waiting for navigation until "${waitUntil}"`);
async _waitForNavigation(progress: Progress, options: types.NavigateOptions): Promise<network.Response | null> {
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
progress.log(`waiting for navigation until "${waitUntil}"`);

const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this, Frame.Events.Navigation, (event: NavigationEvent) => {
// Any failed navigation results in a rejection.
if (event.error)
return true;
progress.log(` navigated to "${this._url}"`);
const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this, Frame.Events.Navigation, (event: NavigationEvent) => {
// Any failed navigation results in a rejection.
if (event.error)
return true;
}).promise;
if (navigationEvent.error)
throw navigationEvent.error;

if (!this._subtreeLifecycleEvents.has(waitUntil))
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
progress.log(` navigated to "${this._url}"`);
return true;
}).promise;
if (navigationEvent.error)
throw navigationEvent.error;

const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined;
return request ? request._finalRequest().response() : null;
});
}
if (!this._subtreeLifecycleEvents.has(waitUntil))
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;

async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise<void> {
return runNavigationTask(this, options, progress => this._waitForLoadState(progress, state));
const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined;
return request ? request._finalRequest().response() : null;
}

async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
Expand Down Expand Up @@ -606,7 +608,9 @@ export class Frame extends EventEmitter {
}

async setContent(html: string, options: types.NavigateOptions = {}): Promise<void> {
return runNavigationTask(this, options, async progress => {
const controller = new ProgressController(this._page._timeoutSettings.navigationTimeout(options));
this.setupNavigationProgressController(controller);
return controller.run(async progress => {
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
progress.log(`setting frame content, waiting until "${waitUntil}"`);
const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`;
Expand Down Expand Up @@ -1083,15 +1087,6 @@ class SignalBarrier {
}
}

async function runNavigationTask<T>(frame: Frame, options: types.TimeoutOptions, task: (progress: Progress) => Promise<T>): Promise<T> {
const page = frame._page;
const controller = new ProgressController(page._timeoutSettings.navigationTimeout(options));
page._disconnectedPromise.then(() => controller.abort(new Error('Navigation failed because page was closed!')));
page._crashedPromise.then(() => controller.abort(new Error('Navigation failed because page crashed!')));
frame._detachedPromise.then(() => controller.abort(new Error('Navigating frame was detached!')));
return controller.run(task);
}

function verifyLifecycle(name: string, waitUntil: types.LifecycleEvent): types.LifecycleEvent {
if (waitUntil as unknown === 'networkidle0')
waitUntil = 'networkidle';
Expand Down
54 changes: 33 additions & 21 deletions src/server/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { ConsoleMessage } from './console';
import * as accessibility from './accessibility';
import { EventEmitter } from 'events';
import { FileChooser } from './fileChooser';
import { runAbortableTask } from './progress';
import { ProgressController, runAbortableTask } from './progress';
import { assert, isError } from '../utils/utils';
import { debugLogger } from '../utils/debugLogger';
import { Selectors } from './selectors';
Expand Down Expand Up @@ -262,34 +262,46 @@ export class Page extends EventEmitter {
this.emit(Page.Events.Console, message);
}

async reload(options?: types.NavigateOptions): Promise<network.Response | null> {
const waitPromise = this.mainFrame().waitForNavigation(options);
await this._delegate.reload();
const response = await waitPromise;
async reload(options: types.NavigateOptions = {}): Promise<network.Response | null> {
const controller = new ProgressController(this._timeoutSettings.navigationTimeout(options));
this.mainFrame().setupNavigationProgressController(controller);
const response = await controller.run(async progress => {
const waitPromise = this.mainFrame()._waitForNavigation(progress, options);
await this._delegate.reload();
return waitPromise;
});
await this._doSlowMo();
return response;
}

async goBack(options?: types.NavigateOptions): Promise<network.Response | null> {
const waitPromise = this.mainFrame().waitForNavigation(options);
const result = await this._delegate.goBack();
if (!result) {
waitPromise.catch(() => {});
return null;
}
const response = await waitPromise;
async goBack(options: types.NavigateOptions = {}): Promise<network.Response | null> {
const controller = new ProgressController(this._timeoutSettings.navigationTimeout(options));
this.mainFrame().setupNavigationProgressController(controller);
const response = await controller.run(async progress => {
const waitPromise = this.mainFrame()._waitForNavigation(progress, options);
const result = await this._delegate.goBack();
if (!result) {
waitPromise.catch(() => {});
return null;
}
return waitPromise;
});
await this._doSlowMo();
return response;
}

async goForward(options?: types.NavigateOptions): Promise<network.Response | null> {
const waitPromise = this.mainFrame().waitForNavigation(options);
const result = await this._delegate.goForward();
if (!result) {
waitPromise.catch(() => {});
return null;
}
const response = await waitPromise;
async goForward(options: types.NavigateOptions = {}): Promise<network.Response | null> {
const controller = new ProgressController(this._timeoutSettings.navigationTimeout(options));
this.mainFrame().setupNavigationProgressController(controller);
const response = await controller.run(async progress => {
const waitPromise = this.mainFrame()._waitForNavigation(progress, options);
const result = await this._delegate.goForward();
if (!result) {
waitPromise.catch(() => {});
return null;
}
return waitPromise;
});
await this._doSlowMo();
return response;
}
Expand Down