This commit is contained in:
Yury Semikhatsky 2024-09-03 12:21:51 -07:00
parent daab42fa41
commit 294a05eafe
12 changed files with 44 additions and 57 deletions

View file

@ -16,6 +16,7 @@
[playwright.ts] [playwright.ts]
./android/ ./android/
./bidi/
./chromium/ ./chromium/
./electron/ ./electron/
./firefox/ ./firefox/

View file

@ -15,19 +15,21 @@
*/ */
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type * as types from '../types'; import type { RegisteredListener } from '../../utils/eventsHelper';
import * as network from '../network'; import { eventsHelper } from '../../utils/eventsHelper';
import type { BrowserOptions } from '../browser'; import type { BrowserOptions } from '../browser';
import { Browser } from '../browser'; import { Browser } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext } from '../browserContext'; import { assertBrowserContextIsNotOwned, BrowserContext } from '../browserContext';
import type { SdkObject } from '../instrumentation'; import type { SdkObject } from '../instrumentation';
import * as network from '../network';
import type { InitScript, Page, PageDelegate } from '../page';
import type { ConnectionTransport } from '../transport'; import type { ConnectionTransport } from '../transport';
import { BidiConnection, BidiSession } from './bidiConnection'; import type * as types from '../types';
import * as bidi from './third_party/bidiProtocol'; import type { BidiSession } from './bidiConnection';
import { InitScript, Page, PageDelegate } from '../page'; import { BidiConnection } from './bidiConnection';
import { eventsHelper, RegisteredListener } from '../../utils/eventsHelper';
import { BidiPage } from './bidiPage';
import { bidiBytesValueToString } from './bidiNetworkManager'; import { bidiBytesValueToString } from './bidiNetworkManager';
import { BidiPage } from './bidiPage';
import * as bidi from './third_party/bidiProtocol';
export class BidiBrowser extends Browser { export class BidiBrowser extends Browser {
private readonly _connection: BidiConnection; private readonly _connection: BidiConnection;
@ -68,7 +70,7 @@ export class BidiBrowser extends Browser {
break; break;
default: default:
throw new Error('Invalid proxy server protocol: ' + options.proxy.server); throw new Error('Invalid proxy server protocol: ' + options.proxy.server);
}; }
if (options.proxy.bypass) if (options.proxy.bypass)
proxy.noProxy = options.proxy.bypass.split(','); proxy.noProxy = options.proxy.bypass.split(',');
// TODO: support authentication. // TODO: support authentication.
@ -175,7 +177,7 @@ export class BidiBrowser extends Browser {
} }
const bidiPage = this._bidiPages.get(event.context); const bidiPage = this._bidiPages.get(event.context);
if (!bidiPage) if (!bidiPage)
return return;
bidiPage.didClose(); bidiPage.didClose();
this._bidiPages.delete(event.context); this._bidiPages.delete(event.context);
} }

View file

@ -38,7 +38,7 @@ export class BidiConnection {
private _lastId = 0; private _lastId = 0;
private _closed = false; private _closed = false;
readonly browserSession: BidiSession; readonly browserSession: BidiSession;
readonly _browsingContextToSession = new Map<string, BidiSession>; readonly _browsingContextToSession = new Map<string, BidiSession>();
constructor(transport: ConnectionTransport, onDisconnect: () => void, protocolLogger: ProtocolLogger, browserLogsCollector: RecentLogsCollector) { constructor(transport: ConnectionTransport, onDisconnect: () => void, protocolLogger: ProtocolLogger, browserLogsCollector: RecentLogsCollector) {
this._transport = transport; this._transport = transport;

View file

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
import type { BidiSession } from './bidiConnection'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import type { BidiSession } from './bidiConnection';
import { BidiDeserializer } from './third_party/bidiDeserializer';
import * as bidi from './third_party/bidiProtocol'; import * as bidi from './third_party/bidiProtocol';
import { BidiSerializer } from './third_party/bidiSerializer'; import { BidiSerializer } from './third_party/bidiSerializer';
import { BidiDeserializer } from './third_party/bidiDeserializer';
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
export class BidiExecutionContext implements js.ExecutionContextDelegate { export class BidiExecutionContext implements js.ExecutionContextDelegate {
private readonly _session: BidiSession; private readonly _session: BidiSession;
@ -63,7 +63,7 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
expression, expression,
target: this._target, target: this._target,
resultOwnership: bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned. resultOwnership: bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned.
serializationOptions: { maxObjectDepth:0, maxDomDepth:0 }, serializationOptions: { maxObjectDepth: 0, maxDomDepth: 0 },
awaitPromise: true, awaitPromise: true,
userActivation: true, userActivation: true,
}); });
@ -91,14 +91,13 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
...objectIds.map(handle => ({ handle })), ...objectIds.map(handle => ({ handle })),
], ],
resultOwnership: returnByValue ? undefined : bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned. resultOwnership: returnByValue ? undefined : bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned.
serializationOptions: returnByValue ? {} : { maxObjectDepth:0, maxDomDepth:0 }, serializationOptions: returnByValue ? {} : { maxObjectDepth: 0, maxDomDepth: 0 },
awaitPromise: true, awaitPromise: true,
userActivation: true, userActivation: true,
}); });
if (response.type === 'exception') if (response.type === 'exception')
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails)); throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
if (response.type === 'success') { if (response.type === 'success') {
if (returnByValue)
if (returnByValue) if (returnByValue)
return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result)); return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result));
const objectId = 'handle' in response.result ? response.result.handle : undefined ; const objectId = 'handle' in response.result ? response.result.handle : undefined ;
@ -131,9 +130,9 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
const response = await this._session.send('script.callFunction', { const response = await this._session.send('script.callFunction', {
functionDeclaration, functionDeclaration,
target: this._target, target: this._target,
arguments: [ arg ], arguments: [arg],
resultOwnership: bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned. resultOwnership: bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned.
serializationOptions: { maxObjectDepth:0, maxDomDepth:0 }, serializationOptions: { maxObjectDepth: 0, maxDomDepth: 0 },
awaitPromise: true, awaitPromise: true,
userActivation: true, userActivation: true,
}); });

View file

@ -19,7 +19,8 @@ import path from 'path';
import { assert, ManualPromise, wrapInASCIIBox } from '../../utils'; import { assert, ManualPromise, wrapInASCIIBox } from '../../utils';
import type { Env } from '../../utils/processLauncher'; import type { Env } from '../../utils/processLauncher';
import type { BrowserOptions } from '../browser'; import type { BrowserOptions } from '../browser';
import { BrowserReadyState, BrowserType, kNoXServerRunningError } from '../browserType'; import type { BrowserReadyState } from '../browserType';
import { BrowserType, kNoXServerRunningError } from '../browserType';
import type { SdkObject } from '../instrumentation'; import type { SdkObject } from '../instrumentation';
import type { ProtocolError } from '../protocolError'; import type { ProtocolError } from '../protocolError';
import type { ConnectionTransport } from '../transport'; import type { ConnectionTransport } from '../transport';
@ -70,11 +71,10 @@ export class BidiFirefox extends BrowserType {
if (userDataDirArg) if (userDataDirArg)
throw this._createUserDataDirArgMisuseError('--profile'); throw this._createUserDataDirArgMisuseError('--profile');
const firefoxArguments = ['--remote-debugging-port=0']; const firefoxArguments = ['--remote-debugging-port=0'];
if (headless) { if (headless)
firefoxArguments.push('--headless'); firefoxArguments.push('--headless');
} else { else
firefoxArguments.push('--foreground'); firefoxArguments.push('--foreground');
}
firefoxArguments.push(`--profile`, userDataDir); firefoxArguments.push(`--profile`, userDataDir);
firefoxArguments.push(...args); firefoxArguments.push(...args);
// TODO: make ephemeral context work without this argument. // TODO: make ephemeral context work without this argument.

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import * as input from '../input'; import type * as input from '../input';
import type * as types from '../types'; import type * as types from '../types';
import type { BidiSession } from './bidiConnection'; import type { BidiSession } from './bidiConnection';
import * as bidi from './third_party/bidiProtocol'; import * as bidi from './third_party/bidiProtocol';

View file

@ -21,7 +21,7 @@ import * as network from '../network';
import type * as frames from '../frames'; import type * as frames from '../frames';
import type * as types from '../types'; import type * as types from '../types';
import * as bidi from './third_party/bidiProtocol'; import * as bidi from './third_party/bidiProtocol';
import { BidiSession } from './bidiConnection'; import type { BidiSession } from './bidiConnection';
export class BidiNetworkManager { export class BidiNetworkManager {
@ -223,7 +223,7 @@ class BidiRequest {
if (redirectedFrom) if (redirectedFrom)
redirectedFrom._redirectedTo = this; redirectedFrom._redirectedTo = this;
// TODO: missing in the spec? // TODO: missing in the spec?
let postDataBuffer = null; const postDataBuffer = null;
this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigation ?? undefined, this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigation ?? undefined,
payload.request.url, 'other', payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers)); payload.request.url, 'other', payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers));
// "raw" headers are the same as "provisional" headers in Bidi. // "raw" headers are the same as "provisional" headers in Bidi.
@ -297,13 +297,13 @@ class BidiRouteImpl implements network.RouteDelegate {
function fromBidiHeaders(bidiHeaders: bidi.Network.Header[]): types.HeadersArray { function fromBidiHeaders(bidiHeaders: bidi.Network.Header[]): types.HeadersArray {
const result: types.HeadersArray = []; const result: types.HeadersArray = [];
for (const {name, value} of bidiHeaders) for (const { name, value } of bidiHeaders)
result.push({ name, value: bidiBytesValueToString(value) }); result.push({ name, value: bidiBytesValueToString(value) });
return result; return result;
} }
function toBidiHeaders(headers: types.HeadersArray): bidi.Network.Header[] { function toBidiHeaders(headers: types.HeadersArray): bidi.Network.Header[] {
return headers.map(({ name, value }) => ({ name, value: { type: 'string', value} })); return headers.map(({ name, value }) => ({ name, value: { type: 'string', value } }));
} }
export function bidiBytesValueToString(value: bidi.Network.BytesValue): string { export function bidiBytesValueToString(value: bidi.Network.BytesValue): string {

View file

@ -25,7 +25,7 @@ import { type InitScript, Page, type PageDelegate } from '../page';
import type { Progress } from '../progress'; import type { Progress } from '../progress';
import type * as types from '../types'; import type * as types from '../types';
import type { BidiBrowserContext } from './bidiBrowser'; import type { BidiBrowserContext } from './bidiBrowser';
import { BidiSession } from './bidiConnection'; import type { BidiSession } from './bidiConnection';
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './bidiInput'; import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './bidiInput';
import * as bidi from './third_party/bidiProtocol'; import * as bidi from './third_party/bidiProtocol';
import { BidiExecutionContext } from './bidiExecutionContext'; import { BidiExecutionContext } from './bidiExecutionContext';
@ -87,7 +87,7 @@ export class BidiPage implements PageDelegate {
} }
private async _initialize() { private async _initialize() {
const { contexts } = await this._session.send('browsingContext.getTree', { root: this._session.sessionId}); const { contexts } = await this._session.send('browsingContext.getTree', { root: this._session.sessionId });
this._handleFrameTree(contexts[0]); this._handleFrameTree(contexts[0]);
await Promise.all([ await Promise.all([
this.updateHttpCredentials(), this.updateHttpCredentials(),
@ -96,7 +96,7 @@ export class BidiPage implements PageDelegate {
]); ]);
} }
private _handleFrameTree(frameTree: bidi.BrowsingContext.Info ) { private _handleFrameTree(frameTree: bidi.BrowsingContext.Info) {
this._onFrameAttached(frameTree.context, frameTree.parent || null); this._onFrameAttached(frameTree.context, frameTree.parent || null);
// this._onFrameNavigated(frameTree.context, true); // this._onFrameNavigated(frameTree.context, true);
if (!frameTree.children) if (!frameTree.children)
@ -149,8 +149,9 @@ export class BidiPage implements PageDelegate {
worldName = 'main'; worldName = 'main';
// Force creating utility world every time the main world is created (e.g. due to navigation). // Force creating utility world every time the main world is created (e.g. due to navigation).
this._touchUtilityWorld(realmInfo.context); this._touchUtilityWorld(realmInfo.context);
} else if (realmInfo.sandbox === UTILITY_WORLD_NAME) } else if (realmInfo.sandbox === UTILITY_WORLD_NAME) {
worldName = 'utility'; worldName = 'utility';
}
const context = new dom.FrameExecutionContext(delegate, frame, worldName); const context = new dom.FrameExecutionContext(delegate, frame, worldName);
(context as any)[contextDelegateSymbol] = delegate; (context as any)[contextDelegateSymbol] = delegate;
if (worldName) if (worldName)
@ -376,7 +377,7 @@ export class BidiPage implements PageDelegate {
if (!(element instanceof Element)) if (!(element instanceof Element))
return null; return null;
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height}; return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
}); });
if (!box) if (!box)
return null; return null;
@ -385,7 +386,7 @@ export class BidiPage implements PageDelegate {
return null; return null;
box.x += position.x; box.x += position.x;
box.y += position.y; box.y += position.y;
return box return box;
} }
// TODO: move to Frame. // TODO: move to Frame.
@ -429,7 +430,7 @@ export class BidiPage implements PageDelegate {
} }
async getContentQuads(handle: dom.ElementHandle<Element>): Promise<types.Quad[] | null | 'error:notconnected'> { async getContentQuads(handle: dom.ElementHandle<Element>): Promise<types.Quad[] | null | 'error:notconnected'> {
let quads = await handle.evaluateInUtility(([injected, node]) => { const quads = await handle.evaluateInUtility(([injected, node]) => {
if (!node.isConnected) if (!node.isConnected)
return 'error:notconnected'; return 'error:notconnected';
const rects = node.getClientRects(); const rects = node.getClientRects();
@ -445,27 +446,6 @@ export class BidiPage implements PageDelegate {
if (!quads || quads === 'error:notconnected') if (!quads || quads === 'error:notconnected')
return quads; return quads;
// TODO: consider transforming quads to support clicks in iframes. // TODO: consider transforming quads to support clicks in iframes.
//
// if (handle._frame !== this._page.mainFrame()) {
// const frameElement = await handle._frame.frameElement();
// quads = await frameElement.evaluateInUtility(([injected, iframe, quads]) => {
// const transform = getComputedStyle(iframe as Element).transform;
// if (transform === 'none')
// return quads;
// const matrix = new DOMMatrixReadOnly(transform);
// for (const quad of quads) {
// for (const point of quad) {
// const p = new DOMPoint(point.x, point.y);
// const transformed = matrix.transformPoint(p);
// point.x = transformed.x;
// point.y = transformed.y;
// }
// }
// return quads;
// }, quads).catch(e => 'error:notconnected' as const);
// if (!quads || quads === 'error:notconnected')
// return null;
// }
const position = await this._framePosition(handle._frame); const position = await this._framePosition(handle._frame);
if (!position) if (!position)
return null; return null;

View file

@ -8,6 +8,8 @@
import type * as Bidi from './bidiProtocol'; import type * as Bidi from './bidiProtocol';
/* eslint-disable object-curly-spacing */
/** /**
* @internal * @internal
*/ */

View file

@ -5,6 +5,8 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
/* eslint-disable curly */
export const getBidiKeyValue = (key: string) => { export const getBidiKeyValue = (key: string) => {
switch (key) { switch (key) {
case '\r': case '\r':

View file

@ -7,6 +7,7 @@
import type * as Bidi from './bidiProtocol'; import type * as Bidi from './bidiProtocol';
/* eslint-disable curly, indent */
/** /**
* @internal * @internal

View file

@ -32,7 +32,7 @@ import { ProgressController } from './progress';
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';
import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings'; import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings';
import { assert, debugMode } from '../utils'; import { debugMode } from '../utils';
import { existsAsync } from '../utils/fileUtils'; import { existsAsync } from '../utils/fileUtils';
import { helper } from './helper'; import { helper } from './helper';
import { RecentLogsCollector } from '../utils/debugLogger'; import { RecentLogsCollector } from '../utils/debugLogger';