Skip to content

Commit 6faf74b

Browse files
authored
fix: move offline/cache/interception switches to BrowserContext (#748)
1 parent 9a126da commit 6faf74b

File tree

13 files changed

+257
-200
lines changed

13 files changed

+257
-200
lines changed

docs/api.md

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ Indicates that the browser is connected.
212212
- `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
213213
- `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
214214
- `userAgent` <?[string]> Specific user agent to use in this context.
215+
- `cacheEnabled` <?[boolean]> Toggles HTTP cache in the context. By default HTTP cache is enabled.
216+
- `interceptNetwork` <?[boolean]> Toggles network interception in the context. Defaults to false.
217+
- `offlineMode` <?[boolean]> Toggles offline network mode in the context. Defaults to false.
215218
- `javaScriptEnabled` <?[boolean]> Whether or not to enable or disable JavaScript in the context. Defaults to true.
216219
- `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.
217220
- `geolocation` <[Object]>
@@ -261,9 +264,12 @@ await context.close();
261264
- [browserContext.cookies([...urls])](#browsercontextcookiesurls)
262265
- [browserContext.newPage(url)](#browsercontextnewpageurl)
263266
- [browserContext.pages()](#browsercontextpages)
267+
- [browserContext.setCacheEnabled([enabled])](#browsercontextsetcacheenabledenabled)
264268
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
265269
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
270+
- [browserContext.setOfflineMode(enabled)](#browsercontextsetofflinemodeenabled)
266271
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
272+
- [browserContext.setRequestInterception(enabled)](#browsercontextsetrequestinterceptionenabled)
267273
<!-- GEN:stop -->
268274

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

322328
An array of all pages inside the browser context.
323329

330+
#### browserContext.setCacheEnabled([enabled])
331+
- `enabled` <[boolean]> sets the `enabled` state of the HTTP cache.
332+
- returns: <[Promise]>
333+
334+
Toggles ignoring HTTP cache for each request based on the enabled state. By default, HTTP cache is enabled.
335+
324336
#### browserContext.setCookies(cookies)
325337
- `cookies` <[Array]<[Object]>>
326338
- `name` <[string]> **required**
@@ -353,6 +365,10 @@ await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667});
353365

354366
> **NOTE** Consider using [browserContext.setPermissions](#browsercontextsetpermissions-permissions) to grant permissions for the page to read its geolocation.
355367
368+
#### browserContext.setOfflineMode(enabled)
369+
- `enabled` <[boolean]> When `true`, enables offline mode for the context.
370+
- returns: <[Promise]>
371+
356372
#### browserContext.setPermissions(origin, permissions[])
357373
- `origin` <[string]> The [origin] to grant permissions to, e.g. "https://example.com".
358374
- `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:
@@ -380,6 +396,32 @@ const context = browser.defaultContext();
380396
await context.setPermissions('https://html5demos.com', ['geolocation']);
381397
```
382398

399+
#### browserContext.setRequestInterception(enabled)
400+
- `enabled` <[boolean]> Whether to enable request interception.
401+
- returns: <[Promise]>
402+
403+
Activating request interception enables `request.abort`, `request.continue` and
404+
`request.respond` methods. This provides the capability to modify network requests that are made by all pages in the context.
405+
406+
Once request interception is enabled, every request will stall unless it's continued, responded or aborted.
407+
An example of a naïve request interceptor that aborts all image requests:
408+
409+
```js
410+
const context = await browser.newContext();
411+
const page = await context.newPage();
412+
page.on('request', interceptedRequest => {
413+
if (interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg'))
414+
interceptedRequest.abort();
415+
else
416+
interceptedRequest.continue();
417+
});
418+
await context.setRequestInterception(true);
419+
await page.goto('https://example.com');
420+
await browser.close();
421+
```
422+
423+
> **NOTE** Enabling request interception disables HTTP cache.
424+
383425
### class: Page
384426

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

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

1225-
#### page.setCacheEnabled([enabled])
1226-
- `enabled` <[boolean]> sets the `enabled` state of the cache.
1227-
- returns: <[Promise]>
1228-
1229-
Toggles ignoring cache for each request based on the enabled state. By default, caching is enabled.
1230-
12311264
#### page.setContent(html[, options])
12321265
- `html` <[string]> HTML markup to assign to the page.
12331266
- `options` <[Object]> Parameters which might have the following properties:
@@ -1279,35 +1312,6 @@ The extra HTTP headers will be sent with every request the page initiates.
12791312

12801313
> **NOTE** page.setExtraHTTPHeaders does not guarantee the order of headers in the outgoing requests.
12811314
1282-
#### page.setOfflineMode(enabled)
1283-
- `enabled` <[boolean]> When `true`, enables offline mode for the page.
1284-
- returns: <[Promise]>
1285-
1286-
#### page.setRequestInterception(enabled)
1287-
- `enabled` <[boolean]> Whether to enable request interception.
1288-
- returns: <[Promise]>
1289-
1290-
Activating request interception enables `request.abort`, `request.continue` and
1291-
`request.respond` methods. This provides the capability to modify network requests that are made by a page.
1292-
1293-
Once request interception is enabled, every request will stall unless it's continued, responded or aborted.
1294-
An example of a naïve request interceptor that aborts all image requests:
1295-
1296-
```js
1297-
const page = await browser.newPage();
1298-
await page.setRequestInterception(true);
1299-
page.on('request', interceptedRequest => {
1300-
if (interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg'))
1301-
interceptedRequest.abort();
1302-
else
1303-
interceptedRequest.continue();
1304-
});
1305-
await page.goto('https://example.com');
1306-
await browser.close();
1307-
```
1308-
1309-
> **NOTE** Enabling request interception disables page caching.
1310-
13111315
#### page.setViewport(viewport)
13121316
- `viewport` <[Object]>
13131317
- `width` <[number]> page width in pixels. **required**

src/browserContext.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export type BrowserContextOptions = {
4242
javaScriptEnabled?: boolean,
4343
bypassCSP?: boolean,
4444
userAgent?: string,
45+
cacheEnabled?: boolean;
46+
interceptNetwork?: boolean;
47+
offlineMode?: boolean;
4548
timezoneId?: string,
4649
geolocation?: types.Geolocation,
4750
permissions?: { [key: string]: string[] };
@@ -112,6 +115,27 @@ export class BrowserContext {
112115
await this._delegate.setGeolocation(geolocation);
113116
}
114117

118+
async setCacheEnabled(enabled: boolean = true) {
119+
if (this._options.cacheEnabled === enabled)
120+
return;
121+
this._options.cacheEnabled = enabled;
122+
await Promise.all(this._existingPages().map(page => page._delegate.setCacheEnabled(enabled)));
123+
}
124+
125+
async setRequestInterception(enabled: boolean) {
126+
if (this._options.interceptNetwork === enabled)
127+
return;
128+
this._options.interceptNetwork = enabled;
129+
await Promise.all(this._existingPages().map(page => page._delegate.setRequestInterception(enabled)));
130+
}
131+
132+
async setOfflineMode(enabled: boolean) {
133+
if (this._options.offlineMode === enabled)
134+
return;
135+
this._options.offlineMode = enabled;
136+
await Promise.all(this._existingPages().map(page => page._delegate.setOfflineMode(enabled)));
137+
}
138+
115139
async close() {
116140
if (this._closed)
117141
return;

src/chromium/crNetworkManager.ts

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,9 @@ export class CRNetworkManager {
2929
private _page: Page;
3030
private _requestIdToRequest = new Map<string, InterceptableRequest>();
3131
private _requestIdToRequestWillBeSentEvent = new Map<string, Protocol.Network.requestWillBeSentPayload>();
32-
private _offline = false;
3332
private _credentials: {username: string, password: string} | null = null;
3433
private _attemptedAuthentications = new Set<string>();
35-
private _userRequestInterceptionEnabled = false;
3634
private _protocolRequestInterceptionEnabled = false;
37-
private _userCacheDisabled = false;
3835
private _requestIdToInterceptionId = new Map<string, string>();
3936
private _eventListeners: RegisteredListener[];
4037

@@ -63,7 +60,17 @@ export class CRNetworkManager {
6360
}
6461

6562
async initialize() {
66-
await this._client.send('Network.enable');
63+
const promises: Promise<any>[] = [
64+
this._client.send('Network.enable')
65+
];
66+
const options = this._page.browserContext()._options;
67+
if (options.offlineMode)
68+
promises.push(this.setOfflineMode(options.offlineMode));
69+
if (this._userRequestInterceptionEnabled())
70+
promises.push(this._updateProtocolRequestInterception());
71+
else if (options.cacheEnabled === false)
72+
promises.push(this._updateProtocolCacheDisabled());
73+
await Promise.all(promises);
6774
}
6875

6976
dispose() {
@@ -75,10 +82,9 @@ export class CRNetworkManager {
7582
await this._updateProtocolRequestInterception();
7683
}
7784

78-
async setOfflineMode(value: boolean) {
79-
this._offline = value;
85+
async setOfflineMode(offline: boolean) {
8086
await this._client.send('Network.emulateNetworkConditions', {
81-
offline: this._offline,
87+
offline,
8288
// values of 0 remove any active throttling. crbug.com/456324#c9
8389
latency: 0,
8490
downloadThroughput: -1,
@@ -91,17 +97,19 @@ export class CRNetworkManager {
9197
}
9298

9399
async setCacheEnabled(enabled: boolean) {
94-
this._userCacheDisabled = !enabled;
95100
await this._updateProtocolCacheDisabled();
96101
}
97102

98103
async setRequestInterception(value: boolean) {
99-
this._userRequestInterceptionEnabled = value;
100104
await this._updateProtocolRequestInterception();
101105
}
102106

103-
async _updateProtocolRequestInterception() {
104-
const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
107+
private _userRequestInterceptionEnabled() : boolean {
108+
return !!this._page.browserContext()._options.interceptNetwork;
109+
}
110+
111+
private async _updateProtocolRequestInterception() {
112+
const enabled = this._userRequestInterceptionEnabled() || !!this._credentials;
105113
if (enabled === this._protocolRequestInterceptionEnabled)
106114
return;
107115
this._protocolRequestInterceptionEnabled = enabled;
@@ -121,13 +129,15 @@ export class CRNetworkManager {
121129
}
122130
}
123131

124-
async _updateProtocolCacheDisabled() {
132+
private async _updateProtocolCacheDisabled() {
133+
const options = this._page.browserContext()._options;
134+
const cacheDisabled = options.cacheEnabled === false;
125135
await this._client.send('Network.setCacheDisabled', {
126-
cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled
136+
cacheDisabled: cacheDisabled || this._protocolRequestInterceptionEnabled
127137
});
128138
}
129139

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

146-
_onAuthRequired(event: Protocol.Fetch.authRequiredPayload) {
156+
private _onAuthRequired(event: Protocol.Fetch.authRequiredPayload) {
147157
let response: 'Default' | 'CancelAuth' | 'ProvideCredentials' = 'Default';
148158
if (this._attemptedAuthentications.has(event.requestId)) {
149159
response = 'CancelAuth';
@@ -158,8 +168,8 @@ export class CRNetworkManager {
158168
}).catch(debugError);
159169
}
160170

161-
_onRequestPaused(event: Protocol.Fetch.requestPausedPayload) {
162-
if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
171+
private _onRequestPaused(event: Protocol.Fetch.requestPausedPayload) {
172+
if (!this._userRequestInterceptionEnabled() && this._protocolRequestInterceptionEnabled) {
163173
this._client.send('Fetch.continueRequest', {
164174
requestId: event.requestId
165175
}).catch(debugError);
@@ -178,7 +188,7 @@ export class CRNetworkManager {
178188
}
179189
}
180190

181-
_onRequest(event: Protocol.Network.requestWillBeSentPayload, interceptionId: string | null) {
191+
private _onRequest(event: Protocol.Network.requestWillBeSentPayload, interceptionId: string | null) {
182192
if (event.request.url.startsWith('data:'))
183193
return;
184194
let redirectChain: network.Request[] = [];
@@ -194,20 +204,20 @@ export class CRNetworkManager {
194204
const frame = event.frameId ? this._page._frameManager.frame(event.frameId) : null;
195205
const isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document';
196206
const documentId = isNavigationRequest ? event.loaderId : undefined;
197-
const request = new InterceptableRequest(this._client, frame, interceptionId, documentId, this._userRequestInterceptionEnabled, event, redirectChain);
207+
const request = new InterceptableRequest(this._client, frame, interceptionId, documentId, this._userRequestInterceptionEnabled(), event, redirectChain);
198208
this._requestIdToRequest.set(event.requestId, request);
199209
this._page._frameManager.requestStarted(request.request);
200210
}
201211

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

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

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

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

248-
_onLoadingFailed(event: Protocol.Network.loadingFailedPayload) {
258+
private _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) {
249259
const request = this._requestIdToRequest.get(event.requestId);
250260
// For certain requestIds we never receive requestWillBeSent event.
251261
// @see https://crbug.com/750469

src/firefox/ffPage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ export class FFPage implements PageDelegate {
9090
promises.push(this._session.send('Page.setJavascriptEnabled', { enabled: false }));
9191
if (options.userAgent)
9292
promises.push(this._session.send('Page.setUserAgent', { userAgent: options.userAgent }));
93+
if (options.cacheEnabled === false)
94+
promises.push(this.setCacheEnabled(false));
95+
if (options.interceptNetwork)
96+
promises.push(this.setRequestInterception(true));
9397
await Promise.all(promises);
9498
}
9599

0 commit comments

Comments
 (0)