chore: allow client operation w/o local utils (#34790)
This commit is contained in:
parent
90ec838318
commit
163aacf4b6
|
@ -1,5 +1,4 @@
|
|||
[*]
|
||||
../common/
|
||||
../protocol/
|
||||
../utils/**
|
||||
../utilsBundle.ts
|
||||
../utils/isomorphic
|
||||
|
|
|
@ -25,6 +25,7 @@ import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
|||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||
import { monotonicTime } from '../utils/isomorphic/time';
|
||||
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
||||
import { connectOverWebSocket } from './webSocket';
|
||||
|
||||
import type { Page } from './page';
|
||||
import type * as types from './types';
|
||||
|
@ -69,9 +70,8 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
|||
return await this._wrapApiCall(async () => {
|
||||
const deadline = options.timeout ? monotonicTime() + options.timeout : 0;
|
||||
const headers = { 'x-playwright-browser': 'android', ...options.headers };
|
||||
const localUtils = this._connection.localUtils();
|
||||
const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
|
||||
const connection = await localUtils.connect(connectParams);
|
||||
const connection = await connectOverWebSocket(this._connection, connectParams);
|
||||
|
||||
let device: AndroidDevice;
|
||||
connection.on('close', () => {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { Stream } from './stream';
|
||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
||||
import { mkdirIfNeeded } from './fileUtils';
|
||||
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type { Readable } from 'stream';
|
||||
|
|
|
@ -20,7 +20,7 @@ import { CDPSession } from './cdpSession';
|
|||
import { ChannelOwner } from './channelOwner';
|
||||
import { isTargetClosedError } from './errors';
|
||||
import { Events } from './events';
|
||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
||||
import { mkdirIfNeeded } from './fileUtils';
|
||||
|
||||
import type { BrowserType } from './browserType';
|
||||
import type { Page } from './page';
|
||||
|
|
|
@ -35,7 +35,7 @@ import { Waiter } from './waiter';
|
|||
import { WebError } from './webError';
|
||||
import { Worker } from './worker';
|
||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
||||
import { mkdirIfNeeded } from './fileUtils';
|
||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||
|
@ -361,11 +361,14 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
}
|
||||
|
||||
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full' } = {}): Promise<void> {
|
||||
const localUtils = this._connection.localUtils();
|
||||
if (!localUtils)
|
||||
throw new Error('Route from har is not supported in thin clients');
|
||||
if (options.update) {
|
||||
await this._recordIntoHAR(har, null, options);
|
||||
return;
|
||||
}
|
||||
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
|
||||
const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url });
|
||||
this._harRouters.push(harRouter);
|
||||
await harRouter.addContextRoute(this);
|
||||
}
|
||||
|
@ -484,8 +487,11 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
|
||||
const needCompressed = harParams.path.endsWith('.zip');
|
||||
if (isCompressed && !needCompressed) {
|
||||
const localUtils = this._connection.localUtils();
|
||||
if (!localUtils)
|
||||
throw new Error('Uncompressed har is not supported in thin clients');
|
||||
await artifact.saveAs(harParams.path + '.tmp');
|
||||
await this._connection.localUtils().harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
|
||||
await localUtils.harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
|
||||
} else {
|
||||
await artifact.saveAs(harParams.path);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { assert } from '../utils/isomorphic/debug';
|
|||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||
import { monotonicTime } from '../utils/isomorphic/time';
|
||||
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
||||
import { connectOverWebSocket } from './webSocket';
|
||||
|
||||
import type { Playwright } from './playwright';
|
||||
import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types';
|
||||
|
@ -124,7 +125,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
|||
return await this._wrapApiCall(async () => {
|
||||
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
||||
const headers = { 'x-playwright-browser': this.name(), ...params.headers };
|
||||
const localUtils = this._connection.localUtils();
|
||||
const connectParams: channels.LocalUtilsConnectParams = {
|
||||
wsEndpoint: params.wsEndpoint,
|
||||
headers,
|
||||
|
@ -134,7 +134,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
|||
};
|
||||
if ((params as any).__testHookRedirectPortForwarding)
|
||||
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
|
||||
const connection = await localUtils.connect(connectParams);
|
||||
const connection = await connectOverWebSocket(this._connection, connectParams);
|
||||
let browser: Browser;
|
||||
connection.on('close', () => {
|
||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||
|
|
|
@ -108,8 +108,8 @@ export class Connection extends EventEmitter {
|
|||
return this._rawBuffers;
|
||||
}
|
||||
|
||||
localUtils(): LocalUtils {
|
||||
return this._localUtils!;
|
||||
localUtils(): LocalUtils | undefined {
|
||||
return this._localUtils;
|
||||
}
|
||||
|
||||
async initializePlaywright(): Promise<Playwright> {
|
||||
|
|
|
@ -20,10 +20,10 @@ import { promisify } from 'util';
|
|||
import { Frame } from './frame';
|
||||
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
||||
import { assert } from '../utils/isomorphic/debug';
|
||||
import { fileUploadSizeLimit, mkdirIfNeeded } from '../common/fileUtils';
|
||||
import { fileUploadSizeLimit, mkdirIfNeeded } from './fileUtils';
|
||||
import { isString } from '../utils/isomorphic/rtti';
|
||||
import { mime } from '../utilsBundle';
|
||||
import { WritableStream } from './writableStream';
|
||||
import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
|
||||
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { ChannelOwner } from './channelOwner';
|
||||
|
@ -327,7 +327,7 @@ export async function convertInputFiles(platform: Platform, files: string | File
|
|||
|
||||
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
|
||||
if (options.path) {
|
||||
const mimeType = mime.getType(options.path);
|
||||
const mimeType = getMimeTypeForPath(options.path);
|
||||
if (mimeType === 'image/png')
|
||||
return 'png';
|
||||
else if (mimeType === 'image/jpeg')
|
||||
|
|
|
@ -20,7 +20,7 @@ import { TargetClosedError, isTargetClosedError } from './errors';
|
|||
import { RawHeaders } from './network';
|
||||
import { Tracing } from './tracing';
|
||||
import { assert } from '../utils/isomorphic/debug';
|
||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
||||
import { mkdirIfNeeded } from './fileUtils';
|
||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||
import { isString } from '../utils/isomorphic/rtti';
|
||||
|
||||
|
|
|
@ -14,17 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Platform } from './platform';
|
||||
import type { Platform } from '../common/platform';
|
||||
|
||||
// Keep in sync with the server.
|
||||
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||
|
||||
export async function mkdirIfNeeded(platform: Platform, filePath: string) {
|
||||
// This will harmlessly throw on windows if the dirname is the root directory.
|
||||
await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {});
|
||||
}
|
||||
|
||||
export async function removeFolders(platform: Platform, dirs: string[]): Promise<Error[]> {
|
||||
return await Promise.all(dirs.map((dir: string) =>
|
||||
platform.fs().promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e)
|
||||
));
|
||||
}
|
|
@ -15,12 +15,8 @@
|
|||
*/
|
||||
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { Connection } from './connection';
|
||||
import * as localUtils from '../common/localUtils';
|
||||
|
||||
import type { HeadersArray, Size } from './types';
|
||||
import type { HarBackend } from '../common/harBackend';
|
||||
import type { Platform } from '../common/platform';
|
||||
import type { Size } from './types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
||||
type DeviceDescriptor = {
|
||||
|
@ -35,8 +31,6 @@ type Devices = { [name: string]: DeviceDescriptor };
|
|||
|
||||
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
||||
readonly devices: Devices;
|
||||
private _harBackends = new Map<string, HarBackend>();
|
||||
private _stackSessions = new Map<string, localUtils.StackSession>();
|
||||
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
|
@ -47,132 +41,34 @@ export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
|||
}
|
||||
|
||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
||||
return await localUtils.zip(this._platform, this._stackSessions, params);
|
||||
return await this._channel.zip(params);
|
||||
}
|
||||
|
||||
async harOpen(params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
||||
return await localUtils.harOpen(this._platform, this._harBackends, params);
|
||||
return await this._channel.harOpen(params);
|
||||
}
|
||||
|
||||
async harLookup(params: channels.LocalUtilsHarLookupParams): Promise<channels.LocalUtilsHarLookupResult> {
|
||||
return await localUtils.harLookup(this._harBackends, params);
|
||||
return await this._channel.harLookup(params);
|
||||
}
|
||||
|
||||
async harClose(params: channels.LocalUtilsHarCloseParams): Promise<void> {
|
||||
return await localUtils.harClose(this._harBackends, params);
|
||||
return await this._channel.harClose(params);
|
||||
}
|
||||
|
||||
async harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise<void> {
|
||||
return await localUtils.harUnzip(params);
|
||||
return await this._channel.harUnzip(params);
|
||||
}
|
||||
|
||||
async tracingStarted(params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> {
|
||||
return await localUtils.tracingStarted(this._stackSessions, params);
|
||||
return await this._channel.tracingStarted(params);
|
||||
}
|
||||
|
||||
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||
return await localUtils.traceDiscarded(this._platform, this._stackSessions, params);
|
||||
return await this._channel.traceDiscarded(params);
|
||||
}
|
||||
|
||||
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
||||
return await localUtils.addStackToTracingNoReply(this._stackSessions, params);
|
||||
}
|
||||
|
||||
async connect(params: channels.LocalUtilsConnectParams): Promise<Connection> {
|
||||
const transport = this._platform.ws ? new WebSocketTransport(this._platform) : new JsonPipeTransport(this);
|
||||
const connectHeaders = await transport.connect(params);
|
||||
const connection = new Connection(this, this._platform, this._instrumentation, connectHeaders);
|
||||
connection.markAsRemote();
|
||||
connection.on('close', () => transport.close());
|
||||
|
||||
let closeError: string | undefined;
|
||||
const onTransportClosed = (reason?: string) => {
|
||||
connection.close(reason || closeError);
|
||||
};
|
||||
transport.onClose(reason => onTransportClosed(reason));
|
||||
connection.onmessage = message => transport.send(message).catch(() => onTransportClosed());
|
||||
transport.onMessage(message => {
|
||||
try {
|
||||
connection!.dispatch(message);
|
||||
} catch (e) {
|
||||
closeError = String(e);
|
||||
transport.close();
|
||||
}
|
||||
});
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
interface Transport {
|
||||
connect(params: channels.LocalUtilsConnectParams): Promise<HeadersArray>;
|
||||
send(message: any): Promise<void>;
|
||||
onMessage(callback: (message: object) => void): void;
|
||||
onClose(callback: (reason?: string) => void): void;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
class JsonPipeTransport implements Transport {
|
||||
private _pipe: channels.JsonPipeChannel | undefined;
|
||||
private _owner: ChannelOwner<channels.LocalUtilsChannel>;
|
||||
|
||||
constructor(owner: ChannelOwner<channels.LocalUtilsChannel>) {
|
||||
this._owner = owner;
|
||||
}
|
||||
|
||||
async connect(params: channels.LocalUtilsConnectParams) {
|
||||
const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => {
|
||||
return await this._owner._channel.connect(params);
|
||||
}, /* isInternal */ true);
|
||||
this._pipe = pipe;
|
||||
return connectHeaders;
|
||||
}
|
||||
|
||||
async send(message: object) {
|
||||
this._owner._wrapApiCall(async () => {
|
||||
await this._pipe!.send({ message });
|
||||
}, /* isInternal */ true);
|
||||
}
|
||||
|
||||
onMessage(callback: (message: object) => void) {
|
||||
this._pipe!.on('message', ({ message }) => callback(message));
|
||||
}
|
||||
|
||||
onClose(callback: (reason?: string) => void) {
|
||||
this._pipe!.on('closed', ({ reason }) => callback(reason));
|
||||
}
|
||||
|
||||
async close() {
|
||||
await this._owner._wrapApiCall(async () => {
|
||||
await this._pipe!.close().catch(() => {});
|
||||
}, /* isInternal */ true);
|
||||
}
|
||||
}
|
||||
|
||||
class WebSocketTransport implements Transport {
|
||||
private _platform: Platform;
|
||||
private _ws: WebSocket | undefined;
|
||||
|
||||
constructor(platform: Platform) {
|
||||
this._platform = platform;
|
||||
}
|
||||
|
||||
async connect(params: channels.LocalUtilsConnectParams) {
|
||||
this._ws = this._platform.ws!(params.wsEndpoint);
|
||||
return [];
|
||||
}
|
||||
|
||||
async send(message: object) {
|
||||
this._ws!.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
onMessage(callback: (message: object) => void) {
|
||||
this._ws!.addEventListener('message', event => callback(JSON.parse(event.data)));
|
||||
}
|
||||
|
||||
onClose(callback: (reason?: string) => void) {
|
||||
this._ws!.addEventListener('close', () => callback());
|
||||
}
|
||||
|
||||
async close() {
|
||||
this._ws!.close();
|
||||
return await this._channel.addStackToTracingNoReply(params);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualProm
|
|||
import { MultiMap } from '../utils/isomorphic/multimap';
|
||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
||||
import { mime } from '../utilsBundle';
|
||||
import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
|
||||
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { Page } from './page';
|
||||
|
@ -413,7 +413,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
|||
else if (options.json)
|
||||
headers['content-type'] = 'application/json';
|
||||
else if (options.path)
|
||||
headers['content-type'] = mime.getType(options.path) || 'application/octet-stream';
|
||||
headers['content-type'] = getMimeTypeForPath(options.path) || 'application/octet-stream';
|
||||
if (length && !('content-length' in headers))
|
||||
headers['content-length'] = String(length);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import { Waiter } from './waiter';
|
|||
import { Worker } from './worker';
|
||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
||||
import { assert } from '../utils/isomorphic/debug';
|
||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
||||
import { mkdirIfNeeded } from './fileUtils';
|
||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||
import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils';
|
||||
import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
||||
|
@ -525,11 +525,14 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
}
|
||||
|
||||
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise<void> {
|
||||
const localUtils = this._connection.localUtils();
|
||||
if (!localUtils)
|
||||
throw new Error('Route from har is not supported in thin clients');
|
||||
if (options.update) {
|
||||
await this._browserContext._recordIntoHAR(har, this, options);
|
||||
return;
|
||||
}
|
||||
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
|
||||
const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url });
|
||||
this._harRouters.push(harRouter);
|
||||
await harRouter.addPageRoute(this);
|
||||
}
|
||||
|
|
|
@ -69,8 +69,8 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
|||
this._isTracing = true;
|
||||
this._connection.setIsTracing(true);
|
||||
}
|
||||
const result = await this._connection.localUtils().tracingStarted({ tracesDir: this._tracesDir, traceName });
|
||||
this._stacksId = result.stacksId;
|
||||
const result = await this._connection.localUtils()?.tracingStarted({ tracesDir: this._tracesDir, traceName });
|
||||
this._stacksId = result?.stacksId;
|
||||
}
|
||||
|
||||
async stopChunk(options: { path?: string } = {}) {
|
||||
|
@ -89,15 +89,19 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
|||
// Not interested in artifacts.
|
||||
await this._channel.tracingStopChunk({ mode: 'discard' });
|
||||
if (this._stacksId)
|
||||
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
||||
await this._connection.localUtils()!.traceDiscarded({ stacksId: this._stacksId });
|
||||
return;
|
||||
}
|
||||
|
||||
const localUtils = this._connection.localUtils();
|
||||
if (!localUtils)
|
||||
throw new Error('Cannot save trace in thin clients');
|
||||
|
||||
const isLocal = !this._connection.isRemote();
|
||||
|
||||
if (isLocal) {
|
||||
const result = await this._channel.tracingStopChunk({ mode: 'entries' });
|
||||
await this._connection.localUtils().zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||
await localUtils.zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -106,7 +110,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
|||
// The artifact may be missing if the browser closed while stopping tracing.
|
||||
if (!result.artifact) {
|
||||
if (this._stacksId)
|
||||
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
||||
await localUtils.traceDiscarded({ stacksId: this._stacksId });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -115,7 +119,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
|||
await artifact.saveAs(filePath);
|
||||
await artifact.delete();
|
||||
|
||||
await this._connection.localUtils().zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||
await localUtils.zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||
}
|
||||
|
||||
_resetStackCounter() {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* 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 { ChannelOwner } from './channelOwner';
|
||||
import { Connection } from './connection';
|
||||
|
||||
import type { HeadersArray } from './types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
||||
export async function connectOverWebSocket(parentConnection: Connection, params: channels.LocalUtilsConnectParams): Promise<Connection> {
|
||||
const localUtils = parentConnection.localUtils();
|
||||
const transport = localUtils ? new JsonPipeTransport(localUtils) : new WebSocketTransport();
|
||||
const connectHeaders = await transport.connect(params);
|
||||
const connection = new Connection(localUtils, parentConnection.platform, parentConnection._instrumentation, connectHeaders);
|
||||
connection.markAsRemote();
|
||||
connection.on('close', () => transport.close());
|
||||
|
||||
let closeError: string | undefined;
|
||||
const onTransportClosed = (reason?: string) => {
|
||||
connection.close(reason || closeError);
|
||||
};
|
||||
transport.onClose(reason => onTransportClosed(reason));
|
||||
connection.onmessage = message => transport.send(message).catch(() => onTransportClosed());
|
||||
transport.onMessage(message => {
|
||||
try {
|
||||
connection!.dispatch(message);
|
||||
} catch (e) {
|
||||
closeError = String(e);
|
||||
transport.close();
|
||||
}
|
||||
});
|
||||
return connection;
|
||||
}
|
||||
|
||||
interface Transport {
|
||||
connect(params: channels.LocalUtilsConnectParams): Promise<HeadersArray>;
|
||||
send(message: any): Promise<void>;
|
||||
onMessage(callback: (message: object) => void): void;
|
||||
onClose(callback: (reason?: string) => void): void;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
class JsonPipeTransport implements Transport {
|
||||
private _pipe: channels.JsonPipeChannel | undefined;
|
||||
private _owner: ChannelOwner<channels.LocalUtilsChannel>;
|
||||
|
||||
constructor(owner: ChannelOwner<channels.LocalUtilsChannel>) {
|
||||
this._owner = owner;
|
||||
}
|
||||
|
||||
async connect(params: channels.LocalUtilsConnectParams) {
|
||||
const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => {
|
||||
return await this._owner._channel.connect(params);
|
||||
}, /* isInternal */ true);
|
||||
this._pipe = pipe;
|
||||
return connectHeaders;
|
||||
}
|
||||
|
||||
async send(message: object) {
|
||||
this._owner._wrapApiCall(async () => {
|
||||
await this._pipe!.send({ message });
|
||||
}, /* isInternal */ true);
|
||||
}
|
||||
|
||||
onMessage(callback: (message: object) => void) {
|
||||
this._pipe!.on('message', ({ message }) => callback(message));
|
||||
}
|
||||
|
||||
onClose(callback: (reason?: string) => void) {
|
||||
this._pipe!.on('closed', ({ reason }) => callback(reason));
|
||||
}
|
||||
|
||||
async close() {
|
||||
await this._owner._wrapApiCall(async () => {
|
||||
await this._pipe!.close().catch(() => {});
|
||||
}, /* isInternal */ true);
|
||||
}
|
||||
}
|
||||
|
||||
class WebSocketTransport implements Transport {
|
||||
private _ws: WebSocket | undefined;
|
||||
|
||||
async connect(params: channels.LocalUtilsConnectParams) {
|
||||
this._ws = new window.WebSocket(params.wsEndpoint);
|
||||
return [];
|
||||
}
|
||||
|
||||
async send(message: object) {
|
||||
this._ws!.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
onMessage(callback: (message: object) => void) {
|
||||
this._ws!.addEventListener('message', event => callback(JSON.parse(event.data)));
|
||||
}
|
||||
|
||||
onClose(callback: (reason?: string) => void) {
|
||||
this._ws!.addEventListener('close', () => callback());
|
||||
}
|
||||
|
||||
async close() {
|
||||
this._ws!.close();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,2 @@
|
|||
[*]
|
||||
../utils/
|
||||
../utils/isomorphic/
|
||||
../utilsBundle.ts
|
||||
../zipBundle.ts
|
||||
|
|
|
@ -14,12 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { webColors, noColors } from '../utils/isomorphic/colors';
|
||||
|
||||
import type * as fs from 'fs';
|
||||
import type * as path from 'path';
|
||||
import type { Colors } from '../utils/isomorphic/colors';
|
||||
|
||||
export type Zone = {
|
||||
|
@ -37,6 +35,8 @@ const noopZone: Zone = {
|
|||
};
|
||||
|
||||
export type Platform = {
|
||||
name: 'node' | 'web' | 'empty';
|
||||
|
||||
calculateSha1(text: string): Promise<string>;
|
||||
colors: Colors;
|
||||
createGuid: () => string;
|
||||
|
@ -46,21 +46,22 @@ export type Platform = {
|
|||
log(name: 'api' | 'channel', message: string | Error | object): void;
|
||||
path: () => typeof path;
|
||||
pathSeparator: string;
|
||||
ws?: (url: string) => WebSocket;
|
||||
zones: { empty: Zone, current: () => Zone; };
|
||||
};
|
||||
|
||||
export const webPlatform: Platform = {
|
||||
name: 'web',
|
||||
|
||||
calculateSha1: async (text: string) => {
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-1', bytes);
|
||||
const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes);
|
||||
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
|
||||
colors: webColors,
|
||||
|
||||
createGuid: () => {
|
||||
return Array.from(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
|
||||
return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
|
||||
fs: () => {
|
||||
|
@ -82,12 +83,12 @@ export const webPlatform: Platform = {
|
|||
|
||||
pathSeparator: '/',
|
||||
|
||||
ws: (url: string) => new WebSocket(url),
|
||||
|
||||
zones: { empty: noopZone, current: () => noopZone },
|
||||
};
|
||||
|
||||
export const emptyPlatform: Platform = {
|
||||
name: 'empty',
|
||||
|
||||
calculateSha1: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface Progress {
|
||||
log(message: string): void;
|
||||
timeUntilDeadline(): number;
|
||||
isRunning(): boolean;
|
||||
cleanupWhenAborted(cleanup: () => any): void;
|
||||
throwIfAborted(): void;
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
[*]
|
||||
../common/
|
||||
../utils/
|
||||
|
||||
../utils/isomorphic
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isUnderTest } from '../utils';
|
||||
import { isUnderTest } from '../utils/isomorphic/debug';
|
||||
|
||||
export class ValidationError extends Error {}
|
||||
export type Validator = (arg: any, path: string, context: ValidatorContext) => any;
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
|
||||
import { Dispatcher } from './dispatcher';
|
||||
import { SdkObject } from '../../server/instrumentation';
|
||||
import * as localUtils from '../../common/localUtils';
|
||||
import { nodePlatform } from '../utils/nodePlatform';
|
||||
import * as localUtils from '../localUtils';
|
||||
import { getUserAgent } from '../utils/userAgent';
|
||||
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
||||
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
||||
|
@ -26,7 +25,7 @@ import { SocksInterceptor } from '../socksInterceptor';
|
|||
import { WebSocketTransport } from '../transport';
|
||||
import { fetchData } from '../utils/network';
|
||||
|
||||
import type { HarBackend } from '../../common/harBackend';
|
||||
import type { HarBackend } from '../harBackend';
|
||||
import type { CallMetadata } from '../instrumentation';
|
||||
import type { Playwright } from '../playwright';
|
||||
import type { RootDispatcher } from './dispatcher';
|
||||
|
@ -50,11 +49,11 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
|||
}
|
||||
|
||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
||||
return await localUtils.zip(nodePlatform, this._stackSessions, params);
|
||||
return await localUtils.zip(this._stackSessions, params);
|
||||
}
|
||||
|
||||
async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarOpenResult> {
|
||||
return await localUtils.harOpen(nodePlatform, this._harBackends, params);
|
||||
return await localUtils.harOpen(this._harBackends, params);
|
||||
}
|
||||
|
||||
async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarLookupResult> {
|
||||
|
@ -74,7 +73,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
|||
}
|
||||
|
||||
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise<void> {
|
||||
return await localUtils.traceDiscarded(nodePlatform, this._stackSessions, params);
|
||||
return await localUtils.traceDiscarded(this._stackSessions, params);
|
||||
}
|
||||
|
||||
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise<void> {
|
||||
|
|
|
@ -18,7 +18,6 @@ import * as fs from 'fs';
|
|||
import * as path from 'path';
|
||||
|
||||
import { assert } from '../utils/isomorphic/debug';
|
||||
import { fileUploadSizeLimit } from '../common/fileUtils';
|
||||
import { mime } from '../utilsBundle';
|
||||
|
||||
import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher';
|
||||
|
@ -27,6 +26,9 @@ import type { Frame } from './frames';
|
|||
import type * as types from './types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
||||
// Keep in sync with the client.
|
||||
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||
|
||||
async function filesExceedUploadLimit(files: string[]) {
|
||||
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
|
||||
return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit;
|
||||
|
|
|
@ -14,11 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ZipFile } from '../utils/zipFile';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import type { HeadersArray } from './types';
|
||||
import { createGuid } from './utils/crypto';
|
||||
import { ZipFile } from './utils/zipFile';
|
||||
|
||||
import type { HeadersArray } from '../common/types';
|
||||
import type * as har from '@trace/har';
|
||||
import type { Platform } from './platform';
|
||||
|
||||
const redirectStatus = [301, 302, 303, 307, 308];
|
||||
|
||||
|
@ -27,11 +30,9 @@ export class HarBackend {
|
|||
private _harFile: har.HARFile;
|
||||
private _zipFile: ZipFile | null;
|
||||
private _baseDir: string | null;
|
||||
private _platform: Platform;
|
||||
|
||||
constructor(platform: Platform, harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
||||
this._platform = platform;
|
||||
this.id = platform.createGuid();
|
||||
constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
||||
this.id = createGuid();
|
||||
this._harFile = harFile;
|
||||
this._baseDir = baseDir;
|
||||
this._zipFile = zipFile;
|
||||
|
@ -79,7 +80,7 @@ export class HarBackend {
|
|||
if (this._zipFile)
|
||||
buffer = await this._zipFile.read(file);
|
||||
else
|
||||
buffer = await this._platform.fs().promises.readFile(this._platform.path().resolve(this._baseDir!, file));
|
||||
buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file));
|
||||
} else {
|
||||
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
|
||||
}
|
|
@ -18,15 +18,15 @@ import * as fs from 'fs';
|
|||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { removeFolders } from './fileUtils';
|
||||
import { calculateSha1 } from './utils/crypto';
|
||||
import { HarBackend } from './harBackend';
|
||||
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||
import { ZipFile } from '../utils/zipFile';
|
||||
import { ZipFile } from './utils/zipFile';
|
||||
import { yauzl, yazl } from '../zipBundle';
|
||||
import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils';
|
||||
import { assert } from '../utils/isomorphic/debug';
|
||||
import { removeFolders } from './utils/fileUtils';
|
||||
|
||||
import type { Platform } from './platform';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type * as har from '@trace/har';
|
||||
import type EventEmitter from 'events';
|
||||
|
@ -39,7 +39,7 @@ export type StackSession = {
|
|||
callStacks: channels.ClientSideCallMetadata[];
|
||||
};
|
||||
|
||||
export async function zip(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsZipParams): Promise<void> {
|
||||
export async function zip(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsZipParams): Promise<void> {
|
||||
const promise = new ManualPromise<void>();
|
||||
const zipFile = new yazl.ZipFile();
|
||||
(zipFile as any as EventEmitter).on('error', error => promise.reject(error));
|
||||
|
@ -77,7 +77,7 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
|||
sourceFiles.add(file);
|
||||
}
|
||||
for (const sourceFile of sourceFiles)
|
||||
addFile(sourceFile, 'resources/src@' + await platform.calculateSha1(sourceFile) + '.txt');
|
||||
addFile(sourceFile, 'resources/src@' + await calculateSha1(sourceFile) + '.txt');
|
||||
}
|
||||
|
||||
if (params.mode === 'write') {
|
||||
|
@ -89,7 +89,7 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
|||
.on('error', error => promise.reject(error));
|
||||
});
|
||||
await promise;
|
||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
||||
await deleteStackSession(stackSessions, params.stacksId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -124,20 +124,20 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
|||
});
|
||||
});
|
||||
await promise;
|
||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
||||
await deleteStackSession(stackSessions, params.stacksId);
|
||||
}
|
||||
|
||||
async function deleteStackSession(platform: Platform, stackSessions: Map<string, StackSession>, stacksId?: string) {
|
||||
async function deleteStackSession(stackSessions: Map<string, StackSession>, stacksId?: string) {
|
||||
const session = stacksId ? stackSessions.get(stacksId) : undefined;
|
||||
if (!session)
|
||||
return;
|
||||
await session.writer;
|
||||
if (session.tmpDir)
|
||||
await removeFolders(platform, [session.tmpDir]);
|
||||
await removeFolders([session.tmpDir]);
|
||||
stackSessions.delete(stacksId!);
|
||||
}
|
||||
|
||||
export async function harOpen(platform: Platform, harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
||||
export async function harOpen(harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
||||
let harBackend: HarBackend;
|
||||
if (params.file.endsWith('.zip')) {
|
||||
const zipFile = new ZipFile(params.file);
|
||||
|
@ -147,10 +147,10 @@ export async function harOpen(platform: Platform, harBackends: Map<string, HarBa
|
|||
return { error: 'Specified archive does not have a .har file' };
|
||||
const har = await zipFile.read(harEntryName);
|
||||
const harFile = JSON.parse(har.toString()) as har.HARFile;
|
||||
harBackend = new HarBackend(platform, harFile, null, zipFile);
|
||||
harBackend = new HarBackend(harFile, null, zipFile);
|
||||
} else {
|
||||
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
|
||||
harBackend = new HarBackend(platform, harFile, path.dirname(params.file), null);
|
||||
harBackend = new HarBackend(harFile, path.dirname(params.file), null);
|
||||
}
|
||||
harBackends.set(harBackend.id, harBackend);
|
||||
return { harId: harBackend.id };
|
||||
|
@ -194,8 +194,8 @@ export async function tracingStarted(stackSessions: Map<string, StackSession>, p
|
|||
return { stacksId: traceStacksFile };
|
||||
}
|
||||
|
||||
export async function traceDiscarded(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
||||
export async function traceDiscarded(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||
await deleteStackSession(stackSessions, params.stacksId);
|
||||
}
|
||||
|
||||
export async function addStackToTracingNoReply(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
|
@ -19,10 +19,14 @@ import { assert, monotonicTime } from '../utils';
|
|||
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||
|
||||
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
||||
import type { Progress as CommonProgress } from '../common/progress';
|
||||
import type { LogName } from './utils/debugLogger';
|
||||
|
||||
export interface Progress extends CommonProgress {
|
||||
export interface Progress {
|
||||
log(message: string): void;
|
||||
timeUntilDeadline(): number;
|
||||
isRunning(): boolean;
|
||||
cleanupWhenAborted(cleanup: () => any): void;
|
||||
throwIfAborted(): void;
|
||||
metadata: CallMetadata;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,16 +45,14 @@ class NodeZone implements Zone {
|
|||
return this._zone.run(func);
|
||||
}
|
||||
|
||||
runIgnoreCurrent<R>(func: () => R): R {
|
||||
return emptyZone.run(func);
|
||||
}
|
||||
|
||||
data<T>(): T | undefined {
|
||||
return this._zone.data('apiZone');
|
||||
}
|
||||
}
|
||||
|
||||
export const nodePlatform: Platform = {
|
||||
name: 'node',
|
||||
|
||||
calculateSha1: (text: string) => {
|
||||
const sha1 = crypto.createHash('sha1');
|
||||
sha1.update(text);
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { yauzl } from '../zipBundle';
|
||||
import { yauzl } from '../../zipBundle';
|
||||
|
||||
import type { Entry, UnzipFile } from '../zipBundle';
|
||||
import type { Entry, UnzipFile } from '../../zipBundle';
|
||||
|
||||
export class ZipFile {
|
||||
private _fileName: string;
|
|
@ -29,7 +29,6 @@ export * from './utils/isomorphic/urlMatch';
|
|||
export * from './utils/isomorphic/headers';
|
||||
export * from './utils/isomorphic/semaphore';
|
||||
export * from './utils/isomorphic/stackTrace';
|
||||
export * from './utils/zipFile';
|
||||
|
||||
export * from './server/utils/ascii';
|
||||
export * from './server/utils/comparators';
|
||||
|
@ -50,6 +49,7 @@ export * from './server/utils/spawnAsync';
|
|||
export * from './server/utils/task';
|
||||
export * from './server/utils/userAgent';
|
||||
export * from './server/utils/wsServer';
|
||||
export * from './server/utils/zipFile';
|
||||
export * from './server/utils/zones';
|
||||
|
||||
export { colors } from './utilsBundle';
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* 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,
|
||||
* 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.
|
||||
|
@ -21,3 +21,426 @@ export function isJsonMimeType(mimeType: string) {
|
|||
export function isTextualMimeType(mimeType: string) {
|
||||
return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/);
|
||||
}
|
||||
export function getMimeTypeForPath(path: string): string | null {
|
||||
const dotIndex = path.lastIndexOf('.');
|
||||
if (dotIndex === -1)
|
||||
return null;
|
||||
const extension = path.substring(dotIndex + 1);
|
||||
return types.get(extension) || null;
|
||||
}
|
||||
|
||||
const types: Map<string, string> = new Map([
|
||||
['ez', 'application/andrew-inset'],
|
||||
['aw', 'application/applixware'],
|
||||
['atom', 'application/atom+xml'],
|
||||
['atomcat', 'application/atomcat+xml'],
|
||||
['atomdeleted', 'application/atomdeleted+xml'],
|
||||
['atomsvc', 'application/atomsvc+xml'],
|
||||
['dwd', 'application/atsc-dwd+xml'],
|
||||
['held', 'application/atsc-held+xml'],
|
||||
['rsat', 'application/atsc-rsat+xml'],
|
||||
['bdoc', 'application/bdoc'],
|
||||
['xcs', 'application/calendar+xml'],
|
||||
['ccxml', 'application/ccxml+xml'],
|
||||
['cdfx', 'application/cdfx+xml'],
|
||||
['cdmia', 'application/cdmi-capability'],
|
||||
['cdmic', 'application/cdmi-container'],
|
||||
['cdmid', 'application/cdmi-domain'],
|
||||
['cdmio', 'application/cdmi-object'],
|
||||
['cdmiq', 'application/cdmi-queue'],
|
||||
['cu', 'application/cu-seeme'],
|
||||
['mpd', 'application/dash+xml'],
|
||||
['davmount', 'application/davmount+xml'],
|
||||
['dbk', 'application/docbook+xml'],
|
||||
['dssc', 'application/dssc+der'],
|
||||
['xdssc', 'application/dssc+xml'],
|
||||
['ecma', 'application/ecmascript'],
|
||||
['es', 'application/ecmascript'],
|
||||
['emma', 'application/emma+xml'],
|
||||
['emotionml', 'application/emotionml+xml'],
|
||||
['epub', 'application/epub+zip'],
|
||||
['exi', 'application/exi'],
|
||||
['exp', 'application/express'],
|
||||
['fdt', 'application/fdt+xml'],
|
||||
['pfr', 'application/font-tdpfr'],
|
||||
['geojson', 'application/geo+json'],
|
||||
['gml', 'application/gml+xml'],
|
||||
['gpx', 'application/gpx+xml'],
|
||||
['gxf', 'application/gxf'],
|
||||
['gz', 'application/gzip'],
|
||||
['hjson', 'application/hjson'],
|
||||
['stk', 'application/hyperstudio'],
|
||||
['ink', 'application/inkml+xml'],
|
||||
['inkml', 'application/inkml+xml'],
|
||||
['ipfix', 'application/ipfix'],
|
||||
['its', 'application/its+xml'],
|
||||
['ear', 'application/java-archive'],
|
||||
['jar', 'application/java-archive'],
|
||||
['war', 'application/java-archive'],
|
||||
['ser', 'application/java-serialized-object'],
|
||||
['class', 'application/java-vm'],
|
||||
['js', 'application/javascript'],
|
||||
['mjs', 'application/javascript'],
|
||||
['json', 'application/json'],
|
||||
['map', 'application/json'],
|
||||
['json5', 'application/json5'],
|
||||
['jsonml', 'application/jsonml+json'],
|
||||
['jsonld', 'application/ld+json'],
|
||||
['lgr', 'application/lgr+xml'],
|
||||
['lostxml', 'application/lost+xml'],
|
||||
['hqx', 'application/mac-binhex40'],
|
||||
['cpt', 'application/mac-compactpro'],
|
||||
['mads', 'application/mads+xml'],
|
||||
['webmanifest', 'application/manifest+json'],
|
||||
['mrc', 'application/marc'],
|
||||
['mrcx', 'application/marcxml+xml'],
|
||||
['ma', 'application/mathematica'],
|
||||
['mb', 'application/mathematica'],
|
||||
['nb', 'application/mathematica'],
|
||||
['mathml', 'application/mathml+xml'],
|
||||
['mbox', 'application/mbox'],
|
||||
['mscml', 'application/mediaservercontrol+xml'],
|
||||
['metalink', 'application/metalink+xml'],
|
||||
['meta4', 'application/metalink4+xml'],
|
||||
['mets', 'application/mets+xml'],
|
||||
['maei', 'application/mmt-aei+xml'],
|
||||
['musd', 'application/mmt-usd+xml'],
|
||||
['mods', 'application/mods+xml'],
|
||||
['m21', 'application/mp21'],
|
||||
['mp21', 'application/mp21'],
|
||||
['m4p', 'application/mp4'],
|
||||
['mp4s', 'application/mp4'],
|
||||
['doc', 'application/msword'],
|
||||
['dot', 'application/msword'],
|
||||
['mxf', 'application/mxf'],
|
||||
['nq', 'application/n-quads'],
|
||||
['nt', 'application/n-triples'],
|
||||
['cjs', 'application/node'],
|
||||
['bin', 'application/octet-stream'],
|
||||
['bpk', 'application/octet-stream'],
|
||||
['buffer', 'application/octet-stream'],
|
||||
['deb', 'application/octet-stream'],
|
||||
['deploy', 'application/octet-stream'],
|
||||
['dist', 'application/octet-stream'],
|
||||
['distz', 'application/octet-stream'],
|
||||
['dll', 'application/octet-stream'],
|
||||
['dmg', 'application/octet-stream'],
|
||||
['dms', 'application/octet-stream'],
|
||||
['dump', 'application/octet-stream'],
|
||||
['elc', 'application/octet-stream'],
|
||||
['exe', 'application/octet-stream'],
|
||||
['img', 'application/octet-stream'],
|
||||
['iso', 'application/octet-stream'],
|
||||
['lrf', 'application/octet-stream'],
|
||||
['mar', 'application/octet-stream'],
|
||||
['msi', 'application/octet-stream'],
|
||||
['msm', 'application/octet-stream'],
|
||||
['msp', 'application/octet-stream'],
|
||||
['pkg', 'application/octet-stream'],
|
||||
['so', 'application/octet-stream'],
|
||||
['oda', 'application/oda'],
|
||||
['opf', 'application/oebps-package+xml'],
|
||||
['ogx', 'application/ogg'],
|
||||
['omdoc', 'application/omdoc+xml'],
|
||||
['onepkg', 'application/onenote'],
|
||||
['onetmp', 'application/onenote'],
|
||||
['onetoc', 'application/onenote'],
|
||||
['onetoc2', 'application/onenote'],
|
||||
['oxps', 'application/oxps'],
|
||||
['relo', 'application/p2p-overlay+xml'],
|
||||
['xer', 'application/patch-ops-error+xml'],
|
||||
['pdf', 'application/pdf'],
|
||||
['pgp', 'application/pgp-encrypted'],
|
||||
['asc', 'application/pgp-signature'],
|
||||
['sig', 'application/pgp-signature'],
|
||||
['prf', 'application/pics-rules'],
|
||||
['p10', 'application/pkcs10'],
|
||||
['p7c', 'application/pkcs7-mime'],
|
||||
['p7m', 'application/pkcs7-mime'],
|
||||
['p7s', 'application/pkcs7-signature'],
|
||||
['p8', 'application/pkcs8'],
|
||||
['ac', 'application/pkix-attr-cert'],
|
||||
['cer', 'application/pkix-cert'],
|
||||
['crl', 'application/pkix-crl'],
|
||||
['pkipath', 'application/pkix-pkipath'],
|
||||
['pki', 'application/pkixcmp'],
|
||||
['pls', 'application/pls+xml'],
|
||||
['ai', 'application/postscript'],
|
||||
['eps', 'application/postscript'],
|
||||
['ps', 'application/postscript'],
|
||||
['provx', 'application/provenance+xml'],
|
||||
['pskcxml', 'application/pskc+xml'],
|
||||
['raml', 'application/raml+yaml'],
|
||||
['owl', 'application/rdf+xml'],
|
||||
['rdf', 'application/rdf+xml'],
|
||||
['rif', 'application/reginfo+xml'],
|
||||
['rnc', 'application/relax-ng-compact-syntax'],
|
||||
['rl', 'application/resource-lists+xml'],
|
||||
['rld', 'application/resource-lists-diff+xml'],
|
||||
['rs', 'application/rls-services+xml'],
|
||||
['rapd', 'application/route-apd+xml'],
|
||||
['sls', 'application/route-s-tsid+xml'],
|
||||
['rusd', 'application/route-usd+xml'],
|
||||
['gbr', 'application/rpki-ghostbusters'],
|
||||
['mft', 'application/rpki-manifest'],
|
||||
['roa', 'application/rpki-roa'],
|
||||
['rsd', 'application/rsd+xml'],
|
||||
['rss', 'application/rss+xml'],
|
||||
['rtf', 'application/rtf'],
|
||||
['sbml', 'application/sbml+xml'],
|
||||
['scq', 'application/scvp-cv-request'],
|
||||
['scs', 'application/scvp-cv-response'],
|
||||
['spq', 'application/scvp-vp-request'],
|
||||
['spp', 'application/scvp-vp-response'],
|
||||
['sdp', 'application/sdp'],
|
||||
['senmlx', 'application/senml+xml'],
|
||||
['sensmlx', 'application/sensml+xml'],
|
||||
['setpay', 'application/set-payment-initiation'],
|
||||
['setreg', 'application/set-registration-initiation'],
|
||||
['shf', 'application/shf+xml'],
|
||||
['sieve', 'application/sieve'],
|
||||
['siv', 'application/sieve'],
|
||||
['smi', 'application/smil+xml'],
|
||||
['smil', 'application/smil+xml'],
|
||||
['rq', 'application/sparql-query'],
|
||||
['srx', 'application/sparql-results+xml'],
|
||||
['gram', 'application/srgs'],
|
||||
['grxml', 'application/srgs+xml'],
|
||||
['sru', 'application/sru+xml'],
|
||||
['ssdl', 'application/ssdl+xml'],
|
||||
['ssml', 'application/ssml+xml'],
|
||||
['swidtag', 'application/swid+xml'],
|
||||
['tei', 'application/tei+xml'],
|
||||
['teicorpus', 'application/tei+xml'],
|
||||
['tfi', 'application/thraud+xml'],
|
||||
['tsd', 'application/timestamped-data'],
|
||||
['toml', 'application/toml'],
|
||||
['trig', 'application/trig'],
|
||||
['ttml', 'application/ttml+xml'],
|
||||
['ubj', 'application/ubjson'],
|
||||
['rsheet', 'application/urc-ressheet+xml'],
|
||||
['td', 'application/urc-targetdesc+xml'],
|
||||
['vxml', 'application/voicexml+xml'],
|
||||
['wasm', 'application/wasm'],
|
||||
['wgt', 'application/widget'],
|
||||
['hlp', 'application/winhlp'],
|
||||
['wsdl', 'application/wsdl+xml'],
|
||||
['wspolicy', 'application/wspolicy+xml'],
|
||||
['xaml', 'application/xaml+xml'],
|
||||
['xav', 'application/xcap-att+xml'],
|
||||
['xca', 'application/xcap-caps+xml'],
|
||||
['xdf', 'application/xcap-diff+xml'],
|
||||
['xel', 'application/xcap-el+xml'],
|
||||
['xns', 'application/xcap-ns+xml'],
|
||||
['xenc', 'application/xenc+xml'],
|
||||
['xht', 'application/xhtml+xml'],
|
||||
['xhtml', 'application/xhtml+xml'],
|
||||
['xlf', 'application/xliff+xml'],
|
||||
['rng', 'application/xml'],
|
||||
['xml', 'application/xml'],
|
||||
['xsd', 'application/xml'],
|
||||
['xsl', 'application/xml'],
|
||||
['dtd', 'application/xml-dtd'],
|
||||
['xop', 'application/xop+xml'],
|
||||
['xpl', 'application/xproc+xml'],
|
||||
['*xsl', 'application/xslt+xml'],
|
||||
['xslt', 'application/xslt+xml'],
|
||||
['xspf', 'application/xspf+xml'],
|
||||
['mxml', 'application/xv+xml'],
|
||||
['xhvml', 'application/xv+xml'],
|
||||
['xvm', 'application/xv+xml'],
|
||||
['xvml', 'application/xv+xml'],
|
||||
['yang', 'application/yang'],
|
||||
['yin', 'application/yin+xml'],
|
||||
['zip', 'application/zip'],
|
||||
['*3gpp', 'audio/3gpp'],
|
||||
['adp', 'audio/adpcm'],
|
||||
['amr', 'audio/amr'],
|
||||
['au', 'audio/basic'],
|
||||
['snd', 'audio/basic'],
|
||||
['kar', 'audio/midi'],
|
||||
['mid', 'audio/midi'],
|
||||
['midi', 'audio/midi'],
|
||||
['rmi', 'audio/midi'],
|
||||
['mxmf', 'audio/mobile-xmf'],
|
||||
['*mp3', 'audio/mp3'],
|
||||
['m4a', 'audio/mp4'],
|
||||
['mp4a', 'audio/mp4'],
|
||||
['m2a', 'audio/mpeg'],
|
||||
['m3a', 'audio/mpeg'],
|
||||
['mp2', 'audio/mpeg'],
|
||||
['mp2a', 'audio/mpeg'],
|
||||
['mp3', 'audio/mpeg'],
|
||||
['mpga', 'audio/mpeg'],
|
||||
['oga', 'audio/ogg'],
|
||||
['ogg', 'audio/ogg'],
|
||||
['opus', 'audio/ogg'],
|
||||
['spx', 'audio/ogg'],
|
||||
['s3m', 'audio/s3m'],
|
||||
['sil', 'audio/silk'],
|
||||
['wav', 'audio/wav'],
|
||||
['*wav', 'audio/wave'],
|
||||
['weba', 'audio/webm'],
|
||||
['xm', 'audio/xm'],
|
||||
['ttc', 'font/collection'],
|
||||
['otf', 'font/otf'],
|
||||
['ttf', 'font/ttf'],
|
||||
['woff', 'font/woff'],
|
||||
['woff2', 'font/woff2'],
|
||||
['exr', 'image/aces'],
|
||||
['apng', 'image/apng'],
|
||||
['avif', 'image/avif'],
|
||||
['bmp', 'image/bmp'],
|
||||
['cgm', 'image/cgm'],
|
||||
['drle', 'image/dicom-rle'],
|
||||
['emf', 'image/emf'],
|
||||
['fits', 'image/fits'],
|
||||
['g3', 'image/g3fax'],
|
||||
['gif', 'image/gif'],
|
||||
['heic', 'image/heic'],
|
||||
['heics', 'image/heic-sequence'],
|
||||
['heif', 'image/heif'],
|
||||
['heifs', 'image/heif-sequence'],
|
||||
['hej2', 'image/hej2k'],
|
||||
['hsj2', 'image/hsj2'],
|
||||
['ief', 'image/ief'],
|
||||
['jls', 'image/jls'],
|
||||
['jp2', 'image/jp2'],
|
||||
['jpg2', 'image/jp2'],
|
||||
['jpe', 'image/jpeg'],
|
||||
['jpeg', 'image/jpeg'],
|
||||
['jpg', 'image/jpeg'],
|
||||
['jph', 'image/jph'],
|
||||
['jhc', 'image/jphc'],
|
||||
['jpm', 'image/jpm'],
|
||||
['jpf', 'image/jpx'],
|
||||
['jpx', 'image/jpx'],
|
||||
['jxr', 'image/jxr'],
|
||||
['jxra', 'image/jxra'],
|
||||
['jxrs', 'image/jxrs'],
|
||||
['jxs', 'image/jxs'],
|
||||
['jxsc', 'image/jxsc'],
|
||||
['jxsi', 'image/jxsi'],
|
||||
['jxss', 'image/jxss'],
|
||||
['ktx', 'image/ktx'],
|
||||
['ktx2', 'image/ktx2'],
|
||||
['png', 'image/png'],
|
||||
['sgi', 'image/sgi'],
|
||||
['svg', 'image/svg+xml'],
|
||||
['svgz', 'image/svg+xml'],
|
||||
['t38', 'image/t38'],
|
||||
['tif', 'image/tiff'],
|
||||
['tiff', 'image/tiff'],
|
||||
['tfx', 'image/tiff-fx'],
|
||||
['webp', 'image/webp'],
|
||||
['wmf', 'image/wmf'],
|
||||
['disposition-notification', 'message/disposition-notification'],
|
||||
['u8msg', 'message/global'],
|
||||
['u8dsn', 'message/global-delivery-status'],
|
||||
['u8mdn', 'message/global-disposition-notification'],
|
||||
['u8hdr', 'message/global-headers'],
|
||||
['eml', 'message/rfc822'],
|
||||
['mime', 'message/rfc822'],
|
||||
['3mf', 'model/3mf'],
|
||||
['gltf', 'model/gltf+json'],
|
||||
['glb', 'model/gltf-binary'],
|
||||
['iges', 'model/iges'],
|
||||
['igs', 'model/iges'],
|
||||
['mesh', 'model/mesh'],
|
||||
['msh', 'model/mesh'],
|
||||
['silo', 'model/mesh'],
|
||||
['mtl', 'model/mtl'],
|
||||
['obj', 'model/obj'],
|
||||
['stpx', 'model/step+xml'],
|
||||
['stpz', 'model/step+zip'],
|
||||
['stpxz', 'model/step-xml+zip'],
|
||||
['stl', 'model/stl'],
|
||||
['vrml', 'model/vrml'],
|
||||
['wrl', 'model/vrml'],
|
||||
['*x3db', 'model/x3d+binary'],
|
||||
['x3dbz', 'model/x3d+binary'],
|
||||
['x3db', 'model/x3d+fastinfoset'],
|
||||
['*x3dv', 'model/x3d+vrml'],
|
||||
['x3dvz', 'model/x3d+vrml'],
|
||||
['x3d', 'model/x3d+xml'],
|
||||
['x3dz', 'model/x3d+xml'],
|
||||
['x3dv', 'model/x3d-vrml'],
|
||||
['appcache', 'text/cache-manifest'],
|
||||
['manifest', 'text/cache-manifest'],
|
||||
['ics', 'text/calendar'],
|
||||
['ifb', 'text/calendar'],
|
||||
['coffee', 'text/coffeescript'],
|
||||
['litcoffee', 'text/coffeescript'],
|
||||
['css', 'text/css'],
|
||||
['csv', 'text/csv'],
|
||||
['htm', 'text/html'],
|
||||
['html', 'text/html'],
|
||||
['shtml', 'text/html'],
|
||||
['jade', 'text/jade'],
|
||||
['jsx', 'text/jsx'],
|
||||
['less', 'text/less'],
|
||||
['markdown', 'text/markdown'],
|
||||
['md', 'text/markdown'],
|
||||
['mml', 'text/mathml'],
|
||||
['mdx', 'text/mdx'],
|
||||
['n3', 'text/n3'],
|
||||
['conf', 'text/plain'],
|
||||
['def', 'text/plain'],
|
||||
['in', 'text/plain'],
|
||||
['ini', 'text/plain'],
|
||||
['list', 'text/plain'],
|
||||
['log', 'text/plain'],
|
||||
['text', 'text/plain'],
|
||||
['txt', 'text/plain'],
|
||||
['rtx', 'text/richtext'],
|
||||
['*rtf', 'text/rtf'],
|
||||
['sgm', 'text/sgml'],
|
||||
['sgml', 'text/sgml'],
|
||||
['shex', 'text/shex'],
|
||||
['slim', 'text/slim'],
|
||||
['slm', 'text/slim'],
|
||||
['spdx', 'text/spdx'],
|
||||
['styl', 'text/stylus'],
|
||||
['stylus', 'text/stylus'],
|
||||
['tsv', 'text/tab-separated-values'],
|
||||
['man', 'text/troff'],
|
||||
['me', 'text/troff'],
|
||||
['ms', 'text/troff'],
|
||||
['roff', 'text/troff'],
|
||||
['t', 'text/troff'],
|
||||
['tr', 'text/troff'],
|
||||
['ttl', 'text/turtle'],
|
||||
['uri', 'text/uri-list'],
|
||||
['uris', 'text/uri-list'],
|
||||
['urls', 'text/uri-list'],
|
||||
['vcard', 'text/vcard'],
|
||||
['vtt', 'text/vtt'],
|
||||
['*xml', 'text/xml'],
|
||||
['yaml', 'text/yaml'],
|
||||
['yml', 'text/yaml'],
|
||||
['3gp', 'video/3gpp'],
|
||||
['3gpp', 'video/3gpp'],
|
||||
['3g2', 'video/3gpp2'],
|
||||
['h261', 'video/h261'],
|
||||
['h263', 'video/h263'],
|
||||
['h264', 'video/h264'],
|
||||
['m4s', 'video/iso.segment'],
|
||||
['jpgv', 'video/jpeg'],
|
||||
['jpm', 'video/jpm'],
|
||||
['jpgm', 'video/jpm'],
|
||||
['mj2', 'video/mj2'],
|
||||
['mjp2', 'video/mj2'],
|
||||
['ts', 'video/mp2t'],
|
||||
['mp4', 'video/mp4'],
|
||||
['mp4v', 'video/mp4'],
|
||||
['mpg4', 'video/mp4'],
|
||||
['m1v', 'video/mpeg'],
|
||||
['m2v', 'video/mpeg'],
|
||||
['mpe', 'video/mpeg'],
|
||||
['mpeg', 'video/mpeg'],
|
||||
['mpg', 'video/mpeg'],
|
||||
['ogv', 'video/ogg'],
|
||||
['mov', 'video/quicktime'],
|
||||
['qt', 'video/quicktime'],
|
||||
['webm', 'video/webm']
|
||||
]);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import type { Frame, Page } from 'playwright-core';
|
||||
import { ZipFile } from '../../packages/playwright-core/lib/utils/zipFile';
|
||||
import { ZipFile } from '../../packages/playwright-core/lib/server/utils/zipFile';
|
||||
import type { TraceModelBackend } from '../../packages/trace-viewer/src/sw/traceModel';
|
||||
import type { StackFrame } from '../../packages/protocol/src/channels';
|
||||
import { parseClientSideCallMetadata } from '../../packages/playwright-core/lib/utils/isomorphic/traceUtils';
|
||||
|
|
Loading…
Reference in New Issue