Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(permissions): make origin optional #1406

Merged
merged 1 commit into from
Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await here as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done as well

```
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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is confusing that we don't call _doClearPermissions here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same way. Fixing.

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