summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts351
1 files changed, 351 insertions, 0 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts
new file mode 100644
index 0000000000..844120d7ff
--- /dev/null
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts
@@ -0,0 +1,351 @@
+/**
+ * @license
+ * Copyright 2017 Google Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type {Protocol} from 'devtools-protocol';
+
+import type {CDPSession} from '../api/CDPSession.js';
+import {Frame, FrameEvent, throwIfDetached} from '../api/Frame.js';
+import type {HTTPResponse} from '../api/HTTPResponse.js';
+import type {WaitTimeoutOptions} from '../api/Page.js';
+import {UnsupportedOperation} from '../common/Errors.js';
+import {Deferred} from '../util/Deferred.js';
+import {disposeSymbol} from '../util/disposable.js';
+import {isErrorLike} from '../util/ErrorLike.js';
+
+import type {
+ DeviceRequestPrompt,
+ DeviceRequestPromptManager,
+} from './DeviceRequestPrompt.js';
+import type {FrameManager} from './FrameManager.js';
+import {IsolatedWorld} from './IsolatedWorld.js';
+import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
+import {
+ LifecycleWatcher,
+ type PuppeteerLifeCycleEvent,
+} from './LifecycleWatcher.js';
+import type {CdpPage} from './Page.js';
+
+/**
+ * @internal
+ */
+export class CdpFrame extends Frame {
+ #url = '';
+ #detached = false;
+ #client!: CDPSession;
+
+ _frameManager: FrameManager;
+ override _id: string;
+ _loaderId = '';
+ _lifecycleEvents = new Set<string>();
+ override _parentId?: string;
+
+ constructor(
+ frameManager: FrameManager,
+ frameId: string,
+ parentFrameId: string | undefined,
+ client: CDPSession
+ ) {
+ super();
+ this._frameManager = frameManager;
+ this.#url = '';
+ this._id = frameId;
+ this._parentId = parentFrameId;
+ this.#detached = false;
+
+ this._loaderId = '';
+
+ this.updateClient(client);
+
+ this.on(FrameEvent.FrameSwappedByActivation, () => {
+ // Emulate loading process for swapped frames.
+ this._onLoadingStarted();
+ this._onLoadingStopped();
+ });
+ }
+
+ /**
+ * This is used internally in DevTools.
+ *
+ * @internal
+ */
+ _client(): CDPSession {
+ return this.#client;
+ }
+
+ /**
+ * Updates the frame ID with the new ID. This happens when the main frame is
+ * replaced by a different frame.
+ */
+ updateId(id: string): void {
+ this._id = id;
+ }
+
+ updateClient(client: CDPSession, keepWorlds = false): void {
+ this.#client = client;
+ if (!keepWorlds) {
+ // Clear the current contexts on previous world instances.
+ if (this.worlds) {
+ this.worlds[MAIN_WORLD].clearContext();
+ this.worlds[PUPPETEER_WORLD].clearContext();
+ }
+ this.worlds = {
+ [MAIN_WORLD]: new IsolatedWorld(
+ this,
+ this._frameManager.timeoutSettings
+ ),
+ [PUPPETEER_WORLD]: new IsolatedWorld(
+ this,
+ this._frameManager.timeoutSettings
+ ),
+ };
+ } else {
+ this.worlds[MAIN_WORLD].frameUpdated();
+ this.worlds[PUPPETEER_WORLD].frameUpdated();
+ }
+ }
+
+ override page(): CdpPage {
+ return this._frameManager.page();
+ }
+
+ override isOOPFrame(): boolean {
+ return this.#client !== this._frameManager.client;
+ }
+
+ @throwIfDetached
+ override async goto(
+ url: string,
+ options: {
+ referer?: string;
+ referrerPolicy?: string;
+ timeout?: number;
+ waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
+ } = {}
+ ): Promise<HTTPResponse | null> {
+ const {
+ referer = this._frameManager.networkManager.extraHTTPHeaders()['referer'],
+ referrerPolicy = this._frameManager.networkManager.extraHTTPHeaders()[
+ 'referer-policy'
+ ],
+ waitUntil = ['load'],
+ timeout = this._frameManager.timeoutSettings.navigationTimeout(),
+ } = options;
+
+ let ensureNewDocumentNavigation = false;
+ const watcher = new LifecycleWatcher(
+ this._frameManager.networkManager,
+ this,
+ waitUntil,
+ timeout
+ );
+ let error = await Deferred.race([
+ navigate(
+ this.#client,
+ url,
+ referer,
+ referrerPolicy as Protocol.Page.ReferrerPolicy,
+ this._id
+ ),
+ watcher.terminationPromise(),
+ ]);
+ if (!error) {
+ error = await Deferred.race([
+ watcher.terminationPromise(),
+ ensureNewDocumentNavigation
+ ? watcher.newDocumentNavigationPromise()
+ : watcher.sameDocumentNavigationPromise(),
+ ]);
+ }
+
+ try {
+ if (error) {
+ throw error;
+ }
+ return await watcher.navigationResponse();
+ } finally {
+ watcher.dispose();
+ }
+
+ async function navigate(
+ client: CDPSession,
+ url: string,
+ referrer: string | undefined,
+ referrerPolicy: Protocol.Page.ReferrerPolicy | undefined,
+ frameId: string
+ ): Promise<Error | null> {
+ try {
+ const response = await client.send('Page.navigate', {
+ url,
+ referrer,
+ frameId,
+ referrerPolicy,
+ });
+ ensureNewDocumentNavigation = !!response.loaderId;
+ if (response.errorText === 'net::ERR_HTTP_RESPONSE_CODE_FAILURE') {
+ return null;
+ }
+ return response.errorText
+ ? new Error(`${response.errorText} at ${url}`)
+ : null;
+ } catch (error) {
+ if (isErrorLike(error)) {
+ return error;
+ }
+ throw error;
+ }
+ }
+ }
+
+ @throwIfDetached
+ override async waitForNavigation(
+ options: {
+ timeout?: number;
+ waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
+ } = {}
+ ): Promise<HTTPResponse | null> {
+ const {
+ waitUntil = ['load'],
+ timeout = this._frameManager.timeoutSettings.navigationTimeout(),
+ } = options;
+ const watcher = new LifecycleWatcher(
+ this._frameManager.networkManager,
+ this,
+ waitUntil,
+ timeout
+ );
+ const error = await Deferred.race([
+ watcher.terminationPromise(),
+ watcher.sameDocumentNavigationPromise(),
+ watcher.newDocumentNavigationPromise(),
+ ]);
+ try {
+ if (error) {
+ throw error;
+ }
+ return await watcher.navigationResponse();
+ } finally {
+ watcher.dispose();
+ }
+ }
+
+ override get client(): CDPSession {
+ return this.#client;
+ }
+
+ override mainRealm(): IsolatedWorld {
+ return this.worlds[MAIN_WORLD];
+ }
+
+ override isolatedRealm(): IsolatedWorld {
+ return this.worlds[PUPPETEER_WORLD];
+ }
+
+ @throwIfDetached
+ override async setContent(
+ html: string,
+ options: {
+ timeout?: number;
+ waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
+ } = {}
+ ): Promise<void> {
+ const {
+ waitUntil = ['load'],
+ timeout = this._frameManager.timeoutSettings.navigationTimeout(),
+ } = options;
+
+ // We rely upon the fact that document.open() will reset frame lifecycle with "init"
+ // lifecycle event. @see https://crrev.com/608658
+ await this.setFrameContent(html);
+
+ const watcher = new LifecycleWatcher(
+ this._frameManager.networkManager,
+ this,
+ waitUntil,
+ timeout
+ );
+ const error = await Deferred.race<void | Error | undefined>([
+ watcher.terminationPromise(),
+ watcher.lifecyclePromise(),
+ ]);
+ watcher.dispose();
+ if (error) {
+ throw error;
+ }
+ }
+
+ override url(): string {
+ return this.#url;
+ }
+
+ override parentFrame(): CdpFrame | null {
+ return this._frameManager._frameTree.parentFrame(this._id) || null;
+ }
+
+ override childFrames(): CdpFrame[] {
+ return this._frameManager._frameTree.childFrames(this._id);
+ }
+
+ #deviceRequestPromptManager(): DeviceRequestPromptManager {
+ const rootFrame = this.page().mainFrame();
+ if (this.isOOPFrame() || rootFrame === null) {
+ return this._frameManager._deviceRequestPromptManager(this.#client);
+ } else {
+ return rootFrame._frameManager._deviceRequestPromptManager(this.#client);
+ }
+ }
+
+ @throwIfDetached
+ override async waitForDevicePrompt(
+ options: WaitTimeoutOptions = {}
+ ): Promise<DeviceRequestPrompt> {
+ return await this.#deviceRequestPromptManager().waitForDevicePrompt(
+ options
+ );
+ }
+
+ _navigated(framePayload: Protocol.Page.Frame): void {
+ this._name = framePayload.name;
+ this.#url = `${framePayload.url}${framePayload.urlFragment || ''}`;
+ }
+
+ _navigatedWithinDocument(url: string): void {
+ this.#url = url;
+ }
+
+ _onLifecycleEvent(loaderId: string, name: string): void {
+ if (name === 'init') {
+ this._loaderId = loaderId;
+ this._lifecycleEvents.clear();
+ }
+ this._lifecycleEvents.add(name);
+ }
+
+ _onLoadingStopped(): void {
+ this._lifecycleEvents.add('DOMContentLoaded');
+ this._lifecycleEvents.add('load');
+ }
+
+ _onLoadingStarted(): void {
+ this._hasStartedLoading = true;
+ }
+
+ override get detached(): boolean {
+ return this.#detached;
+ }
+
+ [disposeSymbol](): void {
+ if (this.#detached) {
+ return;
+ }
+ this.#detached = true;
+ this.worlds[MAIN_WORLD][disposeSymbol]();
+ this.worlds[PUPPETEER_WORLD][disposeSymbol]();
+ }
+
+ exposeFunction(): never {
+ throw new UnsupportedOperation();
+ }
+}