Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request interception and caching compatibility #6996

Merged
merged 8 commits into from Mar 17, 2021
5 changes: 3 additions & 2 deletions docs/api.md
Expand Up @@ -166,7 +166,7 @@
* [page.setGeolocation(options)](#pagesetgeolocationoptions)
* [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled)
* [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled)
* [page.setRequestInterception(value)](#pagesetrequestinterceptionvalue)
* [page.setRequestInterception(value[, cacheSafe])](#pagesetrequestinterceptionvalue-cachesafe)
* [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
* [page.setViewport(viewport)](#pagesetviewportviewport)
* [page.tap(selector)](#pagetapselector)
Expand Down Expand Up @@ -2039,8 +2039,9 @@ await page.setGeolocation({latitude: 59.95, longitude: 30.31667});
- `enabled` <[boolean]> When `true`, enables offline mode for the page.
- returns: <[Promise]>

#### page.setRequestInterception(value)
#### page.setRequestInterception(value[, cacheSafe])
- `value` <[boolean]> Whether to enable request interception.
- `cacheSafe` <[boolean]> Whether to trust browser caching. If set to false, enabling request interception disables page caching. Defaults to false.
- returns: <[Promise]>

Activating request interception enables `request.abort`, `request.continue` and
Expand Down
13 changes: 11 additions & 2 deletions src/common/NetworkManager.ts
Expand Up @@ -54,6 +54,7 @@ export interface InternalNetworkConditions extends NetworkConditions {
*/
export const NetworkManagerEmittedEvents = {
Request: Symbol('NetworkManager.Request'),
RequestServedFromCache: Symbol('NetworkManager.RequestServedFromCache'),
Response: Symbol('NetworkManager.Response'),
RequestFailed: Symbol('NetworkManager.RequestFailed'),
RequestFinished: Symbol('NetworkManager.RequestFinished'),
Expand All @@ -75,6 +76,7 @@ export class NetworkManager extends EventEmitter {
_credentials?: Credentials = null;
_attemptedAuthentications = new Set<string>();
_userRequestInterceptionEnabled = false;
_userRequestInterceptionCacheSafe = false;
_protocolRequestInterceptionEnabled = false;
_userCacheDisabled = false;
_requestIdToInterceptionId = new Map<string, string>();
Expand Down Expand Up @@ -189,8 +191,12 @@ export class NetworkManager extends EventEmitter {
await this._updateProtocolCacheDisabled();
}

async setRequestInterception(value: boolean): Promise<void> {
async setRequestInterception(
value: boolean,
cacheSafe = false
): Promise<void> {
this._userRequestInterceptionEnabled = value;
this._userRequestInterceptionCacheSafe = cacheSafe;
await this._updateProtocolRequestInterception();
}

Expand All @@ -217,7 +223,9 @@ export class NetworkManager extends EventEmitter {
async _updateProtocolCacheDisabled(): Promise<void> {
await this._client.send('Network.setCacheDisabled', {
cacheDisabled:
this._userCacheDisabled || this._userRequestInterceptionEnabled,
this._userCacheDisabled ||
(this._userRequestInterceptionEnabled &&
!this._userRequestInterceptionCacheSafe),
});
}

Expand Down Expand Up @@ -323,6 +331,7 @@ export class NetworkManager extends EventEmitter {
): void {
const request = this._requestIdToRequest.get(event.requestId);
if (request) request._fromMemoryCache = true;
this.emit(NetworkManagerEmittedEvents.RequestServedFromCache, request);
}

_handleRequestRedirect(
Expand Down
27 changes: 22 additions & 5 deletions src/common/Page.ts
Expand Up @@ -287,6 +287,14 @@ export const enum PageEmittedEvents {
* and mutating requests.
*/
Request = 'request',
/**
* Emitted when a request ended up loading from cache. Contains a {@link HTTPRequest}.
*
* @remarks
* For certain requests, might contain undefined.
* @see https://crbug.com/750469
*/
RequestServedFromCache = 'requestservedfromcache',
/**
* Emitted when a request fails, for example by timing out.
*
Expand Down Expand Up @@ -483,6 +491,10 @@ export class Page extends EventEmitter {
networkManager.on(NetworkManagerEmittedEvents.Request, (event) =>
this.emit(PageEmittedEvents.Request, event)
);
networkManager.on(
NetworkManagerEmittedEvents.RequestServedFromCache,
(event) => this.emit(PageEmittedEvents.RequestServedFromCache, event)
);
networkManager.on(NetworkManagerEmittedEvents.Response, (event) =>
this.emit(PageEmittedEvents.Response, event)
);
Expand Down Expand Up @@ -688,16 +700,16 @@ export class Page extends EventEmitter {

/**
* @param value - Whether to enable request interception.
* @param cacheSafe - Whether to trust browser caching. If set to false,
* enabling request interception disables page caching. Defaults to false.
*
* @remarks
* Activating request interception enables {@link HTTPRequest.abort},
* {@link HTTPRequest.continue} and {@link HTTPRequest.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.
*
* **NOTE** Enabling request interception disables page caching.
* continued, responded or aborted; or completed using the browser cache.
*
* @example
* An example of a naïve request interceptor that aborts all image requests:
Expand All @@ -719,8 +731,13 @@ export class Page extends EventEmitter {
* })();
* ```
*/
async setRequestInterception(value: boolean): Promise<void> {
return this._frameManager.networkManager().setRequestInterception(value);
async setRequestInterception(
value: boolean,
cacheSafe = false
): Promise<void> {
return this._frameManager
.networkManager()
.setRequestInterception(value, cacheSafe);
}

/**
Expand Down
14 changes: 14 additions & 0 deletions test/network.spec.ts
Expand Up @@ -355,6 +355,20 @@ describe('network', function () {
expect(requests[0].frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
});
it('Page.Events.RequestServedFromCache', async () => {
const { page, server } = getTestState();

const cached = [];
page.on('requestservedfromcache', (r) =>
cached.push(r.url().split('/').pop())
);

await page.goto(server.PREFIX + '/cached/one-style.html');
expect(cached).toEqual([]);

await page.reload();
expect(cached).toEqual(['one-style.css']);
});
it('Page.Events.Response', async () => {
const { page, server } = getTestState();

Expand Down
30 changes: 30 additions & 0 deletions test/requestinterception.spec.ts
Expand Up @@ -495,6 +495,36 @@ describe('request interception', function () {
expect(urls.has('one-style.html')).toBe(true);
expect(urls.has('one-style.css')).toBe(true);
});
it('should not cache if not cache-safe', async () => {
const { page, server } = getTestState();

// Load and re-load to make sure it's cached.
await page.goto(server.PREFIX + '/cached/one-style.html');

await page.setRequestInterception(true, false);
page.on('request', (request) => request.continue());

const cached = [];
page.on('requestservedfromcache', (r) => cached.push(r));

await page.reload();
expect(cached.length).toBe(0);
});
it('should cache if cache-safe', async () => {
const { page, server } = getTestState();

// Load and re-load to make sure it's cached.
await page.goto(server.PREFIX + '/cached/one-style.html');

await page.setRequestInterception(true, true);
page.on('request', (request) => request.continue());

const cached = [];
page.on('requestservedfromcache', (r) => cached.push(r));

await page.reload();
expect(cached.length).toBe(1);
});
});

describeFailsFirefox('Request.continue', function () {
Expand Down