mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
refactor: turn Frame into EventEmitter (#10711)
This commit is contained in:
parent
854d488693
commit
f70048c84f
@ -11,9 +11,11 @@ To understand frames, you can think of frames as `<iframe>` elements. Just like
|
||||
#### Signature:
|
||||
|
||||
```typescript
|
||||
export declare class Frame
|
||||
export declare class Frame extends EventEmitter
|
||||
```
|
||||
|
||||
**Extends:** [EventEmitter](./puppeteer.eventemitter.md)
|
||||
|
||||
## Remarks
|
||||
|
||||
Frame lifecycles are controlled by three events that are all dispatched on the parent [page](./puppeteer.frame.page.md):
|
||||
|
@ -19,6 +19,7 @@ import {HTTPResponse} from '../api/HTTPResponse.js';
|
||||
import {Page, WaitTimeoutOptions} from '../api/Page.js';
|
||||
import {CDPSession} from '../common/Connection.js';
|
||||
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
||||
import {EventEmitter} from '../common/EventEmitter.js';
|
||||
import {ExecutionContext} from '../common/ExecutionContext.js';
|
||||
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
|
||||
import {
|
||||
@ -223,7 +224,7 @@ export interface FrameAddStyleTagOptions {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class Frame {
|
||||
export class Frame extends EventEmitter {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -251,7 +252,9 @@ export class Frame {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor() {}
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* The page associated with the frame.
|
||||
|
@ -37,6 +37,20 @@ import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from './types.js';
|
||||
import {withSourcePuppeteerURLIfNone} from './util.js';
|
||||
|
||||
/**
|
||||
* We use symbols to prevent external parties listening to these events.
|
||||
* They are internal to Puppeteer.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const FrameEmittedEvents = {
|
||||
FrameNavigated: Symbol('Frame.FrameNavigated'),
|
||||
FrameSwapped: Symbol('Frame.FrameSwapped'),
|
||||
LifecycleEvent: Symbol('Frame.LifecycleEvent'),
|
||||
FrameNavigatedWithinDocument: Symbol('Frame.FrameNavigatedWithinDocument'),
|
||||
FrameDetached: Symbol('Frame.FrameDetached'),
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -106,7 +120,7 @@ export class Frame extends BaseFrame {
|
||||
|
||||
let ensureNewDocumentNavigation = false;
|
||||
const watcher = new LifecycleWatcher(
|
||||
this._frameManager,
|
||||
this._frameManager.networkManager,
|
||||
this,
|
||||
waitUntil,
|
||||
timeout
|
||||
@ -180,7 +194,7 @@ export class Frame extends BaseFrame {
|
||||
timeout = this._frameManager.timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const watcher = new LifecycleWatcher(
|
||||
this._frameManager,
|
||||
this._frameManager.networkManager,
|
||||
this,
|
||||
waitUntil,
|
||||
timeout
|
||||
|
@ -20,12 +20,15 @@ import {Page} from '../api/Page.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.js';
|
||||
|
||||
import {CDPSession, isTargetClosedError} from './Connection.js';
|
||||
import {
|
||||
CDPSession,
|
||||
CDPSessionEmittedEvents,
|
||||
isTargetClosedError,
|
||||
} from './Connection.js';
|
||||
import {DeviceRequestPromptManager} from './DeviceRequestPrompt.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {Frame as CDPFrame} from './Frame.js';
|
||||
import {Frame, FrameEmittedEvents} from './Frame.js';
|
||||
import {FrameTree} from './FrameTree.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
@ -54,8 +57,6 @@ export const FrameManagerEmittedEvents = {
|
||||
FrameNavigatedWithinDocument: Symbol(
|
||||
'FrameManager.FrameNavigatedWithinDocument'
|
||||
),
|
||||
ExecutionContextCreated: Symbol('FrameManager.ExecutionContextCreated'),
|
||||
ExecutionContextDestroyed: Symbol('FrameManager.ExecutionContextDestroyed'),
|
||||
};
|
||||
|
||||
/**
|
||||
@ -111,6 +112,12 @@ export class FrameManager extends EventEmitter {
|
||||
this.#networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
|
||||
this.#timeoutSettings = timeoutSettings;
|
||||
this.setupEventListeners(this.#client);
|
||||
client.once(CDPSessionEmittedEvents.Disconnected, () => {
|
||||
const mainFrame = this._frameTree.getMainFrame();
|
||||
if (mainFrame) {
|
||||
this.#removeFramesRecursively(mainFrame);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setupEventListeners(session: CDPSession) {
|
||||
@ -248,6 +255,7 @@ export class FrameManager extends EventEmitter {
|
||||
}
|
||||
frame._onLifecycleEvent(event.loaderId, event.name);
|
||||
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame);
|
||||
frame.emit(FrameEmittedEvents.LifecycleEvent);
|
||||
}
|
||||
|
||||
#onFrameStartedLoading(frameId: string): void {
|
||||
@ -265,6 +273,7 @@ export class FrameManager extends EventEmitter {
|
||||
}
|
||||
frame._onLoadingStopped();
|
||||
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame);
|
||||
frame.emit(FrameEmittedEvents.LifecycleEvent);
|
||||
}
|
||||
|
||||
#handleFrameTree(
|
||||
@ -309,7 +318,7 @@ export class FrameManager extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
frame = new CDPFrame(this, frameId, parentFrameId, session);
|
||||
frame = new Frame(this, frameId, parentFrameId, session);
|
||||
this._frameTree.addFrame(frame);
|
||||
this.emit(FrameManagerEmittedEvents.FrameAttached, frame);
|
||||
}
|
||||
@ -335,7 +344,7 @@ export class FrameManager extends EventEmitter {
|
||||
frame._id = frameId;
|
||||
} else {
|
||||
// Initial main frame navigation.
|
||||
frame = new CDPFrame(this, frameId, undefined, this.#client);
|
||||
frame = new Frame(this, frameId, undefined, this.#client);
|
||||
}
|
||||
this._frameTree.addFrame(frame);
|
||||
}
|
||||
@ -343,6 +352,7 @@ export class FrameManager extends EventEmitter {
|
||||
frame = await this._frameTree.waitForFrame(frameId);
|
||||
frame._navigated(framePayload);
|
||||
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
|
||||
frame.emit(FrameEmittedEvents.FrameNavigated);
|
||||
}
|
||||
|
||||
async #createIsolatedWorld(session: CDPSession, name: string): Promise<void> {
|
||||
@ -385,7 +395,9 @@ export class FrameManager extends EventEmitter {
|
||||
}
|
||||
frame._navigatedWithinDocument(url);
|
||||
this.emit(FrameManagerEmittedEvents.FrameNavigatedWithinDocument, frame);
|
||||
frame.emit(FrameEmittedEvents.FrameNavigatedWithinDocument);
|
||||
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
|
||||
frame.emit(FrameEmittedEvents.FrameNavigated);
|
||||
}
|
||||
|
||||
#onFrameDetached(
|
||||
@ -402,6 +414,7 @@ export class FrameManager extends EventEmitter {
|
||||
}
|
||||
} else if (reason === 'swap') {
|
||||
this.emit(FrameManagerEmittedEvents.FrameSwapped, frame);
|
||||
frame?.emit(FrameEmittedEvents.FrameSwapped);
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,5 +491,6 @@ export class FrameManager extends EventEmitter {
|
||||
frame._detach();
|
||||
this._frameTree.removeFrame(frame);
|
||||
this.emit(FrameManagerEmittedEvents.FrameDetached, frame);
|
||||
frame.emit(FrameEmittedEvents.FrameDetached, frame);
|
||||
}
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ export class IsolatedWorld implements Realm {
|
||||
await setPageContent(this, html);
|
||||
|
||||
const watcher = new LifecycleWatcher(
|
||||
this.#frameManager,
|
||||
this.#frameManager.networkManager,
|
||||
this.#frame,
|
||||
waitUntil,
|
||||
timeout
|
||||
|
@ -18,12 +18,10 @@ import {HTTPResponse} from '../api/HTTPResponse.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {Deferred} from '../util/Deferred.js';
|
||||
|
||||
import {CDPSessionEmittedEvents} from './Connection.js';
|
||||
import {TimeoutError} from './Errors.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
|
||||
import {Frame, FrameEmittedEvents} from './Frame.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {NetworkManagerEmittedEvents} from './NetworkManager.js';
|
||||
import {NetworkManager, NetworkManagerEmittedEvents} from './NetworkManager.js';
|
||||
import {
|
||||
addEventListener,
|
||||
PuppeteerEventListener,
|
||||
@ -62,7 +60,6 @@ const puppeteerToProtocolLifecycle = new Map<
|
||||
*/
|
||||
export class LifecycleWatcher {
|
||||
#expectedLifecycle: ProtocolLifeCycleEvent[];
|
||||
#frameManager: FrameManager;
|
||||
#frame: Frame;
|
||||
#timeout: number;
|
||||
#navigationRequest: HTTPRequest | null = null;
|
||||
@ -80,7 +77,7 @@ export class LifecycleWatcher {
|
||||
#navigationResponseReceived?: Deferred<void>;
|
||||
|
||||
constructor(
|
||||
frameManager: FrameManager,
|
||||
networkManager: NetworkManager,
|
||||
frame: Frame,
|
||||
waitUntil: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[],
|
||||
timeout: number
|
||||
@ -97,55 +94,46 @@ export class LifecycleWatcher {
|
||||
return protocolEvent as ProtocolLifeCycleEvent;
|
||||
});
|
||||
|
||||
this.#frameManager = frameManager;
|
||||
this.#frame = frame;
|
||||
this.#timeout = timeout;
|
||||
this.#eventListeners = [
|
||||
addEventListener(
|
||||
frameManager.client,
|
||||
CDPSessionEmittedEvents.Disconnected,
|
||||
this.#terminate.bind(
|
||||
this,
|
||||
new Error('Navigation failed because browser has disconnected!')
|
||||
)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager,
|
||||
FrameManagerEmittedEvents.LifecycleEvent,
|
||||
frame,
|
||||
FrameEmittedEvents.LifecycleEvent,
|
||||
this.#checkLifecycleComplete.bind(this)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager,
|
||||
FrameManagerEmittedEvents.FrameNavigatedWithinDocument,
|
||||
frame,
|
||||
FrameEmittedEvents.FrameNavigatedWithinDocument,
|
||||
this.#navigatedWithinDocument.bind(this)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager,
|
||||
FrameManagerEmittedEvents.FrameNavigated,
|
||||
frame,
|
||||
FrameEmittedEvents.FrameNavigated,
|
||||
this.#navigated.bind(this)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager,
|
||||
FrameManagerEmittedEvents.FrameSwapped,
|
||||
frame,
|
||||
FrameEmittedEvents.FrameSwapped,
|
||||
this.#frameSwapped.bind(this)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager,
|
||||
FrameManagerEmittedEvents.FrameDetached,
|
||||
frame,
|
||||
FrameEmittedEvents.FrameDetached,
|
||||
this.#onFrameDetached.bind(this)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager.networkManager,
|
||||
networkManager,
|
||||
NetworkManagerEmittedEvents.Request,
|
||||
this.#onRequest.bind(this)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager.networkManager,
|
||||
networkManager,
|
||||
NetworkManagerEmittedEvents.Response,
|
||||
this.#onResponse.bind(this)
|
||||
),
|
||||
addEventListener(
|
||||
this.#frameManager.networkManager,
|
||||
networkManager,
|
||||
NetworkManagerEmittedEvents.RequestFailed,
|
||||
this.#onRequestFailed.bind(this)
|
||||
),
|
||||
@ -204,10 +192,6 @@ export class LifecycleWatcher {
|
||||
return this.#navigationRequest ? this.#navigationRequest.response() : null;
|
||||
}
|
||||
|
||||
#terminate(error: Error): void {
|
||||
this.#terminationDeferred.resolve(error);
|
||||
}
|
||||
|
||||
sameDocumentNavigationPromise(): Promise<Error | undefined> {
|
||||
return this.#sameDocumentNavigationDeferred.valueOrThrow();
|
||||
}
|
||||
@ -224,25 +208,16 @@ export class LifecycleWatcher {
|
||||
return this.#terminationDeferred.valueOrThrow();
|
||||
}
|
||||
|
||||
#navigatedWithinDocument(frame: Frame): void {
|
||||
if (frame !== this.#frame) {
|
||||
return;
|
||||
}
|
||||
#navigatedWithinDocument(): void {
|
||||
this.#hasSameDocumentNavigation = true;
|
||||
this.#checkLifecycleComplete();
|
||||
}
|
||||
|
||||
#navigated(frame: Frame): void {
|
||||
if (frame !== this.#frame) {
|
||||
return;
|
||||
}
|
||||
#navigated(): void {
|
||||
this.#checkLifecycleComplete();
|
||||
}
|
||||
|
||||
#frameSwapped(frame: Frame): void {
|
||||
if (frame !== this.#frame) {
|
||||
return;
|
||||
}
|
||||
#frameSwapped(): void {
|
||||
this.#swapped = true;
|
||||
this.#checkLifecycleComplete();
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ describe('Launcher specs', function () {
|
||||
const error = await navigationPromise;
|
||||
expect(
|
||||
[
|
||||
'Navigation failed because browser has disconnected!',
|
||||
'Navigating frame was detached',
|
||||
'Protocol error (Page.navigate): Target closed.',
|
||||
].includes(error.message)
|
||||
).toBeTruthy();
|
||||
@ -82,7 +82,7 @@ describe('Launcher specs', function () {
|
||||
});
|
||||
remote.disconnect();
|
||||
const error = await watchdog;
|
||||
expect(error.message).toContain('Protocol error');
|
||||
expect(error.message).toContain('frame got detached');
|
||||
} finally {
|
||||
await close();
|
||||
}
|
||||
@ -766,11 +766,6 @@ describe('Launcher specs', function () {
|
||||
|
||||
const pages = await remoteBrowser.pages();
|
||||
|
||||
await page2.close();
|
||||
await page1.close();
|
||||
remoteBrowser.disconnect();
|
||||
await browser.close();
|
||||
|
||||
expect(
|
||||
pages
|
||||
.map((p: Page) => {
|
||||
@ -778,6 +773,11 @@ describe('Launcher specs', function () {
|
||||
})
|
||||
.sort()
|
||||
).toEqual(['about:blank', server.EMPTY_PAGE]);
|
||||
|
||||
await page2.close();
|
||||
await page1.close();
|
||||
remoteBrowser.disconnect();
|
||||
await browser.close();
|
||||
} finally {
|
||||
await close();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user