chore: allow client operation w/o local utils (#34790)
This commit is contained in:
parent
90ec838318
commit
163aacf4b6
|
|
@ -1,5 +1,4 @@
|
||||||
[*]
|
[*]
|
||||||
../common/
|
../common/
|
||||||
../protocol/
|
../protocol/
|
||||||
../utils/**
|
../utils/isomorphic
|
||||||
../utilsBundle.ts
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
||||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||||
import { monotonicTime } from '../utils/isomorphic/time';
|
import { monotonicTime } from '../utils/isomorphic/time';
|
||||||
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
||||||
|
import { connectOverWebSocket } from './webSocket';
|
||||||
|
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
|
|
@ -69,9 +70,8 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||||
return await this._wrapApiCall(async () => {
|
return await this._wrapApiCall(async () => {
|
||||||
const deadline = options.timeout ? monotonicTime() + options.timeout : 0;
|
const deadline = options.timeout ? monotonicTime() + options.timeout : 0;
|
||||||
const headers = { 'x-playwright-browser': 'android', ...options.headers };
|
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 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;
|
let device: AndroidDevice;
|
||||||
connection.on('close', () => {
|
connection.on('close', () => {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { Stream } from './stream';
|
import { Stream } from './stream';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { CDPSession } from './cdpSession';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { isTargetClosedError } from './errors';
|
import { isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
|
|
||||||
import type { BrowserType } from './browserType';
|
import type { BrowserType } from './browserType';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import { Waiter } from './waiter';
|
||||||
import { WebError } from './webError';
|
import { WebError } from './webError';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
||||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
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> {
|
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) {
|
if (options.update) {
|
||||||
await this._recordIntoHAR(har, null, options);
|
await this._recordIntoHAR(har, null, options);
|
||||||
return;
|
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);
|
this._harRouters.push(harRouter);
|
||||||
await harRouter.addContextRoute(this);
|
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 isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
|
||||||
const needCompressed = harParams.path.endsWith('.zip');
|
const needCompressed = harParams.path.endsWith('.zip');
|
||||||
if (isCompressed && !needCompressed) {
|
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 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 {
|
} else {
|
||||||
await artifact.saveAs(harParams.path);
|
await artifact.saveAs(harParams.path);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { assert } from '../utils/isomorphic/debug';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { monotonicTime } from '../utils/isomorphic/time';
|
import { monotonicTime } from '../utils/isomorphic/time';
|
||||||
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
||||||
|
import { connectOverWebSocket } from './webSocket';
|
||||||
|
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types';
|
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 () => {
|
return await this._wrapApiCall(async () => {
|
||||||
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
||||||
const headers = { 'x-playwright-browser': this.name(), ...params.headers };
|
const headers = { 'x-playwright-browser': this.name(), ...params.headers };
|
||||||
const localUtils = this._connection.localUtils();
|
|
||||||
const connectParams: channels.LocalUtilsConnectParams = {
|
const connectParams: channels.LocalUtilsConnectParams = {
|
||||||
wsEndpoint: params.wsEndpoint,
|
wsEndpoint: params.wsEndpoint,
|
||||||
headers,
|
headers,
|
||||||
|
|
@ -134,7 +134,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
};
|
};
|
||||||
if ((params as any).__testHookRedirectPortForwarding)
|
if ((params as any).__testHookRedirectPortForwarding)
|
||||||
connectParams.socksProxyRedirectPortForTest = (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;
|
let browser: Browser;
|
||||||
connection.on('close', () => {
|
connection.on('close', () => {
|
||||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,8 @@ export class Connection extends EventEmitter {
|
||||||
return this._rawBuffers;
|
return this._rawBuffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
localUtils(): LocalUtils {
|
localUtils(): LocalUtils | undefined {
|
||||||
return this._localUtils!;
|
return this._localUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializePlaywright(): Promise<Playwright> {
|
async initializePlaywright(): Promise<Playwright> {
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ import { promisify } from 'util';
|
||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/debug';
|
||||||
import { fileUploadSizeLimit, mkdirIfNeeded } from '../common/fileUtils';
|
import { fileUploadSizeLimit, mkdirIfNeeded } from './fileUtils';
|
||||||
import { isString } from '../utils/isomorphic/rtti';
|
import { isString } from '../utils/isomorphic/rtti';
|
||||||
import { mime } from '../utilsBundle';
|
|
||||||
import { WritableStream } from './writableStream';
|
import { WritableStream } from './writableStream';
|
||||||
|
import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
|
||||||
|
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { ChannelOwner } from './channelOwner';
|
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 {
|
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
const mimeType = mime.getType(options.path);
|
const mimeType = getMimeTypeForPath(options.path);
|
||||||
if (mimeType === 'image/png')
|
if (mimeType === 'image/png')
|
||||||
return 'png';
|
return 'png';
|
||||||
else if (mimeType === 'image/jpeg')
|
else if (mimeType === 'image/jpeg')
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { TargetClosedError, isTargetClosedError } from './errors';
|
||||||
import { RawHeaders } from './network';
|
import { RawHeaders } from './network';
|
||||||
import { Tracing } from './tracing';
|
import { Tracing } from './tracing';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/debug';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { isString } from '../utils/isomorphic/rtti';
|
import { isString } from '../utils/isomorphic/rtti';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,12 @@
|
||||||
* limitations under the License.
|
* 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 const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||||
|
|
||||||
export async function mkdirIfNeeded(platform: Platform, filePath: string) {
|
export async function mkdirIfNeeded(platform: Platform, filePath: string) {
|
||||||
// This will harmlessly throw on windows if the dirname is the root directory.
|
// 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(() => {});
|
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 { ChannelOwner } from './channelOwner';
|
||||||
import { Connection } from './connection';
|
|
||||||
import * as localUtils from '../common/localUtils';
|
|
||||||
|
|
||||||
import type { HeadersArray, Size } from './types';
|
import type { Size } from './types';
|
||||||
import type { HarBackend } from '../common/harBackend';
|
|
||||||
import type { Platform } from '../common/platform';
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type DeviceDescriptor = {
|
type DeviceDescriptor = {
|
||||||
|
|
@ -35,8 +31,6 @@ type Devices = { [name: string]: DeviceDescriptor };
|
||||||
|
|
||||||
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
||||||
readonly devices: Devices;
|
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) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
|
|
@ -47,132 +41,34 @@ export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
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> {
|
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> {
|
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> {
|
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> {
|
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> {
|
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> {
|
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> {
|
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
||||||
return await localUtils.addStackToTracingNoReply(this._stackSessions, params);
|
return await this._channel.addStackToTracingNoReply(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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualProm
|
||||||
import { MultiMap } from '../utils/isomorphic/multimap';
|
import { MultiMap } from '../utils/isomorphic/multimap';
|
||||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||||
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
||||||
import { mime } from '../utilsBundle';
|
import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
|
||||||
|
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
|
|
@ -413,7 +413,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
else if (options.json)
|
else if (options.json)
|
||||||
headers['content-type'] = 'application/json';
|
headers['content-type'] = 'application/json';
|
||||||
else if (options.path)
|
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))
|
if (length && !('content-length' in headers))
|
||||||
headers['content-length'] = String(length);
|
headers['content-length'] = String(length);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import { Waiter } from './waiter';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/debug';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils';
|
import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils';
|
||||||
import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
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> {
|
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) {
|
if (options.update) {
|
||||||
await this._browserContext._recordIntoHAR(har, this, options);
|
await this._browserContext._recordIntoHAR(har, this, options);
|
||||||
return;
|
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);
|
this._harRouters.push(harRouter);
|
||||||
await harRouter.addPageRoute(this);
|
await harRouter.addPageRoute(this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
this._isTracing = true;
|
this._isTracing = true;
|
||||||
this._connection.setIsTracing(true);
|
this._connection.setIsTracing(true);
|
||||||
}
|
}
|
||||||
const result = await this._connection.localUtils().tracingStarted({ tracesDir: this._tracesDir, traceName });
|
const result = await this._connection.localUtils()?.tracingStarted({ tracesDir: this._tracesDir, traceName });
|
||||||
this._stacksId = result.stacksId;
|
this._stacksId = result?.stacksId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopChunk(options: { path?: string } = {}) {
|
async stopChunk(options: { path?: string } = {}) {
|
||||||
|
|
@ -89,15 +89,19 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
// Not interested in artifacts.
|
// Not interested in artifacts.
|
||||||
await this._channel.tracingStopChunk({ mode: 'discard' });
|
await this._channel.tracingStopChunk({ mode: 'discard' });
|
||||||
if (this._stacksId)
|
if (this._stacksId)
|
||||||
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
await this._connection.localUtils()!.traceDiscarded({ stacksId: this._stacksId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localUtils = this._connection.localUtils();
|
||||||
|
if (!localUtils)
|
||||||
|
throw new Error('Cannot save trace in thin clients');
|
||||||
|
|
||||||
const isLocal = !this._connection.isRemote();
|
const isLocal = !this._connection.isRemote();
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
const result = await this._channel.tracingStopChunk({ mode: 'entries' });
|
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;
|
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.
|
// The artifact may be missing if the browser closed while stopping tracing.
|
||||||
if (!result.artifact) {
|
if (!result.artifact) {
|
||||||
if (this._stacksId)
|
if (this._stacksId)
|
||||||
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
await localUtils.traceDiscarded({ stacksId: this._stacksId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +119,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
await artifact.saveAs(filePath);
|
await artifact.saveAs(filePath);
|
||||||
await artifact.delete();
|
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() {
|
_resetStackCounter() {
|
||||||
|
|
|
||||||
116
packages/playwright-core/src/client/webSocket.ts
Normal file
116
packages/playwright-core/src/client/webSocket.ts
Normal file
|
|
@ -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/
|
../utils/isomorphic/
|
||||||
../utilsBundle.ts
|
|
||||||
../zipBundle.ts
|
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,10 @@
|
||||||
* limitations under the License.
|
* 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 { 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';
|
import type { Colors } from '../utils/isomorphic/colors';
|
||||||
|
|
||||||
export type Zone = {
|
export type Zone = {
|
||||||
|
|
@ -37,6 +35,8 @@ const noopZone: Zone = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Platform = {
|
export type Platform = {
|
||||||
|
name: 'node' | 'web' | 'empty';
|
||||||
|
|
||||||
calculateSha1(text: string): Promise<string>;
|
calculateSha1(text: string): Promise<string>;
|
||||||
colors: Colors;
|
colors: Colors;
|
||||||
createGuid: () => string;
|
createGuid: () => string;
|
||||||
|
|
@ -46,21 +46,22 @@ export type Platform = {
|
||||||
log(name: 'api' | 'channel', message: string | Error | object): void;
|
log(name: 'api' | 'channel', message: string | Error | object): void;
|
||||||
path: () => typeof path;
|
path: () => typeof path;
|
||||||
pathSeparator: string;
|
pathSeparator: string;
|
||||||
ws?: (url: string) => WebSocket;
|
|
||||||
zones: { empty: Zone, current: () => Zone; };
|
zones: { empty: Zone, current: () => Zone; };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const webPlatform: Platform = {
|
export const webPlatform: Platform = {
|
||||||
|
name: 'web',
|
||||||
|
|
||||||
calculateSha1: async (text: string) => {
|
calculateSha1: async (text: string) => {
|
||||||
const bytes = new TextEncoder().encode(text);
|
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('');
|
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join('');
|
||||||
},
|
},
|
||||||
|
|
||||||
colors: webColors,
|
colors: webColors,
|
||||||
|
|
||||||
createGuid: () => {
|
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: () => {
|
fs: () => {
|
||||||
|
|
@ -82,12 +83,12 @@ export const webPlatform: Platform = {
|
||||||
|
|
||||||
pathSeparator: '/',
|
pathSeparator: '/',
|
||||||
|
|
||||||
ws: (url: string) => new WebSocket(url),
|
|
||||||
|
|
||||||
zones: { empty: noopZone, current: () => noopZone },
|
zones: { empty: noopZone, current: () => noopZone },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyPlatform: Platform = {
|
export const emptyPlatform: Platform = {
|
||||||
|
name: 'empty',
|
||||||
|
|
||||||
calculateSha1: async () => {
|
calculateSha1: async () => {
|
||||||
throw new Error('Not implemented');
|
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/
|
../common/
|
||||||
../utils/
|
../utils/isomorphic
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isUnderTest } from '../utils';
|
import { isUnderTest } from '../utils/isomorphic/debug';
|
||||||
|
|
||||||
export class ValidationError extends Error {}
|
export class ValidationError extends Error {}
|
||||||
export type Validator = (arg: any, path: string, context: ValidatorContext) => any;
|
export type Validator = (arg: any, path: string, context: ValidatorContext) => any;
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@
|
||||||
|
|
||||||
import { Dispatcher } from './dispatcher';
|
import { Dispatcher } from './dispatcher';
|
||||||
import { SdkObject } from '../../server/instrumentation';
|
import { SdkObject } from '../../server/instrumentation';
|
||||||
import * as localUtils from '../../common/localUtils';
|
import * as localUtils from '../localUtils';
|
||||||
import { nodePlatform } from '../utils/nodePlatform';
|
|
||||||
import { getUserAgent } from '../utils/userAgent';
|
import { getUserAgent } from '../utils/userAgent';
|
||||||
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
||||||
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
||||||
|
|
@ -26,7 +25,7 @@ import { SocksInterceptor } from '../socksInterceptor';
|
||||||
import { WebSocketTransport } from '../transport';
|
import { WebSocketTransport } from '../transport';
|
||||||
import { fetchData } from '../utils/network';
|
import { fetchData } from '../utils/network';
|
||||||
|
|
||||||
import type { HarBackend } from '../../common/harBackend';
|
import type { HarBackend } from '../harBackend';
|
||||||
import type { CallMetadata } from '../instrumentation';
|
import type { CallMetadata } from '../instrumentation';
|
||||||
import type { Playwright } from '../playwright';
|
import type { Playwright } from '../playwright';
|
||||||
import type { RootDispatcher } from './dispatcher';
|
import type { RootDispatcher } from './dispatcher';
|
||||||
|
|
@ -50,11 +49,11 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
}
|
}
|
||||||
|
|
||||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
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> {
|
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> {
|
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> {
|
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> {
|
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 * as path from 'path';
|
||||||
|
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/debug';
|
||||||
import { fileUploadSizeLimit } from '../common/fileUtils';
|
|
||||||
import { mime } from '../utilsBundle';
|
import { mime } from '../utilsBundle';
|
||||||
|
|
||||||
import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher';
|
import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher';
|
||||||
|
|
@ -27,6 +26,9 @@ import type { Frame } from './frames';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
// Keep in sync with the client.
|
||||||
|
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||||
|
|
||||||
async function filesExceedUploadLimit(files: string[]) {
|
async function filesExceedUploadLimit(files: string[]) {
|
||||||
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
|
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
|
||||||
return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit;
|
return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit;
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,14 @@
|
||||||
* limitations under the License.
|
* 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 * as har from '@trace/har';
|
||||||
import type { Platform } from './platform';
|
|
||||||
|
|
||||||
const redirectStatus = [301, 302, 303, 307, 308];
|
const redirectStatus = [301, 302, 303, 307, 308];
|
||||||
|
|
||||||
|
|
@ -27,11 +30,9 @@ export class HarBackend {
|
||||||
private _harFile: har.HARFile;
|
private _harFile: har.HARFile;
|
||||||
private _zipFile: ZipFile | null;
|
private _zipFile: ZipFile | null;
|
||||||
private _baseDir: string | null;
|
private _baseDir: string | null;
|
||||||
private _platform: Platform;
|
|
||||||
|
|
||||||
constructor(platform: Platform, harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
||||||
this._platform = platform;
|
this.id = createGuid();
|
||||||
this.id = platform.createGuid();
|
|
||||||
this._harFile = harFile;
|
this._harFile = harFile;
|
||||||
this._baseDir = baseDir;
|
this._baseDir = baseDir;
|
||||||
this._zipFile = zipFile;
|
this._zipFile = zipFile;
|
||||||
|
|
@ -79,7 +80,7 @@ export class HarBackend {
|
||||||
if (this._zipFile)
|
if (this._zipFile)
|
||||||
buffer = await this._zipFile.read(file);
|
buffer = await this._zipFile.read(file);
|
||||||
else
|
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 {
|
} else {
|
||||||
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
|
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 os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { removeFolders } from './fileUtils';
|
import { calculateSha1 } from './utils/crypto';
|
||||||
import { HarBackend } from './harBackend';
|
import { HarBackend } from './harBackend';
|
||||||
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
import { ZipFile } from '../utils/zipFile';
|
import { ZipFile } from './utils/zipFile';
|
||||||
import { yauzl, yazl } from '../zipBundle';
|
import { yauzl, yazl } from '../zipBundle';
|
||||||
import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils';
|
import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
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 channels from '@protocol/channels';
|
||||||
import type * as har from '@trace/har';
|
import type * as har from '@trace/har';
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
|
|
@ -39,7 +39,7 @@ export type StackSession = {
|
||||||
callStacks: channels.ClientSideCallMetadata[];
|
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 promise = new ManualPromise<void>();
|
||||||
const zipFile = new yazl.ZipFile();
|
const zipFile = new yazl.ZipFile();
|
||||||
(zipFile as any as EventEmitter).on('error', error => promise.reject(error));
|
(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);
|
sourceFiles.add(file);
|
||||||
}
|
}
|
||||||
for (const sourceFile of sourceFiles)
|
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') {
|
if (params.mode === 'write') {
|
||||||
|
|
@ -89,7 +89,7 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
||||||
.on('error', error => promise.reject(error));
|
.on('error', error => promise.reject(error));
|
||||||
});
|
});
|
||||||
await promise;
|
await promise;
|
||||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
await deleteStackSession(stackSessions, params.stacksId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,20 +124,20 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await promise;
|
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;
|
const session = stacksId ? stackSessions.get(stacksId) : undefined;
|
||||||
if (!session)
|
if (!session)
|
||||||
return;
|
return;
|
||||||
await session.writer;
|
await session.writer;
|
||||||
if (session.tmpDir)
|
if (session.tmpDir)
|
||||||
await removeFolders(platform, [session.tmpDir]);
|
await removeFolders([session.tmpDir]);
|
||||||
stackSessions.delete(stacksId!);
|
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;
|
let harBackend: HarBackend;
|
||||||
if (params.file.endsWith('.zip')) {
|
if (params.file.endsWith('.zip')) {
|
||||||
const zipFile = new ZipFile(params.file);
|
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' };
|
return { error: 'Specified archive does not have a .har file' };
|
||||||
const har = await zipFile.read(harEntryName);
|
const har = await zipFile.read(harEntryName);
|
||||||
const harFile = JSON.parse(har.toString()) as har.HARFile;
|
const harFile = JSON.parse(har.toString()) as har.HARFile;
|
||||||
harBackend = new HarBackend(platform, harFile, null, zipFile);
|
harBackend = new HarBackend(harFile, null, zipFile);
|
||||||
} else {
|
} else {
|
||||||
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
|
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);
|
harBackends.set(harBackend.id, harBackend);
|
||||||
return { harId: harBackend.id };
|
return { harId: harBackend.id };
|
||||||
|
|
@ -194,8 +194,8 @@ export async function tracingStarted(stackSessions: Map<string, StackSession>, p
|
||||||
return { stacksId: traceStacksFile };
|
return { stacksId: traceStacksFile };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function traceDiscarded(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
export async function traceDiscarded(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
await deleteStackSession(stackSessions, params.stacksId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addStackToTracingNoReply(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
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 { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
|
|
||||||
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
||||||
import type { Progress as CommonProgress } from '../common/progress';
|
|
||||||
import type { LogName } from './utils/debugLogger';
|
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;
|
metadata: CallMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,16 +45,14 @@ class NodeZone implements Zone {
|
||||||
return this._zone.run(func);
|
return this._zone.run(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
runIgnoreCurrent<R>(func: () => R): R {
|
|
||||||
return emptyZone.run(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
data<T>(): T | undefined {
|
data<T>(): T | undefined {
|
||||||
return this._zone.data('apiZone');
|
return this._zone.data('apiZone');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nodePlatform: Platform = {
|
export const nodePlatform: Platform = {
|
||||||
|
name: 'node',
|
||||||
|
|
||||||
calculateSha1: (text: string) => {
|
calculateSha1: (text: string) => {
|
||||||
const sha1 = crypto.createHash('sha1');
|
const sha1 = crypto.createHash('sha1');
|
||||||
sha1.update(text);
|
sha1.update(text);
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* 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 {
|
export class ZipFile {
|
||||||
private _fileName: string;
|
private _fileName: string;
|
||||||
|
|
@ -29,7 +29,6 @@ export * from './utils/isomorphic/urlMatch';
|
||||||
export * from './utils/isomorphic/headers';
|
export * from './utils/isomorphic/headers';
|
||||||
export * from './utils/isomorphic/semaphore';
|
export * from './utils/isomorphic/semaphore';
|
||||||
export * from './utils/isomorphic/stackTrace';
|
export * from './utils/isomorphic/stackTrace';
|
||||||
export * from './utils/zipFile';
|
|
||||||
|
|
||||||
export * from './server/utils/ascii';
|
export * from './server/utils/ascii';
|
||||||
export * from './server/utils/comparators';
|
export * from './server/utils/comparators';
|
||||||
|
|
@ -50,6 +49,7 @@ export * from './server/utils/spawnAsync';
|
||||||
export * from './server/utils/task';
|
export * from './server/utils/task';
|
||||||
export * from './server/utils/userAgent';
|
export * from './server/utils/userAgent';
|
||||||
export * from './server/utils/wsServer';
|
export * from './server/utils/wsServer';
|
||||||
|
export * from './server/utils/zipFile';
|
||||||
export * from './server/utils/zones';
|
export * from './server/utils/zones';
|
||||||
|
|
||||||
export { colors } from './utilsBundle';
|
export { colors } from './utilsBundle';
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) Microsoft Corporation.
|
* 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 not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* 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.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
|
|
@ -21,3 +21,426 @@ export function isJsonMimeType(mimeType: string) {
|
||||||
export function isTextualMimeType(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=.*)?$/);
|
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 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 { TraceModelBackend } from '../../packages/trace-viewer/src/sw/traceModel';
|
||||||
import type { StackFrame } from '../../packages/protocol/src/channels';
|
import type { StackFrame } from '../../packages/protocol/src/channels';
|
||||||
import { parseClientSideCallMetadata } from '../../packages/playwright-core/lib/utils/isomorphic/traceUtils';
|
import { parseClientSideCallMetadata } from '../../packages/playwright-core/lib/utils/isomorphic/traceUtils';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue