feat(network): request interception and caching compatibility (#6996)

This patch enables developers to also keep the cache on when doing request interception.
This commit is contained in:
Robin Richtsfeld 2021-03-17 15:42:35 +01:00 committed by GitHub
parent bf60a300e7
commit 8695759a22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 9 deletions

View File

@ -166,7 +166,7 @@
* [page.setGeolocation(options)](#pagesetgeolocationoptions) * [page.setGeolocation(options)](#pagesetgeolocationoptions)
* [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled) * [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled)
* [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled) * [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled)
* [page.setRequestInterception(value)](#pagesetrequestinterceptionvalue) * [page.setRequestInterception(value[, cacheSafe])](#pagesetrequestinterceptionvalue-cachesafe)
* [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) * [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
* [page.setViewport(viewport)](#pagesetviewportviewport) * [page.setViewport(viewport)](#pagesetviewportviewport)
* [page.tap(selector)](#pagetapselector) * [page.tap(selector)](#pagetapselector)
@ -2039,8 +2039,9 @@ await page.setGeolocation({latitude: 59.95, longitude: 30.31667});
- `enabled` <[boolean]> When `true`, enables offline mode for the page. - `enabled` <[boolean]> When `true`, enables offline mode for the page.
- returns: <[Promise]> - returns: <[Promise]>
#### page.setRequestInterception(value) #### page.setRequestInterception(value[, cacheSafe])
- `value` <[boolean]> Whether to enable request interception. - `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]> - returns: <[Promise]>
Activating request interception enables `request.abort`, `request.continue` and Activating request interception enables `request.abort`, `request.continue` and

View File

@ -54,6 +54,7 @@ export interface InternalNetworkConditions extends NetworkConditions {
*/ */
export const NetworkManagerEmittedEvents = { export const NetworkManagerEmittedEvents = {
Request: Symbol('NetworkManager.Request'), Request: Symbol('NetworkManager.Request'),
RequestServedFromCache: Symbol('NetworkManager.RequestServedFromCache'),
Response: Symbol('NetworkManager.Response'), Response: Symbol('NetworkManager.Response'),
RequestFailed: Symbol('NetworkManager.RequestFailed'), RequestFailed: Symbol('NetworkManager.RequestFailed'),
RequestFinished: Symbol('NetworkManager.RequestFinished'), RequestFinished: Symbol('NetworkManager.RequestFinished'),
@ -75,6 +76,7 @@ export class NetworkManager extends EventEmitter {
_credentials?: Credentials = null; _credentials?: Credentials = null;
_attemptedAuthentications = new Set<string>(); _attemptedAuthentications = new Set<string>();
_userRequestInterceptionEnabled = false; _userRequestInterceptionEnabled = false;
_userRequestInterceptionCacheSafe = false;
_protocolRequestInterceptionEnabled = false; _protocolRequestInterceptionEnabled = false;
_userCacheDisabled = false; _userCacheDisabled = false;
_requestIdToInterceptionId = new Map<string, string>(); _requestIdToInterceptionId = new Map<string, string>();
@ -189,8 +191,12 @@ export class NetworkManager extends EventEmitter {
await this._updateProtocolCacheDisabled(); await this._updateProtocolCacheDisabled();
} }
async setRequestInterception(value: boolean): Promise<void> { async setRequestInterception(
value: boolean,
cacheSafe = false
): Promise<void> {
this._userRequestInterceptionEnabled = value; this._userRequestInterceptionEnabled = value;
this._userRequestInterceptionCacheSafe = cacheSafe;
await this._updateProtocolRequestInterception(); await this._updateProtocolRequestInterception();
} }
@ -217,7 +223,9 @@ export class NetworkManager extends EventEmitter {
async _updateProtocolCacheDisabled(): Promise<void> { async _updateProtocolCacheDisabled(): Promise<void> {
await this._client.send('Network.setCacheDisabled', { await this._client.send('Network.setCacheDisabled', {
cacheDisabled: cacheDisabled:
this._userCacheDisabled || this._userRequestInterceptionEnabled, this._userCacheDisabled ||
(this._userRequestInterceptionEnabled &&
!this._userRequestInterceptionCacheSafe),
}); });
} }
@ -323,6 +331,7 @@ export class NetworkManager extends EventEmitter {
): void { ): void {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
if (request) request._fromMemoryCache = true; if (request) request._fromMemoryCache = true;
this.emit(NetworkManagerEmittedEvents.RequestServedFromCache, request);
} }
_handleRequestRedirect( _handleRequestRedirect(

View File

@ -287,6 +287,14 @@ export const enum PageEmittedEvents {
* and mutating requests. * and mutating requests.
*/ */
Request = 'request', 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. * Emitted when a request fails, for example by timing out.
* *
@ -483,6 +491,10 @@ export class Page extends EventEmitter {
networkManager.on(NetworkManagerEmittedEvents.Request, (event) => networkManager.on(NetworkManagerEmittedEvents.Request, (event) =>
this.emit(PageEmittedEvents.Request, event) this.emit(PageEmittedEvents.Request, event)
); );
networkManager.on(
NetworkManagerEmittedEvents.RequestServedFromCache,
(event) => this.emit(PageEmittedEvents.RequestServedFromCache, event)
);
networkManager.on(NetworkManagerEmittedEvents.Response, (event) => networkManager.on(NetworkManagerEmittedEvents.Response, (event) =>
this.emit(PageEmittedEvents.Response, event) this.emit(PageEmittedEvents.Response, event)
); );
@ -688,6 +700,8 @@ export class Page extends EventEmitter {
/** /**
* @param value - Whether to enable request interception. * @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 * @remarks
* Activating request interception enables {@link HTTPRequest.abort}, * Activating request interception enables {@link HTTPRequest.abort},
@ -695,9 +709,7 @@ export class Page extends EventEmitter {
* provides the capability to modify network requests that are made by a page. * 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 * Once request interception is enabled, every request will stall unless it's
* continued, responded or aborted. * continued, responded or aborted; or completed using the browser cache.
*
* **NOTE** Enabling request interception disables page caching.
* *
* @example * @example
* An example of a naïve request interceptor that aborts all image requests: * An example of a naïve request interceptor that aborts all image requests:
@ -719,8 +731,13 @@ export class Page extends EventEmitter {
* })(); * })();
* ``` * ```
*/ */
async setRequestInterception(value: boolean): Promise<void> { async setRequestInterception(
return this._frameManager.networkManager().setRequestInterception(value); value: boolean,
cacheSafe = false
): Promise<void> {
return this._frameManager
.networkManager()
.setRequestInterception(value, cacheSafe);
} }
/** /**

View File

@ -355,6 +355,20 @@ describe('network', function () {
expect(requests[0].frame() === page.mainFrame()).toBe(true); expect(requests[0].frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); 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 () => { it('Page.Events.Response', async () => {
const { page, server } = getTestState(); const { page, server } = getTestState();

View File

@ -495,6 +495,36 @@ describe('request interception', function () {
expect(urls.has('one-style.html')).toBe(true); expect(urls.has('one-style.html')).toBe(true);
expect(urls.has('one-style.css')).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 () { describeFailsFirefox('Request.continue', function () {