Skip to content

Commit

Permalink
fix: move offline/cache/interception switches to BrowserContext (micr…
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s authored Jan 29, 2020
1 parent 9a126da commit 6faf74b
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 200 deletions.
80 changes: 42 additions & 38 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ Indicates that the browser is connected.
- `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`.
- `userAgent` <?[string]> Specific user agent to use in this context.
- `cacheEnabled` <?[boolean]> Toggles HTTP cache in the context. By default HTTP cache is enabled.
- `interceptNetwork` <?[boolean]> Toggles network interception in the context. Defaults to false.
- `offlineMode` <?[boolean]> Toggles offline network mode in the context. Defaults to false.
- `javaScriptEnabled` <?[boolean]> Whether or not to enable or disable JavaScript in the context. Defaults to true.
- `timezoneId` <?[string]> Changes the timezone of the context. See [ICU’s `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.
- `geolocation` <[Object]>
Expand Down Expand Up @@ -261,9 +264,12 @@ await context.close();
- [browserContext.cookies([...urls])](#browsercontextcookiesurls)
- [browserContext.newPage(url)](#browsercontextnewpageurl)
- [browserContext.pages()](#browsercontextpages)
- [browserContext.setCacheEnabled([enabled])](#browsercontextsetcacheenabledenabled)
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setOfflineMode(enabled)](#browsercontextsetofflinemodeenabled)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [browserContext.setRequestInterception(enabled)](#browsercontextsetrequestinterceptionenabled)
<!-- GEN:stop -->

#### browserContext.clearCookies()
Expand Down Expand Up @@ -321,6 +327,12 @@ Creates a new page in the browser context and optionally navigates it to the spe

An array of all pages inside the browser context.

#### browserContext.setCacheEnabled([enabled])
- `enabled` <[boolean]> sets the `enabled` state of the HTTP cache.
- returns: <[Promise]>

Toggles ignoring HTTP cache for each request based on the enabled state. By default, HTTP cache is enabled.

#### browserContext.setCookies(cookies)
- `cookies` <[Array]<[Object]>>
- `name` <[string]> **required**
Expand Down Expand Up @@ -353,6 +365,10 @@ await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667});

> **NOTE** Consider using [browserContext.setPermissions](#browsercontextsetpermissions-permissions) to grant permissions for the page to read its geolocation.
#### browserContext.setOfflineMode(enabled)
- `enabled` <[boolean]> When `true`, enables offline mode for the 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:
Expand Down Expand Up @@ -380,6 +396,32 @@ const context = browser.defaultContext();
await context.setPermissions('https://html5demos.com', ['geolocation']);
```

#### browserContext.setRequestInterception(enabled)
- `enabled` <[boolean]> Whether to enable request interception.
- returns: <[Promise]>

Activating request interception enables `request.abort`, `request.continue` and
`request.respond` methods. This provides the capability to modify network requests that are made by all pages in the context.

Once request interception is enabled, every request will stall unless it's continued, responded or aborted.
An example of a naïve request interceptor that aborts all image requests:

```js
const context = await browser.newContext();
const page = await context.newPage();
page.on('request', interceptedRequest => {
if (interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg'))
interceptedRequest.abort();
else
interceptedRequest.continue();
});
await context.setRequestInterception(true);
await page.goto('https://example.com');
await browser.close();
```

> **NOTE** Enabling request interception disables HTTP cache.
### class: Page

* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
Expand Down Expand Up @@ -471,13 +513,10 @@ page.removeListener('request', logRequest);
- [page.reload([options])](#pagereloadoptions)
- [page.screenshot([options])](#pagescreenshotoptions)
- [page.select(selector, value, options)](#pageselectselector-value-options)
- [page.setCacheEnabled([enabled])](#pagesetcacheenabledenabled)
- [page.setContent(html[, options])](#pagesetcontenthtml-options)
- [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout)
- [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout)
- [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders)
- [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled)
- [page.setRequestInterception(enabled)](#pagesetrequestinterceptionenabled)
- [page.setViewport(viewport)](#pagesetviewportviewport)
- [page.title()](#pagetitle)
- [page.tripleclick(selector[, options])](#pagetripleclickselector-options)
Expand Down Expand Up @@ -1222,12 +1261,6 @@ page.select('select#colors', { value: 'blue' }, { index: 2 }, 'red');

Shortcut for [page.mainFrame().select()](#frameselectselector-values)

#### page.setCacheEnabled([enabled])
- `enabled` <[boolean]> sets the `enabled` state of the cache.
- returns: <[Promise]>

Toggles ignoring cache for each request based on the enabled state. By default, caching is enabled.

#### page.setContent(html[, options])
- `html` <[string]> HTML markup to assign to the page.
- `options` <[Object]> Parameters which might have the following properties:
Expand Down Expand Up @@ -1279,35 +1312,6 @@ The extra HTTP headers will be sent with every request the page initiates.

> **NOTE** page.setExtraHTTPHeaders does not guarantee the order of headers in the outgoing requests.
#### page.setOfflineMode(enabled)
- `enabled` <[boolean]> When `true`, enables offline mode for the page.
- returns: <[Promise]>

#### page.setRequestInterception(enabled)
- `enabled` <[boolean]> Whether to enable request interception.
- returns: <[Promise]>

Activating request interception enables `request.abort`, `request.continue` and
`request.respond` methods. This provides the capability to modify network requests that are made by a page.

Once request interception is enabled, every request will stall unless it's continued, responded or aborted.
An example of a naïve request interceptor that aborts all image requests:

```js
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
if (interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg'))
interceptedRequest.abort();
else
interceptedRequest.continue();
});
await page.goto('https://example.com');
await browser.close();
```

> **NOTE** Enabling request interception disables page caching.
#### page.setViewport(viewport)
- `viewport` <[Object]>
- `width` <[number]> page width in pixels. **required**
Expand Down
24 changes: 24 additions & 0 deletions src/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export type BrowserContextOptions = {
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
userAgent?: string,
cacheEnabled?: boolean;
interceptNetwork?: boolean;
offlineMode?: boolean;
timezoneId?: string,
geolocation?: types.Geolocation,
permissions?: { [key: string]: string[] };
Expand Down Expand Up @@ -112,6 +115,27 @@ export class BrowserContext {
await this._delegate.setGeolocation(geolocation);
}

async setCacheEnabled(enabled: boolean = true) {
if (this._options.cacheEnabled === enabled)
return;
this._options.cacheEnabled = enabled;
await Promise.all(this._existingPages().map(page => page._delegate.setCacheEnabled(enabled)));
}

async setRequestInterception(enabled: boolean) {
if (this._options.interceptNetwork === enabled)
return;
this._options.interceptNetwork = enabled;
await Promise.all(this._existingPages().map(page => page._delegate.setRequestInterception(enabled)));
}

async setOfflineMode(enabled: boolean) {
if (this._options.offlineMode === enabled)
return;
this._options.offlineMode = enabled;
await Promise.all(this._existingPages().map(page => page._delegate.setOfflineMode(enabled)));
}

async close() {
if (this._closed)
return;
Expand Down
58 changes: 34 additions & 24 deletions src/chromium/crNetworkManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,9 @@ export class CRNetworkManager {
private _page: Page;
private _requestIdToRequest = new Map<string, InterceptableRequest>();
private _requestIdToRequestWillBeSentEvent = new Map<string, Protocol.Network.requestWillBeSentPayload>();
private _offline = false;
private _credentials: {username: string, password: string} | null = null;
private _attemptedAuthentications = new Set<string>();
private _userRequestInterceptionEnabled = false;
private _protocolRequestInterceptionEnabled = false;
private _userCacheDisabled = false;
private _requestIdToInterceptionId = new Map<string, string>();
private _eventListeners: RegisteredListener[];

Expand Down Expand Up @@ -63,7 +60,17 @@ export class CRNetworkManager {
}

async initialize() {
await this._client.send('Network.enable');
const promises: Promise<any>[] = [
this._client.send('Network.enable')
];
const options = this._page.browserContext()._options;
if (options.offlineMode)
promises.push(this.setOfflineMode(options.offlineMode));
if (this._userRequestInterceptionEnabled())
promises.push(this._updateProtocolRequestInterception());
else if (options.cacheEnabled === false)
promises.push(this._updateProtocolCacheDisabled());
await Promise.all(promises);
}

dispose() {
Expand All @@ -75,10 +82,9 @@ export class CRNetworkManager {
await this._updateProtocolRequestInterception();
}

async setOfflineMode(value: boolean) {
this._offline = value;
async setOfflineMode(offline: boolean) {
await this._client.send('Network.emulateNetworkConditions', {
offline: this._offline,
offline,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
Expand All @@ -91,17 +97,19 @@ export class CRNetworkManager {
}

async setCacheEnabled(enabled: boolean) {
this._userCacheDisabled = !enabled;
await this._updateProtocolCacheDisabled();
}

async setRequestInterception(value: boolean) {
this._userRequestInterceptionEnabled = value;
await this._updateProtocolRequestInterception();
}

async _updateProtocolRequestInterception() {
const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
private _userRequestInterceptionEnabled() : boolean {
return !!this._page.browserContext()._options.interceptNetwork;
}

private async _updateProtocolRequestInterception() {
const enabled = this._userRequestInterceptionEnabled() || !!this._credentials;
if (enabled === this._protocolRequestInterceptionEnabled)
return;
this._protocolRequestInterceptionEnabled = enabled;
Expand All @@ -121,13 +129,15 @@ export class CRNetworkManager {
}
}

async _updateProtocolCacheDisabled() {
private async _updateProtocolCacheDisabled() {
const options = this._page.browserContext()._options;
const cacheDisabled = options.cacheEnabled === false;
await this._client.send('Network.setCacheDisabled', {
cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled
cacheDisabled: cacheDisabled || this._protocolRequestInterceptionEnabled
});
}

_onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
private _onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
// Request interception doesn't happen for data URLs with Network Service.
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
const requestId = event.requestId;
Expand All @@ -143,7 +153,7 @@ export class CRNetworkManager {
this._onRequest(event, null);
}

_onAuthRequired(event: Protocol.Fetch.authRequiredPayload) {
private _onAuthRequired(event: Protocol.Fetch.authRequiredPayload) {
let response: 'Default' | 'CancelAuth' | 'ProvideCredentials' = 'Default';
if (this._attemptedAuthentications.has(event.requestId)) {
response = 'CancelAuth';
Expand All @@ -158,8 +168,8 @@ export class CRNetworkManager {
}).catch(debugError);
}

_onRequestPaused(event: Protocol.Fetch.requestPausedPayload) {
if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
private _onRequestPaused(event: Protocol.Fetch.requestPausedPayload) {
if (!this._userRequestInterceptionEnabled() && this._protocolRequestInterceptionEnabled) {
this._client.send('Fetch.continueRequest', {
requestId: event.requestId
}).catch(debugError);
Expand All @@ -178,7 +188,7 @@ export class CRNetworkManager {
}
}

_onRequest(event: Protocol.Network.requestWillBeSentPayload, interceptionId: string | null) {
private _onRequest(event: Protocol.Network.requestWillBeSentPayload, interceptionId: string | null) {
if (event.request.url.startsWith('data:'))
return;
let redirectChain: network.Request[] = [];
Expand All @@ -194,20 +204,20 @@ export class CRNetworkManager {
const frame = event.frameId ? this._page._frameManager.frame(event.frameId) : null;
const isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document';
const documentId = isNavigationRequest ? event.loaderId : undefined;
const request = new InterceptableRequest(this._client, frame, interceptionId, documentId, this._userRequestInterceptionEnabled, event, redirectChain);
const request = new InterceptableRequest(this._client, frame, interceptionId, documentId, this._userRequestInterceptionEnabled(), event, redirectChain);
this._requestIdToRequest.set(event.requestId, request);
this._page._frameManager.requestStarted(request.request);
}

_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
private _createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
const getResponseBody = async () => {
const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
};
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);
}

_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) {
private _handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) {
const response = this._createResponse(request, responsePayload);
request.request._redirectChain.push(request.request);
response._requestFinished(new Error('Response body is unavailable for redirect responses'));
Expand All @@ -218,7 +228,7 @@ export class CRNetworkManager {
this._page._frameManager.requestFinished(request.request);
}

_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
private _onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
const request = this._requestIdToRequest.get(event.requestId);
// FileUpload sends a response without a matching request.
if (!request)
Expand All @@ -227,7 +237,7 @@ export class CRNetworkManager {
this._page._frameManager.requestReceivedResponse(response);
}

_onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) {
private _onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) {
const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
Expand All @@ -245,7 +255,7 @@ export class CRNetworkManager {
this._page._frameManager.requestFinished(request.request);
}

_onLoadingFailed(event: Protocol.Network.loadingFailedPayload) {
private _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) {
const request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
Expand Down
4 changes: 4 additions & 0 deletions src/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export class FFPage implements PageDelegate {
promises.push(this._session.send('Page.setJavascriptEnabled', { enabled: false }));
if (options.userAgent)
promises.push(this._session.send('Page.setUserAgent', { userAgent: options.userAgent }));
if (options.cacheEnabled === false)
promises.push(this.setCacheEnabled(false));
if (options.interceptNetwork)
promises.push(this.setRequestInterception(true));
await Promise.all(promises);
}

Expand Down
Loading

0 comments on commit 6faf74b

Please sign in to comment.