Skip to content

Commit

Permalink
changes from last review
Browse files Browse the repository at this point in the history
  • Loading branch information
Thiago Perrotta committed Mar 22, 2023
1 parent ed2be2c commit 00e4cc6
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 57 deletions.
107 changes: 57 additions & 50 deletions src/bidiMapper/domains/script/scriptEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,57 @@ import {IEventManager} from '../events/EventManager.js';

import {Realm} from './realm.js';

async function longPoll(
channel: Script.Channel,
channelHandle: string | undefined,
eventManager: IEventManager,
realm: Realm
) {
const channelId = channel.value.channel;

// TODO(#294): Remove this loop after the realm is destroyed.
// Rely on the CDP throwing exception in such a case.
for (;;) {
const message = await realm.cdpClient.sendCommand(
'Runtime.callFunctionOn',
{
functionDeclaration: String(
async (channelHandle: {
getMessage: () => Promise<unknown>;
sendMessage: (message: string) => void;
}) => channelHandle.getMessage()
),
arguments: [
{
objectId: channelHandle,
},
],
awaitPromise: true,
executionContextId: realm.executionContextId,
generateWebDriverValue: true,
}
);

eventManager.registerPromiseEvent(
realm
.cdpToBidiValue(message, channel.value.ownership ?? 'none')
.then((data) => ({
method: Script.EventNames.MessageEvent,
params: {
channel: channelId,
data,
source: {
realm: realm.realmId,
context: realm.browsingContextId,
},
},
})),
realm.browsingContextId,
Script.EventNames.MessageEvent
);
}
}

// As `script.evaluate` wraps call into serialization script, `lineNumber`
// should be adjusted.
const CALL_FUNCTION_STACKTRACE_LINE_OFFSET = 1;
Expand Down Expand Up @@ -402,14 +453,12 @@ export class ScriptEvaluator {
}

case 'channel': {
const channelName = argumentValue.value.channel;

const argEvalResult = await realm.cdpClient.sendCommand(
'Runtime.callFunctionOn',
{
functionDeclaration: String(() => {
const queue: unknown[] = [];
let resolver: null | (() => void) = null;
let queueNonEmptyResolver: null | (() => void) = null;

return {
/**
Expand All @@ -421,7 +470,7 @@ export class ScriptEvaluator {
queue.length > 0
? Promise.resolve()
: new Promise<void>((resolve) => {
resolver = resolve;
queueNonEmptyResolver = resolve;
});
await onMessage;
return queue.shift();
Expand All @@ -433,9 +482,9 @@ export class ScriptEvaluator {
*/
sendMessage(message: string) {
queue.push(message);
if (resolver !== null) {
resolver();
resolver = null;
if (queueNonEmptyResolver !== null) {
queueNonEmptyResolver();
queueNonEmptyResolver = null;
}
},
};
Expand All @@ -448,49 +497,7 @@ export class ScriptEvaluator {
const channelHandle = argEvalResult.result.objectId;

// Long-poll the message queue asynchronously.
(async () => {
// TODO: Remove this loop after the realm is destroyed.
// Rely on the CDP throwing exception in such a case.
for (;;) {
const message = await realm.cdpClient.sendCommand(
'Runtime.callFunctionOn',
{
functionDeclaration: String(
async (channelHandle: {
getMessage: () => Promise<unknown>;
sendMessage: (message: string) => void;
}) => channelHandle.getMessage()
),
arguments: [
{
objectId: channelHandle,
},
],
awaitPromise: true,
executionContextId: realm.executionContextId,
generateWebDriverValue: true,
}
);

this.#eventManager.registerEvent(
{
method: Script.EventNames.MessageEvent,
params: {
channel: channelName,
data: await realm.cdpToBidiValue(
message,
argumentValue.value.ownership ?? 'none'
),
source: {
realm: realm.realmId,
context: realm.browsingContextId,
},
},
},
realm.browsingContextId
);
}
})();
longPoll(argumentValue, channelHandle, this.#eventManager, realm);

const sendMessageArgResult = await realm.cdpClient.sendCommand(
'Runtime.callFunctionOn',
Expand Down
10 changes: 6 additions & 4 deletions src/protocol-parser/protocol-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ export namespace CommonDataTypes {

// BrowsingContext = text;
export const BrowsingContextSchema = zod.string();

export const MaxDepthSchema = zod.number().int().nonnegative().max(MAX_INT);
}

/** @see https://w3c.github.io/webdriver-bidi/#module-script */
Expand Down Expand Up @@ -306,7 +308,9 @@ export namespace Script {

const ChannelPropertiesSchema = zod.object({
channel: ChannelIdSchema,
maxDepth: zod.number().int().nonnegative().max(MAX_INT).optional(),
// TODO(#294): maxDepth: CommonDataTypes.MaxDepthSchema.optional(),
// See: https://github.com/w3c/webdriver-bidi/pull/361/files#r1141961142
maxDepth: zod.number().int().min(1).max(1).optional(),
ownership: ResultOwnershipSchema.optional(),
});

Expand Down Expand Up @@ -358,9 +362,7 @@ export namespace BrowsingContext {
// ?root: browsingContext.BrowsingContext,
// }
const GetTreeParametersSchema = zod.object({
// TODO(##294): maxDepth: zod.number().int().nonnegative().max(MAX_INT).optional(),
// See: https://github.com/w3c/webdriver-bidi/pull/361/files#r1141961142
maxDepth: zod.number().int().min(1).max(1).optional(),
maxDepth: CommonDataTypes.MaxDepthSchema.optional(),
root: CommonDataTypes.BrowsingContextSchema.optional(),
});

Expand Down
76 changes: 73 additions & 3 deletions tests/test_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@
from test_helpers import execute_command, read_JSON_message, subscribe


# TODO(#294): Implement E2E tests.
@pytest.mark.asyncio
async def test_channel(websocket, context_id):
async def test_channel_twoMessageEvents(websocket, context_id):
await subscribe(websocket, "script.message")

# The channel was successfully created if there's no thrown exception.
await execute_command(
websocket,
{
Expand Down Expand Up @@ -84,3 +82,75 @@ async def test_channel(websocket, context_id):
}
}
}


@pytest.mark.asyncio
@pytest.mark.parametrize(
"test_input,expected",
[(1, {
'type': 'number',
'value': 1
}), ("2n", {
'type': 'bigint',
'value': '2'
}), ('"3"', {
'type': 'string',
'value': '3'
}),
("{'a': 'x', b: 'y'}", {
'handle': ANY_STR,
'type': 'object',
'value': [['a', {
'type': 'string',
'value': 'x'
}], ['b', {
'type': 'string',
'value': 'y'
}]]
}), ("() => {}", {
'handle': ANY_STR,
'type': 'function'
})])
async def test_channel_complexTypes(test_input, expected, websocket,
context_id):
await subscribe(websocket, "script.message")

await execute_command(
websocket,
{
"method": "script.callFunction",
"params": {
# A small delay is needed to avoid a race condition.
"functionDeclaration": """(binding) => {
setTimeout(() => {"""
f'binding({test_input});\n'
"""}, 1);
}""",
"arguments": [{
"type": "channel",
"value": {
"channel": "MY_CHANNEL",
"ownership": "root",
},
}],
"target": {
"context": context_id
},
"awaitPromise": False,
"resultOwnership": "root"
}
})

resp = await read_JSON_message(websocket)

assert resp == {
"method": "script.message",
"params": {
"channel": "MY_CHANNEL",
"data": expected,
"source": {
"context": context_id,
"realm": ANY_STR,
}
}
}

0 comments on commit 00e4cc6

Please sign in to comment.