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 8401462 commit 6520cfc
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 63 deletions.
58 changes: 31 additions & 27 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-options) 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 @@ -246,7 +246,7 @@ Creates a new browser context. It won't share cookies/cache with other browser c
- `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-options) 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 @@ -290,6 +290,7 @@ await context.close();
- [browserContext.close()](#browsercontextclose)
- [browserContext.cookies([urls])](#browsercontextcookiesurls)
- [browserContext.exposeFunction(name, playwrightFunction)](#browsercontextexposefunctionname-playwrightfunction)
- [browserContext.grantPermissions(permissions[][, options])](#browsercontextgrantpermissionspermissions-options)
- [browserContext.newPage()](#browsercontextnewpage)
- [browserContext.pages()](#browsercontextpages)
- [browserContext.route(url, handler)](#browsercontextrouteurl-handler)
Expand All @@ -299,7 +300,6 @@ await context.close();
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [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']);
await context.grantPermissions(['clipboard-read']);
// do stuff ..
context.clearPermissions();
```
Expand Down Expand Up @@ -447,6 +447,31 @@ const crypto = require('crypto');
})();
```

#### 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)
- `'notifications'`
- `'push'`
- `'camera'`
- `'microphone'`
- `'background-sync'`
- `'ambient-light-sensor'`
- `'accelerometer'`
- `'gyroscope'`
- `'magnetometer'`
- `'accessibility-events'`
- `'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.newPage()
- returns: <[Promise]<[Page]>>

Expand Down Expand Up @@ -544,27 +569,6 @@ 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:
- `'geolocation'`
- `'midi'`
- `'midi-sysex'` (system-exclusive midi)
- `'notifications'`
- `'push'`
- `'camera'`
- `'microphone'`
- `'background-sync'`
- `'ambient-light-sensor'`
- `'accelerometer'`
- `'gyroscope'`
- `'magnetometer'`
- `'accessibility-events'`
- `'clipboard-read'`
- `'clipboard-write'`
- `'payment-handler'`
- returns: <[Promise]>

#### 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 @@ -3824,6 +3828,7 @@ const backgroundPage = await backroundPageTarget.page();
- [browserContext.close()](#browsercontextclose)
- [browserContext.cookies([urls])](#browsercontextcookiesurls)
- [browserContext.exposeFunction(name, playwrightFunction)](#browsercontextexposefunctionname-playwrightfunction)
- [browserContext.grantPermissions(permissions[][, options])](#browsercontextgrantpermissionspermissions-options)
- [browserContext.newPage()](#browsercontextnewpage)
- [browserContext.pages()](#browsercontextpages)
- [browserContext.route(url, handler)](#browsercontextrouteurl-handler)
Expand All @@ -3833,7 +3838,6 @@ const backgroundPage = await backroundPageTarget.page();
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
<!-- GEN:stop -->

Expand Down
2 changes: 1 addition & 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
27 changes: 23 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,8 @@ 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 _doClearPermissions(): 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 +104,24 @@ 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 }) {
let origin = '*';
if (options && options.origin) {
const url = new URL(options.origin);
origin = url.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();
await this._doClearPermissions();
}

setDefaultNavigationTimeout(timeout: number) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
Expand Down
10 changes: 5 additions & 5 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,10 @@ 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() {
async _doClearPermissions() {
await this._browser._session.send('Browser.resetPermissions', { browserContextId: this._browserContextId || undefined });
}

Expand Down
18 changes: 9 additions & 9 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,23 @@ 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() {
async _doClearPermissions() {
await this._browser._connection.send('Browser.resetPermissions', { browserContextId: this._browserContextId || undefined });
}

Expand Down
8 changes: 4 additions & 4 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)
await 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 @@ -257,7 +257,7 @@ export class WKBrowserContext extends BrowserContextBase {
await this._browser._browserSession.send('Playwright.grantPermissions', { origin, browserContextId: this._browserContextId, permissions: filtered });
}

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

Expand Down
5 changes: 2 additions & 3 deletions 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 Expand Up @@ -73,8 +73,7 @@ module.exports.describe = function ({ testRunner, expect, FFOX, WEBKIT }) {
expect(error.message).toContain('Invalid longitude "undefined"');
});
it('should use context options', async({browser, server}) => {
const options = { geolocation: { longitude: 10, latitude: 10 }, permissions: {} };
options.permissions[server.PREFIX] = ['geolocation'];
const options = { geolocation: { longitude: 10, latitude: 10 }, permissions: ['geolocation'] };
const context = await browser.newContext(options);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
Expand Down
Loading

0 comments on commit 6520cfc

Please sign in to comment.