Skip to content

Commit

Permalink
feat(permissions): make origin optional
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Mar 17, 2020
1 parent 6731d37 commit 643a166
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 39 deletions.
20 changes: 12 additions & 8 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Indicates that the browser is connected.
- `longitude` <[number]> Longitude between -180 and 180.
- `accuracy` <[number]> Optional non-negative accuracy value.
- `locale` <?[string]> Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules.
- `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details.
- `permissions` <[Array]<[string]>> A list of permissions to grant to all pages in this context. See [browserContext.grantPermissions](#browsercontextgrantpermissionspermissions-optoins) for more details.
- `extraHTTPHeaders` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
- `offline` <[boolean]> Whether to emulate network being offline. Defaults to `false`.
- `httpCredentials` <[Object]> Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
Expand Down Expand Up @@ -299,7 +299,7 @@ await context.close();
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [browserContext.grantPermissions(permissions[], options)](#browsercontextsetpermissionspermissions-options)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
<!-- GEN:stop -->

Expand Down Expand Up @@ -381,7 +381,7 @@ Clears all permission overrides for the browser context.

```js
const context = await browser.newContext();
context.setPermissions('https://example.com', ['clipboard-read']);
context.grantPermissions(['clipboard-read']);
// do stuff ..
context.clearPermissions();
```
Expand Down Expand Up @@ -544,9 +544,9 @@ To disable authentication, pass `null`.
- `offline` <[boolean]> Whether to emulate network being offline for the browser context.
- returns: <[Promise]>

#### browserContext.setPermissions(origin, permissions[])
- `origin` <[string]> The [origin] to grant permissions to, e.g. "https://example.com".
- `permissions` <[Array]<[string]>> An array of permissions to grant. All permissions that are not listed here will be automatically denied. Permissions can be one of the following values:
#### browserContext.grantPermissions(permissions[], options)
- `permissions` <[Array]<[string]>> A permission or an array of permissions to grant. Permissions can be one of the following values:
- `'*'`
- `'geolocation'`
- `'midi'`
- `'midi-sysex'` (system-exclusive midi)
Expand All @@ -563,8 +563,12 @@ To disable authentication, pass `null`.
- `'clipboard-read'`
- `'clipboard-write'`
- `'payment-handler'`
- `options` <[Object]>
- `origin` <[string]> The [origin] to grant permissions to, e.g. "https://example.com".
- returns: <[Promise]>

Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if specified.

#### browserContext.waitForEvent(event[, optionsOrPredicate])
- `event` <[string]> Event name, same one would pass into `browserContext.on(event)`.
- `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object.
Expand All @@ -577,7 +581,7 @@ is fired.

```js
const context = await browser.newContext();
await context.setPermissions('https://html5demos.com', ['geolocation']);
await context.grantPermissions(['geolocation']);
```

### class: Page
Expand Down Expand Up @@ -3793,7 +3797,7 @@ const backgroundPage = await backroundPageTarget.page();
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [browserContext.grantPermissions(permissions[], options)](#browsercontextsetpermissionspermissions-origin)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
<!-- GEN:stop -->

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"main": "index.js",
"playwright": {
"chromium_revision": "750417",
"firefox_revision": "1042",
"firefox_revision": "1043",
"webkit_revision": "1179"
},
"scripts": {
Expand Down Expand Up @@ -46,6 +46,7 @@
"extract-zip": "^1.6.6",
"https-proxy-agent": "^3.0.0",
"jpeg-js": "^0.3.6",
"playwright": "^0.11.1-next.1583993157193",
"pngjs": "^3.4.0",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0",
Expand Down
21 changes: 17 additions & 4 deletions src/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type BrowserContextOptions = {
locale?: string,
timezoneId?: string,
geolocation?: types.Geolocation,
permissions?: { [key: string]: string[] },
permissions?: string[],
extraHTTPHeaders?: network.Headers,
offline?: boolean,
httpCredentials?: types.Credentials,
Expand All @@ -46,7 +46,7 @@ export interface BrowserContext {
cookies(urls?: string | string[]): Promise<network.NetworkCookie[]>;
addCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>;
clearCookies(): Promise<void>;
setPermissions(origin: string, permissions: string[]): Promise<void>;
grantPermissions(permissions: string[], options?: { origin?: string }): Promise<void>;
clearPermissions(): Promise<void>;
setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
Expand All @@ -67,6 +67,7 @@ export abstract class BrowserContextBase extends platform.EventEmitter implement
_closed = false;
private readonly _closePromise: Promise<Error>;
private _closePromiseFulfill: ((error: Error) => void) | undefined;
private _permissions = new Map<string, string[]>();

constructor(options: BrowserContextOptions) {
super();
Expand All @@ -92,8 +93,7 @@ export abstract class BrowserContextBase extends platform.EventEmitter implement
abstract cookies(...urls: string[]): Promise<network.NetworkCookie[]>;
abstract addCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>;
abstract clearCookies(): Promise<void>;
abstract setPermissions(origin: string, permissions: string[]): Promise<void>;
abstract clearPermissions(): Promise<void>;
abstract _doGrantPermissions(origin: string, permissions: string[]): Promise<void>;
abstract setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
abstract setHTTPCredentials(httpCredentials: types.Credentials | null): Promise<void>;
abstract setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
Expand All @@ -103,6 +103,19 @@ export abstract class BrowserContextBase extends platform.EventEmitter implement
abstract route(url: types.URLMatch, handler: network.RouteHandler): Promise<void>;
abstract close(): Promise<void>;

async grantPermissions(permissions: string[], options?: { origin?: string }) {
const origin = options && options.origin || '*';
const existing = new Set(this._permissions.get(origin) || []);
permissions.forEach(p => existing.add(p));
const list = [...existing.values()];
this._permissions.set(origin, list);
await this._doGrantPermissions(origin, list);
}

async clearPermissions() {
this._permissions.clear();
}

setDefaultNavigationTimeout(timeout: number) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
Expand Down
9 changes: 5 additions & 4 deletions src/chromium/crBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,8 @@ export class CRBrowserContext extends BrowserContextBase {
}

async _initialize() {
const entries = Object.entries(this._options.permissions || {});
await Promise.all(entries.map(entry => this.setPermissions(entry[0], entry[1])));
if (this._options.permissions)
await this.grantPermissions(this._options.permissions);
if (this._options.geolocation)
await this.setGeolocation(this._options.geolocation);
if (this._options.offline)
Expand Down Expand Up @@ -302,7 +302,7 @@ export class CRBrowserContext extends BrowserContextBase {
await this._browser._session.send('Storage.clearCookies', { browserContextId: this._browserContextId || undefined });
}

async setPermissions(origin: string, permissions: string[]): Promise<void> {
async _doGrantPermissions(origin: string, permissions: string[]) {
const webPermissionToProtocol = new Map<string, Protocol.Browser.PermissionType>([
['geolocation', 'geolocation'],
['midi', 'midi'],
Expand All @@ -327,10 +327,11 @@ export class CRBrowserContext extends BrowserContextBase {
throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._browser._session.send('Browser.grantPermissions', { origin, browserContextId: this._browserContextId || undefined, permissions: filtered });
await this._browser._session.send('Browser.grantPermissions', { origin: origin === '*' ? undefined : origin, browserContextId: this._browserContextId || undefined, permissions: filtered });
}

async clearPermissions() {
super.clearPermissions();
await this._browser._session.send('Browser.resetPermissions', { browserContextId: this._browserContextId || undefined });
}

Expand Down
17 changes: 9 additions & 8 deletions src/firefox/ffBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export class FFBrowserContext extends BrowserContextBase {
}

async _initialize() {
const entries = Object.entries(this._options.permissions || {});
await Promise.all(entries.map(entry => this.setPermissions(entry[0], entry[1])));
if (this._options.permissions)
await this.grantPermissions(this._options.permissions);
if (this._options.geolocation)
await this.setGeolocation(this._options.geolocation);
if (this._options.extraHTTPHeaders)
Expand Down Expand Up @@ -227,23 +227,24 @@ export class FFBrowserContext extends BrowserContextBase {
await this._browser._connection.send('Browser.clearCookies', { browserContextId: this._browserContextId || undefined });
}

async setPermissions(origin: string, permissions: string[]): Promise<void> {
const webPermissionToProtocol = new Map<string, 'geo' | 'microphone' | 'camera' | 'desktop-notifications'>([
async _doGrantPermissions(origin: string, permissions: string[]) {
const webPermissionToProtocol = new Map<string, 'geo' | 'desktop-notification' | 'persistent-storage' | 'push'>([
['geolocation', 'geo'],
['microphone', 'microphone'],
['camera', 'camera'],
['notifications', 'desktop-notifications'],
['persistent-storage', 'persistent-storage'],
['push', 'push'],
['notifications', 'desktop-notification'],
]);
const filtered = permissions.map(permission => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission)
throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._browser._connection.send('Browser.grantPermissions', {origin, browserContextId: this._browserContextId || undefined, permissions: filtered});
await this._browser._connection.send('Browser.grantPermissions', { origin: origin, browserContextId: this._browserContextId || undefined, permissions: filtered});
}

async clearPermissions() {
super.clearPermissions();
await this._browser._connection.send('Browser.resetPermissions', { browserContextId: this._browserContextId || undefined });
}

Expand Down
7 changes: 4 additions & 3 deletions src/webkit/wkBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ export class WKBrowserContext extends BrowserContextBase {
await this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId: this._browserContextId, ignore: true });
if (this._options.locale)
await this._browser._browserSession.send('Playwright.setLanguages', { browserContextId: this._browserContextId, languages: [this._options.locale] });
const entries = Object.entries(this._options.permissions || {});
await Promise.all(entries.map(entry => this.setPermissions(entry[0], entry[1])));
if (this._options.permissions)
this.grantPermissions(this._options.permissions);
if (this._options.geolocation)
await this.setGeolocation(this._options.geolocation);
if (this._options.offline)
Expand Down Expand Up @@ -244,7 +244,7 @@ export class WKBrowserContext extends BrowserContextBase {
await this._browser._browserSession.send('Playwright.deleteAllCookies', { browserContextId: this._browserContextId });
}

async setPermissions(origin: string, permissions: string[]): Promise<void> {
async _doGrantPermissions(origin: string, permissions: string[]) {
const webPermissionToProtocol = new Map<string, string>([
['geolocation', 'geolocation'],
]);
Expand All @@ -258,6 +258,7 @@ export class WKBrowserContext extends BrowserContextBase {
}

async clearPermissions() {
super.clearPermissions();
await this._browser._browserSession.send('Playwright.resetPermissions', { browserContextId: this._browserContextId });
}

Expand Down
2 changes: 1 addition & 1 deletion test/geolocation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports.describe = function ({ testRunner, expect, FFOX, WEBKIT }) {

describe.fail(FFOX)('Overrides.setGeolocation', function() {
it('should work', async({page, server, context}) => {
await context.setPermissions(server.PREFIX, ['geolocation']);
await context.grantPermissions(['geolocation']);
await page.goto(server.EMPTY_PAGE);
await context.setGeolocation({longitude: 10, latitude: 10});
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
Expand Down
52 changes: 42 additions & 10 deletions test/permissions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const path = require('path');
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({testRunner, expect, WEBKIT}) {
module.exports.fdescribe = function({testRunner, expect, WEBKIT, FFOX}) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
Expand All @@ -38,23 +38,55 @@ module.exports.describe = function({testRunner, expect, WEBKIT}) {
});
it('should deny permission when not listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.setPermissions(server.EMPTY_PAGE, []);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied');
});
it('should fail when bad permission is given', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let error = {};
await context.setPermissions(server.EMPTY_PAGE, ['foo']).catch(e => error = e);
await context.grantPermissions(['foo']).catch(e => error = e, { origin: server.EMPTY_PAGE });
expect(error.message).toBe('Unknown permission: foo');
});
it('should grant permission when listed', async({page, server, context}) => {
it('should grant geolocation permission when listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.setPermissions(server.EMPTY_PAGE, ['geolocation']);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
});
it.fail(FFOX)('should grant notifications permission when listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it.fail(FFOX)('should accumulate when adding', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it.fail(FFOX)('should clear permissions', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.clearPermissions();
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).not.toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it('should grant permission when listed for all domains', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
});
it('should grant permission when creating context', async({server, browser}) => {
const context = await browser.newContext({ permissions: ['geolocation'] });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.close();
});
it('should reset permissions', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.setPermissions(server.EMPTY_PAGE, ['geolocation']);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
Expand All @@ -71,9 +103,9 @@ module.exports.describe = function({testRunner, expect, WEBKIT}) {
});
});
expect(await page.evaluate(() => window['events'])).toEqual(['prompt']);
await context.setPermissions(server.EMPTY_PAGE, []);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied']);
await context.setPermissions(server.EMPTY_PAGE, ['geolocation']);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']);
await context.clearPermissions();
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']);
Expand All @@ -86,8 +118,8 @@ module.exports.describe = function({testRunner, expect, WEBKIT}) {
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('prompt');

await context.setPermissions(server.EMPTY_PAGE, []);
await otherContext.setPermissions(server.EMPTY_PAGE, ['geolocation']);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
await otherContext.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');

Expand Down

0 comments on commit 643a166

Please sign in to comment.