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

feat(oop iframes): integrate OOP iframes with the frame manager #7556

Merged
merged 36 commits into from Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
df7b551
feat(oop iframes): integrate OOP iframes with the frame manager
jschfflr Sep 10, 2021
cb7181a
Merge branch 'main' into oopiframes
jschfflr Sep 16, 2021
06efa14
Merge remote-tracking branch 'origin/main' into oopiframes
jschfflr Sep 28, 2021
8b4e6ab
chore: address comments
jschfflr Sep 29, 2021
33a0697
chore: doc
jschfflr Sep 29, 2021
596aca6
chore: remove waitForDebugger
jschfflr Sep 30, 2021
809967e
Merge branch 'main' into oopiframes
jschfflr Sep 30, 2021
427161f
chore: remove comment
jschfflr Sep 30, 2021
60dc380
chore: refactor
jschfflr Sep 30, 2021
98ff402
chore: refactor
jschfflr Sep 30, 2021
2bff462
chore: remove eslint comment
jschfflr Sep 30, 2021
3409e0d
Merge remote-tracking branch 'origin/main' into oopiframes
jschfflr Oct 12, 2021
a0c20d1
chore: remove changes to target.spec.js
jschfflr Oct 12, 2021
4c7c086
chore: waitForFrame
jschfflr Oct 12, 2021
caff71a
chore: fix
jschfflr Oct 12, 2021
d23f0c7
chore: fix waitForFrame
jschfflr Oct 13, 2021
dbb40dc
chore: fix waitForFrame
jschfflr Oct 13, 2021
a1ff4f6
chore: fix waitForFrame
jschfflr Oct 13, 2021
88592b5
Merge branch 'main' into oopiframes
jschfflr Oct 27, 2021
81e227b
chore: rebase test
jschfflr Oct 27, 2021
e949b21
chore: waitForFrame
jschfflr Oct 27, 2021
2d9cc48
Merge branch 'main' into oopiframes
jschfflr Oct 27, 2021
719c45f
chore: fix session for initial frame tree
jschfflr Oct 27, 2021
dc473e7
Merge branch 'main' into oopiframes
jschfflr Oct 27, 2021
b3fdd34
chore: fix test
jschfflr Oct 27, 2021
aa3b26f
chore: eslint fix
jschfflr Oct 27, 2021
0e93395
chore: remove promise
jschfflr Oct 27, 2021
1c3867e
chore: debug protocol
jschfflr Oct 27, 2021
18696ef
chore: only oop iframe tests
jschfflr Oct 27, 2021
a1281cd
chore: test
jschfflr Oct 27, 2021
30995b3
chore: update
jschfflr Oct 28, 2021
33b1558
Merge remote-tracking branch 'origin/main' into oopiframes
jschfflr Oct 28, 2021
d8adbb8
chore: update docs
jschfflr Oct 28, 2021
ee378a3
chore: ignore session closed errors
jschfflr Oct 28, 2021
9e04124
Merge branch 'main' into oopiframes
jschfflr Oct 28, 2021
7d7210a
chore: address comment
jschfflr Oct 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions mocha-config/puppeteer-unit-tests.js
Expand Up @@ -25,8 +25,8 @@ module.exports = {
],
spec: 'test/*.spec.ts',
extension: ['js', 'ts'],
retries: process.env.CI ? 2 : 0,
// retries: process.env.CI ? 2 : 0,
jschfflr marked this conversation as resolved.
Show resolved Hide resolved
parallel: !!process.env.PARALLEL,
timeout: 25 * 1000,
reporter: process.env.CI ? 'spec' : 'dot',
// reporter: process.env.CI ? 'spec' : 'dot',
jschfflr marked this conversation as resolved.
Show resolved Hide resolved
};
4 changes: 4 additions & 0 deletions src/common/Connection.ts
Expand Up @@ -339,6 +339,10 @@ export class CDPSession extends EventEmitter {
this._connection = null;
this.emit(CDPSessionEmittedEvents.Disconnected);
}

id(): string {
return this._sessionId;
}
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/common/DOMWorld.ts
Expand Up @@ -37,6 +37,7 @@ import {
} from './EvalTypes.js';
import { isNode } from '../environment.js';
import { Protocol } from 'devtools-protocol';
import { CDPSession } from './Connection.js';

// predicateQueryHandler and checkWaitForOptions are declared here so that
// TypeScript knows about them when used in the predicate function below.
Expand Down Expand Up @@ -72,6 +73,7 @@ export interface PageBinding {
*/
export class DOMWorld {
private _frameManager: FrameManager;
private _client: CDPSession;
private _frame: Frame;
private _timeoutSettings: TimeoutSettings;
private _documentPromise?: Promise<ElementHandle> = null;
Expand All @@ -96,15 +98,17 @@ export class DOMWorld {
`${name}_${contextId}`;

constructor(
client: CDPSession,
frameManager: FrameManager,
frame: Frame,
timeoutSettings: TimeoutSettings
) {
this._client = client;
jschfflr marked this conversation as resolved.
Show resolved Hide resolved
this._frameManager = frameManager;
this._frame = frame;
this._timeoutSettings = timeoutSettings;
this._setContext(null);
frameManager._client.on('Runtime.bindingCalled', (event) =>
this._client.on('Runtime.bindingCalled', (event) =>
this._onBindingCalled(event)
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/common/ExecutionContext.ts
Expand Up @@ -62,6 +62,10 @@ export class ExecutionContext {
* @internal
*/
_contextName: string;
/**
* @internal
*/
_uniqueId: string;

/**
* @internal
Expand All @@ -75,6 +79,7 @@ export class ExecutionContext {
this._world = world;
this._contextId = contextPayload.id;
this._contextName = contextPayload.name;
this._uniqueId = contextPayload.uniqueId;
}

/**
Expand Down
195 changes: 139 additions & 56 deletions src/common/FrameManager.ts
Expand Up @@ -14,8 +14,6 @@
* limitations under the License.
*/

import { debug } from '../common/Debug.js';

import { EventEmitter } from './EventEmitter.js';
import { assert } from './assert.js';
import { helper, debugError } from './helper.js';
Expand All @@ -27,7 +25,7 @@ import {
import { DOMWorld, WaitForSelectorOptions } from './DOMWorld.js';
import { NetworkManager } from './NetworkManager.js';
import { TimeoutSettings } from './TimeoutSettings.js';
import { CDPSession } from './Connection.js';
import { Connection, CDPSession } from './Connection.js';
import { JSHandle, ElementHandle } from './JSHandle.js';
import { MouseButton } from './Input.js';
import { Page } from './Page.js';
Expand Down Expand Up @@ -86,36 +84,43 @@ export class FrameManager extends EventEmitter {
this._page = page;
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
this._timeoutSettings = timeoutSettings;
this._client.on('Page.frameAttached', (event) =>
this._onFrameAttached(event.frameId, event.parentFrameId)
);
this._client.on('Page.frameNavigated', (event) =>
this._onFrameNavigated(event.frame)
);
this._client.on('Page.navigatedWithinDocument', (event) =>
this._onFrameNavigatedWithinDocument(event.frameId, event.url)
);
this._client.on('Page.frameDetached', (event) =>
this._onFrameDetached(event.frameId)
);
this._client.on('Page.frameStoppedLoading', (event) =>
this._onFrameStoppedLoading(event.frameId)
);
this._client.on('Runtime.executionContextCreated', (event) =>
this._onExecutionContextCreated(event.context)
);
this._client.on('Runtime.executionContextDestroyed', (event) =>
this._onExecutionContextDestroyed(event.executionContextId)
);
this._client.on('Runtime.executionContextsCleared', () =>
this._onExecutionContextsCleared()
);
this._client.on('Page.lifecycleEvent', (event) =>
this._onLifecycleEvent(event)
);
this._client.on('Target.attachedToTarget', async (event) =>
this._onFrameMoved(event)
);
this.setupEventListeners(this._client);
}

private setupEventListeners(session: CDPSession) {
session.on('Page.frameAttached', (event) => {
this._onFrameAttached(event.frameId, event.parentFrameId);
});
session.on('Page.frameNavigated', (event) => {
this._onFrameNavigated(event.frame);
});
session.on('Page.navigatedWithinDocument', (event) => {
this._onFrameNavigatedWithinDocument(event.frameId, event.url);
});
session.on('Page.frameDetached', (event) => {
this._onFrameDetached(event.frameId);
});
session.on('Page.frameStoppedLoading', (event) => {
this._onFrameStoppedLoading(event.frameId);
});
session.on('Runtime.executionContextCreated', (event) => {
this._onExecutionContextCreated(event.context);
});
session.on('Runtime.executionContextDestroyed', (event) => {
this._onExecutionContextDestroyed(event.executionContextId);
});
session.on('Runtime.executionContextsCleared', () => {
this._onExecutionContextsCleared(session);
});
session.on('Page.lifecycleEvent', (event) => {
this._onLifecycleEvent(event);
});
session.on('Target.attachedToTarget', async (event) => {
this._onAttachedToTarget(event);
});
session.on('Target.detachedFromTarget', async (event) => {
this._onDetachedFromTarget(event);
});
}

async initialize(): Promise<void> {
Expand All @@ -130,7 +135,9 @@ export class FrameManager extends EventEmitter {
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
this._client
.send('Runtime.enable')
.then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
.then(() =>
this._ensureIsolatedWorld(this._client, UTILITY_WORLD_NAME)
),
this._networkManager.initialize(),
]);
}
Expand Down Expand Up @@ -218,18 +225,53 @@ export class FrameManager extends EventEmitter {
return watcher.navigationResponse();
}

private async _onFrameMoved(event: Protocol.Target.AttachedToTargetEvent) {
private async _onAttachedToTarget(
event: Protocol.Target.AttachedToTargetEvent
) {
if (event.targetInfo.type !== 'iframe') {
return;
}

// TODO(sadym): Remove debug message once proper OOPIF support is
// implemented: https://github.com/puppeteer/puppeteer/issues/2548
debug('puppeteer:frame')(
`The frame '${event.targetInfo.targetId}' moved to another session. ` +
`Out-of-process iframes (OOPIF) are not supported by Puppeteer yet. ` +
`https://github.com/puppeteer/puppeteer/issues/2548`
const frame = this._frames.get(event.targetInfo.targetId);
const session = Connection.fromSession(this._client).session(
event.sessionId
);
frame.updateClient(session);
session.on('Page.frameNavigated', (event) => {
jschfflr marked this conversation as resolved.
Show resolved Hide resolved
this._onFrameNavigated(event.frame);
});
session.on('Runtime.executionContextCreated', (event) => {
this._onExecutionContextCreated(event.context);
});
session.on('Runtime.executionContextDestroyed', (event) => {
this._onExecutionContextDestroyed(event.executionContextId);
});
session.on('Runtime.executionContextsCleared', () => {
this._onExecutionContextsCleared(session);
});

const result = await Promise.all([
session.send('Page.enable'),
session.send('Page.getFrameTree'),
]);

const { frameTree } = result[1];
this._handleFrameTree(frameTree);
await Promise.all([
session.send('Page.setLifecycleEventsEnabled', { enabled: true }),
session
.send('Runtime.enable')
.then(() => this._ensureIsolatedWorld(session, UTILITY_WORLD_NAME)),
session.send('Runtime.enable'),
session.send('Runtime.runIfWaitingForDebugger'),
]);
}

private async _onDetachedFromTarget(
event: Protocol.Target.DetachedFromTargetEvent
) {
const frame = this._frames.get(event.targetId);
frame.updateClient(this._client);
}

_onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void {
Expand Down Expand Up @@ -316,24 +358,27 @@ export class FrameManager extends EventEmitter {
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
}

async _ensureIsolatedWorld(name: string): Promise<void> {
if (this._isolatedWorlds.has(name)) return;
this._isolatedWorlds.add(name);
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
async _ensureIsolatedWorld(session: CDPSession, name: string): Promise<void> {
const key = `${session.id()}:${name}`;
if (this._isolatedWorlds.has(key)) return;
this._isolatedWorlds.add(key);
OrKoN marked this conversation as resolved.
Show resolved Hide resolved
await session.send('Page.addScriptToEvaluateOnNewDocument', {
source: `//# sourceURL=${EVALUATION_SCRIPT_URL}`,
worldName: name,
});
// Frames might be removed before we send this.
await Promise.all(
this.frames().map((frame) =>
this._client
.send('Page.createIsolatedWorld', {
frameId: frame._id,
worldName: name,
grantUniveralAccess: true,
})
.catch(debugError)
OrKoN marked this conversation as resolved.
Show resolved Hide resolved
)
this.frames()
.filter((frame) => frame._client === session)
.map((frame) =>
session
.send('Page.createIsolatedWorld', {
frameId: frame._id,
worldName: name,
grantUniveralAccess: true,
})
.catch(debugError)
)
);
}

Expand All @@ -347,6 +392,9 @@ export class FrameManager extends EventEmitter {

_onFrameDetached(frameId: string): void {
const frame = this._frames.get(frameId);
if (frame.isOOPFrame()) {
jschfflr marked this conversation as resolved.
Show resolved Hide resolved
return;
}
if (frame) this._removeFramesRecursively(frame);
}

Expand All @@ -370,7 +418,11 @@ export class FrameManager extends EventEmitter {
world = frame._secondaryWorld;
}
}
const context = new ExecutionContext(this._client, contextPayload, world);
const context = new ExecutionContext(
frame._client || this._client,
contextPayload,
world
);
if (world) world._setContext(context);
this._contextIdToContext.set(contextPayload.id, context);
}
Expand All @@ -382,8 +434,9 @@ export class FrameManager extends EventEmitter {
if (context._world) context._world._setContext(null);
}

private _onExecutionContextsCleared(): void {
private _onExecutionContextsCleared(session: CDPSession): void {
for (const context of this._contextIdToContext.values()) {
if (context._client !== session) continue;
if (context._world) context._world._setContext(null);
}
this._contextIdToContext.clear();
Expand Down Expand Up @@ -562,6 +615,10 @@ export class Frame {
* @internal
*/
_childFrames: Set<Frame>;
/**
* @internal
*/
_client: CDPSession;

/**
* @internal
Expand All @@ -578,12 +635,15 @@ export class Frame {
this._detached = false;

this._loaderId = '';
this._client = frameManager._client;
this._mainWorld = new DOMWorld(
this._client,
frameManager,
this,
frameManager._timeoutSettings
);
this._secondaryWorld = new DOMWorld(
this._client,
frameManager,
this,
frameManager._timeoutSettings
Expand All @@ -593,6 +653,29 @@ export class Frame {
if (this._parentFrame) this._parentFrame._childFrames.add(this);
}

/**
* @internal
*/
updateClient(client: CDPSession): void {
this._client = client;
this._mainWorld = new DOMWorld(
client,
this._frameManager,
this,
this._frameManager._timeoutSettings
);
this._secondaryWorld = new DOMWorld(
client,
this._frameManager,
this,
this._frameManager._timeoutSettings
);
}

isOOPFrame(): boolean {
return this._client !== this._frameManager._client;
}

/**
* @remarks
*
Expand Down