Skip to content

Commit

Permalink
chore: refactor goBack/goForward/reload (#3859)
Browse files Browse the repository at this point in the history
These methods are the only users of waitForNavigation and
waitForLoadState on the server side. This refactor lifts the
Progress wrapper to the top-most goBack/goForward/reload call
and leaves waitForNavigation/waitForLoadState as internal helpers.
This way we get a single Progress for the actual api call.
  • Loading branch information
dgozman authored Sep 14, 2020
1 parent 2a66f8a commit 2f0d202
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 56 deletions.
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 @@ -1087,15 +1091,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

0 comments on commit 2f0d202

Please sign in to comment.