diff --git a/docs/api.md b/docs/api.md index c55e9e6b338..9ed6fc6c8bd 100644 --- a/docs/api.md +++ b/docs/api.md @@ -35,6 +35,7 @@ Next Release: **Sep 6, 2018** * [browser.browserContexts()](#browserbrowsercontexts) * [browser.close()](#browserclose) * [browser.createIncognitoBrowserContext()](#browsercreateincognitobrowsercontext) + * [browser.defaultBrowserContext()](#browserdefaultbrowsercontext) * [browser.disconnect()](#browserdisconnect) * [browser.newPage()](#browsernewpage) * [browser.pages()](#browserpages) @@ -48,9 +49,11 @@ Next Release: **Sep 6, 2018** * [event: 'targetcreated'](#event-targetcreated-1) * [event: 'targetdestroyed'](#event-targetdestroyed-1) * [browserContext.browser()](#browsercontextbrowser) + * [browserContext.clearPermissionOverrides()](#browsercontextclearpermissionoverrides) * [browserContext.close()](#browsercontextclose) * [browserContext.isIncognito()](#browsercontextisincognito) * [browserContext.newPage()](#browsercontextnewpage) + * [browserContext.overridePermissions(origin, permissions)](#browsercontextoverridepermissionsorigin-permissions) * [browserContext.pages()](#browsercontextpages) * [browserContext.targets()](#browsercontexttargets) - [class: Page](#class-page) @@ -612,6 +615,11 @@ const page = await context.newPage(); 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() 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. +#### 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() - 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. + +#### 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() - 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" [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" +[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin "Origin" [Page]: #class-page "Page" [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" diff --git a/lib/Browser.js b/lib/Browser.js index 61531516cc7..8bc2fc3db2b 100644 --- a/lib/Browser.js +++ b/lib/Browser.js @@ -37,11 +37,11 @@ class Browser extends EventEmitter { this._connection = connection; this._closeCallback = closeCallback || new Function(); - this._defaultContext = new BrowserContext(this, null); + this._defaultContext = new BrowserContext(this._connection, this, null); /** @type {Map} */ this._contexts = new Map(); 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} */ this._targets = new Map(); @@ -65,7 +65,7 @@ class Browser extends EventEmitter { */ async createIncognitoBrowserContext() { 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); return context; } @@ -77,6 +77,13 @@ class Browser extends EventEmitter { return [this._defaultContext, ...Array.from(this._contexts.values())]; } + /** + * @return {!BrowserContext} + */ + defaultBrowserContext() { + return this._defaultContext; + } + /** * @param {?string} contextId */ @@ -231,11 +238,13 @@ Browser.Events = { class BrowserContext extends EventEmitter { /** + * @param {!Puppeteer.Connection} connection * @param {!Browser} browser * @param {?string} contextId */ - constructor(browser, contextId) { + constructor(connection, browser, contextId) { super(); + this._connection = connection; this._browser = browser; this._id = contextId; } @@ -266,6 +275,43 @@ class BrowserContext extends EventEmitter { return !!this._id; } + /** + * @param {string} origin + * @param {!Array} 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} */ diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index e207e931255..51e1ca59447 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -29,6 +29,7 @@ module.exports.addTests = function({testRunner, expect}) { expect(defaultContext.isIncognito()).toBe(false); let error = null; await defaultContext.close().catch(e => error = e); + expect(browser.defaultBrowserContext()).toBe(defaultContext); expect(error.message).toContain('cannot be closed'); }); it('should create new incognito context', async function({browser, server}) { diff --git a/test/page.spec.js b/test/page.spec.js index a4d7d3f5bf7..50bb0e79123 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -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() { it('should work', async({page, server}) => { const result = await page.evaluate(() => 7 * 3);