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: implement dedicated workers #1619

Merged
merged 1 commit into from
Dec 14, 2023
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
54 changes: 31 additions & 23 deletions src/bidiMapper/domains/context/BrowsingContextImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,32 +447,44 @@ export class BrowsingContextImpl {
this.#cdpTarget.cdpClient.on(
'Runtime.executionContextCreated',
(params: Protocol.Runtime.ExecutionContextCreatedEvent) => {
if (params.context.auxData.frameId !== this.id) {
const {auxData, name, uniqueId, id} = params.context;
if (!auxData || auxData.frameId !== this.id) {
return;
}
// Only this execution contexts are supported for now.
if (!['default', 'isolated'].includes(params.context.auxData.type)) {
return;

let origin: string;
let sandbox: string | undefined;
// Only these execution contexts are supported for now.
switch (auxData.type) {
case 'isolated':
sandbox = name;
// Sandbox should have the same origin as the context itself, but in CDP
// it has an empty one.
origin = this.#defaultRealm.origin;
break;
case 'default':
origin = serializeOrigin(params.context.origin);
break;
default:
return;
}
const realm = new Realm(
this.#realmStorage,
this.#browsingContextStorage,
params.context.uniqueId,
uniqueId,
this.id,
params.context.id,
this.#getOrigin(params),
id,
origin,
// XXX: differentiate types.
'window',
// Sandbox name for isolated world.
params.context.auxData.type === 'isolated'
? params.context.name
: undefined,
sandbox,
this.#cdpTarget.cdpClient,
this.#eventManager,
this.#logger
);

if (params.context.auxData.isDefault) {
if (auxData.isDefault) {
this.#maybeDefaultRealm = realm;

// Initialize ChannelProxy listeners for all the channels of all the
Expand Down Expand Up @@ -541,18 +553,6 @@ export class BrowsingContextImpl {
});
}

#getOrigin(params: Protocol.Runtime.ExecutionContextCreatedEvent) {
if (params.context.auxData.type === 'isolated') {
// Sandbox should have the same origin as the context itself, but in CDP
// it has an empty one.
return this.#defaultRealm.origin;
}
// https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin
return ['://', ''].includes(params.context.origin)
? 'null'
: params.context.origin;
}

#documentChanged(loaderId?: Protocol.Network.LoaderId) {
// Same document navigation.
if (loaderId === undefined || this.#loaderId === loaderId) {
Expand Down Expand Up @@ -1001,6 +1001,14 @@ export class BrowsingContextImpl {
}
}

export function serializeOrigin(origin: string) {
// https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin
if (['://', ''].includes(origin)) {
origin = 'null';
}
return origin;
}

function getImageFormatParameters(
params: Readonly<BrowsingContext.CaptureScreenshotParameters>
) {
Expand Down
170 changes: 109 additions & 61 deletions src/bidiMapper/domains/context/BrowsingContextProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ import type {ICdpClient} from '../../../cdp/CdpClient.js';
import type {ICdpConnection} from '../../../cdp/CdpConnection.js';
import {
BrowsingContext,
type EmptyResult,
InvalidArgumentException,
type EmptyResult,
} from '../../../protocol/protocol.js';
import {CdpErrorConstants} from '../../../utils/CdpErrorConstants.js';
import {type LoggerFn, LogType} from '../../../utils/log.js';
import {LogType, type LoggerFn} from '../../../utils/log.js';
import type {EventManager} from '../events/EventManager.js';
import type {NetworkStorage} from '../network/NetworkStorage.js';
import type {PreloadScriptStorage} from '../script/PreloadScriptStorage.js';
import {Realm} from '../script/Realm.js';
import type {RealmStorage} from '../script/RealmStorage.js';

import {BrowsingContextImpl} from './BrowsingContextImpl.js';
import {BrowsingContextImpl, serializeOrigin} from './BrowsingContextImpl.js';
import type {BrowsingContextStorage} from './BrowsingContextStorage.js';
import {CdpTarget} from './CdpTarget.js';

Expand Down Expand Up @@ -335,89 +336,136 @@ export class BrowsingContextProcessor {
parentSessionCdpClient: ICdpClient
) {
const {sessionId, targetInfo} = params;

const targetCdpClient = this.#cdpConnection.getCdpClient(sessionId);

if (!this.#isValidTarget(targetInfo)) {
// DevTools or some other not supported by BiDi target. Just release
// debugger and ignore them.
targetCdpClient
.sendCommand('Runtime.runIfWaitingForDebugger')
.then(() =>
parentSessionCdpClient.sendCommand('Target.detachFromTarget', params)
)
.catch((error) => this.#logger?.(LogType.debugError, error));
return;
}

this.#logger?.(
LogType.debugInfo,
'AttachedToTarget event received:',
params
);

this.#setEventListeners(targetCdpClient);
switch (targetInfo.type) {
case 'page':
case 'iframe': {
if (targetInfo.targetId === this.#selfTargetId) {
break;
}

this.#setEventListeners(targetCdpClient);

const cdpTarget = CdpTarget.create(
targetInfo.targetId,
targetCdpClient,
this.#browserCdpClient,
sessionId,
this.#realmStorage,
this.#eventManager,
this.#preloadScriptStorage,
this.#networkStorage,
this.#acceptInsecureCerts
);

const maybeContext = this.#browsingContextStorage.findContext(
targetInfo.targetId
);
const maybeContext = this.#browsingContextStorage.findContext(
targetInfo.targetId
);
if (maybeContext) {
// OOPiF.
maybeContext.updateCdpTarget(cdpTarget);
} else {
// New context.
BrowsingContextImpl.create(
cdpTarget,
this.#realmStorage,
targetInfo.targetId,
null,
this.#eventManager,
this.#browsingContextStorage,
this.#logger
);
}
return;
}
case 'worker': {
this.#setEventListeners(targetCdpClient);

const cdpTarget = CdpTarget.create(
targetInfo.targetId,
targetCdpClient,
this.#browserCdpClient,
sessionId,
this.#realmStorage,
this.#eventManager,
this.#preloadScriptStorage,
this.#networkStorage,
this.#acceptInsecureCerts
);

const cdpTarget = CdpTarget.create(
targetInfo.targetId,
targetCdpClient,
this.#browserCdpClient,
sessionId,
this.#realmStorage,
this.#eventManager,
this.#preloadScriptStorage,
this.#networkStorage,
this.#acceptInsecureCerts
);
this.#handleWorkerTarget(cdpTarget);
return;
}
}

if (maybeContext) {
// OOPiF.
maybeContext.updateCdpTarget(cdpTarget);
} else {
// New context.
BrowsingContextImpl.create(
cdpTarget,
// DevTools or some other not supported by BiDi target. Just release
// debugger and ignore them.
targetCdpClient
.sendCommand('Runtime.runIfWaitingForDebugger')
.then(() =>
parentSessionCdpClient.sendCommand('Target.detachFromTarget', params)
)
.catch((error) => this.#logger?.(LogType.debugError, error));
}

#workers = new Map<string, Realm>();
jrandolf-2 marked this conversation as resolved.
Show resolved Hide resolved
#handleWorkerTarget(cdpTarget: CdpTarget) {
cdpTarget.cdpClient.on('Runtime.executionContextCreated', (params) => {
const {uniqueId, id, origin} = params.context;
const realm = new Realm(
this.#realmStorage,
targetInfo.targetId,
null,
this.#eventManager,
this.#browsingContextStorage,
uniqueId,
cdpTarget.targetId,
id,
serializeOrigin(origin),
'dedicated-worker',
undefined,
cdpTarget.cdpClient,
this.#eventManager,
this.#logger
);
}
this.#workers.set(cdpTarget.cdpSessionId, realm);
});
}

#handleDetachedFromTargetEvent(
params: Protocol.Target.DetachedFromTargetEvent
) {
// XXX: params.targetId is deprecated. Update this class to track using
// params.sessionId instead.
// https://github.com/GoogleChromeLabs/chromium-bidi/issues/60
const contextId = params.targetId!;
this.#browsingContextStorage.findContext(contextId)?.dispose();

this.#preloadScriptStorage
.find({targetId: contextId})
.map((preloadScript) => preloadScript.dispose(contextId));
const context = this.#browsingContextStorage
.getAllContexts()
.find((context) => context.cdpTarget.cdpSessionId === params.sessionId);
if (context) {
context.dispose();
this.#preloadScriptStorage
.find({targetId: context.id})
.map((preloadScript) => preloadScript.dispose(context.id));
return;
}

const worker = this.#workers.get(params.sessionId);
if (worker) {
this.#realmStorage.deleteRealms({
cdpSessionId: worker.cdpClient.sessionId,
});
}
}

#handleTargetInfoChangedEvent(
params: Protocol.Target.TargetInfoChangedEvent
) {
const contextId = params.targetInfo.targetId;
this.#browsingContextStorage
.findContext(contextId)
?.onTargetInfoChanged(params);
}
jrandolf-2 marked this conversation as resolved.
Show resolved Hide resolved

#isValidTarget(target: Protocol.Target.TargetInfo) {
if (target.targetId === this.#selfTargetId) {
return false;
const context = this.#browsingContextStorage.findContext(
params.targetInfo.targetId
);
if (context) {
context.onTargetInfoChanged(params);
}
return ['page', 'iframe'].includes(target.type);
}
}
52 changes: 51 additions & 1 deletion tests/script/test_realm.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import pytest
from anys import ANY_STR
from test_helpers import (execute_command, read_JSON_message,
send_JSON_command, subscribe)
send_JSON_command, subscribe, wait_for_event)


@pytest.mark.asyncio
Expand Down Expand Up @@ -149,3 +149,53 @@ async def test_realm_realmDestroyed_sandbox(websocket, context_id):
"realm": ANY_STR,
}
} == response


@pytest.mark.asyncio
async def test_realm_worker(websocket, context_id, html):
worker_url = 'data:application/javascript,while(true){}'
url = html(f"<script>window.w = new Worker('{worker_url}');</script>")

await subscribe(websocket,
["script.realmDestroyed", "script.realmCreated"])

await send_JSON_command(
websocket, {
"method": "browsingContext.navigate",
"params": {
"context": context_id,
"url": url,
"wait": "complete",
}
})

# Wait for worker to be created
while True:
message = await wait_for_event(websocket, "script.realmCreated")
if message["params"] == {
"realm": ANY_STR,
"origin": worker_url,
"context": ANY_STR,
"type": "dedicated-worker"
}:
realm = message["params"]["realm"]
break

# Then demolish it!
await send_JSON_command(
websocket, {
"method": "script.evaluate",
"params": {
"target": {
"context": context_id
},
"expression": "window.w.terminate()",
"awaitPromise": True
}
})

# Wait for confirmation that worker was destroyed
while True:
jrandolf-2 marked this conversation as resolved.
Show resolved Hide resolved
message = await wait_for_event(websocket, "script.realmDestroyed")
if message["params"] == {"realm": realm}:
break
Loading