feat(browsercontext): implement BrowserContext.overridePermissions (#3159)
Introduce an API to manage permissions per browser context: - BrowserContext.overridePermissions(origin, permissions) - BrowserContext.clearPermissionOverrides() Fixes #846.
This commit is contained in:
parent
df459ba6cd
commit
50d6c2d3c6
50
docs/api.md
50
docs/api.md
@ -35,6 +35,7 @@ Next Release: **Sep 6, 2018**
|
|||||||
* [browser.browserContexts()](#browserbrowsercontexts)
|
* [browser.browserContexts()](#browserbrowsercontexts)
|
||||||
* [browser.close()](#browserclose)
|
* [browser.close()](#browserclose)
|
||||||
* [browser.createIncognitoBrowserContext()](#browsercreateincognitobrowsercontext)
|
* [browser.createIncognitoBrowserContext()](#browsercreateincognitobrowsercontext)
|
||||||
|
* [browser.defaultBrowserContext()](#browserdefaultbrowsercontext)
|
||||||
* [browser.disconnect()](#browserdisconnect)
|
* [browser.disconnect()](#browserdisconnect)
|
||||||
* [browser.newPage()](#browsernewpage)
|
* [browser.newPage()](#browsernewpage)
|
||||||
* [browser.pages()](#browserpages)
|
* [browser.pages()](#browserpages)
|
||||||
@ -48,9 +49,11 @@ Next Release: **Sep 6, 2018**
|
|||||||
* [event: 'targetcreated'](#event-targetcreated-1)
|
* [event: 'targetcreated'](#event-targetcreated-1)
|
||||||
* [event: 'targetdestroyed'](#event-targetdestroyed-1)
|
* [event: 'targetdestroyed'](#event-targetdestroyed-1)
|
||||||
* [browserContext.browser()](#browsercontextbrowser)
|
* [browserContext.browser()](#browsercontextbrowser)
|
||||||
|
* [browserContext.clearPermissionOverrides()](#browsercontextclearpermissionoverrides)
|
||||||
* [browserContext.close()](#browsercontextclose)
|
* [browserContext.close()](#browsercontextclose)
|
||||||
* [browserContext.isIncognito()](#browsercontextisincognito)
|
* [browserContext.isIncognito()](#browsercontextisincognito)
|
||||||
* [browserContext.newPage()](#browsercontextnewpage)
|
* [browserContext.newPage()](#browsercontextnewpage)
|
||||||
|
* [browserContext.overridePermissions(origin, permissions)](#browsercontextoverridepermissionsorigin-permissions)
|
||||||
* [browserContext.pages()](#browsercontextpages)
|
* [browserContext.pages()](#browsercontextpages)
|
||||||
* [browserContext.targets()](#browsercontexttargets)
|
* [browserContext.targets()](#browsercontexttargets)
|
||||||
- [class: Page](#class-page)
|
- [class: Page](#class-page)
|
||||||
@ -612,6 +615,11 @@ const page = await context.newPage();
|
|||||||
await page.goto('https://example.com');
|
await page.goto('https://example.com');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### browser.defaultBrowserContext()
|
||||||
|
- returns: <[BrowserContext]>
|
||||||
|
|
||||||
|
Returns the default browser context. The default browser context can not be closed.
|
||||||
|
|
||||||
#### browser.disconnect()
|
#### browser.disconnect()
|
||||||
|
|
||||||
Disconnects Puppeteer from the browser, but leaves the Chromium process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
|
Disconnects Puppeteer from the browser, but leaves the Chromium process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
|
||||||
@ -698,6 +706,18 @@ Emitted when a target inside the browser context is destroyed, for example when
|
|||||||
|
|
||||||
The browser this browser context belongs to.
|
The browser this browser context belongs to.
|
||||||
|
|
||||||
|
#### browserContext.clearPermissionOverrides()
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
|
Clears all permission overrides for the browser context.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const context = browser.defaultBrowserContext();
|
||||||
|
context.overridePermissions('https://example.com', ['clipboard-read']);
|
||||||
|
// do stuff ..
|
||||||
|
context.clearPermissionOverrides();
|
||||||
|
```
|
||||||
|
|
||||||
#### browserContext.close()
|
#### browserContext.close()
|
||||||
- returns: <[Promise]>
|
- returns: <[Promise]>
|
||||||
|
|
||||||
@ -719,6 +739,35 @@ The default browser context is the only non-incognito browser context.
|
|||||||
|
|
||||||
Creates a new page in the browser context.
|
Creates a new page in the browser context.
|
||||||
|
|
||||||
|
|
||||||
|
#### browserContext.overridePermissions(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]>
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
const context = browser.defaultBrowserContext();
|
||||||
|
await context.overridePermissions('https://html5demos.com', ['geolocation']);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
#### browserContext.pages()
|
#### browserContext.pages()
|
||||||
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage).
|
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage).
|
||||||
|
|
||||||
@ -3165,6 +3214,7 @@ TimeoutError is emitted whenever certain operations are terminated due to timeou
|
|||||||
[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function "Function"
|
[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function "Function"
|
||||||
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
|
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
|
||||||
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object"
|
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object"
|
||||||
|
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin "Origin"
|
||||||
[Page]: #class-page "Page"
|
[Page]: #class-page "Page"
|
||||||
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
|
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
|
||||||
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
|
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
|
||||||
|
@ -37,11 +37,11 @@ class Browser extends EventEmitter {
|
|||||||
this._connection = connection;
|
this._connection = connection;
|
||||||
this._closeCallback = closeCallback || new Function();
|
this._closeCallback = closeCallback || new Function();
|
||||||
|
|
||||||
this._defaultContext = new BrowserContext(this, null);
|
this._defaultContext = new BrowserContext(this._connection, this, null);
|
||||||
/** @type {Map<string, BrowserContext>} */
|
/** @type {Map<string, BrowserContext>} */
|
||||||
this._contexts = new Map();
|
this._contexts = new Map();
|
||||||
for (const contextId of contextIds)
|
for (const contextId of contextIds)
|
||||||
this._contexts.set(contextId, new BrowserContext(this, contextId));
|
this._contexts.set(contextId, new BrowserContext(this._connection, this, contextId));
|
||||||
|
|
||||||
/** @type {Map<string, Target>} */
|
/** @type {Map<string, Target>} */
|
||||||
this._targets = new Map();
|
this._targets = new Map();
|
||||||
@ -65,7 +65,7 @@ class Browser extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
async createIncognitoBrowserContext() {
|
async createIncognitoBrowserContext() {
|
||||||
const {browserContextId} = await this._connection.send('Target.createBrowserContext');
|
const {browserContextId} = await this._connection.send('Target.createBrowserContext');
|
||||||
const context = new BrowserContext(this, browserContextId);
|
const context = new BrowserContext(this._connection, this, browserContextId);
|
||||||
this._contexts.set(browserContextId, context);
|
this._contexts.set(browserContextId, context);
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
@ -77,6 +77,13 @@ class Browser extends EventEmitter {
|
|||||||
return [this._defaultContext, ...Array.from(this._contexts.values())];
|
return [this._defaultContext, ...Array.from(this._contexts.values())];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!BrowserContext}
|
||||||
|
*/
|
||||||
|
defaultBrowserContext() {
|
||||||
|
return this._defaultContext;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {?string} contextId
|
* @param {?string} contextId
|
||||||
*/
|
*/
|
||||||
@ -231,11 +238,13 @@ Browser.Events = {
|
|||||||
|
|
||||||
class BrowserContext extends EventEmitter {
|
class BrowserContext extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
|
* @param {!Puppeteer.Connection} connection
|
||||||
* @param {!Browser} browser
|
* @param {!Browser} browser
|
||||||
* @param {?string} contextId
|
* @param {?string} contextId
|
||||||
*/
|
*/
|
||||||
constructor(browser, contextId) {
|
constructor(connection, browser, contextId) {
|
||||||
super();
|
super();
|
||||||
|
this._connection = connection;
|
||||||
this._browser = browser;
|
this._browser = browser;
|
||||||
this._id = contextId;
|
this._id = contextId;
|
||||||
}
|
}
|
||||||
@ -266,6 +275,43 @@ class BrowserContext extends EventEmitter {
|
|||||||
return !!this._id;
|
return !!this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} origin
|
||||||
|
* @param {!Array<string>} permissions
|
||||||
|
*/
|
||||||
|
async overridePermissions(origin, permissions) {
|
||||||
|
const webPermissionToProtocol = new Map([
|
||||||
|
['geolocation', 'geolocation'],
|
||||||
|
['midi', 'midi'],
|
||||||
|
['notifications', 'notifications'],
|
||||||
|
['push', 'push'],
|
||||||
|
['camera', 'videoCapture'],
|
||||||
|
['microphone', 'audioCapture'],
|
||||||
|
['background-sync', 'backgroundSync'],
|
||||||
|
['ambient-light-sensor', 'sensors'],
|
||||||
|
['accelerometer', 'sensors'],
|
||||||
|
['gyroscope', 'sensors'],
|
||||||
|
['magnetometer', 'sensors'],
|
||||||
|
['accessibility-events', 'accessibilityEvents'],
|
||||||
|
['clipboard-read', 'clipboardRead'],
|
||||||
|
['clipboard-write', 'clipboardWrite'],
|
||||||
|
['payment-handler', 'paymentHandler'],
|
||||||
|
// chrome-specific permissions we have.
|
||||||
|
['midi-sysex', 'midiSysex'],
|
||||||
|
]);
|
||||||
|
permissions = permissions.map(permission => {
|
||||||
|
const protocolPermission = webPermissionToProtocol.get(permission);
|
||||||
|
if (!protocolPermission)
|
||||||
|
throw new Error('Unknown permission: ' + permission);
|
||||||
|
return protocolPermission;
|
||||||
|
});
|
||||||
|
await this._connection.send('Browser.grantPermissions', {origin, browserContextId: this._id || undefined, permissions});
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearPermissionOverrides() {
|
||||||
|
await this._connection.send('Browser.resetPermissions', {browserContextId: this._id || undefined});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {!Promise<!Puppeteer.Page>}
|
* @return {!Promise<!Puppeteer.Page>}
|
||||||
*/
|
*/
|
||||||
|
@ -29,6 +29,7 @@ module.exports.addTests = function({testRunner, expect}) {
|
|||||||
expect(defaultContext.isIncognito()).toBe(false);
|
expect(defaultContext.isIncognito()).toBe(false);
|
||||||
let error = null;
|
let error = null;
|
||||||
await defaultContext.close().catch(e => error = e);
|
await defaultContext.close().catch(e => error = e);
|
||||||
|
expect(browser.defaultBrowserContext()).toBe(defaultContext);
|
||||||
expect(error.message).toContain('cannot be closed');
|
expect(error.message).toContain('cannot be closed');
|
||||||
});
|
});
|
||||||
it('should create new incognito context', async function({browser, server}) {
|
it('should create new incognito context', async function({browser, server}) {
|
||||||
|
@ -75,6 +75,59 @@ module.exports.addTests = function({testRunner, expect, headless}) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('BrowserContext.overridePermissions', function() {
|
||||||
|
function getPermission(page, name) {
|
||||||
|
return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should be prompt by default', async({page, server, context}) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
expect(await getPermission(page, 'geolocation')).toBe('prompt');
|
||||||
|
});
|
||||||
|
it('should deny permission when not listed', async({page, server, context}) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await context.overridePermissions(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 = null;
|
||||||
|
await context.overridePermissions(server.EMPTY_PAGE, ['foo']).catch(e => error = e);
|
||||||
|
expect(error.message).toBe('Unknown permission: foo');
|
||||||
|
});
|
||||||
|
it('should grant permission when listed', async({page, server, context}) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']);
|
||||||
|
expect(await getPermission(page, 'geolocation')).toBe('granted');
|
||||||
|
});
|
||||||
|
it('should reset permissions', async({page, server, context}) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']);
|
||||||
|
expect(await getPermission(page, 'geolocation')).toBe('granted');
|
||||||
|
await context.clearPermissionOverrides();
|
||||||
|
expect(await getPermission(page, 'geolocation')).toBe('prompt');
|
||||||
|
});
|
||||||
|
it('should trigger permission onchange', async({page, server, context}) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.events = [];
|
||||||
|
return navigator.permissions.query({name: 'clipboard-read'}).then(function(result) {
|
||||||
|
window.events.push(result.state);
|
||||||
|
result.onchange = function() {
|
||||||
|
window.events.push(result.state);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(await page.evaluate(() => window.events)).toEqual(['prompt']);
|
||||||
|
await context.overridePermissions(server.EMPTY_PAGE, []);
|
||||||
|
expect(await page.evaluate(() => window.events)).toEqual(['prompt', 'denied']);
|
||||||
|
await context.overridePermissions(server.EMPTY_PAGE, ['clipboard-read']);
|
||||||
|
expect(await page.evaluate(() => window.events)).toEqual(['prompt', 'denied', 'granted']);
|
||||||
|
await context.clearPermissionOverrides();
|
||||||
|
expect(await page.evaluate(() => window.events)).toEqual(['prompt', 'denied', 'granted', 'prompt']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Page.evaluate', function() {
|
describe('Page.evaluate', function() {
|
||||||
it('should work', async({page, server}) => {
|
it('should work', async({page, server}) => {
|
||||||
const result = await page.evaluate(() => 7 * 3);
|
const result = await page.evaluate(() => 7 * 3);
|
||||||
|
Loading…
Reference in New Issue
Block a user