feat: move permissions back into the context (#320)

This commit is contained in:
Pavel Feldman 2019-12-20 13:07:14 -08:00 committed by GitHub
parent dd6ba432ab
commit ad22a46fde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 294 additions and 395 deletions

View File

@ -19,6 +19,14 @@
* [chromiumPlaywright.errors](#chromiumplaywrighterrors)
* [chromiumPlaywright.executablePath()](#chromiumplaywrightexecutablepath)
* [chromiumPlaywright.launch([options])](#chromiumplaywrightlaunchoptions)
- [class: Browser](#class-browser)
* [event: 'disconnected'](#event-disconnected)
* [browser.browserContexts()](#browserbrowsercontexts)
* [browser.close()](#browserclose)
* [browser.defaultContext()](#browserdefaultcontext)
* [browser.disconnect()](#browserdisconnect)
* [browser.isConnected()](#browserisconnected)
* [browser.newContext(options)](#browsernewcontextoptions)
- [class: BrowserFetcher](#class-browserfetcher)
* [browserFetcher.canDownload(revision)](#browserfetchercandownloadrevision)
* [browserFetcher.download(revision[, progressCallback])](#browserfetcherdownloadrevision-progresscallback)
@ -32,18 +40,9 @@
* [browserServer.process()](#browserserverprocess)
* [browserServer.wsEndpoint()](#browserserverwsendpoint)
- [class: ChromiumBrowser](#class-chromiumbrowser)
* [event: 'disconnected'](#event-disconnected)
* [event: 'targetchanged'](#event-targetchanged)
* [event: 'targetcreated'](#event-targetcreated)
* [event: 'targetdestroyed'](#event-targetdestroyed)
* [chromiumBrowser.browserContexts()](#chromiumbrowserbrowsercontexts)
* [chromiumBrowser.chromium](#chromiumbrowserchromium)
* [chromiumBrowser.close()](#chromiumbrowserclose)
* [chromiumBrowser.defaultContext()](#chromiumbrowserdefaultcontext)
* [chromiumBrowser.disconnect()](#chromiumbrowserdisconnect)
* [chromiumBrowser.isConnected()](#chromiumbrowserisconnected)
* [chromiumBrowser.newContext(options)](#chromiumbrowsernewcontextoptions)
* [chromiumBrowser.process()](#chromiumbrowserprocess)
* [chromiumBrowser.browserTarget()](#chromiumbrowserbrowsertarget)
* [chromiumBrowser.pageTarget(page)](#chromiumbrowserpagetargetpage)
* [chromiumBrowser.serviceWorker(target)](#chromiumbrowserserviceworkertarget)
@ -53,16 +52,15 @@
* [chromiumBrowser.waitForTarget(predicate[, options])](#chromiumbrowserwaitfortargetpredicate-options)
- [class: BrowserContext](#class-browsercontext)
* [browserContext.clearCookies()](#browsercontextclearcookies)
* [browserContext.clearPermissions()](#browsercontextclearpermissions)
* [browserContext.close()](#browsercontextclose)
* [browserContext.cookies([...urls])](#browsercontextcookiesurls)
* [browserContext.newPage()](#browsercontextnewpage)
* [browserContext.pages()](#browsercontextpages)
* [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
* [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [class: ChromiumOverrides](#class-chromiumoverrides)
* [chromiumOverrides.setGeolocation(options)](#chromiumoverridessetgeolocationoptions)
- [class: ChromiumPermissions](#class-chromiumpermissions)
* [chromiumPermissions.clearOverrides()](#chromiumpermissionsclearoverrides)
* [chromiumPermissions.override(origin, permissions)](#chromiumpermissionsoverrideorigin-permissions)
- [class: Page](#class-page)
* [event: 'close'](#event-close)
* [event: 'console'](#event-console)
@ -159,7 +157,7 @@
- [class: ChromiumPDF](#class-chromiumpdf)
* [chromiumPDF.generate([options])](#chromiumpdfgenerateoptions)
- [class: FirefoxBrowser](#class-firefoxbrowser)
* [firefoxBrowser.wsEndpoint()](#firefoxbrowserwsendpoint)
- [class: WebKitBrowser](#class-webkitbrowser)
- [class: Dialog](#class-dialog)
* [dialog.accept([promptText])](#dialogacceptprompttext)
* [dialog.defaultValue()](#dialogdefaultvalue)
@ -227,22 +225,14 @@
* [elementHandle.$$eval(selector, pageFunction[, ...args])](#elementhandleevalselector-pagefunction-args)
* [elementHandle.$eval(selector, pageFunction[, ...args])](#elementhandleevalselector-pagefunction-args-1)
* [elementHandle.$x(expression)](#elementhandlexexpression)
* [elementHandle.asElement()](#elementhandleaselement)
* [elementHandle.boundingBox()](#elementhandleboundingbox)
* [elementHandle.click([options])](#elementhandleclickoptions)
* [elementHandle.contentFrame()](#elementhandlecontentframe)
* [elementHandle.dblclick([options])](#elementhandledblclickoptions)
* [elementHandle.dispose()](#elementhandledispose)
* [elementHandle.evaluate(pageFunction[, ...args])](#elementhandleevaluatepagefunction-args)
* [elementHandle.evaluateHandle(pageFunction[, ...args])](#elementhandleevaluatehandlepagefunction-args)
* [elementHandle.executionContext()](#elementhandleexecutioncontext)
* [elementHandle.fill(value)](#elementhandlefillvalue)
* [elementHandle.focus()](#elementhandlefocus)
* [elementHandle.getProperties()](#elementhandlegetproperties)
* [elementHandle.getProperty(propertyName)](#elementhandlegetpropertypropertyname)
* [elementHandle.hover([options])](#elementhandlehoveroptions)
* [elementHandle.isIntersectingViewport()](#elementhandleisintersectingviewport)
* [elementHandle.jsonValue()](#elementhandlejsonvalue)
* [elementHandle.press(key[, options])](#elementhandlepresskey-options)
* [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
* [elementHandle.select(...values)](#elementhandleselectvalues)
@ -454,6 +444,102 @@ const browser = await playwright.launch({
>
> See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
### class: Browser
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
A Browser is created when Playwright connects to a browser instance, either through [`playwright.launch`](#playwrightlaunchoptions) or [`playwright.connect`](#playwrightconnectoptions).
An example of using a [Browser] to create a [Page]:
```js
const playwright = require('playwright');
(async () => {
const browser = await playwright.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
await browser.close();
})();
```
An example of disconnecting from and reconnecting to a [Browser]:
```js
const playwright = require('playwright');
(async () => {
const browserServer = await playwright.launchServer();
const browserWSEndpoint = browserServer.wsEndpoint();
// Use the endpoint to establish a connection
const browser = await playwright.connect({browserWSEndpoint});
// Close Chromium
await browser.close();
})();
```
#### event: 'disconnected'
Emitted when Playwright gets disconnected from the Chromium instance. This might happen because of one of the following:
- Chromium is closed or crashed
- The [`browser.disconnect`](#browserdisconnect) method was called
#### browser.browserContexts()
- returns: <[Array]<[BrowserContext]>>
Returns an array of all open browser contexts. In a newly created browser, this will return
a single instance of [BrowserContext].
#### browser.close()
- returns: <[Promise]>
Closes Chromium and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore.
#### browser.defaultContext()
- returns: <[BrowserContext]>
Returns the default browser context. The default browser context can not be closed.
#### browser.disconnect()
Disconnects Playwright from the browser, but leaves the Chromium process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
#### browser.isConnected()
- returns: <[boolean]>
Indicates that the browser is connected.
#### browser.newContext(options)
- `options` <[Object]>
- `ignoreHTTPSErrors` <?[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
- `bypassCSP` <?[boolean]> Toggles bypassing page's Content-Security-Policy.
- `viewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport.
- `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels.
- `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
- `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `userAgent` <?[string]> Specific user agent to use in this page
- `mediaType` <?[string]> Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
- `colorScheme` <?"dark"|"light"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`.
- `javaScriptEnabled` <?[boolean]> Whether or not to enable or disable JavaScript in the page. Defaults to true.
- `timezoneId` <?[string]> Changes the timezone of the page. See [ICUs `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs.
- returns: <[Promise]<[BrowserContext]>>
Creates a new browser context. It won't share cookies/cache with other browser contexts.
```js
(async () => {
const browser = await playwright.launch();
// Create a new incognito browser context.
const context = await browser.newContext();
// Create a new page in a pristine context.
const page = await context.newPage();
// Do stuff
await page.goto('https://example.com');
})();
```
### class: BrowserFetcher
BrowserFetcher can download and manage different versions of Chromium.
@ -535,51 +621,17 @@ Learn more about the [devtools protocol](https://chromedevtools.github.io/devtoo
### class: ChromiumBrowser
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
A Browser is created when Playwright connects to a Chromium instance, either through [`playwright.launch`](#playwrightlaunchoptions) or [`playwright.connect`](#playwrightconnectoptions).
An example of using a [Browser] to create a [Page]:
```js
const playwright = require('playwright');
(async () => {
const browser = await playwright.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
await browser.close();
})();
```
An example of disconnecting from and reconnecting to a [Browser]:
```js
const playwright = require('playwright');
(async () => {
const browserServer = await playwright.launchServer();
const browserWSEndpoint = browserServer.wsEndpoint();
// Use the endpoint to establish a connection
const browser = await playwright.connect({browserWSEndpoint});
// Close Chromium
await browser.close();
})();
```
* extends: [Browser]
Chromium-specific features including Tracing, service worker support, etc.
You can use [`chromium.startTracing`](#chromiumstarttracingpage-options) and [`chromium.stopTracing`](#chromiumstoptracing) to create a trace file which can be opened in Chrome DevTools or [timeline viewer](https://chromedevtools.github.io/timeline-viewer/).
You can use [`chromiumBrowser.startTracing`](#chromiumbrowserstarttracingpage-options) and [`chromiumBrowser.stopTracing`](#chromiumbrowserstoptracing) to create a trace file which can be opened in Chrome DevTools or [timeline viewer](https://chromedevtools.github.io/timeline-viewer/).
```js
await browser.chromium.startTracing(page, {path: 'trace.json'});
await browser.startTracing(page, {path: 'trace.json'});
await page.goto('https://www.google.com');
await browser.chromium.stopTracing();
await browser.stopTracing();
```
#### event: 'disconnected'
Emitted when Playwright gets disconnected from the Chromium instance. This might happen because of one of the following:
- Chromium is closed or crashed
- The [`browser.disconnect`](#browserdisconnect) method was called
#### event: 'targetchanged'
- <[Target]>
@ -602,71 +654,6 @@ Emitted when a target is destroyed, for example when a page is closed.
> **NOTE** This includes target destructions in incognito browser contexts.
#### chromiumBrowser.browserContexts()
- returns: <[Array]<[BrowserContext]>>
Returns an array of all open browser contexts. In a newly created browser, this will return
a single instance of [BrowserContext].
#### chromiumBrowser.chromium
- returns: <[Chromium]>
#### chromiumBrowser.close()
- returns: <[Promise]>
Closes Chromium and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore.
#### chromiumBrowser.defaultContext()
- returns: <[BrowserContext]>
Returns the default browser context. The default browser context can not be closed.
#### chromiumBrowser.disconnect()
Disconnects Playwright from the browser, but leaves the Chromium process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
#### chromiumBrowser.isConnected()
- returns: <[boolean]>
Indicates that the browser is connected.
#### chromiumBrowser.newContext(options)
- `options` <[Object]>
- `ignoreHTTPSErrors` <?[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
- `bypassCSP` <?[boolean]> Toggles bypassing page's Content-Security-Policy.
- `viewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport.
- `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels.
- `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
- `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `userAgent` <?[string]> Specific user agent to use in this page
- `mediaType` <?[string]> Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
- `colorScheme` <?"dark"|"light"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`.
- `javaScriptEnabled` <?[boolean]> Whether or not to enable or disable JavaScript in the page. Defaults to true.
- `timezoneId` <?[string]> Changes the timezone of the page. See [ICUs `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs.
- returns: <[Promise]<[BrowserContext]>>
Creates a new browser context. It won't share cookies/cache with other browser contexts.
```js
(async () => {
const browser = await playwright.launch();
// Create a new incognito browser context.
const context = await browser.newContext();
// Create a new page in a pristine context.
const page = await context.newPage();
// Do stuff
await page.goto('https://example.com');
})();
```
#### chromiumBrowser.process()
- returns: <?[ChildProcess]> Spawned browser process. Returns `null` if the browser instance was created with [`playwright.connect`](#playwrightconnectoptions) method.
#### chromiumBrowser.browserTarget()
- returns: <[Target]>
@ -744,6 +731,18 @@ await context.close();
Clears context bookies.
#### browserContext.clearPermissions()
- returns: <[Promise]>
Clears all permission overrides for the browser context.
```js
const context = browser.defaultContext();
context.setPermissions('https://example.com', ['clipboard-read']);
// do stuff ..
context.clearPermissions();
```
#### browserContext.close()
- returns: <[Promise]>
@ -798,38 +797,7 @@ An array of all pages inside the browser context.
await browserContext.setCookies([cookieObject1, cookieObject2]);
```
### class: ChromiumOverrides
#### chromiumOverrides.setGeolocation(options)
- `options` <[Object]>
- `latitude` <[number]> Latitude between -90 and 90.
- `longitude` <[number]> Longitude between -180 and 180.
- `accuracy` <[number]> Optional non-negative accuracy value.
- returns: <[Promise]>
Sets the page's geolocation.
```js
await browserContext.overrides.setGeolocation({latitude: 59.95, longitude: 30.31667});
```
> **NOTE** Consider using [browserContext.permissions.override](#permissionsoverrideorigin-permissions) to grant permissions for the page to read its geolocation.
### class: ChromiumPermissions
#### chromiumPermissions.clearOverrides()
- returns: <[Promise]>
Clears all permission overrides for the browser context.
```js
const context = browser.defaultContext();
context.permissions.override('https://example.com', ['clipboard-read']);
// do stuff ..
context.permissions.clearOverrides();
```
#### chromiumPermissions.override(origin, permissions)
#### 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'`
@ -853,9 +821,26 @@ context.permissions.clearOverrides();
```js
const context = browser.defaultContext();
await context.permissions.override('https://html5demos.com', ['geolocation']);
await context.setPermissions('https://html5demos.com', ['geolocation']);
```
### class: ChromiumOverrides
#### chromiumOverrides.setGeolocation(options)
- `options` <[Object]>
- `latitude` <[number]> Latitude between -90 and 90.
- `longitude` <[number]> Longitude between -180 and 180.
- `accuracy` <[number]> Optional non-negative accuracy value.
- returns: <[Promise]>
Sets the page's geolocation.
```js
await browserContext.overrides.setGeolocation({latitude: 59.95, longitude: 30.31667});
```
> **NOTE** Consider using [browserContext.setPermissions](#browsercontextsetpermissions-permissions) to grant permissions for the page to read its geolocation.
### class: Page
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
@ -2180,12 +2165,15 @@ The `format` options are:
### class: FirefoxBrowser
* extends: [Browser]
Firefox-specific features.
#### firefoxBrowser.wsEndpoint()
- returns: <[string]> Browser websocket url.
### class: WebKitBrowser
Browser websocket endpoint which can be used as an argument to [playwright.connect](#playwrightconnectoptions).
* extends: [Browser]
WebKit-specific features.
### class: Dialog
@ -3045,9 +3033,6 @@ expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10');
The method evaluates the XPath expression relative to the elementHandle. If there are no such elements, the method will resolve to an empty array.
#### elementHandle.asElement()
- returns: <[ElementHandle]>
#### elementHandle.boundingBox()
- returns: <[Promise]<?[Object]>>
- x <[number]> the x coordinate of the element in pixels.
@ -3091,42 +3076,6 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e
> **NOTE** `elementHandle.dblclick()` dispatches two `click` events and a single `dblclick` event.
#### elementHandle.dispose()
- returns: <[Promise]> Promise which resolves when the element handle is successfully disposed.
The `elementHandle.dispose` method stops referencing the element handle.
#### elementHandle.evaluate(pageFunction[, ...args])
- `pageFunction` <[function]\([Object]\)> Function to be evaluated in browser context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
This method passes this handle as the first argument to `pageFunction`.
If `pageFunction` returns a [Promise], then `handle.evaluate` would wait for the promise to resolve and return its value.
Examples:
```js
const tweetHandle = await page.$('.tweet .retweets');
expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
```
#### elementHandle.evaluateHandle(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
This method passes this handle as the first argument to `pageFunction`.
The only difference between `evaluateHandle.evaluate` and `evaluateHandle.evaluateHandle` is that `executionContext.evaluateHandle` returns in-page object (JSHandle).
If the function passed to the `evaluateHandle.evaluateHandle` returns a [Promise], then `evaluateHandle.evaluateHandle` would wait for the promise to resolve and return its value.
See [Page.evaluateHandle](#pageevaluatehandlepagefunction-args) for more details.
#### elementHandle.executionContext()
- returns: <[ExecutionContext]>
#### elementHandle.fill(value)
- `value` <[string]> Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
- returns: <[Promise]> Promise which resolves when the element is successfully filled.
@ -3139,29 +3088,6 @@ If element is not a text `<input>`, `<textarea>` or `[contenteditable]` element,
Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
#### elementHandle.getProperties()
- returns: <[Promise]<[Map]<[string], [JSHandle]>>>
The method returns a map with property names as keys and JSHandle instances for the property values.
```js
const listHandle = await page.evaluateHandle(() => document.body.children);
const properties = await listHandle.getProperties();
const children = [];
for (const property of properties.values()) {
const element = property.asElement();
if (element)
children.push(element);
}
children; // holds elementHandles to all children of document.body
```
#### elementHandle.getProperty(propertyName)
- `propertyName` <[string]> property to get
- returns: <[Promise]<[JSHandle]>>
Fetches a single property from the objectHandle.
#### elementHandle.hover([options])
- `options` <[Object]>
- `relativePoint` <[Object]> A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element.
@ -3176,13 +3102,6 @@ If the element is detached from DOM, the method throws an error.
#### elementHandle.isIntersectingViewport()
- returns: <[Promise]<[boolean]>> Resolves to true if the element is visible in the current viewport.
#### elementHandle.jsonValue()
- returns: <[Promise]<[Object]>>
Returns a JSON representation of the object. The JSON is generated by running [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) on the object in page and consequent [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) in playwright.
> **NOTE** The method will throw if the referenced object is not stringifiable.
#### elementHandle.press(key[, options])
- `key` <[string]> Name of key to press, such as `ArrowLeft`. See [USKeyboardLayout] for a list of all key names.
- `options` <[Object]>

View File

@ -3,15 +3,16 @@
import { BrowserContext, BrowserContextOptions } from './browserContext';
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
export interface Browser {
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
disconnect(): void;
isConnected(): boolean;
export class Browser extends EventEmitter {
newContext(options?: BrowserContextOptions): Promise<BrowserContext> { throw new Error('Not implemented'); }
browserContexts(): BrowserContext[] { throw new Error('Not implemented'); }
defaultContext(): BrowserContext { throw new Error('Not implemented'); }
browserContexts(): BrowserContext[];
defaultContext(): BrowserContext;
close(): Promise<void>;
disconnect(): void { throw new Error('Not implemented'); }
isConnected(): boolean { throw new Error('Not implemented'); }
close(): Promise<void> { throw new Error('Not implemented'); }
}
export class BrowserServer<T extends Browser> {

View File

@ -24,9 +24,13 @@ export interface BrowserContextDelegate {
pages(): Promise<Page[]>;
newPage(): Promise<Page>;
close(): Promise<void>;
cookies(): Promise<network.NetworkCookie[]>;
clearCookies(): Promise<void>;
setCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>;
clearCookies(): Promise<void>;
setPermissions(origin: string, permissions: string[]): Promise<void>;
clearPermissions(): Promise<void>;
}
export type BrowserContextOptions = {
@ -64,12 +68,20 @@ export class BrowserContext {
return network.filterCookies(await this._delegate.cookies(), urls);
}
async setCookies(cookies: network.SetNetworkCookieParam[]) {
await this._delegate.setCookies(network.rewriteCookies(cookies));
}
async clearCookies() {
await this._delegate.clearCookies();
}
async setCookies(cookies: network.SetNetworkCookieParam[]) {
await this._delegate.setCookies(network.rewriteCookies(cookies));
async setPermissions(origin: string, permissions: string[]): Promise<void> {
await this._delegate.setPermissions(origin, permissions);
}
async clearPermissions() {
await this._delegate.clearPermissions();
}
async close() {

View File

@ -10,5 +10,4 @@ export { CRCoverage as ChromiumCoverage } from './features/crCoverage';
export { CRInterception as ChromiumInterception } from './features/crInterception';
export { CROverrides as ChromiumOverrides } from './features/crOverrides';
export { CRPDF as ChromiumPDF } from './features/crPdf';
export { CRPermissions as ChromiumPermissions } from './features/crPermissions';
export { CRWorker as ChromiumWorker, CRWorkers as ChromiumWorkers } from './features/crWorkers';

View File

@ -27,13 +27,12 @@ import { Protocol } from './protocol';
import { CRFrameManager } from './crFrameManager';
import * as browser from '../browser';
import * as network from '../network';
import { CRPermissions } from './features/crPermissions';
import { CROverrides } from './features/crOverrides';
import { CRWorker } from './features/crWorkers';
import { ConnectionTransport } from '../transport';
import { readProtocolStream } from './crProtocolHelper';
export class CRBrowser extends EventEmitter implements browser.Browser {
export class CRBrowser extends browser.Browser {
_connection: CRConnection;
_client: CRSession;
private _defaultContext: BrowserContext;
@ -129,9 +128,41 @@ export class CRBrowser extends EventEmitter implements browser.Browser {
setCookies: async (cookies: network.SetNetworkCookieParam[]): Promise<void> => {
await this._client.send('Storage.setCookies', { cookies, browserContextId: contextId || undefined });
},
setPermissions: async (origin: string, permissions: string[]): Promise<void> => {
const webPermissionToProtocol = new Map<string, Protocol.Browser.PermissionType>([
['geolocation', 'geolocation'],
['midi', 'midi'],
['notifications', 'notifications'],
['camera', 'videoCapture'],
['microphone', 'audioCapture'],
['background-sync', 'backgroundSync'],
['ambient-light-sensor', 'sensors'],
['accelerometer', 'sensors'],
['gyroscope', 'sensors'],
['magnetometer', 'sensors'],
['accessibility-events', 'accessibilityEvents'],
['clipboard-read', 'clipboardReadWrite'],
['clipboard-write', 'clipboardSanitizedWrite'],
['payment-handler', 'paymentHandler'],
// chrome-specific permissions we have.
['midi-sysex', 'midiSysex'],
]);
const filtered = permissions.map(permission => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission)
throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._client.send('Browser.grantPermissions', { origin, browserContextId: contextId || undefined, permissions: filtered });
},
clearPermissions: async () => {
await this._client.send('Browser.resetPermissions', { browserContextId: contextId || undefined });
}
}, options);
overrides = new CROverrides(context);
(context as any).permissions = new CRPermissions(this._client, contextId);
(context as any).overrides = overrides;
return context;
}
@ -161,7 +192,7 @@ export class CRBrowser extends EventEmitter implements browser.Browser {
this._targets.set(event.targetInfo.targetId, target);
if (target._isInitialized || await target._initializedPromise)
this.emit(Events.Browser.TargetCreated, target);
this.emit(Events.CRBrowser.TargetCreated, target);
}
async _targetDestroyed(event: { targetId: string; }) {
@ -170,7 +201,7 @@ export class CRBrowser extends EventEmitter implements browser.Browser {
this._targets.delete(event.targetId);
target._didClose();
if (await target._initializedPromise)
this.emit(Events.Browser.TargetDestroyed, target);
this.emit(Events.CRBrowser.TargetDestroyed, target);
}
_targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload) {
@ -180,7 +211,7 @@ export class CRBrowser extends EventEmitter implements browser.Browser {
const wasInitialized = target._isInitialized;
target._targetInfoChanged(event.targetInfo);
if (wasInitialized && previousURL !== target.url())
this.emit(Events.Browser.TargetChanged, target);
this.emit(Events.CRBrowser.TargetChanged, target);
}
async _closePage(page: Page) {
@ -204,15 +235,15 @@ export class CRBrowser extends EventEmitter implements browser.Browser {
return existingTarget;
let resolve: (target: CRTarget) => void;
const targetPromise = new Promise<CRTarget>(x => resolve = x);
this.on(Events.Browser.TargetCreated, check);
this.on(Events.Browser.TargetChanged, check);
this.on(Events.CRBrowser.TargetCreated, check);
this.on(Events.CRBrowser.TargetChanged, check);
try {
if (!timeout)
return await targetPromise;
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
} finally {
this.removeListener(Events.Browser.TargetCreated, check);
this.removeListener(Events.Browser.TargetChanged, check);
this.removeListener(Events.CRBrowser.TargetCreated, check);
this.removeListener(Events.CRBrowser.TargetChanged, check);
}
function check(target: CRTarget) {

View File

@ -16,13 +16,13 @@
*/
export const Events = {
Browser: {
CRBrowser: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
},
Workers: {
CRWorkers: {
WorkerCreated: 'workercreated',
WorkerDestroyed: 'workerdestroyed',
}

View File

@ -1,61 +0,0 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Protocol } from '../protocol';
import { CRSession } from '../crConnection';
export class CRPermissions {
private _client: CRSession;
private _browserContextId: string;
constructor(client: CRSession, browserContextId: string | null) {
this._client = client;
this._browserContextId = browserContextId;
}
async override(origin: string, permissions: string[]) {
const webPermissionToProtocol = new Map<string, Protocol.Browser.PermissionType>([
['geolocation', 'geolocation'],
['midi', 'midi'],
['notifications', 'notifications'],
['camera', 'videoCapture'],
['microphone', 'audioCapture'],
['background-sync', 'backgroundSync'],
['ambient-light-sensor', 'sensors'],
['accelerometer', 'sensors'],
['gyroscope', 'sensors'],
['magnetometer', 'sensors'],
['accessibility-events', 'accessibilityEvents'],
['clipboard-read', 'clipboardReadWrite'],
['clipboard-write', 'clipboardSanitizedWrite'],
['payment-handler', 'paymentHandler'],
// chrome-specific permissions we have.
['midi-sysex', 'midiSysex'],
]);
const filtered = permissions.map(permission => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission)
throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._client.send('Browser.grantPermissions', {origin, browserContextId: this._browserContextId || undefined, permissions: filtered});
}
async clearOverrides() {
await this._client.send('Browser.resetPermissions', {browserContextId: this._browserContextId || undefined});
}
}

View File

@ -41,13 +41,13 @@ export class CRWorkers extends EventEmitter {
const session = CRConnection.fromSession(client).session(event.sessionId);
const worker = new CRWorker(session, event.targetInfo.url, addToConsole, handleException);
this._workers.set(event.sessionId, worker);
this.emit(Events.Workers.WorkerCreated, worker);
this.emit(Events.CRWorkers.WorkerCreated, worker);
});
client.on('Target.detachedFromTarget', event => {
const worker = this._workers.get(event.sessionId);
if (!worker)
return;
this.emit(Events.Workers.WorkerDestroyed, worker);
this.emit(Events.CRWorkers.WorkerDestroyed, worker);
this._workers.delete(event.sessionId);
});
}

View File

@ -1,49 +0,0 @@
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FFConnection } from '../ffConnection';
export class FFPermissions {
private _connection: FFConnection;
private _browserContextId: string;
constructor(connection: FFConnection, browserContextId: string | null) {
this._connection = connection;
this._browserContextId = browserContextId;
}
async override(origin: string, permissions: Array<string>) {
const webPermissionToProtocol = new Map([
['geolocation', 'geo'],
['microphone', 'microphone'],
['camera', 'camera'],
['notifications', 'desktop-notifications'],
]);
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._browserContextId || undefined, permissions});
}
async clearOverrides() {
await this._connection.send('Browser.resetPermissions', {browserContextId: this._browserContextId || undefined});
}
}

View File

@ -2,6 +2,5 @@
// Licensed under the MIT license.
export { FFInterception as FirefoxInterception } from './features/ffInterception';
export { FFPermissions as FirefoxPermissions } from './features/ffPermissions';
export { FFBrowser as FirefoxBrowser } from './ffBrowser';
export { FFPlaywright as Firefox } from './ffPlaywright';

View File

@ -15,19 +15,17 @@
* limitations under the License.
*/
import { EventEmitter } from 'events';
import { helper, RegisteredListener, assert } from '../helper';
import { FFConnection, ConnectionEvents, FFSessionEvents } from './ffConnection';
import { Events } from '../events';
import { FFPermissions } from './features/ffPermissions';
import { Page } from '../page';
import { FFFrameManager } from './ffFrameManager';
import * as browser from '../browser';
import * as network from '../network';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { Events } from '../events';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
import { Page } from '../page';
import { ConnectionTransport } from '../transport';
import { ConnectionEvents, FFConnection, FFSessionEvents } from './ffConnection';
import { FFFrameManager } from './ffFrameManager';
export class FFBrowser extends EventEmitter implements browser.Browser {
export class FFBrowser extends browser.Browser {
_connection: FFConnection;
_targets: Map<string, Target>;
private _defaultContext: BrowserContext;
@ -196,8 +194,28 @@ export class FFBrowser extends EventEmitter implements browser.Browser {
setCookies: async (cookies: network.SetNetworkCookieParam[]): Promise<void> => {
await this._connection.send('Browser.setCookies', { browserContextId: browserContextId || undefined, cookies });
},
setPermissions: async (origin: string, permissions: string[]): Promise<void> => {
const webPermissionToProtocol = new Map([
['geolocation', 'geo'],
['microphone', 'microphone'],
['camera', 'camera'],
['notifications', 'desktop-notifications'],
]);
const filtered = 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: browserContextId || undefined, permissions: filtered});
},
clearPermissions: async () => {
await this._connection.send('Browser.resetPermissions', { browserContextId: browserContextId || undefined });
}
}, options);
(context as any).permissions = new FFPermissions(this._connection, browserContextId);
return context;
}
}

View File

@ -27,7 +27,7 @@ import { Events } from '../events';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { ConnectionTransport } from '../transport';
export class WKBrowser extends EventEmitter implements browser.Browser {
export class WKBrowser extends browser.Browser {
readonly _connection: WKConnection;
private _defaultContext: BrowserContext;
private _contexts = new Map<string, BrowserContext>();
@ -206,6 +206,14 @@ export class WKBrowser extends EventEmitter implements browser.Browser {
const cc = cookies.map(c => ({ ...c, session: c.expires === -1 || c.expires === undefined })) as Protocol.Browser.SetCookieParam[];
await this._connection.send('Browser.setCookies', { cookies: cc, browserContextId });
},
setPermissions: async (origin: string, permissions: string[]): Promise<void> => {
throw new Error('Permissions are not supported in WebKit');
},
clearPermissions: async () => {
throw new Error('Permissions are not supported in WebKit');
}
}, options);
return context;
}

View File

@ -24,7 +24,7 @@ module.exports.describe = function ({ testRunner, expect }) {
// It was removed from WebKit in https://webkit.org/b/126630
describe('Overrides.setGeolocation', function() {
it('should work', async({page, server, context}) => {
await context.permissions.override(server.PREFIX, ['geolocation']);
await context.setPermissions(server.PREFIX, ['geolocation']);
await page.goto(server.EMPTY_PAGE);
await context.overrides.setGeolocation({longitude: 10, latitude: 10});
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {

View File

@ -35,25 +35,25 @@ module.exports.describe = function({testRunner, expect, WEBKIT}) {
});
it.skip(WEBKIT)('should deny permission when not listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.permissions.override(server.EMPTY_PAGE, []);
await context.setPermissions(server.EMPTY_PAGE, []);
expect(await getPermission(page, 'geolocation')).toBe('denied');
});
it.skip(WEBKIT)('should fail when bad permission is given', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let error = {};
await context.permissions.override(server.EMPTY_PAGE, ['foo']).catch(e => error = e);
await context.setPermissions(server.EMPTY_PAGE, ['foo']).catch(e => error = e);
expect(error.message).toBe('Unknown permission: foo');
});
it.skip(WEBKIT)('should grant permission when listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.permissions.override(server.EMPTY_PAGE, ['geolocation']);
await context.setPermissions(server.EMPTY_PAGE, ['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
});
it.skip(WEBKIT)('should reset permissions', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.permissions.override(server.EMPTY_PAGE, ['geolocation']);
await context.setPermissions(server.EMPTY_PAGE, ['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.permissions.clearOverrides();
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
});
it.skip(WEBKIT)('should trigger permission onchange', async({page, server, context}) => {
@ -68,11 +68,11 @@ module.exports.describe = function({testRunner, expect, WEBKIT}) {
});
});
expect(await page.evaluate(() => window['events'])).toEqual(['prompt']);
await context.permissions.override(server.EMPTY_PAGE, []);
await context.setPermissions(server.EMPTY_PAGE, []);
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied']);
await context.permissions.override(server.EMPTY_PAGE, ['geolocation']);
await context.setPermissions(server.EMPTY_PAGE, ['geolocation']);
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']);
await context.permissions.clearOverrides();
await context.clearPermissions();
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']);
});
it.skip(WEBKIT)('should isolate permissions between browser contexs', async({page, server, context, newContext}) => {
@ -83,12 +83,12 @@ module.exports.describe = function({testRunner, expect, WEBKIT}) {
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('prompt');
await context.permissions.override(server.EMPTY_PAGE, []);
await otherContext.permissions.override(server.EMPTY_PAGE, ['geolocation']);
await context.setPermissions(server.EMPTY_PAGE, []);
await otherContext.setPermissions(server.EMPTY_PAGE, ['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('denied');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
await context.permissions.clearOverrides();
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
});

View File

@ -37,6 +37,12 @@ Documentation.Class = class {
constructor(name, membersArray, extendsName = null, comment = '') {
this.name = name;
this.membersArray = membersArray;
this.comment = comment;
this.extends = extendsName;
this.index();
}
index() {
/** @type {!Map<string, !Documentation.Member>} */
this.members = new Map();
/** @type {!Map<string, !Documentation.Member>} */
@ -51,9 +57,8 @@ Documentation.Class = class {
this.events = new Map();
/** @type {!Array<!Documentation.Member>} */
this.eventsArray = [];
this.comment = comment;
this.extends = extendsName;
for (const member of membersArray) {
for (const member of this.membersArray) {
this.members.set(member.name, member);
if (member.kind === 'method') {
this.methods.set(member.name, member);

View File

@ -27,9 +27,9 @@ module.exports = { checkSources, expandPrefix };
function checkSources(sources) {
// special treatment for Events.js
const classEvents = new Map();
const eventsSource = sources.find(source => source.name().startsWith('events.'));
if (eventsSource) {
const {Events} = eventsSource.filePath().endsWith('.js') ? require(eventsSource.filePath()) : require(path.join(eventsSource.filePath(), '..', '..', '..', 'lib', 'chromium', 'events.js'));
const eventsSources = sources.filter(source => source.name().startsWith('events.ts'));
for (const eventsSource of eventsSources) {
const {Events} = require(eventsSource.filePath().endsWith('.js') ? eventsSource.filePath() : eventsSource.filePath().replace('/src/', '/lib/').replace('.ts', '.js'));
for (const [className, events] of Object.entries(Events))
classEvents.set(className, Array.from(Object.values(events)).filter(e => typeof e === 'string').map(e => Documentation.Member.createEvent(e)));
}

View File

@ -299,6 +299,23 @@ module.exports = async function(page, sources) {
errors.push(...outline.errors);
}
const documentation = new Documentation(classes);
// Push base class documentation to derived classes.
for (const [name, clazz] of documentation.classes.entries()) {
if (!clazz.extends || clazz.extends === 'EventEmitter' || clazz.extends === 'Error')
continue;
const superClass = documentation.classes.get(clazz.extends);
if (!superClass) {
errors.push(`Undefined superclass: ${superClass} in ${name}`);
continue;
}
for (const memberName of clazz.members.keys()) {
if (superClass.members.has(memberName))
errors.push(`Member documentation overrides base: ${name}.${memberName} over ${clazz.extends}.${memberName}`);
}
clazz.membersArray = [...clazz.membersArray, ...superClass.membersArray];
clazz.index();
}
return { documentation, errors };
};