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 evaluate for dedicated workers #1625

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
22 changes: 16 additions & 6 deletions src/bidiMapper/domains/context/BrowsingContextProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,17 @@ export class BrowsingContextProcessor {
this.#acceptInsecureCerts
);

this.#handleWorkerTarget(cdpTarget);
const browsingContext =
parentSessionCdpClient.sessionId &&
this.#browsingContextStorage.findContextBySession(
parentSessionCdpClient.sessionId
);
// If there is no browsing context, this worker is already terminated.
if (!browsingContext) {
break;
}

this.#handleWorkerTarget(cdpTarget, browsingContext.id);
return;
}
}
Expand All @@ -416,14 +426,14 @@ export class BrowsingContextProcessor {
}

#workers = new Map<string, Realm>();
#handleWorkerTarget(cdpTarget: CdpTarget) {
#handleWorkerTarget(cdpTarget: CdpTarget, browsingContextId: string) {
cdpTarget.cdpClient.on('Runtime.executionContextCreated', (params) => {
const {uniqueId, id, origin} = params.context;
const realm = new Realm(
this.#realmStorage,
this.#browsingContextStorage,
uniqueId,
cdpTarget.targetId,
browsingContextId,
id,
serializeOrigin(origin),
'dedicated-worker',
Expand All @@ -439,9 +449,9 @@ export class BrowsingContextProcessor {
#handleDetachedFromTargetEvent(
params: Protocol.Target.DetachedFromTargetEvent
) {
const context = this.#browsingContextStorage
.getAllContexts()
.find((context) => context.cdpTarget.cdpSessionId === params.sessionId);
const context = this.#browsingContextStorage.findContextBySession(
params.sessionId
);
if (context) {
context.dispose();
this.#preloadScriptStorage
Expand Down
9 changes: 9 additions & 0 deletions src/bidiMapper/domains/context/BrowsingContextStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ export class BrowsingContextStorage {
return this.findTopLevelContextId(parentId);
}

findContextBySession(sessionId: string): BrowsingContextImpl | undefined {
for (const context of this.#contexts.values()) {
if (context.cdpTarget.cdpSessionId === sessionId) {
return context;
}
}
return;
}

/** Gets the context with the given ID, if any, otherwise throws. */
getContext(id: BrowsingContext.BrowsingContext): BrowsingContextImpl {
const result = this.findContext(id);
Expand Down
29 changes: 20 additions & 9 deletions src/bidiMapper/domains/script/Realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,10 @@ export class Realm {

get navigableId(): string {
return (
this.#browsingContextStorage.findContext(this.#browsingContextId)
?.navigableId ?? 'UNKNOWN'
(this.browsingContextId &&
this.#browsingContextStorage.findContext(this.browsingContextId)
?.navigableId) ??
'UNKNOWN'
);
}

Expand All @@ -250,13 +252,22 @@ export class Realm {
}

get realmInfo(): Script.RealmInfo {
return {
realm: this.realmId,
origin: this.origin,
type: this.type,
context: this.browsingContextId,
...(this.sandbox === undefined ? {} : {sandbox: this.sandbox}),
};
switch (this.type) {
case 'window':
return {
realm: this.realmId,
origin: this.origin,
type: this.type,
context: this.browsingContextId,
...(this.sandbox === undefined ? {} : {sandbox: this.sandbox}),
};
default:
return {
realm: this.realmId,
origin: this.origin,
type: this.type,
};
}
}

async evaluate(
Expand Down
10 changes: 10 additions & 0 deletions tests/script/__snapshots__/test_evaluate.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# serializer version: 1
# name: test_scriptEvaluate_dedicated_worker[websocket0]
dict({
'result': dict({
'type': 'string',
'value': 'hello world',
}),
'type': 'success',
})
# ---
64 changes: 63 additions & 1 deletion tests/script/test_evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

import pytest
from anys import ANY_STR
from test_helpers import execute_command, get_tree
from syrupy.filters import props
from test_helpers import (execute_command, get_tree, send_JSON_command,
subscribe, wait_for_command, wait_for_event)


@pytest.mark.asyncio
Expand Down Expand Up @@ -395,3 +397,63 @@ async def test_scriptEvaluate_realm(websocket, context_id):
"exceptionDetails": ANY,
"realm": realm
} == result


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

await subscribe(websocket, ["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,
"type": "dedicated-worker"
}:
realm = message["params"]["realm"]
break

# Set up a listener on the page.
command_id = await send_JSON_command(
websocket, {
"method": "script.evaluate",
"params": {
"target": {
"context": context_id
},
"expression": "new Promise(resolve => window.w.addEventListener('message', ({data}) => resolve(data), {once: true}))",
"awaitPromise": True
}
})

# Post a message from the worker.
await send_JSON_command(
websocket, {
"method": "script.evaluate",
"params": {
"target": {
"realm": realm
},
"expression": "self.postMessage('hello world')",
"awaitPromise": True
}
})

# Check the promise
assert await wait_for_command(
websocket, command_id) == snapshot(exclude=props("realm"))
3 changes: 1 addition & 2 deletions tests/script/test_realm.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ async def test_realm_realmDestroyed_sandbox(websocket, context_id):


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

Expand All @@ -175,7 +175,6 @@ async def test_realm_worker(websocket, context_id, html):
if message["params"] == {
"realm": ANY_STR,
"origin": worker_url,
"context": ANY_STR,
"type": "dedicated-worker"
}:
realm = message["params"]["realm"]
Expand Down
6 changes: 5 additions & 1 deletion tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,14 @@ async def execute_command(websocket, command: dict) -> dict:
logger.info(
f"Executing command with method '{command['method']}' and params '{command['params']}'..."
)
return await wait_for_command(websocket, command["id"])


async def wait_for_command(websocket, command_id: int) -> dict:
while True:
# Wait for the command to be finished.
resp = await read_JSON_message(websocket)
if "id" in resp and resp["id"] == command["id"]:
if "id" in resp and resp["id"] == command_id:
if "result" in resp:
return resp["result"]
raise Exception({
Expand Down
Loading