Skip to content

Commit de72d10

Browse files
feat: Extend browser.createUserContext with unhandledPromptBehavior parameter (#3440)
Spec: w3c/webdriver-bidi#896
1 parent d05c4cd commit de72d10

File tree

6 files changed

+121
-2
lines changed

6 files changed

+121
-2
lines changed

src/bidiMapper/modules/browser/BrowserProcessor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ export class BrowserProcessor {
110110
context.browserContextId,
111111
).acceptInsecureCerts = params['acceptInsecureCerts'];
112112

113+
this.#userContextStorage.getConfig(
114+
context.browserContextId,
115+
).userPromptHandler = params['unhandledPromptBehavior'];
116+
113117
return {
114118
userContext: context.browserContextId,
115119
};

src/bidiMapper/modules/browser/UserContextConfig.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
* limitations under the License.
1616
*/
1717

18-
import type {BrowsingContext, Emulation} from '../../../protocol/protocol.js';
18+
import type {
19+
BrowsingContext,
20+
Emulation,
21+
Session,
22+
} from '../../../protocol/protocol.js';
1923

2024
/**
2125
* Represents a user context configurations. Each new CDP target of the given user context
@@ -35,6 +39,7 @@ export class UserContextConfig {
3539
| null;
3640
locale?: string | null;
3741
screenOrientation?: Emulation.ScreenOrientation | null;
42+
userPromptHandler?: Session.UserPromptHandler;
3843

3944
constructor(userContextId: string) {
4045
this.userContextId = userContextId;

src/bidiMapper/modules/cdp/CdpTarget.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ export class CdpTarget {
308308
frame.id,
309309
frame.parentId,
310310
parentBrowsingContext.userContext,
311+
this.#userContextConfig,
311312
parentBrowsingContext.cdpTarget,
312313
this.#eventManager,
313314
this.#browsingContextStorage,

src/bidiMapper/modules/cdp/CdpTargetManager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export class CdpTargetManager {
135135
params.frameId,
136136
params.parentFrameId,
137137
parentBrowsingContext.userContext,
138+
this.#userContextStorage.getConfig(parentBrowsingContext.userContext),
138139
parentBrowsingContext.cdpTarget,
139140
this.#eventManager,
140141
this.#browsingContextStorage,
@@ -245,6 +246,7 @@ export class CdpTargetManager {
245246
targetInfo.targetId,
246247
parentId,
247248
userContext,
249+
this.#userContextStorage.getConfig(userContext),
248250
cdpTarget,
249251
this.#eventManager,
250252
this.#browsingContextStorage,

src/bidiMapper/modules/context/BrowsingContextImpl.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {type LoggerFn, LogType} from '../../../utils/log.js';
3737
import {getTimestamp} from '../../../utils/time.js';
3838
import {inchesFromCm} from '../../../utils/unitConversions.js';
3939
import {uuidv4} from '../../../utils/uuid.js';
40+
import type {UserContextConfig} from '../browser/UserContextConfig.js';
4041
import type {CdpTarget} from '../cdp/CdpTarget.js';
4142
import type {Realm} from '../script/Realm.js';
4243
import type {RealmStorage} from '../script/RealmStorage.js';
@@ -86,6 +87,7 @@ export class BrowsingContextImpl {
8687
readonly #realmStorage: RealmStorage;
8788
// The deferred will be resolved when the default realm is created.
8889
readonly #unhandledPromptBehavior?: Session.UserPromptHandler;
90+
readonly #userContextConfig: UserContextConfig;
8991

9092
// Set when the user prompt is opened. Required to provide the type in closing event.
9193
#lastUserPromptType?: BrowsingContext.UserPromptType;
@@ -94,6 +96,7 @@ export class BrowsingContextImpl {
9496
id: BrowsingContext.BrowsingContext,
9597
parentId: BrowsingContext.BrowsingContext | null,
9698
userContext: string,
99+
userContextConfig: UserContextConfig,
97100
cdpTarget: CdpTarget,
98101
eventManager: EventManager,
99102
browsingContextStorage: BrowsingContextStorage,
@@ -103,6 +106,7 @@ export class BrowsingContextImpl {
103106
unhandledPromptBehavior?: Session.UserPromptHandler,
104107
logger?: LoggerFn,
105108
) {
109+
this.#userContextConfig = userContextConfig;
106110
this.#cdpTarget = cdpTarget;
107111
this.#id = id;
108112
this.#parentId = parentId;
@@ -129,6 +133,7 @@ export class BrowsingContextImpl {
129133
id: BrowsingContext.BrowsingContext,
130134
parentId: BrowsingContext.BrowsingContext | null,
131135
userContext: string,
136+
userContextConfig: UserContextConfig,
132137
cdpTarget: CdpTarget,
133138
eventManager: EventManager,
134139
browsingContextStorage: BrowsingContextStorage,
@@ -142,6 +147,7 @@ export class BrowsingContextImpl {
142147
id,
143148
parentId,
144149
userContext,
150+
userContextConfig,
145151
cdpTarget,
146152
eventManager,
147153
browsingContextStorage,
@@ -890,19 +896,27 @@ export class BrowsingContextImpl {
890896
}
891897
}
892898

899+
/**
900+
* Returns either custom UserContext's prompt handler, global or default one.
901+
*/
893902
#getPromptHandler(
894903
promptType: BrowsingContext.UserPromptType,
895904
): Session.UserPromptHandlerType {
896905
const defaultPromptHandler = Session.UserPromptHandlerType.Dismiss;
906+
897907
switch (promptType) {
898908
case BrowsingContext.UserPromptType.Alert:
899909
return (
910+
this.#userContextConfig.userPromptHandler?.alert ??
911+
this.#userContextConfig.userPromptHandler?.default ??
900912
this.#unhandledPromptBehavior?.alert ??
901913
this.#unhandledPromptBehavior?.default ??
902914
defaultPromptHandler
903915
);
904916
case BrowsingContext.UserPromptType.Beforeunload:
905917
return (
918+
this.#userContextConfig.userPromptHandler?.beforeUnload ??
919+
this.#userContextConfig.userPromptHandler?.default ??
906920
this.#unhandledPromptBehavior?.beforeUnload ??
907921
this.#unhandledPromptBehavior?.default ??
908922
// In WebDriver Classic spec, `beforeUnload` prompt should be accepted by
@@ -914,12 +928,16 @@ export class BrowsingContextImpl {
914928
);
915929
case BrowsingContext.UserPromptType.Confirm:
916930
return (
931+
this.#userContextConfig.userPromptHandler?.confirm ??
932+
this.#userContextConfig.userPromptHandler?.default ??
917933
this.#unhandledPromptBehavior?.confirm ??
918934
this.#unhandledPromptBehavior?.default ??
919935
defaultPromptHandler
920936
);
921937
case BrowsingContext.UserPromptType.Prompt:
922938
return (
939+
this.#userContextConfig.userPromptHandler?.prompt ??
940+
this.#userContextConfig.userPromptHandler?.default ??
923941
this.#unhandledPromptBehavior?.prompt ??
924942
this.#unhandledPromptBehavior?.default ??
925943
defaultPromptHandler

tests/browser/test_create_user_context.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
import pytest
1717
from anys import ANY_STR
1818
from test_helpers import (ANY_UUID, AnyExtending, execute_command,
19-
read_JSON_message, send_JSON_command)
19+
read_JSON_message, send_JSON_command, subscribe,
20+
wait_for_event)
2021

2122

2223
@pytest.mark.asyncio
@@ -255,3 +256,91 @@ async def test_browser_create_user_context_accept_insecure_certs_respected(
255256
'message': 'net::ERR_CERT_AUTHORITY_INVALID',
256257
'type': 'error',
257258
})
259+
260+
261+
# All the combinations will be tested in WPT, keep only a few cases here to save
262+
# runtime.
263+
@pytest.mark.asyncio
264+
@pytest.mark.parametrize("behavior", [None, "accept"])
265+
@pytest.mark.parametrize("default", [None, "ignore"])
266+
@pytest.mark.parametrize("prompt_type", ["alert"])
267+
async def test_browser_create_user_context_unhandled_prompt_behavior(
268+
websocket, prompt_type, behavior, default, create_context):
269+
PROMPT_MESSAGE = "SOME MESSAGE"
270+
271+
unhandled_prompt_behavior = {}
272+
273+
if behavior is not None:
274+
unhandled_prompt_behavior[prompt_type] = behavior
275+
if default is not None:
276+
unhandled_prompt_behavior["default"] = default
277+
278+
user_context = await execute_command(
279+
websocket, {
280+
"method": "browser.createUserContext",
281+
"params": {
282+
"unhandledPromptBehavior": unhandled_prompt_behavior
283+
}
284+
})
285+
286+
context_id = await create_context(
287+
user_context_id=user_context["userContext"])
288+
289+
await subscribe(websocket, [
290+
"browsingContext.userPromptOpened", "browsingContext.userPromptClosed"
291+
])
292+
await send_JSON_command(
293+
websocket, {
294+
"method": "script.evaluate",
295+
"params": {
296+
"expression": f"""{prompt_type}('{PROMPT_MESSAGE}')""",
297+
"awaitPromise": True,
298+
"target": {
299+
"context": context_id,
300+
}
301+
}
302+
})
303+
response = await wait_for_event(websocket,
304+
"browsingContext.userPromptOpened")
305+
if behavior is not None:
306+
expected_handler = behavior
307+
elif default is not None:
308+
expected_handler = default
309+
else:
310+
expected_handler = 'dismiss'
311+
312+
assert response == {
313+
'type': 'event',
314+
"method": "browsingContext.userPromptOpened",
315+
"params": {
316+
"context": context_id,
317+
"type": prompt_type,
318+
'handler': expected_handler,
319+
"message": PROMPT_MESSAGE,
320+
**({
321+
"defaultValue": ""
322+
} if prompt_type == "prompt" else {}),
323+
}
324+
}
325+
326+
if expected_handler == 'ignore':
327+
# Dismiss the prompt manually.
328+
await send_JSON_command(
329+
websocket, {
330+
"method": "browsingContext.handleUserPrompt",
331+
"params": {
332+
"context": context_id,
333+
"accept": False
334+
}
335+
})
336+
response = await wait_for_event(websocket,
337+
"browsingContext.userPromptClosed")
338+
assert response == {
339+
'type': 'event',
340+
"method": "browsingContext.userPromptClosed",
341+
"params": {
342+
"context": context_id,
343+
"accepted": expected_handler == 'accept',
344+
"type": prompt_type
345+
}
346+
}

0 commit comments

Comments
 (0)