diff --git a/src/server/snapshot/snapshotRenderer.ts b/src/server/snapshot/snapshotRenderer.ts
index 8979745740..1d80ff6235 100644
--- a/src/server/snapshot/snapshotRenderer.ts
+++ b/src/server/snapshot/snapshotRenderer.ts
@@ -21,11 +21,13 @@ export class SnapshotRenderer {
private _index: number;
readonly snapshotName: string | undefined;
private _resources: ResourceSnapshot[];
+ private _snapshot: FrameSnapshot;
constructor(resources: ResourceSnapshot[], snapshots: FrameSnapshot[], index: number) {
this._resources = resources;
this._snapshots = snapshots;
this._index = index;
+ this._snapshot = snapshots[index];
this.snapshotName = snapshots[index].snapshotName;
}
@@ -73,10 +75,10 @@ export class SnapshotRenderer {
return (n as any)._string;
};
- const snapshot = this._snapshots[this._index];
+ const snapshot = this._snapshot;
let html = visit(snapshot.html, this._index);
if (!html)
- return { html: '', resources: {} };
+ return { html: '', pageId: snapshot.pageId, frameId: snapshot.frameId, index: this._index };
if (snapshot.doctype)
html = `` + html;
@@ -85,26 +87,46 @@ export class SnapshotRenderer {
`;
- const resources: { [key: string]: { resourceId: string, sha1?: string } } = {};
- // First capture all resources for all frames, to account for memory cache.
- for (const resource of this._resources) {
- if (resource.timestamp >= snapshot.timestamp)
- break;
- resources[resource.url] = { resourceId: resource.resourceId };
- }
- // Then overwrite with the ones from our frame.
+ return { html, pageId: snapshot.pageId, frameId: snapshot.frameId, index: this._index };
+ }
+
+ resourceByUrl(url: string): ResourceSnapshot | undefined {
+ const snapshot = this._snapshot;
+ let result: ResourceSnapshot | undefined;
+
+ // First try locating exact resource belonging to this frame.
for (const resource of this._resources) {
if (resource.timestamp >= snapshot.timestamp)
break;
if (resource.frameId !== snapshot.frameId)
continue;
- resources[resource.url] = { resourceId: resource.resourceId };
+ if (resource.url === url) {
+ result = resource;
+ break;
+ }
}
- for (const o of snapshot.resourceOverrides) {
- const resource = resources[o.url];
- resource.sha1 = o.sha1;
+
+ if (!result) {
+ // Then fall back to resource with this URL to account for memory cache.
+ for (const resource of this._resources) {
+ if (resource.timestamp >= snapshot.timestamp)
+ break;
+ if (resource.url === url)
+ return resource;
+ }
}
- return { html, resources };
+
+ if (result) {
+ // Patch override if necessary.
+ for (const o of snapshot.resourceOverrides) {
+ if (url === o.url && o.sha1) {
+ result = { ...result, responseSha1: o.sha1 };
+ break;
+ }
+ }
+ }
+
+ return result;
}
}
diff --git a/src/server/snapshot/snapshotServer.ts b/src/server/snapshot/snapshotServer.ts
index 8982405025..9ad97df8e3 100644
--- a/src/server/snapshot/snapshotServer.ts
+++ b/src/server/snapshot/snapshotServer.ts
@@ -62,7 +62,7 @@ export class SnapshotServer {
private _serveServiceWorker(request: http.IncomingMessage, response: http.ServerResponse): boolean {
function serviceWorkerMain(self: any /* ServiceWorkerGlobalScope */) {
- const snapshotResources = new Map();
+ const snapshotIds = new Map();
self.addEventListener('install', function(event: any) {
});
@@ -71,10 +71,6 @@ export class SnapshotServer {
event.waitUntil(self.clients.claim());
});
- function respond404(): Response {
- return new Response(null, { status: 404 });
- }
-
function respondNotAvailable(): Response {
return new Response('', { status: 200, headers: { 'Content-Type': 'text/html' } });
}
@@ -100,34 +96,26 @@ export class SnapshotServer {
if (request.mode === 'navigate') {
const htmlResponse = await fetch(event.request);
- const { html, resources }: RenderedFrameSnapshot = await htmlResponse.json();
+ const { html, frameId, index }: RenderedFrameSnapshot = await htmlResponse.json();
if (!html)
return respondNotAvailable();
- snapshotResources.set(snapshotUrl, resources);
+ snapshotIds.set(snapshotUrl, { frameId, index });
const response = new Response(html, { status: 200, headers: { 'Content-Type': 'text/html' } });
return response;
}
- const resources = snapshotResources.get(snapshotUrl)!;
- const urlWithoutHash = removeHash(request.url);
- const resource = resources[urlWithoutHash];
- if (!resource)
- return respond404();
-
- const fetchUrl = resource.sha1 ?
- `/resources/${resource.resourceId}/override/${resource.sha1}` :
- `/resources/${resource.resourceId}`;
+ const { frameId, index } = snapshotIds.get(snapshotUrl)!;
+ const url = removeHash(request.url);
+ const complexUrl = btoa(JSON.stringify({ frameId, index, url }));
+ const fetchUrl = `/resources/${complexUrl}`;
const fetchedResponse = await fetch(fetchUrl);
- const headers = new Headers(fetchedResponse.headers);
// We make a copy of the response, instead of just forwarding,
// so that response url is not inherited as "/resources/...", but instead
// as the original request url.
+
// Response url turns into resource base uri that is used to resolve
// relative links, e.g. url(/foo/bar) in style sheets.
- if (resource.sha1) {
- // No cache, so that we refetch overridden resources.
- headers.set('Cache-Control', 'no-cache');
- }
+ const headers = new Headers(fetchedResponse.headers);
const response = new Response(fetchedResponse.body, {
status: fetchedResponse.status,
statusText: fetchedResponse.statusText,
@@ -178,32 +166,13 @@ export class SnapshotServer {
}
private _serveResource(request: http.IncomingMessage, response: http.ServerResponse): boolean {
- // - /resources/
- // - /resources//override/
- const parts = request.url!.split('/');
- if (!parts[0])
- parts.shift();
- if (!parts[parts.length - 1])
- parts.pop();
- if (parts[0] !== 'resources')
- return false;
-
- let resourceId;
- let overrideSha1;
- if (parts.length === 2) {
- resourceId = parts[1];
- } else if (parts.length === 4 && parts[2] === 'override') {
- resourceId = parts[1];
- overrideSha1 = parts[3];
- } else {
- return false;
- }
-
- const resource = this._snapshotStorage.resourceById(resourceId);
+ const { frameId, index, url } = JSON.parse(Buffer.from(request.url!.substring('/resources/'.length), 'base64').toString());
+ const snapshot = this._snapshotStorage.snapshotByIndex(frameId, index);
+ const resource = snapshot?.resourceByUrl(url);
if (!resource)
return false;
- const sha1 = overrideSha1 || resource.responseSha1;
+ const sha1 = resource.responseSha1;
try {
const content = this._snapshotStorage.resourceContent(sha1);
if (!content)
diff --git a/src/server/snapshot/snapshotStorage.ts b/src/server/snapshot/snapshotStorage.ts
index dc1f5b7777..6234ab76d6 100644
--- a/src/server/snapshot/snapshotStorage.ts
+++ b/src/server/snapshot/snapshotStorage.ts
@@ -21,13 +21,12 @@ import { SnapshotRenderer } from './snapshotRenderer';
export interface SnapshotStorage {
resources(): ResourceSnapshot[];
resourceContent(sha1: string): Buffer | undefined;
- resourceById(resourceId: string): ResourceSnapshot | undefined;
snapshotByName(pageOrFrameId: string, snapshotName: string): SnapshotRenderer | undefined;
+ snapshotByIndex(frameId: string, index: number): SnapshotRenderer | undefined;
}
export abstract class BaseSnapshotStorage extends EventEmitter implements SnapshotStorage {
protected _resources: ResourceSnapshot[] = [];
- protected _resourceMap = new Map();
protected _frameSnapshots = new Map r.snapshotName === snapshotName);
}
+
+ snapshotByIndex(frameId: string, index: number): SnapshotRenderer | undefined {
+ const snapshot = this._frameSnapshots.get(frameId);
+ return snapshot?.renderer[index];
+ }
+
}
diff --git a/src/server/snapshot/snapshotTypes.ts b/src/server/snapshot/snapshotTypes.ts
index c41c4a7264..cf5368fd08 100644
--- a/src/server/snapshot/snapshotTypes.ts
+++ b/src/server/snapshot/snapshotTypes.ts
@@ -15,7 +15,6 @@
*/
export type ResourceSnapshot = {
- resourceId: string,
pageId: string,
frameId: string,
url: string,
@@ -65,5 +64,7 @@ export type FrameSnapshot = {
export type RenderedFrameSnapshot = {
html: string;
- resources: { [key: string]: { resourceId: string, sha1?: string } };
+ pageId: string;
+ frameId: string;
+ index: number;
};
diff --git a/src/server/snapshot/snapshotter.ts b/src/server/snapshot/snapshotter.ts
index 85ea367593..49985acb9e 100644
--- a/src/server/snapshot/snapshotter.ts
+++ b/src/server/snapshot/snapshotter.ts
@@ -207,7 +207,6 @@ export class Snapshotter {
const resource: ResourceSnapshot = {
pageId: response.frame()._page.guid,
frameId: response.frame().guid,
- resourceId: response.guid,
url,
type: response.request().resourceType(),
contentType,
diff --git a/src/server/trace/viewer/traceViewer.ts b/src/server/trace/viewer/traceViewer.ts
index 0275f58d2d..8098eb0bf6 100644
--- a/src/server/trace/viewer/traceViewer.ts
+++ b/src/server/trace/viewer/traceViewer.ts
@@ -49,11 +49,11 @@ export class TraceViewer {
// - "/sha1/" - trace resource bodies, used by network previews.
//
// Served by SnapshotServer
- // - "/resources/" - network resources from the trace.
+ // - "/resources/" - network resources from the trace.
// - "/snapshot/" - root for snapshot frame.
// - "/snapshot/pageId/..." - actual snapshot html.
// - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources
- // and translates them into "/resources/".
+ // and translates them into network requests.
const actionTraces = fs.readdirSync(tracesDir).filter(name => name.endsWith('.trace'));
const debugNames = actionTraces.map(name => {
const tracePrefix = path.join(tracesDir, name.substring(0, name.indexOf('.trace')));
diff --git a/tests/snapshotter.spec.ts b/tests/snapshotter.spec.ts
index 738c5220aa..0c897afc32 100644
--- a/tests/snapshotter.spec.ts
+++ b/tests/snapshotter.spec.ts
@@ -62,9 +62,8 @@ it.describe('snapshots', () => {
});
await page.setContent('');
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot');
- const { resources } = snapshot.render();
- const cssHref = `http://localhost:${server.PORT}/style.css`;
- expect(resources[cssHref]).toBeTruthy();
+ const resource = snapshot.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
+ expect(resource).toBeTruthy();
});
it('should collect multiple', async ({ page, toImpl, snapshotter }) => {
@@ -126,10 +125,8 @@ it.describe('snapshots', () => {
await page.evaluate(() => { (document.styleSheets[0].cssRules[0] as any).style.color = 'blue'; });
const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1');
- const { resources } = snapshot2.render();
- const cssHref = `http://localhost:${server.PORT}/style.css`;
- const { sha1 } = resources[cssHref];
- expect(snapshotter.resourceContent(sha1).toString()).toBe('button { color: blue; }');
+ const resource = snapshot2.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
+ expect(snapshotter.resourceContent(resource.responseSha1).toString()).toBe('button { color: blue; }');
});
it('should capture iframe', async ({ page, contextFactory, server, toImpl, browserName, snapshotter, snapshotPort }) => {