mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
![]() |
/**
|
||
|
* Copyright (c) Microsoft Corporation.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
import type { ResourceSnapshot } from '../../server/trace/common/snapshotTypes';
|
||
|
import { SnapshotStorage } from './snapshotStorage';
|
||
|
import type { Point } from '../../common/types';
|
||
|
import { URLSearchParams } from 'url';
|
||
|
import { SnapshotRenderer } from './snapshotRenderer';
|
||
|
|
||
|
const kBlobUrlPrefix = 'http://playwright.bloburl/#';
|
||
|
|
||
|
export class SnapshotServer {
|
||
|
private _snapshotStorage: SnapshotStorage;
|
||
|
private _snapshotIds = new Map<string, SnapshotRenderer>();
|
||
|
|
||
|
constructor(snapshotStorage: SnapshotStorage) {
|
||
|
this._snapshotStorage = snapshotStorage;
|
||
|
}
|
||
|
|
||
|
serveSnapshot(pathname: string, searchParams: URLSearchParams, snapshotUrl: string): Response {
|
||
|
const snapshot = this._snapshot(pathname.substring('/snapshot'.length), searchParams);
|
||
|
if (!snapshot)
|
||
|
return new Response(null, { status: 404 });
|
||
|
const renderedSnapshot = snapshot.render();
|
||
|
this._snapshotIds.set(snapshotUrl, snapshot);
|
||
|
return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html' } });
|
||
|
}
|
||
|
|
||
|
serveSnapshotSize(pathname: string, searchParams: URLSearchParams): Response {
|
||
|
const snapshot = this._snapshot(pathname.substring('/snapshotSize'.length), searchParams);
|
||
|
return this._respondWithJson(snapshot ? snapshot.viewport() : {});
|
||
|
}
|
||
|
|
||
|
private _snapshot(pathname: string, params: URLSearchParams) {
|
||
|
const name = params.get('name')!;
|
||
|
return this._snapshotStorage.snapshotByName(pathname.slice(1), name);
|
||
|
}
|
||
|
|
||
|
private _respondWithJson(object: any): Response {
|
||
|
return new Response(JSON.stringify(object), {
|
||
|
status: 200,
|
||
|
headers: {
|
||
|
'Cache-Control': 'public, max-age=31536000',
|
||
|
'Content-Type': 'application/json'
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async serveResource(requestUrl: string, snapshotUrl: string): Promise<Response> {
|
||
|
const snapshot = this._snapshotIds.get(snapshotUrl)!;
|
||
|
const url = requestUrl.startsWith(kBlobUrlPrefix) ? requestUrl.substring(kBlobUrlPrefix.length) : removeHash(requestUrl);
|
||
|
const resource = snapshot?.resourceByUrl(url);
|
||
|
if (!resource)
|
||
|
return new Response(null, { status: 404 });
|
||
|
|
||
|
const sha1 = resource.response.content._sha1;
|
||
|
if (!sha1)
|
||
|
return new Response(null, { status: 404 });
|
||
|
return this._innerServeResource(sha1, resource);
|
||
|
}
|
||
|
|
||
|
private async _innerServeResource(sha1: string, resource: ResourceSnapshot): Promise<Response> {
|
||
|
const content = await this._snapshotStorage.resourceContent(sha1);
|
||
|
if (!content)
|
||
|
return new Response(null, { status: 404 });
|
||
|
|
||
|
let contentType = resource.response.content.mimeType;
|
||
|
const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(contentType);
|
||
|
if (isTextEncoding && !contentType.includes('charset'))
|
||
|
contentType = `${contentType}; charset=utf-8`;
|
||
|
|
||
|
const headers = new Headers();
|
||
|
headers.set('Content-Type', contentType);
|
||
|
for (const { name, value } of resource.response.headers)
|
||
|
headers.set(name, value);
|
||
|
headers.delete('Content-Encoding');
|
||
|
headers.delete('Access-Control-Allow-Origin');
|
||
|
headers.set('Access-Control-Allow-Origin', '*');
|
||
|
headers.delete('Content-Length');
|
||
|
headers.set('Content-Length', String(content.size));
|
||
|
headers.set('Cache-Control', 'public, max-age=31536000');
|
||
|
return new Response(content, { headers });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
declare global {
|
||
|
interface Window {
|
||
|
showSnapshot: (url: string, point?: Point) => Promise<void>;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function removeHash(url: string) {
|
||
|
try {
|
||
|
const u = new URL(url);
|
||
|
u.hash = '';
|
||
|
return u.toString();
|
||
|
} catch (e) {
|
||
|
return url;
|
||
|
}
|
||
|
}
|