chromium-bidi
Advanced tools
Comparing version 0.4.31 to 0.4.32
@@ -18,3 +18,3 @@ /** | ||
import type { EmptyResult } from '../../../protocol/protocol.js'; | ||
import type { ICdpConnection } from '../../bidiMapper'; | ||
import type { ICdpConnection } from '../../BidiMapper.js'; | ||
export declare class BrowserProcessor { | ||
@@ -21,0 +21,0 @@ #private; |
@@ -18,3 +18,3 @@ /** | ||
import type { Cdp } from '../../../protocol/protocol.js'; | ||
import type { ICdpConnection } from '../../bidiMapper.js'; | ||
import type { ICdpConnection } from '../../BidiMapper.js'; | ||
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage.js'; | ||
@@ -21,0 +21,0 @@ export declare class CdpProcessor { |
@@ -63,3 +63,3 @@ /** | ||
reload(ignoreCache: boolean, wait: BrowsingContext.ReadinessState): Promise<BrowsingContext.NavigateResult>; | ||
setViewport(viewport: BrowsingContext.Viewport | null): Promise<void>; | ||
setViewport(viewport?: BrowsingContext.Viewport | null, devicePixelRatio?: number | null): Promise<void>; | ||
handleUserPrompt(params: BrowsingContext.HandleUserPromptParameters): Promise<void>; | ||
@@ -66,0 +66,0 @@ activate(): Promise<void>; |
@@ -479,4 +479,4 @@ "use strict"; | ||
} | ||
async setViewport(viewport) { | ||
if (viewport === null) { | ||
async setViewport(viewport, devicePixelRatio) { | ||
if (viewport === null && devicePixelRatio === null) { | ||
await this.#cdpTarget.cdpClient.sendCommand('Emulation.clearDeviceMetricsOverride'); | ||
@@ -487,5 +487,5 @@ } | ||
await this.#cdpTarget.cdpClient.sendCommand('Emulation.setDeviceMetricsOverride', { | ||
width: viewport.width, | ||
height: viewport.height, | ||
deviceScaleFactor: 0, | ||
width: viewport ? viewport.width : 0, | ||
height: viewport ? viewport.height : 0, | ||
deviceScaleFactor: devicePixelRatio ? devicePixelRatio : 0, | ||
mobile: false, | ||
@@ -522,19 +522,48 @@ dontSetVisibleSize: true, | ||
await this.#cdpTarget.cdpClient.sendCommand('Page.bringToFront'); | ||
const { cssVisualViewport } = await this.#cdpTarget.cdpClient.sendCommand('Page.getLayoutMetrics'); | ||
const viewport = { | ||
x: cssVisualViewport.pageX, | ||
y: cssVisualViewport.pageY, | ||
width: cssVisualViewport.clientWidth, | ||
height: cssVisualViewport.clientHeight, | ||
}; | ||
let captureBeyondViewport = false; | ||
let script; | ||
params.origin ??= 'viewport'; | ||
switch (params.origin) { | ||
case 'document': { | ||
script = String(() => { | ||
const element = document.documentElement; | ||
return { | ||
x: 0, | ||
y: 0, | ||
width: element.scrollWidth, | ||
height: element.scrollHeight, | ||
}; | ||
}); | ||
captureBeyondViewport = true; | ||
break; | ||
} | ||
case 'viewport': { | ||
script = String(() => { | ||
const viewport = window.visualViewport; | ||
return { | ||
x: viewport.pageLeft, | ||
y: viewport.pageTop, | ||
width: viewport.width, | ||
height: viewport.height, | ||
}; | ||
}); | ||
break; | ||
} | ||
} | ||
const realm = await this.getOrCreateSandbox(undefined); | ||
const originResult = await realm.callFunction(script, { type: 'undefined' }, [], false, "none" /* Script.ResultOwnership.None */, {}, false); | ||
(0, assert_js_1.assert)(originResult.type === 'success'); | ||
const origin = deserializeDOMRect(originResult.result); | ||
(0, assert_js_1.assert)(origin); | ||
const rect = params.clip | ||
? getIntersectionRect(await this.#parseRect(params.clip), viewport) | ||
: viewport; | ||
? getIntersectionRect(await this.#parseRect(params.clip), origin) | ||
: origin; | ||
if (rect.width === 0 || rect.height === 0) { | ||
throw new protocol_js_1.UnableToCaptureScreenException(`Unable to capture screenshot with zero dimensions: width=${rect.width}, height=${rect.height}`); | ||
} | ||
const result = await this.#cdpTarget.cdpClient.sendCommand('Page.captureScreenshot', { clip: { ...rect, scale: 1.0 }, ...formatParameters }); | ||
return { | ||
data: result.data, | ||
}; | ||
return this.#cdpTarget.cdpClient.sendCommand('Page.captureScreenshot', { | ||
clip: { ...rect, scale: 1.0 }, | ||
...formatParameters, | ||
captureBeyondViewport, | ||
}); | ||
} | ||
@@ -628,3 +657,3 @@ async print(params) { | ||
switch (clip.type) { | ||
case 'viewport': | ||
case 'box': | ||
return { x: clip.x, y: clip.y, width: clip.width, height: clip.height }; | ||
@@ -687,3 +716,6 @@ case 'element': { | ||
case 'image/webp': { | ||
return { format: 'webp' }; | ||
return { | ||
format: 'webp', | ||
...(quality === undefined ? {} : { quality: Math.round(quality * 100) }), | ||
}; | ||
} | ||
@@ -690,0 +722,0 @@ } |
@@ -99,3 +99,3 @@ "use strict"; | ||
} | ||
await context.setViewport(params.viewport); | ||
await context.setViewport(params.viewport, params.devicePixelRatio); | ||
return {}; | ||
@@ -102,0 +102,0 @@ } |
@@ -25,11 +25,2 @@ import type Protocol from 'devtools-protocol'; | ||
/** | ||
* Calls `Fetch.disable` followed by `Fetch.enable`. | ||
* The order is important. Do not use `Promise.all`. | ||
* | ||
* This is necessary because `Fetch.disable` removes all intercepts. | ||
* In a situation where there are two or more intercepts and one of them is | ||
* removed, the `Fetch.enable` call will restore the remaining intercepts. | ||
*/ | ||
fetchApply(): Promise<void>; | ||
/** | ||
* All the ProxyChannels from all the preload scripts of the given | ||
@@ -36,0 +27,0 @@ * BrowsingContext. |
@@ -56,14 +56,2 @@ "use strict"; | ||
/** | ||
* Calls `Fetch.disable` followed by `Fetch.enable`. | ||
* The order is important. Do not use `Promise.all`. | ||
* | ||
* This is necessary because `Fetch.disable` removes all intercepts. | ||
* In a situation where there are two or more intercepts and one of them is | ||
* removed, the `Fetch.enable` call will restore the remaining intercepts. | ||
*/ | ||
async fetchApply() { | ||
await this.fetchDisable(); | ||
await this.fetchEnable(); | ||
} | ||
/** | ||
* Enables all the required CDP domains and unblocks the target. | ||
@@ -82,3 +70,4 @@ */ | ||
this.#cdpClient.sendCommand('Network.enable'), | ||
this.fetchApply(), | ||
// XXX: #1080: Do not always enable the fetch domain globally. | ||
this.fetchEnable(), | ||
this.#cdpClient.sendCommand('Target.setAutoAttach', { | ||
@@ -85,0 +74,0 @@ autoAttach: true, |
@@ -54,9 +54,9 @@ /** | ||
count: number; | ||
"__#82255@#x": number; | ||
"__#82255@#y": number; | ||
"__#82255@#time": number; | ||
"__#82257@#x": number; | ||
"__#82257@#y": number; | ||
"__#82257@#time": number; | ||
compare(context: any): boolean; | ||
}; | ||
"__#82255@#DOUBLE_CLICK_TIME_MS": number; | ||
"__#82255@#MAX_DOUBLE_CLICK_RADIUS": number; | ||
"__#82257@#DOUBLE_CLICK_TIME_MS": number; | ||
"__#82257@#MAX_DOUBLE_CLICK_RADIUS": number; | ||
}; | ||
@@ -63,0 +63,0 @@ setClickCount(button: number, context: InstanceType<typeof PointerSource.ClickContext>): number; |
@@ -98,12 +98,8 @@ "use strict"; | ||
}); | ||
// https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused | ||
cdpTarget.cdpClient.on('Fetch.requestPaused', (params) => { | ||
if (params.networkId) { | ||
networkManager.#networkStorage.addBlockedRequest(params.networkId, { | ||
request: params.requestId, // intercept request id | ||
// TODO: Populate phase. | ||
// TODO: Populate response / ResponseData. | ||
}); | ||
networkManager | ||
.#getOrCreateNetworkRequest(params.networkId) | ||
.onRequestPaused(params); | ||
.onRequestPaused(params, networkManager.#networkStorage); | ||
} | ||
@@ -110,0 +106,0 @@ }); |
@@ -9,3 +9,3 @@ import { Network, type EmptyResult } from '../../../protocol/protocol.js'; | ||
addIntercept(params: Network.AddInterceptParameters): Promise<Network.AddInterceptResult>; | ||
continueRequest(_params: Network.ContinueRequestParameters): EmptyResult; | ||
continueRequest(params: Network.ContinueRequestParameters): Promise<EmptyResult>; | ||
continueResponse(_params: Network.ContinueResponseParameters): EmptyResult; | ||
@@ -16,3 +16,8 @@ continueWithAuth(_params: Network.ContinueWithAuthParameters): EmptyResult; | ||
removeIntercept(params: Network.RemoveInterceptParameters): Promise<EmptyResult>; | ||
/** | ||
* Attempts to parse the given url. | ||
* Throws an InvalidArgumentException if the url is invalid. | ||
*/ | ||
static parseUrlString(url: string): URL; | ||
static parseUrlPatterns(urlPatterns: Network.UrlPattern[]): Network.UrlPattern[]; | ||
} |
@@ -24,4 +24,3 @@ "use strict"; | ||
}); | ||
// TODO: Add try/catch. Remove the intercept if CDP Fetch commands fail. | ||
await this.#applyIntercepts(); | ||
await this.#fetchApply(); | ||
return { | ||
@@ -31,4 +30,22 @@ intercept, | ||
} | ||
continueRequest(_params) { | ||
throw new protocol_js_1.UnknownCommandException('Not implemented yet.'); | ||
async continueRequest(params) { | ||
const networkId = params.request; | ||
const blockedRequest = this.#getBlockedRequest(networkId); | ||
const { request: fetchId, phase } = blockedRequest; | ||
if (phase !== "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */) { | ||
throw new protocol_js_1.InvalidArgumentException(`Blocked request for network id '${networkId}' is not in 'BeforeRequestSent' phase`); | ||
} | ||
if (params.url !== undefined) { | ||
NetworkProcessor.parseUrlString(params.url); | ||
} | ||
const { url, method } = params; | ||
// TODO: Set / expand. | ||
// ; headers | ||
// ; cookies | ||
// ; body | ||
await this.#networkStorage | ||
.getRequest(networkId) | ||
?.continueRequest(fetchId, url, method); | ||
this.#networkStorage.removeBlockedRequest(networkId); | ||
return {}; | ||
} | ||
@@ -52,3 +69,2 @@ continueResponse(_params) { | ||
this.#networkStorage.removeBlockedRequest(networkId); | ||
// TODO: Remove from network request map? | ||
return {}; | ||
@@ -61,13 +77,40 @@ } | ||
this.#networkStorage.removeIntercept(params.intercept); | ||
// TODO: Add try/catch. Remove the intercept if CDP Fetch commands fail. | ||
await this.#applyIntercepts(); | ||
await this.#fetchApply(); | ||
return {}; | ||
} | ||
/** Applies all existing network intercepts to all CDP targets concurrently. */ | ||
async #applyIntercepts() { | ||
async #fetchEnable() { | ||
await Promise.all(this.#browsingContextStorage.getAllContexts().map(async (context) => { | ||
await context.cdpTarget.fetchApply(); | ||
await context.cdpTarget.fetchEnable(); | ||
})); | ||
} | ||
/** Removes all existing network intercepts from all CDP targets concurrently. */ | ||
async #fetchDisable() { | ||
await Promise.all(this.#browsingContextStorage.getAllContexts().map(async (context) => { | ||
await context.cdpTarget.fetchDisable(); | ||
})); | ||
} | ||
/** | ||
* Either enables or disables the Fetch domain. | ||
* | ||
* If enabling, applies all existing network intercepts to all CDP targets. | ||
* If disabling, removes all existing network intercepts from all CDP targets. | ||
* | ||
* Disabling is only performed when there are no remaining intercepts or | ||
* // blocked requests. | ||
*/ | ||
async #fetchApply() { | ||
if (this.#networkStorage.hasIntercepts() || | ||
this.#networkStorage.hasBlockedRequests()) { | ||
// TODO: Add try/catch. Remove the intercept if CDP Fetch commands fail. | ||
await this.#fetchEnable(); | ||
} | ||
else { | ||
// The last intercept has been removed, and there are no pending | ||
// blocked requests. | ||
// Disable the Fetch domain. | ||
await this.#fetchDisable(); | ||
} | ||
} | ||
/** | ||
* Returns the blocked request associated with the given network ID. | ||
@@ -83,2 +126,14 @@ * If none, throws a NoSuchRequestException. | ||
} | ||
/** | ||
* Attempts to parse the given url. | ||
* Throws an InvalidArgumentException if the url is invalid. | ||
*/ | ||
static parseUrlString(url) { | ||
try { | ||
return new URL(url); | ||
} | ||
catch (error) { | ||
throw new protocol_js_1.InvalidArgumentException(`Invalid URL '${url}': ${error}`); | ||
} | ||
} | ||
static parseUrlPatterns(urlPatterns) { | ||
@@ -88,8 +143,3 @@ return urlPatterns.map((urlPattern) => { | ||
case 'string': { | ||
try { | ||
new URL(urlPattern.pattern); | ||
} | ||
catch (error) { | ||
throw new protocol_js_1.InvalidArgumentException(`Invalid URL '${urlPattern.pattern}': ${error}`); | ||
} | ||
NetworkProcessor.parseUrlString(urlPattern.pattern); | ||
return urlPattern; | ||
@@ -96,0 +146,0 @@ } |
@@ -7,4 +7,5 @@ /** | ||
import type { EventManager } from '../events/EventManager.js'; | ||
import { type Network } from '../../../protocol/protocol.js'; | ||
import { Network } from '../../../protocol/protocol.js'; | ||
import type { CdpTarget } from '../context/CdpTarget.js'; | ||
import type { NetworkStorage } from './NetworkStorage.js'; | ||
/** Abstracts one individual network request. */ | ||
@@ -34,5 +35,8 @@ export declare class NetworkRequest { | ||
/** Fired whenever a network request interception is hit. */ | ||
onRequestPaused(_event: Protocol.Fetch.RequestPausedEvent): void; | ||
failRequest(fetchRequestId: Protocol.Fetch.RequestId, errorReason: Protocol.Network.ErrorReason): Promise<void>; | ||
onRequestPaused(params: Protocol.Fetch.RequestPausedEvent, networkStorage: NetworkStorage): void; | ||
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-failRequest */ | ||
failRequest(networkId: Network.Request, errorReason: Protocol.Network.ErrorReason): Promise<void>; | ||
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueRequest */ | ||
continueRequest(networkId: Protocol.Fetch.RequestId, url?: string, method?: string): Promise<void>; | ||
dispose(): void; | ||
} |
@@ -24,2 +24,3 @@ "use strict"; | ||
const assert_js_1 = require("../../../utils/assert.js"); | ||
const NetworkUtils_js_1 = require("./NetworkUtils.js"); | ||
/** Abstracts one individual network request. */ | ||
@@ -36,4 +37,8 @@ class NetworkRequest { | ||
requestId; | ||
/** Indicates whether the request is blocked by a network intercept. */ | ||
#isBlocked = false; | ||
// TODO: Handle auth required? | ||
/** | ||
* Indicates the network intercept phase, if the request is currently blocked. | ||
* Undefined necessarily implies that the request is not blocked. | ||
*/ | ||
#interceptPhase = undefined; | ||
#servedFromCache = false; | ||
@@ -82,3 +87,4 @@ #redirectCount; | ||
// is the only place we can find out | ||
Boolean(this.#response.info && !this.#response.info.hasExtraInfo); | ||
Boolean(this.#response.info && !this.#response.info.hasExtraInfo) || | ||
this.#interceptPhase === "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */; | ||
if (this.#request.info && requestExtraInfoCompleted) { | ||
@@ -94,3 +100,4 @@ this.#beforeRequestSentDeferred.resolve({ | ||
// Don't expect extra info if the flag is false | ||
Boolean(this.#response.info && !this.#response.info.hasExtraInfo); | ||
Boolean(this.#response.info && !this.#response.info.hasExtraInfo) || | ||
this.#interceptPhase === "responseStarted" /* Network.InterceptPhase.ResponseStarted */; | ||
if (this.#response.info && responseExtraInfoCompleted) { | ||
@@ -144,11 +151,77 @@ this.#responseCompletedDeferred.resolve({ | ||
/** Fired whenever a network request interception is hit. */ | ||
onRequestPaused(_event) { | ||
this.#isBlocked = true; | ||
onRequestPaused(params, networkStorage) { | ||
// The stage of the request can be determined by presence of | ||
// responseErrorReason and responseStatusCode -- the request is at | ||
// the response stage if either of these fields is present and in the | ||
// request stage otherwise. | ||
let phase; | ||
if (params.responseErrorReason === undefined && | ||
params.responseStatusCode === undefined) { | ||
phase = "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */; | ||
} | ||
else if (params.responseStatusCode === 401 && | ||
params.responseStatusText === 'Unauthorized') { | ||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 | ||
phase = "authRequired" /* Network.InterceptPhase.AuthRequired */; | ||
} | ||
else { | ||
phase = "responseStarted" /* Network.InterceptPhase.ResponseStarted */; | ||
} | ||
const headers = (0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpFetchHeaderEntryArray)( | ||
// TODO: Use params.request.headers if request? | ||
params.responseHeaders); | ||
(0, assert_js_1.assert)(this.requestId === params.networkId); | ||
networkStorage.addBlockedRequest(this.requestId, { | ||
request: params.requestId, | ||
phase, | ||
// TODO: Finish populating response / ResponseData. | ||
response: { | ||
url: params.request.url, | ||
// TODO: populate. | ||
protocol: '', | ||
status: params.responseStatusCode ?? 0, | ||
statusText: params.responseStatusText ?? '', | ||
// TODO: populate. | ||
fromCache: false, | ||
headers, | ||
// TODO: populate. | ||
mimeType: '', | ||
// TODO: populate. | ||
bytesReceived: 0, | ||
headersSize: (0, NetworkUtils_js_1.computeResponseHeadersSize)(headers), | ||
// TODO: consider removing from spec. | ||
bodySize: 0, | ||
// TODO: consider removing from spec. | ||
content: { | ||
size: 0, | ||
}, | ||
// TODO: populate. | ||
authChallenge: undefined, | ||
}, | ||
}); | ||
this.#interceptPhase = phase; | ||
this.#emitEventsIfReady(); | ||
} | ||
async failRequest(fetchRequestId, errorReason) { | ||
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-failRequest */ | ||
async failRequest(networkId, errorReason) { | ||
await this.#cdpTarget.cdpClient.sendCommand('Fetch.failRequest', { | ||
requestId: fetchRequestId, | ||
requestId: networkId, | ||
errorReason, | ||
}); | ||
this.#interceptPhase = undefined; | ||
} | ||
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueRequest */ | ||
async continueRequest(networkId, url, method) { | ||
// TODO: Expand. | ||
await this.#cdpTarget.cdpClient.sendCommand('Fetch.continueRequest', { | ||
requestId: networkId, | ||
url, | ||
method, | ||
// TODO: Set? | ||
// postData:, | ||
// headers:, | ||
// interceptResponse:, | ||
}); | ||
this.#interceptPhase = undefined; | ||
} | ||
dispose() { | ||
@@ -165,5 +238,5 @@ const result = { | ||
} | ||
#getBaseEventParams() { | ||
#getBaseEventParams(phase) { | ||
return { | ||
isBlocked: this.#isBlocked, | ||
isBlocked: phase !== undefined && phase === this.#interceptPhase, | ||
context: this.#context, | ||
@@ -192,2 +265,3 @@ navigation: this.#getNavigationId(), | ||
: []; | ||
const headers = (0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#request.info?.request.headers); | ||
return { | ||
@@ -197,3 +271,3 @@ request: this.#request.info?.requestId ?? NetworkRequest.#unknown, | ||
method: this.#request.info?.request.method ?? NetworkRequest.#unknown, | ||
headers: NetworkRequest.#getHeaders(this.#request.info?.request.headers), | ||
headers, | ||
cookies, | ||
@@ -246,3 +320,3 @@ // TODO: implement. | ||
params: { | ||
...this.#getBaseEventParams(), | ||
...this.#getBaseEventParams("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */), | ||
initiator: { | ||
@@ -279,3 +353,3 @@ type: NetworkRequest.#getInitiatorType(this.#request.info.initiator.type), | ||
} | ||
const headers = NetworkRequest.#getHeaders(this.#response.info.response.headers); | ||
const headers = (0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#response.info.response.headers); | ||
return { | ||
@@ -297,3 +371,3 @@ method: protocol_js_1.ChromiumBidi.Network.EventNames.ResponseCompleted, | ||
bytesReceived: this.#response.info.response.encodedDataLength, | ||
headersSize: this.#computeResponseHeadersSize(headers), | ||
headersSize: (0, NetworkUtils_js_1.computeResponseHeadersSize)(headers), | ||
// TODO: consider removing from spec. | ||
@@ -309,23 +383,5 @@ bodySize: 0, | ||
} | ||
#computeResponseHeadersSize(headers) { | ||
return headers.reduce((total, header) => { | ||
return (total + header.name.length + header.value.value.length + 4 // 4 = ': ' + '\r\n' | ||
); | ||
}, 0); | ||
} | ||
#isIgnoredEvent() { | ||
return this.#request.info?.request.url.endsWith('/favicon.ico') ?? false; | ||
} | ||
static #getHeaders(headers) { | ||
if (!headers) { | ||
return []; | ||
} | ||
return Object.entries(headers).map(([name, value]) => ({ | ||
name, | ||
value: { | ||
type: 'string', | ||
value, | ||
}, | ||
})); | ||
} | ||
static #getInitiatorType(initiatorType) { | ||
@@ -332,0 +388,0 @@ switch (initiatorType) { |
@@ -42,2 +42,4 @@ /** | ||
removeIntercept(intercept: Network.Intercept): void; | ||
/** Returns true iff there's at least one added intercept. */ | ||
hasIntercepts(): boolean; | ||
/** Gets parameters for CDP 'Fetch.enable' command from the intercept map. */ | ||
@@ -48,2 +50,4 @@ getFetchEnableParams(): Protocol.Fetch.EnableRequest; | ||
deleteRequest(id: Network.Request): void; | ||
/** Returns true iff there's at least one blocked network request. */ | ||
hasBlockedRequests(): boolean; | ||
/** Converts a URL pattern from the spec to a CDP URL pattern. */ | ||
@@ -68,4 +72,4 @@ static cdpFromSpecUrlPattern(urlPattern: Network.UrlPattern): string; | ||
request: Protocol.Fetch.RequestId; | ||
phase?: Network.InterceptPhase; | ||
response?: Network.ResponseData; | ||
phase: Network.InterceptPhase; | ||
response: Network.ResponseData; | ||
}): void; | ||
@@ -78,4 +82,4 @@ removeBlockedRequest(requestId: Network.Request): void; | ||
request: Protocol.Fetch.RequestId; | ||
phase?: Network.InterceptPhase; | ||
response?: Network.ResponseData; | ||
phase: Network.InterceptPhase; | ||
response: Network.ResponseData; | ||
} | undefined; | ||
@@ -82,0 +86,0 @@ /** #@see https://w3c.github.io/webdriver-bidi/#get-the-network-intercepts */ |
@@ -38,2 +38,9 @@ "use strict"; | ||
addIntercept(value) { | ||
// Check if the given intercept entry already exists. | ||
for (const [interceptId, { urlPatterns, phases },] of this.#interceptMap.entries()) { | ||
if (JSON.stringify(value.urlPatterns) === JSON.stringify(urlPatterns) && | ||
JSON.stringify(value.phases) === JSON.stringify(phases)) { | ||
return interceptId; | ||
} | ||
} | ||
const interceptId = (0, uuid_js_1.uuidv4)(); | ||
@@ -53,2 +60,6 @@ this.#interceptMap.set(interceptId, value); | ||
} | ||
/** Returns true iff there's at least one added intercept. */ | ||
hasIntercepts() { | ||
return this.#interceptMap.size > 0; | ||
} | ||
/** Gets parameters for CDP 'Fetch.enable' command from the intercept map. */ | ||
@@ -61,13 +72,6 @@ getFetchEnableParams() { | ||
for (const phase of value.phases) { | ||
if (phase === "authRequired" /* Network.InterceptPhase.AuthRequired */) { | ||
patterns.push({ | ||
urlPattern, | ||
}); | ||
} | ||
else { | ||
patterns.push({ | ||
urlPattern, | ||
requestStage: NetworkStorage.requestStageFromPhase(phase), | ||
}); | ||
} | ||
patterns.push({ | ||
urlPattern, | ||
requestStage: NetworkStorage.requestStageFromPhase(phase), | ||
}); | ||
} | ||
@@ -98,2 +102,6 @@ } | ||
} | ||
/** Returns true iff there's at least one blocked network request. */ | ||
hasBlockedRequests() { | ||
return this.#blockedRequestMap.size > 0; | ||
} | ||
/** Converts a URL pattern from the spec to a CDP URL pattern. */ | ||
@@ -148,5 +156,4 @@ static cdpFromSpecUrlPattern(urlPattern) { | ||
case "responseStarted" /* Network.InterceptPhase.ResponseStarted */: | ||
case "authRequired" /* Network.InterceptPhase.AuthRequired */: | ||
return 'Response'; | ||
case "authRequired" /* Network.InterceptPhase.AuthRequired */: | ||
throw new Error('AuthRequired is not a valid intercept phase for request stage.'); | ||
} | ||
@@ -153,0 +160,0 @@ } |
@@ -22,10 +22,5 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const path_1 = __importDefault(require("path")); | ||
const os_1 = __importDefault(require("os")); | ||
const promises_1 = require("fs/promises"); | ||
const argparse_1 = __importDefault(require("argparse")); | ||
const browsers_1 = require("@puppeteer/browsers"); | ||
const bidiServerRunner_js_1 = require("./bidiServerRunner.js"); | ||
const MapperServer_js_1 = require("./MapperServer.js"); | ||
const reader_js_1 = require("./reader.js"); | ||
const WebSocketServer_js_1 = require("./WebSocketServer.js"); | ||
function parseArguments() { | ||
@@ -64,80 +59,10 @@ const parser = new argparse_1.default.ArgumentParser({ | ||
const verbose = args.verbose === true; | ||
(0, bidiServerRunner_js_1.debugInfo)('Launching BiDi server...'); | ||
new bidiServerRunner_js_1.BidiServerRunner().run(port, (bidiServer, chromeArgs) => { | ||
return onNewBidiConnectionOpen(channel, headless, bidiServer, verbose, chromeArgs); | ||
}); | ||
(0, bidiServerRunner_js_1.debugInfo)('BiDi server launched'); | ||
(0, WebSocketServer_js_1.debugInfo)('Launching BiDi server...'); | ||
WebSocketServer_js_1.WebSocketServer.run(port, channel, headless, verbose); | ||
(0, WebSocketServer_js_1.debugInfo)('BiDi server launched'); | ||
} | ||
catch (e) { | ||
(0, bidiServerRunner_js_1.debugInfo)('Error', e); | ||
(0, WebSocketServer_js_1.debugInfo)('Error launching BiDi server', e); | ||
} | ||
})(); | ||
/** | ||
* On each new BiDi connection: | ||
* 1. Launch Chromium (using Puppeteer for now). | ||
* 2. Get `BiDi-CDP` mapper JS binaries using `mapperReader`. | ||
* 3. Run `BiDi-CDP` mapper in launched browser. | ||
* 4. Bind `BiDi-CDP` mapper to the `BiDi server`. | ||
* | ||
* @return delegate to be called when the connection is closed | ||
*/ | ||
async function onNewBidiConnectionOpen(channel, headless, bidiTransport, verbose, chromeArgs) { | ||
// 1. Launch the browser using @puppeteer/browsers. | ||
const profileDir = await (0, promises_1.mkdtemp)(path_1.default.join(os_1.default.tmpdir(), 'web-driver-bidi-server-')); | ||
// See https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md | ||
const chromeArguments = [ | ||
...(headless ? ['--headless', '--hide-scrollbars', '--mute-audio'] : []), | ||
// keep-sorted start | ||
'--disable-component-update', | ||
'--disable-default-apps', | ||
'--disable-features=DialMediaRouteProvider', | ||
'--disable-notifications', | ||
'--disable-popup-blocking', | ||
'--enable-automation', | ||
'--no-default-browser-check', | ||
'--no-first-run', | ||
'--password-store=basic', | ||
'--remote-debugging-port=9222', | ||
'--use-mock-keychain', | ||
`--user-data-dir=${profileDir}`, | ||
// keep-sorted end | ||
...(chromeArgs | ||
? chromeArgs.filter((arg) => !arg.startsWith('--headless')) | ||
: []), | ||
'about:blank', | ||
]; | ||
const executablePath = process.env['BROWSER_BIN'] ?? | ||
(0, browsers_1.computeSystemExecutablePath)({ | ||
browser: browsers_1.Browser.CHROME, | ||
channel, | ||
}); | ||
if (!executablePath) { | ||
throw new Error('Could not find Chrome binary'); | ||
} | ||
const browser = (0, browsers_1.launch)({ | ||
executablePath, | ||
args: chromeArguments, | ||
}); | ||
const wsEndpoint = await browser.waitForLineOutput(browsers_1.CDP_WEBSOCKET_ENDPOINT_REGEX); | ||
// 2. Get `BiDi-CDP` mapper JS binaries using `readMapperTabFile`. | ||
const bidiMapperScript = await (0, reader_js_1.readMapperTabFile)(); | ||
// 3. Run `BiDi-CDP` mapper in launched browser. | ||
const mapperServer = await MapperServer_js_1.MapperServer.create(wsEndpoint, bidiMapperScript, verbose); | ||
// 4. Bind `BiDi-CDP` mapper to the `BiDi server`. | ||
// Forward messages from BiDi Mapper to the client. | ||
mapperServer.setOnMessage(async (message) => { | ||
await bidiTransport.sendMessage(message); | ||
}); | ||
// Forward messages from the client to BiDi Mapper. | ||
bidiTransport.setOnMessage(async (message) => { | ||
await mapperServer.sendMessage(message); | ||
}); | ||
// Return delegate to be called when the connection is closed. | ||
return async () => { | ||
// Close the mapper server. | ||
mapperServer.close(); | ||
// Close browser. | ||
await browser.close(); | ||
}; | ||
} | ||
//# sourceMappingURL=index.js.map |
@@ -17,2 +17,2 @@ /** | ||
*/ | ||
export declare function readMapperTabFile(): Promise<string>; | ||
export declare function getMapperTabSource(): Promise<string>; |
@@ -22,9 +22,9 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.readMapperTabFile = void 0; | ||
exports.getMapperTabSource = void 0; | ||
const promises_1 = __importDefault(require("fs/promises")); | ||
const path_1 = __importDefault(require("path")); | ||
async function readMapperTabFile() { | ||
async function getMapperTabSource() { | ||
return promises_1.default.readFile(path_1.default.join(__dirname, '../../iife/mapperTab.js'), 'utf8'); | ||
} | ||
exports.readMapperTabFile = readMapperTabFile; | ||
exports.getMapperTabSource = getMapperTabSource; | ||
//# sourceMappingURL=reader.js.map |
@@ -17,3 +17,3 @@ /** | ||
*/ | ||
import type { IBidiParser } from '../bidiMapper/bidiMapper.js'; | ||
import type { IBidiParser } from '../bidiMapper/BidiMapper.js'; | ||
import type { BrowsingContext, Cdp, Input, Network, Script, Session } from '../protocol/protocol.js'; | ||
@@ -20,0 +20,0 @@ export declare class BidiParser implements IBidiParser { |
@@ -21,2 +21,3 @@ /** | ||
interface Window { | ||
runMapperInstance: ((...args: any) => Promise<void>) | null; | ||
cdp: { | ||
@@ -29,5 +30,4 @@ send: (message: string) => void; | ||
sendDebugMessage?: ((message: string) => void) | null; | ||
setSelfTargetId: (targetId: string) => void; | ||
} | ||
} | ||
export {}; |
@@ -21,3 +21,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bidiMapper_js_1 = require("../bidiMapper/bidiMapper.js"); | ||
const BidiMapper_js_1 = require("../bidiMapper/BidiMapper.js"); | ||
const CdpConnection_js_1 = require("../cdp/CdpConnection.js"); | ||
@@ -28,9 +28,12 @@ const log_js_1 = require("../utils/log.js"); | ||
const mapperTabPage_js_1 = require("./mapperTabPage.js"); | ||
// Initiate `setSelfTargetId` as soon as possible to prevent race condition. | ||
const waitSelfTargetIdPromise = waitSelfTargetId(); | ||
void (async () => { | ||
(0, mapperTabPage_js_1.generatePage)(); | ||
// Needed to filter out info related to BiDi target. | ||
const selfTargetId = await waitSelfTargetIdPromise; | ||
const bidiServer = await bidiMapper_js_1.BidiServer.createAndStart(new Transport_js_1.WindowBidiTransport(), | ||
(0, mapperTabPage_js_1.generatePage)(); | ||
const mapperTabToServerTransport = new Transport_js_1.WindowBidiTransport(); | ||
const cdpTransport = new Transport_js_1.WindowCdpTransport(); | ||
/** | ||
* Set `window.runMapper` to a function which launches the BiDi mapper instance. | ||
* @param selfTargetId Needed to filter out info related to BiDi target. | ||
*/ | ||
window.runMapperInstance = async (selfTargetId) => { | ||
console.log('Launching Mapper instance with selfTargetId:', selfTargetId); | ||
await BidiMapper_js_1.BidiServer.createAndStart(mapperTabToServerTransport, | ||
/** | ||
@@ -40,17 +43,5 @@ * A CdpTransport implementation that uses the window.cdp bindings | ||
*/ | ||
new CdpConnection_js_1.CdpConnection(new Transport_js_1.WindowCdpTransport(), mapperTabPage_js_1.log), selfTargetId, new BidiParser_js_1.BidiParser(), mapperTabPage_js_1.log); | ||
(0, mapperTabPage_js_1.log)(log_js_1.LogType.debugInfo, 'Launched'); | ||
bidiServer.emitOutgoingMessage(bidiMapper_js_1.OutgoingMessage.createResolved({ | ||
launched: true, | ||
}), 'launched'); | ||
})(); | ||
// Needed to filter out info related to BiDi target. | ||
async function waitSelfTargetId() { | ||
return new Promise((resolve) => { | ||
window.setSelfTargetId = (targetId) => { | ||
(0, mapperTabPage_js_1.log)(log_js_1.LogType.debugInfo, 'Current target ID:', targetId); | ||
resolve(targetId); | ||
}; | ||
}); | ||
} | ||
new CdpConnection_js_1.CdpConnection(cdpTransport, mapperTabPage_js_1.log), selfTargetId, new BidiParser_js_1.BidiParser(), mapperTabPage_js_1.log); | ||
(0, mapperTabPage_js_1.log)(log_js_1.LogType.debugInfo, 'Mapper instance has been launched'); | ||
}; | ||
//# sourceMappingURL=bidiTab.js.map |
@@ -17,3 +17,3 @@ /** | ||
*/ | ||
import type { BidiTransport } from '../bidiMapper/bidiMapper.js'; | ||
import type { BidiTransport } from '../bidiMapper/BidiMapper.js'; | ||
import { type ChromiumBidi } from '../protocol/protocol.js'; | ||
@@ -20,0 +20,0 @@ import type { ITransport } from '../utils/transport.js'; |
@@ -232,2 +232,6 @@ /** | ||
context: BrowsingContext.BrowsingContext; | ||
/** | ||
* @defaultValue `"viewport"` | ||
*/ | ||
origin?: 'viewport' | 'document'; | ||
format?: BrowsingContext.ImageFormat; | ||
@@ -264,3 +268,3 @@ clip?: BrowsingContext.ClipRectangle; | ||
type BoxClipRectangle = { | ||
type: 'viewport'; | ||
type: 'box'; | ||
x: number; | ||
@@ -467,3 +471,7 @@ y: number; | ||
context: BrowsingContext.BrowsingContext; | ||
viewport: BrowsingContext.Viewport | null; | ||
viewport?: BrowsingContext.Viewport | null; | ||
/** | ||
* Must be greater than `0`. | ||
*/ | ||
devicePixelRatio?: number | null; | ||
}; | ||
@@ -470,0 +478,0 @@ } |
{ | ||
"name": "chromium-bidi", | ||
"version": "0.4.31", | ||
"version": "0.4.32", | ||
"description": "An implementation of the WebDriver BiDi protocol for Chromium implemented as a JavaScript layer translating between BiDi and CDP, running inside a Chrome tab.", | ||
@@ -9,2 +9,3 @@ "scripts": { | ||
"clean": "rimraf lib .eslintcache .wireit", | ||
"local-http-server": "python3 tools/run_local_http_server.py", | ||
"e2e-headful": "wireit", | ||
@@ -205,3 +206,3 @@ "e2e-headless": "wireit", | ||
"debug": "4.3.4", | ||
"devtools-protocol": "0.0.1205644", | ||
"devtools-protocol": "0.0.1208070", | ||
"eslint": "8.50.0", | ||
@@ -208,0 +209,0 @@ "eslint-config-prettier": "8.10.0", |
@@ -156,4 +156,8 @@ # WebDriver BiDi for Chromium [![chromium-bidi on npm](https://img.shields.io/npm/v/chromium-bidi)](https://www.npmjs.com/package/chromium-bidi) | ||
### Starting the Server | ||
```sh | ||
pre-commit install --hook-type pre-push | ||
``` | ||
### Starting WebDriver BiDi Server | ||
This will run the server on port `8080`: | ||
@@ -270,4 +274,22 @@ | ||
#### Examples | ||
### Local http server | ||
E2E tests use local http | ||
server [`pytest-httpserver`](https://pytest-httpserver.readthedocs.io/), which is run | ||
automatically with the tests. However, | ||
sometimes it is useful to run the http server outside the test | ||
case, for example for manual debugging. This can be done by running: | ||
```sh | ||
python3 tools/run_local_http_server.py | ||
``` | ||
or use npm: | ||
```sh | ||
npm run local-http-server | ||
``` | ||
### Examples | ||
Refer to [examples/README.md](examples/README.md). | ||
@@ -274,0 +296,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3932044
225
73925
464