From 3a467c39cb60de4237081ee201bd86051887c2f2 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Fri, 23 Feb 2024 10:16:58 +0100 Subject: [PATCH] feat: implement permissions for WebDriver BiDi (#11979) --- docs/webdriver-bidi.md | 10 ++-- .../puppeteer-core/src/bidi/BrowserContext.ts | 59 +++++++++++++++++-- .../src/bidi/core/Connection.ts | 5 ++ .../src/bidi/core/UserContext.ts | 18 ++++++ test/TestExpectations.json | 36 ++++------- 5 files changed, 94 insertions(+), 34 deletions(-) diff --git a/docs/webdriver-bidi.md b/docs/webdriver-bidi.md index d223b31d65c..ad906e1edfa 100644 --- a/docs/webdriver-bidi.md +++ b/docs/webdriver-bidi.md @@ -94,6 +94,11 @@ This is an exciting step towards a more unified and efficient cross-browser auto - Page.pdf (only `format`, `height`, `landscape`, `margin`, `pageRanges`, `printBackground`, `scale`, `width` are supported) - Page.createPDFStream (only `format`, `height`, `landscape`, `margin`, `pageRanges`, `printBackground`, `scale`, `width` are supported) +- Permissions (Supported in Chrome only) + + - BrowserContext.clearPermissionOverrides() + - BrowserContext.overridePermissions() + ## Puppeteer features not yet supported over WebDriver BiDi - [Request interception](https://pptr.dev/guides/request-interception) @@ -111,11 +116,6 @@ This is an exciting step towards a more unified and efficient cross-browser auto - HTTPRequest.responseForRequest() - Page.setRequestInterception() -- Permissions - - - BrowserContext.clearPermissionOverrides() - - BrowserContext.overridePermissions() - - Various emulations (most are supported with Chrome) - Page.emulate() (supported only in Chrome) diff --git a/packages/puppeteer-core/src/bidi/BrowserContext.ts b/packages/puppeteer-core/src/bidi/BrowserContext.ts index c6df16f45a9..9976e4cc6a5 100644 --- a/packages/puppeteer-core/src/bidi/BrowserContext.ts +++ b/packages/puppeteer-core/src/bidi/BrowserContext.ts @@ -6,11 +6,12 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; +import type {Permission} from '../api/Browser.js'; +import {WEB_PERMISSION_TO_PROTOCOL_PERMISSION} from '../api/Browser.js'; import type {BrowserContextEvents} from '../api/BrowserContext.js'; import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js'; import {PageEvent, type Page} from '../api/Page.js'; import type {Target} from '../api/Target.js'; -import {UnsupportedOperation} from '../common/Errors.js'; import {EventEmitter} from '../common/EventEmitter.js'; import {debugError} from '../common/util.js'; import type {Viewport} from '../common/Viewport.js'; @@ -62,6 +63,8 @@ export class BidiBrowserContext extends BrowserContext { ] >(); + #overrides: Array<{origin: string; permission: Permission}> = []; + private constructor( browser: BidiBrowser, userContext: UserContext, @@ -202,12 +205,58 @@ export class BidiBrowserContext extends BrowserContext { return this.userContext.id !== UserContext.DEFAULT; } - override overridePermissions(): never { - throw new UnsupportedOperation(); + override async overridePermissions( + origin: string, + permissions: Permission[] + ): Promise { + const permissionsSet = new Set( + permissions.map(permission => { + const protocolPermission = + WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission); + if (!protocolPermission) { + throw new Error('Unknown permission: ' + permission); + } + return permission; + }) + ); + await Promise.all( + Array.from(WEB_PERMISSION_TO_PROTOCOL_PERMISSION.keys()).map( + permission => { + const result = this.userContext.setPermissions( + origin, + { + name: permission, + }, + permissionsSet.has(permission) + ? Bidi.Permissions.PermissionState.Granted + : Bidi.Permissions.PermissionState.Denied + ); + this.#overrides.push({origin, permission}); + // TODO: some permissions are outdated and setting them to denied does + // not work. + if (!permissionsSet.has(permission)) { + return result.catch(debugError); + } + return result; + } + ) + ); } - override clearPermissionOverrides(): never { - throw new UnsupportedOperation(); + override async clearPermissionOverrides(): Promise { + const promises = this.#overrides.map(({permission, origin}) => { + return this.userContext + .setPermissions( + origin, + { + name: permission, + }, + Bidi.Permissions.PermissionState.Prompt + ) + .catch(debugError); + }); + this.#overrides = []; + await Promise.all(promises); } override get id(): string | undefined { diff --git a/packages/puppeteer-core/src/bidi/core/Connection.ts b/packages/puppeteer-core/src/bidi/core/Connection.ts index 0c02d468500..528409d5e69 100644 --- a/packages/puppeteer-core/src/bidi/core/Connection.ts +++ b/packages/puppeteer-core/src/bidi/core/Connection.ts @@ -107,6 +107,11 @@ export interface Commands { returnType: Bidi.EmptyResult; }; + 'permissions.setPermission': { + params: Bidi.Permissions.SetPermissionParameters; + returnType: Bidi.EmptyResult; + }; + 'session.end': { params: Bidi.EmptyParams; returnType: Bidi.EmptyResult; diff --git a/packages/puppeteer-core/src/bidi/core/UserContext.ts b/packages/puppeteer-core/src/bidi/core/UserContext.ts index b1c7e6eceb6..72859c6a53e 100644 --- a/packages/puppeteer-core/src/bidi/core/UserContext.ts +++ b/packages/puppeteer-core/src/bidi/core/UserContext.ts @@ -214,6 +214,24 @@ export class UserContext extends EventEmitter<{ }); } + @throwIfDisposed(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) + async setPermissions( + origin: string, + descriptor: Bidi.Permissions.PermissionDescriptor, + state: Bidi.Permissions.PermissionState + ): Promise { + await this.#session.send('permissions.setPermission', { + origin, + descriptor, + state, + // @ts-expect-error not standard implementation. + 'goog:userContext': this.#id, + }); + } + [disposeSymbol](): void { this.#reason ??= 'User context already closed, probably because the browser disconnected/closed.'; diff --git a/test/TestExpectations.json b/test/TestExpectations.json index 8030fb10ccd..4d1368f1333 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -17,12 +17,6 @@ "parameters": ["webDriverBiDi"], "expectations": ["SKIP"] }, - { - "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[device-request-prompt.spec] *", "platforms": ["darwin", "linux", "win32"], @@ -233,12 +227,6 @@ "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should be prompt by default", "platforms": ["darwin", "linux", "win32"], @@ -704,12 +692,6 @@ "parameters": ["webDriverBiDi"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[page.spec] Page Page.setGeolocation should work", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[page.spec] Page Page.setOfflineMode should emulate navigator.onLine", "platforms": ["darwin", "linux", "win32"], @@ -922,6 +904,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should fail when bad permission is given", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant permission when listed", "platforms": ["darwin", "linux", "win32"], @@ -1572,12 +1560,6 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, - { - "testIdPattern": "[idle_override.spec] Emulate idle state changing idle state emulation causes change of the IdleDetector state", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors Response.securityDetails Network redirects should report SecurityDetails", "platforms": ["darwin", "linux", "win32"], @@ -2937,6 +2919,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[page.spec] Page Page.setGeolocation should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[page.spec] Page Page.setGeolocation should work", "platforms": ["darwin", "linux", "win32"],