screenshot/test_1

This commit is contained in:
ramkumarp
2025-03-27 11:21:02 +00:00
commit 616d466383
470 changed files with 144705 additions and 0 deletions

View File

@@ -0,0 +1,314 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Network = exports.BidiBrowserContext = exports.BidiBrowser = void 0;
var _eventsHelper = require("../utils/eventsHelper");
var _browser = require("../browser");
var _browserContext = require("../browserContext");
var network = _interopRequireWildcard(require("../network"));
var _bidiConnection = require("./bidiConnection");
var _bidiNetworkManager = require("./bidiNetworkManager");
var _bidiPage = require("./bidiPage");
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* 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.
*/
class BidiBrowser extends _browser.Browser {
static async connect(parent, transport, options) {
const browser = new BidiBrowser(parent, transport, options);
if (options.__testHookOnConnectToBrowser) await options.__testHookOnConnectToBrowser();
let proxy;
if (options.proxy) {
proxy = {
proxyType: 'manual'
};
const url = new URL(options.proxy.server); // Validate proxy server.
switch (url.protocol) {
case 'http:':
proxy.httpProxy = url.host;
break;
case 'https:':
proxy.httpsProxy = url.host;
break;
case 'socks4:':
proxy.socksProxy = url.host;
proxy.socksVersion = 4;
break;
case 'socks5:':
proxy.socksProxy = url.host;
proxy.socksVersion = 5;
break;
default:
throw new Error('Invalid proxy server protocol: ' + options.proxy.server);
}
if (options.proxy.bypass) proxy.noProxy = options.proxy.bypass.split(',');
// TODO: support authentication.
}
browser._bidiSessionInfo = await browser._browserSession.send('session.new', {
capabilities: {
alwaysMatch: {
acceptInsecureCerts: false,
proxy,
unhandledPromptBehavior: {
default: bidi.Session.UserPromptHandlerType.Ignore
},
webSocketUrl: true
}
}
});
await browser._browserSession.send('session.subscribe', {
events: ['browsingContext', 'network', 'log', 'script']
});
if (options.persistent) {
browser._defaultContext = new BidiBrowserContext(browser, undefined, options.persistent);
await browser._defaultContext._initialize();
// Create default page as we cannot get access to the existing one.
const page = await browser._defaultContext.doCreateNewPage();
await page.waitForInitializedOrError();
}
return browser;
}
constructor(parent, transport, options) {
super(parent, options);
this._connection = void 0;
this._browserSession = void 0;
this._bidiSessionInfo = void 0;
this._contexts = new Map();
this._bidiPages = new Map();
this._eventListeners = void 0;
this._connection = new _bidiConnection.BidiConnection(transport, this._onDisconnect.bind(this), options.protocolLogger, options.browserLogsCollector);
this._browserSession = this._connection.browserSession;
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._browserSession, 'browsingContext.contextCreated', this._onBrowsingContextCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'script.realmDestroyed', this._onScriptRealmDestroyed.bind(this))];
}
_onDisconnect() {
this._didClose();
}
async doCreateNewContext(options) {
const {
userContext
} = await this._browserSession.send('browser.createUserContext', {});
const context = new BidiBrowserContext(this, userContext, options);
await context._initialize();
this._contexts.set(userContext, context);
return context;
}
contexts() {
return Array.from(this._contexts.values());
}
version() {
return this._bidiSessionInfo.capabilities.browserVersion;
}
userAgent() {
return this._bidiSessionInfo.capabilities.userAgent;
}
isConnected() {
return !this._connection.isClosed();
}
_onBrowsingContextCreated(event) {
if (event.parent) {
const parentFrameId = event.parent;
for (const page of this._bidiPages.values()) {
const parentFrame = page._page._frameManager.frame(parentFrameId);
if (!parentFrame) continue;
page._session.addFrameBrowsingContext(event.context);
page._page._frameManager.frameAttached(event.context, parentFrameId);
const frame = page._page._frameManager.frame(event.context);
if (frame) frame._url = event.url;
return;
}
return;
}
let context = this._contexts.get(event.userContext);
if (!context) context = this._defaultContext;
if (!context) return;
const session = this._connection.createMainFrameBrowsingContextSession(event.context);
const opener = event.originalOpener && this._bidiPages.get(event.originalOpener);
const page = new _bidiPage.BidiPage(context, session, opener || null);
page._page.mainFrame()._url = event.url;
this._bidiPages.set(event.context, page);
}
_onBrowsingContextDestroyed(event) {
if (event.parent) {
this._browserSession.removeFrameBrowsingContext(event.context);
const parentFrameId = event.parent;
for (const page of this._bidiPages.values()) {
const parentFrame = page._page._frameManager.frame(parentFrameId);
if (!parentFrame) continue;
page._page._frameManager.frameDetached(event.context);
return;
}
return;
}
const bidiPage = this._bidiPages.get(event.context);
if (!bidiPage) return;
bidiPage.didClose();
this._bidiPages.delete(event.context);
}
_onScriptRealmDestroyed(event) {
for (const page of this._bidiPages.values()) {
if (page._onRealmDestroyed(event)) return;
}
}
}
exports.BidiBrowser = BidiBrowser;
class BidiBrowserContext extends _browserContext.BrowserContext {
constructor(browser, browserContextId, options) {
super(browser, options, browserContextId);
this._authenticateProxyViaHeader();
}
_bidiPages() {
return [...this._browser._bidiPages.values()].filter(bidiPage => bidiPage._browserContext === this);
}
possiblyUninitializedPages() {
return this._bidiPages().map(bidiPage => bidiPage._page);
}
async doCreateNewPage() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const {
context
} = await this._browser._browserSession.send('browsingContext.create', {
type: bidi.BrowsingContext.CreateType.Window,
userContext: this._browserContextId
});
return this._browser._bidiPages.get(context)._page;
}
async doGetCookies(urls) {
const {
cookies
} = await this._browser._browserSession.send('storage.getCookies', {
partition: {
type: 'storageKey',
userContext: this._browserContextId
}
});
return network.filterCookies(cookies.map(c => {
var _c$expiry;
const copy = {
name: c.name,
value: (0, _bidiNetworkManager.bidiBytesValueToString)(c.value),
domain: c.domain,
path: c.path,
httpOnly: c.httpOnly,
secure: c.secure,
expires: (_c$expiry = c.expiry) !== null && _c$expiry !== void 0 ? _c$expiry : -1,
sameSite: c.sameSite ? fromBidiSameSite(c.sameSite) : 'None'
};
return copy;
}), urls);
}
async addCookies(cookies) {
cookies = network.rewriteCookies(cookies);
const promises = cookies.map(c => {
const cookie = {
name: c.name,
value: {
type: 'string',
value: c.value
},
domain: c.domain,
path: c.path,
httpOnly: c.httpOnly,
secure: c.secure,
sameSite: c.sameSite && toBidiSameSite(c.sameSite),
expiry: c.expires === -1 || c.expires === undefined ? undefined : Math.round(c.expires)
};
return this._browser._browserSession.send('storage.setCookie', {
cookie,
partition: {
type: 'storageKey',
userContext: this._browserContextId
}
});
});
await Promise.all(promises);
}
async doClearCookies() {
await this._browser._browserSession.send('storage.deleteCookies', {
partition: {
type: 'storageKey',
userContext: this._browserContextId
}
});
}
async doGrantPermissions(origin, permissions) {}
async doClearPermissions() {}
async setGeolocation(geolocation) {}
async setExtraHTTPHeaders(headers) {}
async setUserAgent(userAgent) {}
async setOffline(offline) {}
async doSetHTTPCredentials(httpCredentials) {
this._options.httpCredentials = httpCredentials;
for (const page of this.pages()) await page._delegate.updateHttpCredentials();
}
async doAddInitScript(initScript) {
await Promise.all(this.pages().map(page => page._delegate.addInitScript(initScript)));
}
async doRemoveNonInternalInitScripts() {}
async doUpdateRequestInterception() {}
onClosePersistent() {}
async clearCache() {}
async doClose(reason) {
if (!this._browserContextId) {
// Closing persistent context should close the browser.
await this._browser.close({
reason
});
return;
}
await this._browser._browserSession.send('browser.removeUserContext', {
userContext: this._browserContextId
});
this._browser._contexts.delete(this._browserContextId);
}
async cancelDownload(uuid) {}
}
exports.BidiBrowserContext = BidiBrowserContext;
function fromBidiSameSite(sameSite) {
switch (sameSite) {
case 'strict':
return 'Strict';
case 'lax':
return 'Lax';
case 'none':
return 'None';
}
return 'None';
}
function toBidiSameSite(sameSite) {
switch (sameSite) {
case 'Strict':
return bidi.Network.SameSite.Strict;
case 'Lax':
return bidi.Network.SameSite.Lax;
case 'None':
return bidi.Network.SameSite.None;
}
return bidi.Network.SameSite.None;
}
let Network = exports.Network = void 0;
(function (_Network) {
let SameSite = /*#__PURE__*/function (SameSite) {
SameSite["Strict"] = "strict";
SameSite["Lax"] = "lax";
SameSite["None"] = "none";
return SameSite;
}({});
_Network.SameSite = SameSite;
})(Network || (exports.Network = Network = {}));

View File

@@ -0,0 +1,125 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BidiChromium = void 0;
var _os = _interopRequireDefault(require("os"));
var _utils = require("../../utils");
var _ascii = require("../utils/ascii");
var _browserType = require("../browserType");
var _bidiBrowser = require("./bidiBrowser");
var _bidiConnection = require("./bidiConnection");
var _chromiumSwitches = require("../chromium/chromiumSwitches");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* 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.
*/
class BidiChromium extends _browserType.BrowserType {
constructor(parent) {
super(parent, 'bidi');
this._useBidi = true;
}
async connectToTransport(transport, options) {
// Chrome doesn't support Bidi, we create Bidi over CDP which is used by Chrome driver.
// bidiOverCdp depends on chromium-bidi which we only have in devDependencies, so
// we load bidiOverCdp dynamically.
const bidiTransport = await require('./bidiOverCdp').connectBidiOverCdp(transport);
transport[kBidiOverCdpWrapper] = bidiTransport;
return _bidiBrowser.BidiBrowser.connect(this.attribution.playwright, bidiTransport, options);
}
doRewriteStartupLog(error) {
if (!error.logs) return error;
if (error.logs.includes('Missing X server')) error.logs = '\n' + (0, _ascii.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1);
// These error messages are taken from Chromium source code as of July, 2020:
// https://github.com/chromium/chromium/blob/70565f67e79f79e17663ad1337dc6e63ee207ce9/content/browser/zygote_host/zygote_host_impl_linux.cc
if (!error.logs.includes('crbug.com/357670') && !error.logs.includes('No usable sandbox!') && !error.logs.includes('crbug.com/638180')) return error;
error.logs = [`Chromium sandboxing failed!`, `================================`, `To avoid the sandboxing issue, do either of the following:`, ` - (preferred): Configure your environment to support sandboxing`, ` - (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option`, `================================`, ``].join('\n');
return error;
}
amendEnvironment(env, userDataDir, executable, browserArguments) {
return env;
}
attemptToGracefullyCloseBrowser(transport) {
const bidiTransport = transport[kBidiOverCdpWrapper];
if (bidiTransport) transport = bidiTransport;
transport.send({
method: 'browser.close',
params: {},
id: _bidiConnection.kBrowserCloseMessageId
});
}
defaultArgs(options, isPersistent, userDataDir) {
const chromeArguments = this._innerDefaultArgs(options);
chromeArguments.push(`--user-data-dir=${userDataDir}`);
chromeArguments.push('--remote-debugging-port=0');
if (isPersistent) chromeArguments.push('about:blank');else chromeArguments.push('--no-startup-window');
return chromeArguments;
}
readyState(options) {
(0, _utils.assert)(options.useWebSocket);
return new ChromiumReadyState();
}
_innerDefaultArgs(options) {
const {
args = []
} = options;
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
if (userDataDirArg) throw this._createUserDataDirArgMisuseError('--user-data-dir');
if (args.find(arg => arg.startsWith('--remote-debugging-pipe'))) throw new Error('Playwright manages remote debugging connection itself.');
if (args.find(arg => !arg.startsWith('-'))) throw new Error('Arguments can not specify page to be opened');
const chromeArguments = [..._chromiumSwitches.chromiumSwitches];
if (_os.default.platform() === 'darwin') {
// See https://github.com/microsoft/playwright/issues/7362
chromeArguments.push('--enable-use-zoom-for-dsf=false');
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025.
if (options.headless) chromeArguments.push('--use-angle');
}
if (options.devtools) chromeArguments.push('--auto-open-devtools-for-tabs');
if (options.headless) {
chromeArguments.push('--headless');
chromeArguments.push('--hide-scrollbars', '--mute-audio', '--blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4');
}
if (options.chromiumSandbox !== true) chromeArguments.push('--no-sandbox');
const proxy = options.proxyOverride || options.proxy;
if (proxy) {
const proxyURL = new URL(proxy.server);
const isSocks = proxyURL.protocol === 'socks5:';
// https://www.chromium.org/developers/design-documents/network-settings
if (isSocks && !this.attribution.playwright.options.socksProxyPort) {
// https://www.chromium.org/developers/design-documents/network-stack/socks-proxy
chromeArguments.push(`--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE ${proxyURL.hostname}"`);
}
chromeArguments.push(`--proxy-server=${proxy.server}`);
const proxyBypassRules = [];
// https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578
if (this.attribution.playwright.options.socksProxyPort) proxyBypassRules.push('<-loopback>');
if (proxy.bypass) proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t));
if (!process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK && !proxyBypassRules.includes('<-loopback>')) proxyBypassRules.push('<-loopback>');
if (proxyBypassRules.length > 0) chromeArguments.push(`--proxy-bypass-list=${proxyBypassRules.join(';')}`);
}
chromeArguments.push(...args);
return chromeArguments;
}
}
exports.BidiChromium = BidiChromium;
class ChromiumReadyState extends _browserType.BrowserReadyState {
onBrowserOutput(message) {
const match = message.match(/DevTools listening on (.*)/);
if (match) this._wsEndpoint.resolve(match[1]);
}
}
const kBidiOverCdpWrapper = Symbol('kBidiConnectionWrapper');

View File

@@ -0,0 +1,204 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.kBrowserCloseMessageId = exports.BidiSession = exports.BidiConnection = void 0;
var _events = require("events");
var _debugLogger = require("../utils/debugLogger");
var _helper = require("../helper");
var _protocolError = require("../protocolError");
/**
* 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.
*/
// BidiPlaywright uses this special id to issue Browser.close command which we
// should ignore.
const kBrowserCloseMessageId = exports.kBrowserCloseMessageId = 0;
class BidiConnection {
constructor(transport, onDisconnect, protocolLogger, browserLogsCollector) {
this._transport = void 0;
this._onDisconnect = void 0;
this._protocolLogger = void 0;
this._browserLogsCollector = void 0;
this._browserDisconnectedLogs = void 0;
this._lastId = 0;
this._closed = false;
this.browserSession = void 0;
this._browsingContextToSession = new Map();
this._transport = transport;
this._onDisconnect = onDisconnect;
this._protocolLogger = protocolLogger;
this._browserLogsCollector = browserLogsCollector;
this.browserSession = new BidiSession(this, '', message => {
this.rawSend(message);
});
this._transport.onmessage = this._dispatchMessage.bind(this);
// onclose should be set last, since it can be immediately called.
this._transport.onclose = this._onClose.bind(this);
}
nextMessageId() {
return ++this._lastId;
}
rawSend(message) {
this._protocolLogger('send', message);
this._transport.send(message);
}
_dispatchMessage(message) {
this._protocolLogger('receive', message);
const object = message;
// Bidi messages do not have a common session identifier, so we
// route them based on BrowsingContext.
if (object.type === 'event') {
var _object$params$source;
// Route page events to the right session.
let context;
if ('context' in object.params) context = object.params.context;else if (object.method === 'log.entryAdded' || object.method === 'script.message') context = (_object$params$source = object.params.source) === null || _object$params$source === void 0 ? void 0 : _object$params$source.context;
if (context) {
const session = this._browsingContextToSession.get(context);
if (session) {
session.dispatchMessage(message);
return;
}
}
} else if (message.id) {
// Find caller session.
for (const session of this._browsingContextToSession.values()) {
if (session.hasCallback(message.id)) {
session.dispatchMessage(message);
return;
}
}
}
this.browserSession.dispatchMessage(message);
}
_onClose(reason) {
this._closed = true;
this._transport.onmessage = undefined;
this._transport.onclose = undefined;
this._browserDisconnectedLogs = _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs(), reason);
this.browserSession.dispose();
this._onDisconnect();
}
isClosed() {
return this._closed;
}
close() {
if (!this._closed) this._transport.close();
}
createMainFrameBrowsingContextSession(bowsingContextId) {
const result = new BidiSession(this, bowsingContextId, message => this.rawSend(message));
this._browsingContextToSession.set(bowsingContextId, result);
return result;
}
}
exports.BidiConnection = BidiConnection;
class BidiSession extends _events.EventEmitter {
constructor(connection, sessionId, rawSend) {
super();
this.connection = void 0;
this.sessionId = void 0;
this._disposed = false;
this._rawSend = void 0;
this._callbacks = new Map();
this._crashed = false;
this._browsingContexts = new Set();
this.on = void 0;
this.addListener = void 0;
this.off = void 0;
this.removeListener = void 0;
this.once = void 0;
this.setMaxListeners(0);
this.connection = connection;
this.sessionId = sessionId;
this._rawSend = rawSend;
this.on = super.on;
this.off = super.removeListener;
this.addListener = super.addListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
addFrameBrowsingContext(context) {
this._browsingContexts.add(context);
this.connection._browsingContextToSession.set(context, this);
}
removeFrameBrowsingContext(context) {
this._browsingContexts.delete(context);
this.connection._browsingContextToSession.delete(context);
}
async send(method, params) {
if (this._crashed || this._disposed || this.connection._browserDisconnectedLogs) throw new _protocolError.ProtocolError(this._crashed ? 'crashed' : 'closed', undefined, this.connection._browserDisconnectedLogs);
const id = this.connection.nextMessageId();
const messageObj = {
id,
method,
params
};
this._rawSend(messageObj);
return new Promise((resolve, reject) => {
this._callbacks.set(id, {
resolve,
reject,
error: new _protocolError.ProtocolError('error', method)
});
});
}
sendMayFail(method, params) {
return this.send(method, params).catch(error => _debugLogger.debugLogger.log('error', error));
}
markAsCrashed() {
this._crashed = true;
}
isDisposed() {
return this._disposed;
}
dispose() {
this._disposed = true;
this.connection._browsingContextToSession.delete(this.sessionId);
for (const context of this._browsingContexts) this.connection._browsingContextToSession.delete(context);
this._browsingContexts.clear();
for (const callback of this._callbacks.values()) {
callback.error.type = this._crashed ? 'crashed' : 'closed';
callback.error.logs = this.connection._browserDisconnectedLogs;
callback.reject(callback.error);
}
this._callbacks.clear();
}
hasCallback(id) {
return this._callbacks.has(id);
}
dispatchMessage(message) {
const object = message;
if (object.id === kBrowserCloseMessageId) return;
if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id);
this._callbacks.delete(object.id);
if (object.type === 'error') {
callback.error.setMessage(object.error + '\nMessage: ' + object.message);
callback.reject(callback.error);
} else if (object.type === 'success') {
callback.resolve(object.result);
} else {
callback.error.setMessage('Internal error, unexpected response type: ' + JSON.stringify(object));
callback.reject(callback.error);
}
} else if (object.id) {
// Response might come after session has been disposed and rejected all callbacks.
} else {
Promise.resolve().then(() => this.emit(object.method, object.params));
}
}
}
exports.BidiSession = BidiSession;

View File

@@ -0,0 +1,205 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BidiExecutionContext = void 0;
exports.createHandle = createHandle;
var _utils = require("../../utils");
var _utilityScriptSerializers = require("../isomorphic/utilityScriptSerializers");
var js = _interopRequireWildcard(require("../javascript"));
var dom = _interopRequireWildcard(require("../dom"));
var _bidiDeserializer = require("./third_party/bidiDeserializer");
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
var _bidiSerializer = require("./third_party/bidiSerializer");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* 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.
*/
class BidiExecutionContext {
constructor(session, realmInfo) {
this._session = void 0;
this._target = void 0;
this._session = session;
if (realmInfo.type === 'window') {
// Simple realm does not seem to work for Window contexts.
this._target = {
context: realmInfo.context,
sandbox: realmInfo.sandbox
};
} else {
this._target = {
realm: realmInfo.realm
};
}
}
async rawEvaluateJSON(expression) {
const response = await this._session.send('script.evaluate', {
expression,
target: this._target,
serializationOptions: {
maxObjectDepth: 10,
maxDomDepth: 10
},
awaitPromise: true,
userActivation: true
});
if (response.type === 'success') return _bidiDeserializer.BidiDeserializer.deserialize(response.result);
if (response.type === 'exception') throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
async rawEvaluateHandle(context, expression) {
const response = await this._session.send('script.evaluate', {
expression,
target: this._target,
resultOwnership: bidi.Script.ResultOwnership.Root,
// Necessary for the handle to be returned.
serializationOptions: {
maxObjectDepth: 0,
maxDomDepth: 0
},
awaitPromise: true,
userActivation: true
});
if (response.type === 'success') {
if ('handle' in response.result) return createHandle(context, response.result);
throw new js.JavaScriptErrorInEvaluate('Cannot get handle: ' + JSON.stringify(response.result));
}
if (response.type === 'exception') throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
async evaluateWithArguments(functionDeclaration, returnByValue, utilityScript, values, handles) {
const response = await this._session.send('script.callFunction', {
functionDeclaration,
target: this._target,
arguments: [{
handle: utilityScript._objectId
}, ...values.map(_bidiSerializer.BidiSerializer.serialize), ...handles.map(handle => ({
handle: handle._objectId
}))],
resultOwnership: returnByValue ? undefined : bidi.Script.ResultOwnership.Root,
// Necessary for the handle to be returned.
serializationOptions: returnByValue ? {} : {
maxObjectDepth: 0,
maxDomDepth: 0
},
awaitPromise: true,
userActivation: true
});
if (response.type === 'exception') throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
if (response.type === 'success') {
if (returnByValue) return (0, _utilityScriptSerializers.parseEvaluationResultValue)(_bidiDeserializer.BidiDeserializer.deserialize(response.result));
return createHandle(utilityScript._context, response.result);
}
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
async getProperties(handle) {
const names = await handle.evaluate(object => {
const names = [];
const descriptors = Object.getOwnPropertyDescriptors(object);
for (const name in descriptors) {
var _descriptors$name;
if ((_descriptors$name = descriptors[name]) !== null && _descriptors$name !== void 0 && _descriptors$name.enumerable) names.push(name);
}
return names;
});
const values = await Promise.all(names.map(name => handle.evaluateHandle((object, name) => object[name], name)));
const map = new Map();
for (let i = 0; i < names.length; i++) map.set(names[i], values[i]);
return map;
}
async releaseHandle(handle) {
if (!handle._objectId) return;
await this._session.send('script.disown', {
target: this._target,
handles: [handle._objectId]
});
}
async nodeIdForElementHandle(handle) {
const shared = await this._remoteValueForReference({
handle: handle._objectId
});
// TODO: store sharedId in the handle.
if (!('sharedId' in shared)) throw new Error('Element is not a node');
return {
sharedId: shared.sharedId
};
}
async remoteObjectForNodeId(context, nodeId) {
const result = await this._remoteValueForReference(nodeId, true);
if (!('handle' in result)) throw new Error('Can\'t get remote object for nodeId');
return createHandle(context, result);
}
async contentFrameIdForFrame(handle) {
const contentWindow = await this._rawCallFunction('e => e.contentWindow', {
handle: handle._objectId
});
if ((contentWindow === null || contentWindow === void 0 ? void 0 : contentWindow.type) === 'window') return contentWindow.value.context;
return null;
}
async frameIdForWindowHandle(handle) {
if (!handle._objectId) throw new Error('JSHandle is not a DOM node handle');
const contentWindow = await this._remoteValueForReference({
handle: handle._objectId
});
if (contentWindow.type === 'window') return contentWindow.value.context;
return null;
}
async _remoteValueForReference(reference, createHandle) {
return await this._rawCallFunction('e => e', reference, createHandle);
}
async _rawCallFunction(functionDeclaration, arg, createHandle) {
const response = await this._session.send('script.callFunction', {
functionDeclaration,
target: this._target,
arguments: [arg],
// "Root" is necessary for the handle to be returned.
resultOwnership: createHandle ? bidi.Script.ResultOwnership.Root : bidi.Script.ResultOwnership.None,
serializationOptions: {
maxObjectDepth: 0,
maxDomDepth: 0
},
awaitPromise: true,
userActivation: true
});
if (response.type === 'exception') throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
if (response.type === 'success') return response.result;
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
}
exports.BidiExecutionContext = BidiExecutionContext;
function renderPreview(remoteObject) {
if (remoteObject.type === 'undefined') return 'undefined';
if (remoteObject.type === 'null') return 'null';
if ('value' in remoteObject) return String(remoteObject.value);
return `<${remoteObject.type}>`;
}
function remoteObjectValue(remoteObject) {
if (remoteObject.type === 'undefined') return undefined;
if (remoteObject.type === 'null') return null;
if (remoteObject.type === 'number' && typeof remoteObject.value === 'string') return js.parseUnserializableValue(remoteObject.value);
if ('value' in remoteObject) return remoteObject.value;
return undefined;
}
function createHandle(context, remoteObject) {
if (remoteObject.type === 'node') {
(0, _utils.assert)(context instanceof dom.FrameExecutionContext);
return new dom.ElementHandle(context, remoteObject.handle);
}
const objectId = 'handle' in remoteObject ? remoteObject.handle : undefined;
return new js.JSHandle(context, remoteObject.type, renderPreview(remoteObject), objectId, remoteObjectValue(remoteObject));
}

View File

@@ -0,0 +1,105 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BidiFirefox = void 0;
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _utils = require("../../utils");
var _ascii = require("../utils/ascii");
var _browserType = require("../browserType");
var _bidiBrowser = require("./bidiBrowser");
var _bidiConnection = require("./bidiConnection");
var _firefoxPrefs = require("./third_party/firefoxPrefs");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* 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.
*/
class BidiFirefox extends _browserType.BrowserType {
constructor(parent) {
super(parent, 'bidi');
this._useBidi = true;
}
async connectToTransport(transport, options) {
return _bidiBrowser.BidiBrowser.connect(this.attribution.playwright, transport, options);
}
doRewriteStartupLog(error) {
if (!error.logs) return error;
// https://github.com/microsoft/playwright/issues/6500
if (error.logs.includes(`as root in a regular user's session is not supported.`)) error.logs = '\n' + (0, _ascii.wrapInASCIIBox)(`Firefox is unable to launch if the $HOME folder isn't owned by the current user.\nWorkaround: Set the HOME=/root environment variable${process.env.GITHUB_ACTION ? ' in your GitHub Actions workflow file' : ''} when running Playwright.`, 1);
if (error.logs.includes('no DISPLAY environment variable specified')) error.logs = '\n' + (0, _ascii.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1);
return error;
}
amendEnvironment(env, userDataDir, executable, browserArguments) {
if (!_path.default.isAbsolute(_os.default.homedir())) throw new Error(`Cannot launch Firefox with relative home directory. Did you set ${_os.default.platform() === 'win32' ? 'USERPROFILE' : 'HOME'} to a relative path?`);
env = {
...env,
'MOZ_CRASHREPORTER': '1',
'MOZ_CRASHREPORTER_NO_REPORT': '1',
'MOZ_CRASHREPORTER_SHUTDOWN': '1'
};
if (_os.default.platform() === 'linux') {
// Always remove SNAP_NAME and SNAP_INSTANCE_NAME env variables since they
// confuse Firefox: in our case, builds never come from SNAP.
// See https://github.com/microsoft/playwright/issues/20555
return {
...env,
SNAP_NAME: undefined,
SNAP_INSTANCE_NAME: undefined
};
}
return env;
}
attemptToGracefullyCloseBrowser(transport) {
transport.send({
method: 'browser.close',
params: {},
id: _bidiConnection.kBrowserCloseMessageId
});
}
async prepareUserDataDir(options, userDataDir) {
await (0, _firefoxPrefs.createProfile)({
path: userDataDir,
preferences: options.firefoxUserPrefs || {}
});
}
defaultArgs(options, isPersistent, userDataDir) {
const {
args = [],
headless
} = options;
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));
if (userDataDirArg) throw this._createUserDataDirArgMisuseError('--profile');
const firefoxArguments = ['--remote-debugging-port=0'];
if (headless) firefoxArguments.push('--headless');else firefoxArguments.push('--foreground');
firefoxArguments.push(`--profile`, userDataDir);
firefoxArguments.push(...args);
return firefoxArguments;
}
readyState(options) {
(0, _utils.assert)(options.useWebSocket);
return new FirefoxReadyState();
}
}
exports.BidiFirefox = BidiFirefox;
class FirefoxReadyState extends _browserType.BrowserReadyState {
onBrowserOutput(message) {
// Bidi WebSocket in Firefox.
const match = message.match(/WebDriver BiDi listening on (ws:\/\/.*)$/);
if (match) this._wsEndpoint.resolve(match[1] + '/session');
}
}

View File

@@ -0,0 +1,157 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RawTouchscreenImpl = exports.RawMouseImpl = exports.RawKeyboardImpl = void 0;
var _input = require("../input");
var _bidiKeyboard = require("./third_party/bidiKeyboard");
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* 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.
*/
class RawKeyboardImpl {
constructor(session) {
this._session = void 0;
this._session = session;
}
setSession(session) {
this._session = session;
}
async keydown(modifiers, keyName, description, autoRepeat) {
keyName = (0, _input.resolveSmartModifierString)(keyName);
const actions = [];
actions.push({
type: 'keyDown',
value: (0, _bidiKeyboard.getBidiKeyValue)(keyName)
});
await this._performActions(actions);
}
async keyup(modifiers, keyName, description) {
keyName = (0, _input.resolveSmartModifierString)(keyName);
const actions = [];
actions.push({
type: 'keyUp',
value: (0, _bidiKeyboard.getBidiKeyValue)(keyName)
});
await this._performActions(actions);
}
async sendText(text) {
const actions = [];
for (const char of text) {
const value = (0, _bidiKeyboard.getBidiKeyValue)(char);
actions.push({
type: 'keyDown',
value
});
actions.push({
type: 'keyUp',
value
});
}
await this._performActions(actions);
}
async _performActions(actions) {
await this._session.send('input.performActions', {
context: this._session.sessionId,
actions: [{
type: 'key',
id: 'pw_keyboard',
actions
}]
});
}
}
exports.RawKeyboardImpl = RawKeyboardImpl;
class RawMouseImpl {
constructor(session) {
this._session = void 0;
this._session = session;
}
async move(x, y, button, buttons, modifiers, forClick) {
await this._performActions([{
type: 'pointerMove',
x,
y
}]);
}
async down(x, y, button, buttons, modifiers, clickCount) {
await this._performActions([{
type: 'pointerDown',
button: toBidiButton(button)
}]);
}
async up(x, y, button, buttons, modifiers, clickCount) {
await this._performActions([{
type: 'pointerUp',
button: toBidiButton(button)
}]);
}
async wheel(x, y, buttons, modifiers, deltaX, deltaY) {
// Bidi throws when x/y are not integers.
x = Math.floor(x);
y = Math.floor(y);
await this._session.send('input.performActions', {
context: this._session.sessionId,
actions: [{
type: 'wheel',
id: 'pw_mouse_wheel',
actions: [{
type: 'scroll',
x,
y,
deltaX,
deltaY
}]
}]
});
}
async _performActions(actions) {
await this._session.send('input.performActions', {
context: this._session.sessionId,
actions: [{
type: 'pointer',
id: 'pw_mouse',
parameters: {
pointerType: bidi.Input.PointerType.Mouse
},
actions
}]
});
}
}
exports.RawMouseImpl = RawMouseImpl;
class RawTouchscreenImpl {
constructor(session) {
this._session = void 0;
this._session = session;
}
async tap(x, y, modifiers) {}
}
exports.RawTouchscreenImpl = RawTouchscreenImpl;
function toBidiButton(button) {
switch (button) {
case 'left':
return 0;
case 'right':
return 2;
case 'middle':
return 1;
}
throw new Error('Unknown button: ' + button);
}

View File

@@ -0,0 +1,337 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BidiNetworkManager = void 0;
exports.bidiBytesValueToString = bidiBytesValueToString;
var _eventsHelper = require("../utils/eventsHelper");
var _cookieStore = require("../cookieStore");
var network = _interopRequireWildcard(require("../network"));
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* 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.
*/
class BidiNetworkManager {
constructor(bidiSession, page, onNavigationResponseStarted) {
this._session = void 0;
this._requests = void 0;
this._page = void 0;
this._eventListeners = void 0;
this._onNavigationResponseStarted = void 0;
this._userRequestInterceptionEnabled = false;
this._protocolRequestInterceptionEnabled = false;
this._credentials = void 0;
this._intercepId = void 0;
this._session = bidiSession;
this._requests = new Map();
this._page = page;
this._onNavigationResponseStarted = onNavigationResponseStarted;
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.beforeRequestSent', this._onBeforeRequestSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseStarted', this._onResponseStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseCompleted', this._onResponseCompleted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.fetchError', this._onFetchError.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.authRequired', this._onAuthRequired.bind(this))];
}
dispose() {
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
}
_onBeforeRequestSent(param) {
if (param.request.url.startsWith('data:')) return;
const redirectedFrom = param.redirectCount ? this._requests.get(param.request.request) || null : null;
const frame = redirectedFrom ? redirectedFrom.request.frame() : param.context ? this._page._frameManager.frame(param.context) : null;
if (!frame) return;
if (redirectedFrom) this._requests.delete(redirectedFrom._id);
let route;
if (param.intercepts) {
// We do not support intercepting redirects.
if (redirectedFrom) {
var _redirectedFrom$_orig, _redirectedFrom$_orig2;
let params = {};
if ((_redirectedFrom$_orig = redirectedFrom._originalRequestRoute) !== null && _redirectedFrom$_orig !== void 0 && _redirectedFrom$_orig._alreadyContinuedHeaders) params = toBidiRequestHeaders((_redirectedFrom$_orig2 = redirectedFrom._originalRequestRoute._alreadyContinuedHeaders) !== null && _redirectedFrom$_orig2 !== void 0 ? _redirectedFrom$_orig2 : []);
this._session.sendMayFail('network.continueRequest', {
request: param.request.request,
...params
});
} else {
route = new BidiRouteImpl(this._session, param.request.request);
}
}
const request = new BidiRequest(frame, redirectedFrom, param, route);
this._requests.set(request._id, request);
this._page._frameManager.requestStarted(request.request, route);
}
_onResponseStarted(params) {
const request = this._requests.get(params.request.request);
if (!request) return;
const getResponseBody = async () => {
throw new Error(`Response body is not available for requests in Bidi`);
};
const timings = params.request.timings;
const startTime = timings.requestTime;
function relativeToStart(time) {
if (!time) return -1;
return time - startTime;
}
const timing = {
startTime: startTime,
requestStart: relativeToStart(timings.requestStart),
responseStart: relativeToStart(timings.responseStart),
domainLookupStart: relativeToStart(timings.dnsStart),
domainLookupEnd: relativeToStart(timings.dnsEnd),
connectStart: relativeToStart(timings.connectStart),
secureConnectionStart: relativeToStart(timings.tlsStart),
connectEnd: relativeToStart(timings.connectEnd)
};
const response = new network.Response(request.request, params.response.status, params.response.statusText, fromBidiHeaders(params.response.headers), timing, getResponseBody, false);
response._serverAddrFinished();
response._securityDetailsFinished();
// "raw" headers are the same as "provisional" headers in Bidi.
response.setRawResponseHeaders(null);
response.setResponseHeadersSize(params.response.headersSize);
this._page._frameManager.requestReceivedResponse(response);
if (params.navigation) this._onNavigationResponseStarted(params);
}
_onResponseCompleted(params) {
const request = this._requests.get(params.request.request);
if (!request) return;
const response = request.request._existingResponse();
// TODO: body size is the encoded size
response.setTransferSize(params.response.bodySize);
response.setEncodedBodySize(params.response.bodySize);
// Keep redirected requests in the map for future reference as redirectedFrom.
const isRedirected = response.status() >= 300 && response.status() <= 399;
const responseEndTime = params.request.timings.responseEnd - response.timing().startTime;
if (isRedirected) {
response._requestFinished(responseEndTime);
} else {
this._requests.delete(request._id);
response._requestFinished(responseEndTime);
}
response._setHttpVersion(params.response.protocol);
this._page._frameManager.reportRequestFinished(request.request, response);
}
_onFetchError(params) {
const request = this._requests.get(params.request.request);
if (!request) return;
this._requests.delete(request._id);
const response = request.request._existingResponse();
if (response) {
response.setTransferSize(null);
response.setEncodedBodySize(null);
response._requestFinished(-1);
}
request.request._setFailureText(params.errorText);
// TODO: support canceled flag
this._page._frameManager.requestFailed(request.request, params.errorText === 'NS_BINDING_ABORTED');
}
_onAuthRequired(params) {
var _params$response$auth;
const isBasic = (_params$response$auth = params.response.authChallenges) === null || _params$response$auth === void 0 ? void 0 : _params$response$auth.some(challenge => challenge.scheme.startsWith('Basic'));
const credentials = this._page._browserContext._options.httpCredentials;
if (isBasic && credentials) {
this._session.sendMayFail('network.continueWithAuth', {
request: params.request.request,
action: 'provideCredentials',
credentials: {
type: 'password',
username: credentials.username,
password: credentials.password
}
});
} else {
this._session.sendMayFail('network.continueWithAuth', {
request: params.request.request,
action: 'default'
});
}
}
async setRequestInterception(value) {
this._userRequestInterceptionEnabled = value;
await this._updateProtocolRequestInterception();
}
async setCredentials(credentials) {
this._credentials = credentials;
await this._updateProtocolRequestInterception();
}
async _updateProtocolRequestInterception(initial) {
const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
if (enabled === this._protocolRequestInterceptionEnabled) return;
this._protocolRequestInterceptionEnabled = enabled;
if (initial && !enabled) return;
const cachePromise = this._session.send('network.setCacheBehavior', {
cacheBehavior: enabled ? 'bypass' : 'default'
});
let interceptPromise = Promise.resolve(undefined);
if (enabled) {
interceptPromise = this._session.send('network.addIntercept', {
phases: [bidi.Network.InterceptPhase.AuthRequired, bidi.Network.InterceptPhase.BeforeRequestSent],
urlPatterns: [{
type: 'pattern'
}]
// urlPatterns: [{ type: 'string', pattern: '*' }],
}).then(r => {
this._intercepId = r.intercept;
});
} else if (this._intercepId) {
interceptPromise = this._session.send('network.removeIntercept', {
intercept: this._intercepId
});
this._intercepId = undefined;
}
await Promise.all([cachePromise, interceptPromise]);
}
}
exports.BidiNetworkManager = BidiNetworkManager;
class BidiRequest {
constructor(frame, redirectedFrom, payload, route) {
var _payload$navigation;
this.request = void 0;
this._id = void 0;
this._redirectedTo = void 0;
// Only first request in the chain can be intercepted, so this will
// store the first and only Route in the chain (if any).
this._originalRequestRoute = void 0;
this._id = payload.request.request;
if (redirectedFrom) redirectedFrom._redirectedTo = this;
// TODO: missing in the spec?
const postDataBuffer = null;
this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, (_payload$navigation = payload.navigation) !== null && _payload$navigation !== void 0 ? _payload$navigation : undefined, payload.request.url, 'other', payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers));
// "raw" headers are the same as "provisional" headers in Bidi.
this.request.setRawRequestHeaders(null);
this.request._setBodySize(payload.request.bodySize || 0);
this._originalRequestRoute = route !== null && route !== void 0 ? route : redirectedFrom === null || redirectedFrom === void 0 ? void 0 : redirectedFrom._originalRequestRoute;
route === null || route === void 0 || route._setRequest(this.request);
}
_finalRequest() {
let request = this;
while (request._redirectedTo) request = request._redirectedTo;
return request;
}
}
class BidiRouteImpl {
constructor(session, requestId) {
this._requestId = void 0;
this._session = void 0;
this._request = void 0;
this._alreadyContinuedHeaders = void 0;
this._session = session;
this._requestId = requestId;
}
_setRequest(request) {
this._request = request;
}
async continue(overrides) {
// Firefox does not update content-length header.
let headers = overrides.headers || this._request.headers();
if (overrides.postData && headers) {
headers = headers.map(header => {
if (header.name.toLowerCase() === 'content-length') return {
name: header.name,
value: overrides.postData.byteLength.toString()
};
return header;
});
}
this._alreadyContinuedHeaders = headers;
await this._session.sendMayFail('network.continueRequest', {
request: this._requestId,
url: overrides.url,
method: overrides.method,
...toBidiRequestHeaders(this._alreadyContinuedHeaders),
body: overrides.postData ? {
type: 'base64',
value: Buffer.from(overrides.postData).toString('base64')
} : undefined
});
}
async fulfill(response) {
const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
await this._session.sendMayFail('network.provideResponse', {
request: this._requestId,
statusCode: response.status,
reasonPhrase: network.statusText(response.status),
...toBidiResponseHeaders(response.headers),
body: {
type: 'base64',
value: base64body
}
});
}
async abort(errorCode) {
await this._session.sendMayFail('network.failRequest', {
request: this._requestId
});
}
}
function fromBidiHeaders(bidiHeaders) {
const result = [];
for (const {
name,
value
} of bidiHeaders) result.push({
name,
value: bidiBytesValueToString(value)
});
return result;
}
function toBidiRequestHeaders(allHeaders) {
const bidiHeaders = toBidiHeaders(allHeaders);
return {
headers: bidiHeaders
};
}
function toBidiResponseHeaders(headers) {
const setCookieHeaders = headers.filter(h => h.name.toLowerCase() === 'set-cookie');
const otherHeaders = headers.filter(h => h.name.toLowerCase() !== 'set-cookie');
const rawCookies = setCookieHeaders.map(h => (0, _cookieStore.parseRawCookie)(h.value));
const cookies = rawCookies.filter(Boolean).map(c => {
return {
...c,
value: {
type: 'string',
value: c.value
},
sameSite: toBidiSameSite(c.sameSite)
};
});
return {
cookies,
headers: toBidiHeaders(otherHeaders)
};
}
function toBidiHeaders(headers) {
return headers.map(({
name,
value
}) => ({
name,
value: {
type: 'string',
value
}
}));
}
function bidiBytesValueToString(value) {
if (value.type === 'string') return value.value;
if (value.type === 'base64') return Buffer.from(value.type, 'base64').toString('binary');
return 'unknown value type: ' + value.type;
}
function toBidiSameSite(sameSite) {
if (!sameSite) return undefined;
if (sameSite === 'Strict') return bidi.Network.SameSite.Strict;
if (sameSite === 'Lax') return bidi.Network.SameSite.Lax;
return bidi.Network.SameSite.None;
}

View File

@@ -0,0 +1,103 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.connectBidiOverCdp = connectBidiOverCdp;
var bidiMapper = _interopRequireWildcard(require("chromium-bidi/lib/cjs/bidiMapper/BidiMapper"));
var bidiCdpConnection = _interopRequireWildcard(require("chromium-bidi/lib/cjs/cdp/CdpConnection"));
var _debugLogger = require("../utils/debugLogger");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* 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.
*/
const bidiServerLogger = (prefix, ...args) => {
_debugLogger.debugLogger.log(prefix, args);
};
async function connectBidiOverCdp(cdp) {
let server = undefined;
const bidiTransport = new BidiTransportImpl();
const bidiConnection = new BidiConnection(bidiTransport, () => {
var _server;
return (_server = server) === null || _server === void 0 ? void 0 : _server.close();
});
const cdpTransportImpl = new CdpTransportImpl(cdp);
const cdpConnection = new bidiCdpConnection.MapperCdpConnection(cdpTransportImpl, bidiServerLogger);
// Make sure onclose event is propagated.
cdp.onclose = () => {
var _bidiConnection$onclo;
return (_bidiConnection$onclo = bidiConnection.onclose) === null || _bidiConnection$onclo === void 0 ? void 0 : _bidiConnection$onclo.call(bidiConnection);
};
server = await bidiMapper.BidiServer.createAndStart(bidiTransport, cdpConnection, await cdpConnection.createBrowserSession(), /* selfTargetId= */'', undefined, bidiServerLogger);
return bidiConnection;
}
class BidiTransportImpl {
constructor() {
this._handler = void 0;
this._bidiConnection = void 0;
}
setOnMessage(handler) {
this._handler = handler;
}
sendMessage(message) {
var _this$_bidiConnection, _this$_bidiConnection2;
return (_this$_bidiConnection = (_this$_bidiConnection2 = this._bidiConnection).onmessage) === null || _this$_bidiConnection === void 0 ? void 0 : _this$_bidiConnection.call(_this$_bidiConnection2, message);
}
close() {
var _this$_bidiConnection3, _this$_bidiConnection4;
(_this$_bidiConnection3 = (_this$_bidiConnection4 = this._bidiConnection).onclose) === null || _this$_bidiConnection3 === void 0 || _this$_bidiConnection3.call(_this$_bidiConnection4);
}
}
class BidiConnection {
constructor(bidiTransport, closeCallback) {
this._bidiTransport = void 0;
this._closeCallback = void 0;
this.onmessage = void 0;
this.onclose = void 0;
this._bidiTransport = bidiTransport;
this._bidiTransport._bidiConnection = this;
this._closeCallback = closeCallback;
}
send(s) {
var _this$_bidiTransport$, _this$_bidiTransport;
(_this$_bidiTransport$ = (_this$_bidiTransport = this._bidiTransport)._handler) === null || _this$_bidiTransport$ === void 0 || _this$_bidiTransport$.call(_this$_bidiTransport, s);
}
close() {
this._closeCallback();
}
}
class CdpTransportImpl {
constructor(connection) {
this._connection = void 0;
this._handler = void 0;
this._bidiConnection = void 0;
this._connection = connection;
this._connection.onmessage = message => {
var _this$_handler;
(_this$_handler = this._handler) === null || _this$_handler === void 0 || _this$_handler.call(this, JSON.stringify(message));
};
}
setOnMessage(handler) {
this._handler = handler;
}
sendMessage(message) {
return this._connection.send(JSON.parse(message));
}
close() {
this._connection.close();
}
}

View File

@@ -0,0 +1,502 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BidiPage = void 0;
var _utils = require("../../utils");
var _eventsHelper = require("../utils/eventsHelper");
var _browserContext = require("../browserContext");
var dialog = _interopRequireWildcard(require("../dialog"));
var dom = _interopRequireWildcard(require("../dom"));
var _page = require("../page");
var _bidiExecutionContext = require("./bidiExecutionContext");
var _bidiInput = require("./bidiInput");
var _bidiNetworkManager = require("./bidiNetworkManager");
var _bidiPdf = require("./bidiPdf");
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* 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.
*/
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const kPlaywrightBindingChannel = 'playwrightChannel';
class BidiPage {
constructor(browserContext, bidiSession, opener) {
this.rawMouse = void 0;
this.rawKeyboard = void 0;
this.rawTouchscreen = void 0;
this._page = void 0;
this._session = void 0;
this._opener = void 0;
this._realmToContext = void 0;
this._sessionListeners = [];
this._browserContext = void 0;
this._networkManager = void 0;
this._pdf = void 0;
this._initScriptIds = [];
this._session = bidiSession;
this._opener = opener;
this.rawKeyboard = new _bidiInput.RawKeyboardImpl(bidiSession);
this.rawMouse = new _bidiInput.RawMouseImpl(bidiSession);
this.rawTouchscreen = new _bidiInput.RawTouchscreenImpl(bidiSession);
this._realmToContext = new Map();
this._page = new _page.Page(this, browserContext);
this._browserContext = browserContext;
this._networkManager = new _bidiNetworkManager.BidiNetworkManager(this._session, this._page, this._onNavigationResponseStarted.bind(this));
this._pdf = new _bidiPdf.BidiPDF(this._session);
this._page.on(_page.Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame, false));
this._sessionListeners = [_eventsHelper.eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'script.message', this._onScriptMessage.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.contextDestroyed', this._onBrowsingContextDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationStarted', this._onNavigationStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationAborted', this._onNavigationAborted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationFailed', this._onNavigationFailed.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.fragmentNavigated', this._onFragmentNavigated.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.domContentLoaded', this._onDomContentLoaded.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.load', this._onLoad.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.userPromptOpened', this._onUserPromptOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'log.entryAdded', this._onLogEntryAdded.bind(this))];
// Initialize main frame.
// TODO: Wait for first execution context to be created and maybe about:blank navigated.
this._initialize().then(() => {
var _this$_opener;
return this._page.reportAsNew((_this$_opener = this._opener) === null || _this$_opener === void 0 ? void 0 : _this$_opener._page);
}, error => {
var _this$_opener2;
return this._page.reportAsNew((_this$_opener2 = this._opener) === null || _this$_opener2 === void 0 ? void 0 : _this$_opener2._page, error);
});
}
async _initialize() {
// Initialize main frame.
this._onFrameAttached(this._session.sessionId, null);
await Promise.all([this.updateHttpCredentials(), this.updateRequestInterception(), this._updateViewport(), this._installMainBinding(), this._addAllInitScripts()]);
}
async _addAllInitScripts() {
return Promise.all(this._page.allInitScripts().map(initScript => this.addInitScript(initScript)));
}
didClose() {
this._session.dispose();
_eventsHelper.eventsHelper.removeEventListeners(this._sessionListeners);
this._page._didClose();
}
_onFrameAttached(frameId, parentFrameId) {
return this._page._frameManager.frameAttached(frameId, parentFrameId);
}
_removeContextsForFrame(frame, notifyFrame) {
for (const [contextId, context] of this._realmToContext) {
if (context.frame === frame) {
this._realmToContext.delete(contextId);
if (notifyFrame) frame._contextDestroyed(context);
}
}
}
_onRealmCreated(realmInfo) {
if (this._realmToContext.has(realmInfo.realm)) return;
if (realmInfo.type !== 'window') return;
const frame = this._page._frameManager.frame(realmInfo.context);
if (!frame) return;
let worldName;
if (!realmInfo.sandbox) {
worldName = 'main';
// Force creating utility world every time the main world is created (e.g. due to navigation).
this._touchUtilityWorld(realmInfo.context);
} else if (realmInfo.sandbox === UTILITY_WORLD_NAME) {
worldName = 'utility';
} else {
return;
}
const delegate = new _bidiExecutionContext.BidiExecutionContext(this._session, realmInfo);
const context = new dom.FrameExecutionContext(delegate, frame, worldName);
frame._contextCreated(worldName, context);
this._realmToContext.set(realmInfo.realm, context);
}
async _touchUtilityWorld(context) {
await this._session.sendMayFail('script.evaluate', {
expression: '1 + 1',
target: {
context,
sandbox: UTILITY_WORLD_NAME
},
serializationOptions: {
maxObjectDepth: 10,
maxDomDepth: 10
},
awaitPromise: true,
userActivation: true
});
}
_onRealmDestroyed(params) {
const context = this._realmToContext.get(params.realm);
if (!context) return false;
this._realmToContext.delete(params.realm);
context.frame._contextDestroyed(context);
return true;
}
// TODO: route the message directly to the browser
_onBrowsingContextDestroyed(params) {
this._browserContext._browser._onBrowsingContextDestroyed(params);
}
_onNavigationStarted(params) {
const frameId = params.context;
this._page._frameManager.frameRequestedNavigation(frameId, params.navigation);
const url = params.url.toLowerCase();
if (url.startsWith('file:') || url.startsWith('data:') || url === 'about:blank') {
// Navigation to file urls doesn't emit network events, so we fire 'commit' event right when navigation is started.
// Doing it in domcontentload would be too late as we'd clear frame tree.
const frame = this._page._frameManager.frame(frameId);
if (frame) this._page._frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, '', params.navigation, /* initial */false);
}
}
// TODO: there is no separate event for committed navigation, so we approximate it with responseStarted.
_onNavigationResponseStarted(params) {
const frameId = params.context;
const frame = this._page._frameManager.frame(frameId);
(0, _utils.assert)(frame);
this._page._frameManager.frameCommittedNewDocumentNavigation(frameId, params.response.url, '', params.navigation, /* initial */false);
// if (!initial)
// this._firstNonInitialNavigationCommittedFulfill();
}
_onDomContentLoaded(params) {
const frameId = params.context;
this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded');
}
_onLoad(params) {
this._page._frameManager.frameLifecycleEvent(params.context, 'load');
}
_onNavigationAborted(params) {
this._page._frameManager.frameAbortedNavigation(params.context, 'Navigation aborted', params.navigation || undefined);
}
_onNavigationFailed(params) {
this._page._frameManager.frameAbortedNavigation(params.context, 'Navigation failed', params.navigation || undefined);
}
_onFragmentNavigated(params) {
this._page._frameManager.frameCommittedSameDocumentNavigation(params.context, params.url);
}
_onUserPromptOpened(event) {
this._page.emitOnContext(_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog(this._page, event.type, event.message, async (accept, userText) => {
await this._session.send('browsingContext.handleUserPrompt', {
context: event.context,
accept,
userText
});
}, event.defaultValue));
}
_onLogEntryAdded(params) {
var _params$stackTrace;
if (params.type !== 'console') return;
const entry = params;
const context = this._realmToContext.get(params.source.realm);
if (!context) return;
const callFrame = (_params$stackTrace = params.stackTrace) === null || _params$stackTrace === void 0 ? void 0 : _params$stackTrace.callFrames[0];
const location = callFrame !== null && callFrame !== void 0 ? callFrame : {
url: '',
lineNumber: 1,
columnNumber: 1
};
this._page._addConsoleMessage(entry.method, entry.args.map(arg => (0, _bidiExecutionContext.createHandle)(context, arg)), location, params.text || undefined);
}
async navigateFrame(frame, url, referrer) {
const {
navigation
} = await this._session.send('browsingContext.navigate', {
context: frame._id,
url
});
return {
newDocumentId: navigation || undefined
};
}
async updateExtraHTTPHeaders() {}
async updateEmulateMedia() {}
async updateEmulatedViewportSize() {
await this._updateViewport();
}
async updateUserAgent() {}
async bringToFront() {
await this._session.send('browsingContext.activate', {
context: this._session.sessionId
});
}
async _updateViewport() {
const options = this._browserContext._options;
const deviceSize = this._page.emulatedSize();
if (deviceSize === null) return;
const viewportSize = deviceSize.viewport;
await this._session.send('browsingContext.setViewport', {
context: this._session.sessionId,
viewport: {
width: viewportSize.width,
height: viewportSize.height
},
devicePixelRatio: options.deviceScaleFactor || 1
});
}
async updateRequestInterception() {
await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
}
async updateOffline() {}
async updateHttpCredentials() {
await this._networkManager.setCredentials(this._browserContext._options.httpCredentials);
}
async updateFileChooserInterception() {}
async reload() {
await this._session.send('browsingContext.reload', {
context: this._session.sessionId,
// ignoreCache: true,
wait: bidi.BrowsingContext.ReadinessState.Interactive
});
}
async goBack() {
return await this._session.send('browsingContext.traverseHistory', {
context: this._session.sessionId,
delta: -1
}).then(() => true).catch(() => false);
}
async goForward() {
return await this._session.send('browsingContext.traverseHistory', {
context: this._session.sessionId,
delta: +1
}).then(() => true).catch(() => false);
}
async requestGC() {
throw new Error('Method not implemented.');
}
// TODO: consider calling this only when bindings are added.
async _installMainBinding() {
const functionDeclaration = addMainBinding.toString();
const args = [{
type: 'channel',
value: {
channel: kPlaywrightBindingChannel,
ownership: bidi.Script.ResultOwnership.Root
}
}];
const promises = [];
promises.push(this._session.send('script.addPreloadScript', {
functionDeclaration,
arguments: args
}));
promises.push(this._session.send('script.callFunction', {
functionDeclaration,
arguments: args,
target: toBidiExecutionContext(await this._page.mainFrame()._mainContext())._target,
awaitPromise: false,
userActivation: false
}));
await Promise.all(promises);
}
async _onScriptMessage(event) {
if (event.channel !== kPlaywrightBindingChannel) return;
const pageOrError = await this._page.waitForInitializedOrError();
if (pageOrError instanceof Error) return;
const context = this._realmToContext.get(event.source.realm);
if (!context) return;
if (event.data.type !== 'string') return;
await this._page._onBindingCalled(event.data.value, context);
}
async addInitScript(initScript) {
const {
script
} = await this._session.send('script.addPreloadScript', {
// TODO: remove function call from the source.
functionDeclaration: `() => { return ${initScript.source} }`,
// TODO: push to iframes?
contexts: [this._session.sessionId]
});
if (!initScript.internal) this._initScriptIds.push(script);
}
async removeNonInternalInitScripts() {
const promises = this._initScriptIds.map(script => this._session.send('script.removePreloadScript', {
script
}));
this._initScriptIds = [];
await Promise.all(promises);
}
async closePage(runBeforeUnload) {
await this._session.send('browsingContext.close', {
context: this._session.sessionId,
promptUnload: runBeforeUnload
});
}
async setBackgroundColor(color) {}
async takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, scale) {
const rect = documentRect || viewportRect;
const {
data
} = await this._session.send('browsingContext.captureScreenshot', {
context: this._session.sessionId,
format: {
type: `image/${format === 'png' ? 'png' : 'jpeg'}`,
quality: quality ? quality / 100 : 0.8
},
origin: documentRect ? 'document' : 'viewport',
clip: {
type: 'box',
...rect
}
});
return Buffer.from(data, 'base64');
}
async getContentFrame(handle) {
const executionContext = toBidiExecutionContext(handle._context);
const frameId = await executionContext.contentFrameIdForFrame(handle);
if (!frameId) return null;
return this._page._frameManager.frame(frameId);
}
async getOwnerFrame(handle) {
// TODO: switch to utility world?
const windowHandle = await handle.evaluateHandle(node => {
var _node$ownerDocument;
const doc = (_node$ownerDocument = node.ownerDocument) !== null && _node$ownerDocument !== void 0 ? _node$ownerDocument : node;
return doc.defaultView;
});
if (!windowHandle) return null;
const executionContext = toBidiExecutionContext(handle._context);
return executionContext.frameIdForWindowHandle(windowHandle);
}
async getBoundingBox(handle) {
const box = await handle.evaluate(element => {
if (!(element instanceof Element)) return null;
const rect = element.getBoundingClientRect();
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height
};
});
if (!box) return null;
const position = await this._framePosition(handle._frame);
if (!position) return null;
box.x += position.x;
box.y += position.y;
return box;
}
// TODO: move to Frame.
async _framePosition(frame) {
if (frame === this._page.mainFrame()) return {
x: 0,
y: 0
};
const element = await frame.frameElement();
const box = await element.boundingBox();
if (!box) return null;
const style = await element.evaluateInUtility(([injected, iframe]) => injected.describeIFrameStyle(iframe), {}).catch(e => 'error:notconnected');
if (style === 'error:notconnected' || style === 'transformed') return null;
// Content box is offset by border and padding widths.
box.x += style.left;
box.y += style.top;
return box;
}
async scrollRectIntoViewIfNeeded(handle, rect) {
return await handle.evaluateInUtility(([injected, node]) => {
node.scrollIntoView({
block: 'center',
inline: 'center',
behavior: 'instant'
});
}, null).then(() => 'done').catch(e => {
if (e instanceof Error && e.message.includes('Node is detached from document')) return 'error:notconnected';
if (e instanceof Error && e.message.includes('Node does not have a layout object')) return 'error:notvisible';
throw e;
});
}
async setScreencastOptions(options) {}
rafCountForStablePosition() {
return 1;
}
async getContentQuads(handle) {
const quads = await handle.evaluateInUtility(([injected, node]) => {
if (!node.isConnected) return 'error:notconnected';
const rects = node.getClientRects();
if (!rects) return null;
return [...rects].map(rect => [{
x: rect.left,
y: rect.top
}, {
x: rect.right,
y: rect.top
}, {
x: rect.right,
y: rect.bottom
}, {
x: rect.left,
y: rect.bottom
}]);
}, null);
if (!quads || quads === 'error:notconnected') return quads;
// TODO: consider transforming quads to support clicks in iframes.
const position = await this._framePosition(handle._frame);
if (!position) return null;
quads.forEach(quad => quad.forEach(point => {
point.x += position.x;
point.y += position.y;
}));
return quads;
}
async setInputFilePaths(handle, paths) {
const fromContext = toBidiExecutionContext(handle._context);
await this._session.send('input.setFiles', {
context: this._session.sessionId,
element: await fromContext.nodeIdForElementHandle(handle),
files: paths
});
}
async adoptElementHandle(handle, to) {
const fromContext = toBidiExecutionContext(handle._context);
const nodeId = await fromContext.nodeIdForElementHandle(handle);
const executionContext = toBidiExecutionContext(to);
return await executionContext.remoteObjectForNodeId(to, nodeId);
}
async getAccessibilityTree(needle) {
throw new Error('Method not implemented.');
}
async inputActionEpilogue() {}
async resetForReuse() {}
async pdf(options) {
return this._pdf.generate(options);
}
async getFrameElement(frame) {
const parent = frame.parentFrame();
if (!parent) throw new Error('Frame has been detached.');
const parentContext = await parent._mainContext();
const list = await parentContext.evaluateHandle(() => {
return [...document.querySelectorAll('iframe,frame')];
});
const length = await list.evaluate(list => list.length);
let foundElement = null;
for (let i = 0; i < length; i++) {
const element = await list.evaluateHandle((list, i) => list[i], i);
const candidate = await element.contentFrame();
if (frame === candidate) {
foundElement = element;
break;
} else {
element.dispose();
}
}
list.dispose();
if (!foundElement) throw new Error('Frame has been detached.');
return foundElement;
}
shouldToggleStyleSheetToSyncAnimations() {
return true;
}
}
exports.BidiPage = BidiPage;
function addMainBinding(callback) {
globalThis['__playwright__binding__'] = callback;
}
function toBidiExecutionContext(executionContext) {
return executionContext.delegate;
}

140
node_modules/playwright-core/lib/server/bidi/bidiPdf.js generated vendored Normal file
View File

@@ -0,0 +1,140 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BidiPDF = void 0;
var _utils = require("../../utils");
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
const PagePaperFormats = {
letter: {
width: 8.5,
height: 11
},
legal: {
width: 8.5,
height: 14
},
tabloid: {
width: 11,
height: 17
},
ledger: {
width: 17,
height: 11
},
a0: {
width: 33.1,
height: 46.8
},
a1: {
width: 23.4,
height: 33.1
},
a2: {
width: 16.54,
height: 23.4
},
a3: {
width: 11.7,
height: 16.54
},
a4: {
width: 8.27,
height: 11.7
},
a5: {
width: 5.83,
height: 8.27
},
a6: {
width: 4.13,
height: 5.83
}
};
const unitToPixels = {
'px': 1,
'in': 96,
'cm': 37.8,
'mm': 3.78
};
function convertPrintParameterToInches(text) {
if (text === undefined) return undefined;
let unit = text.substring(text.length - 2).toLowerCase();
let valueText = '';
if (unitToPixels.hasOwnProperty(unit)) {
valueText = text.substring(0, text.length - 2);
} else {
// In case of unknown unit try to parse the whole parameter as number of pixels.
// This is consistent with phantom's paperSize behavior.
unit = 'px';
valueText = text;
}
const value = Number(valueText);
(0, _utils.assert)(!isNaN(value), 'Failed to parse parameter value: ' + text);
const pixels = value * unitToPixels[unit];
return pixels / 96;
}
class BidiPDF {
constructor(session) {
this._session = void 0;
this._session = session;
}
async generate(options) {
const {
scale = 1,
printBackground = false,
landscape = false,
pageRanges = '',
margin = {}
} = options;
let paperWidth = 8.5;
let paperHeight = 11;
if (options.format) {
const format = PagePaperFormats[options.format.toLowerCase()];
(0, _utils.assert)(format, 'Unknown paper format: ' + options.format);
paperWidth = format.width;
paperHeight = format.height;
} else {
paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
}
const {
data
} = await this._session.send('browsingContext.print', {
context: this._session.sessionId,
background: printBackground,
margin: {
bottom: convertPrintParameterToInches(margin.bottom) || 0,
left: convertPrintParameterToInches(margin.left) || 0,
right: convertPrintParameterToInches(margin.right) || 0,
top: convertPrintParameterToInches(margin.top) || 0
},
orientation: landscape ? 'landscape' : 'portrait',
page: {
width: paperWidth,
height: paperHeight
},
pageRanges: pageRanges ? pageRanges.split(',').map(r => r.trim()) : undefined,
scale
});
return Buffer.from(data, 'base64');
}
}
exports.BidiPDF = BidiPDF;

View File

@@ -0,0 +1,93 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BidiDeserializer = void 0;
/**
* @license
* Copyright 2024 Google Inc.
* Modifications copyright (c) Microsoft Corporation.
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-disable object-curly-spacing */
/**
* @internal
*/
class BidiDeserializer {
static deserialize(result) {
var _result$value, _result$value2, _result$value3, _result$value4;
if (!result) return undefined;
switch (result.type) {
case 'array':
return (_result$value = result.value) === null || _result$value === void 0 ? void 0 : _result$value.map(value => {
return BidiDeserializer.deserialize(value);
});
case 'set':
return (_result$value2 = result.value) === null || _result$value2 === void 0 ? void 0 : _result$value2.reduce((acc, value) => {
return acc.add(BidiDeserializer.deserialize(value));
}, new Set());
case 'object':
return (_result$value3 = result.value) === null || _result$value3 === void 0 ? void 0 : _result$value3.reduce((acc, tuple) => {
const {
key,
value
} = BidiDeserializer._deserializeTuple(tuple);
acc[key] = value;
return acc;
}, {});
case 'map':
return (_result$value4 = result.value) === null || _result$value4 === void 0 ? void 0 : _result$value4.reduce((acc, tuple) => {
const {
key,
value
} = BidiDeserializer._deserializeTuple(tuple);
return acc.set(key, value);
}, new Map());
case 'promise':
return {};
case 'regexp':
return new RegExp(result.value.pattern, result.value.flags);
case 'date':
return new Date(result.value);
case 'undefined':
return undefined;
case 'null':
return null;
case 'number':
return BidiDeserializer._deserializeNumber(result.value);
case 'bigint':
return BigInt(result.value);
case 'boolean':
return Boolean(result.value);
case 'string':
return result.value;
}
throw new Error(`Deserialization of type ${result.type} not supported.`);
}
static _deserializeNumber(value) {
switch (value) {
case '-0':
return -0;
case 'NaN':
return NaN;
case 'Infinity':
return Infinity;
case '-Infinity':
return -Infinity;
default:
return value;
}
}
static _deserializeTuple([serializedKey, serializedValue]) {
const key = typeof serializedKey === 'string' ? serializedKey : BidiDeserializer.deserialize(serializedKey);
const value = BidiDeserializer.deserialize(serializedValue);
return {
key,
value
};
}
}
exports.BidiDeserializer = BidiDeserializer;

View File

@@ -0,0 +1,240 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getBidiKeyValue = void 0;
/**
* @license
* Copyright 2024 Google Inc.
* Modifications copyright (c) Microsoft Corporation.
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-disable curly */
const getBidiKeyValue = keyName => {
switch (keyName) {
case '\r':
case '\n':
keyName = 'Enter';
break;
}
// Measures the number of code points rather than UTF-16 code units.
if ([...keyName].length === 1) {
return keyName;
}
switch (keyName) {
case 'Cancel':
return '\uE001';
case 'Help':
return '\uE002';
case 'Backspace':
return '\uE003';
case 'Tab':
return '\uE004';
case 'Clear':
return '\uE005';
case 'Enter':
return '\uE007';
case 'Shift':
case 'ShiftLeft':
return '\uE008';
case 'Control':
case 'ControlLeft':
return '\uE009';
case 'Alt':
case 'AltLeft':
return '\uE00A';
case 'Pause':
return '\uE00B';
case 'Escape':
return '\uE00C';
case 'PageUp':
return '\uE00E';
case 'PageDown':
return '\uE00F';
case 'End':
return '\uE010';
case 'Home':
return '\uE011';
case 'ArrowLeft':
return '\uE012';
case 'ArrowUp':
return '\uE013';
case 'ArrowRight':
return '\uE014';
case 'ArrowDown':
return '\uE015';
case 'Insert':
return '\uE016';
case 'Delete':
return '\uE017';
case 'NumpadEqual':
return '\uE019';
case 'Numpad0':
return '\uE01A';
case 'Numpad1':
return '\uE01B';
case 'Numpad2':
return '\uE01C';
case 'Numpad3':
return '\uE01D';
case 'Numpad4':
return '\uE01E';
case 'Numpad5':
return '\uE01F';
case 'Numpad6':
return '\uE020';
case 'Numpad7':
return '\uE021';
case 'Numpad8':
return '\uE022';
case 'Numpad9':
return '\uE023';
case 'NumpadMultiply':
return '\uE024';
case 'NumpadAdd':
return '\uE025';
case 'NumpadSubtract':
return '\uE027';
case 'NumpadDecimal':
return '\uE028';
case 'NumpadDivide':
return '\uE029';
case 'F1':
return '\uE031';
case 'F2':
return '\uE032';
case 'F3':
return '\uE033';
case 'F4':
return '\uE034';
case 'F5':
return '\uE035';
case 'F6':
return '\uE036';
case 'F7':
return '\uE037';
case 'F8':
return '\uE038';
case 'F9':
return '\uE039';
case 'F10':
return '\uE03A';
case 'F11':
return '\uE03B';
case 'F12':
return '\uE03C';
case 'Meta':
case 'MetaLeft':
return '\uE03D';
case 'ShiftRight':
return '\uE050';
case 'ControlRight':
return '\uE051';
case 'AltRight':
return '\uE052';
case 'MetaRight':
return '\uE053';
case 'Space':
return ' ';
case 'Digit0':
return '0';
case 'Digit1':
return '1';
case 'Digit2':
return '2';
case 'Digit3':
return '3';
case 'Digit4':
return '4';
case 'Digit5':
return '5';
case 'Digit6':
return '6';
case 'Digit7':
return '7';
case 'Digit8':
return '8';
case 'Digit9':
return '9';
case 'KeyA':
return 'a';
case 'KeyB':
return 'b';
case 'KeyC':
return 'c';
case 'KeyD':
return 'd';
case 'KeyE':
return 'e';
case 'KeyF':
return 'f';
case 'KeyG':
return 'g';
case 'KeyH':
return 'h';
case 'KeyI':
return 'i';
case 'KeyJ':
return 'j';
case 'KeyK':
return 'k';
case 'KeyL':
return 'l';
case 'KeyM':
return 'm';
case 'KeyN':
return 'n';
case 'KeyO':
return 'o';
case 'KeyP':
return 'p';
case 'KeyQ':
return 'q';
case 'KeyR':
return 'r';
case 'KeyS':
return 's';
case 'KeyT':
return 't';
case 'KeyU':
return 'u';
case 'KeyV':
return 'v';
case 'KeyW':
return 'w';
case 'KeyX':
return 'x';
case 'KeyY':
return 'y';
case 'KeyZ':
return 'z';
case 'Semicolon':
return ';';
case 'Equal':
return '=';
case 'Comma':
return ',';
case 'Minus':
return '-';
case 'Period':
return '.';
case 'Slash':
return '/';
case 'Backquote':
return '`';
case 'BracketLeft':
return '[';
case 'Backslash':
return '\\';
case 'BracketRight':
return ']';
case 'Quote':
return '"';
default:
throw new Error(`Unknown key: "${keyName}"`);
}
};
exports.getBidiKeyValue = getBidiKeyValue;

View File

@@ -0,0 +1,139 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Storage = exports.Session = exports.Script = exports.Network = exports.Log = exports.Input = exports.ErrorCode = exports.BrowsingContext = exports.Browser = void 0;
/**
* @license
* Copyright 2024 Google Inc.
* Modifications copyright (c) Microsoft Corporation.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* THIS FILE IS AUTOGENERATED by cddlconv 0.1.5.
* Run `node tools/generate-bidi-types.mjs` to regenerate.
* @see https://github.com/w3c/webdriver-bidi/blob/master/index.bs
*/
/**
* Must be between `-9007199254740991` and `9007199254740991`, inclusive.
*/
/**
* Must be between `0` and `9007199254740991`, inclusive.
*/
let ErrorCode = exports.ErrorCode = /*#__PURE__*/function (ErrorCode) {
ErrorCode["InvalidArgument"] = "invalid argument";
ErrorCode["InvalidSelector"] = "invalid selector";
ErrorCode["InvalidSessionId"] = "invalid session id";
ErrorCode["MoveTargetOutOfBounds"] = "move target out of bounds";
ErrorCode["NoSuchAlert"] = "no such alert";
ErrorCode["NoSuchElement"] = "no such element";
ErrorCode["NoSuchFrame"] = "no such frame";
ErrorCode["NoSuchHandle"] = "no such handle";
ErrorCode["NoSuchHistoryEntry"] = "no such history entry";
ErrorCode["NoSuchIntercept"] = "no such intercept";
ErrorCode["NoSuchNode"] = "no such node";
ErrorCode["NoSuchRequest"] = "no such request";
ErrorCode["NoSuchScript"] = "no such script";
ErrorCode["NoSuchStoragePartition"] = "no such storage partition";
ErrorCode["NoSuchUserContext"] = "no such user context";
ErrorCode["SessionNotCreated"] = "session not created";
ErrorCode["UnableToCaptureScreen"] = "unable to capture screen";
ErrorCode["UnableToCloseBrowser"] = "unable to close browser";
ErrorCode["UnableToSetCookie"] = "unable to set cookie";
ErrorCode["UnableToSetFileInput"] = "unable to set file input";
ErrorCode["UnderspecifiedStoragePartition"] = "underspecified storage partition";
ErrorCode["UnknownCommand"] = "unknown command";
ErrorCode["UnknownError"] = "unknown error";
ErrorCode["UnsupportedOperation"] = "unsupported operation";
return ErrorCode;
}({});
let Session = exports.Session = void 0;
(function (_Session10) {
let UserPromptHandlerType = /*#__PURE__*/function (UserPromptHandlerType) {
UserPromptHandlerType["Accept"] = "accept";
UserPromptHandlerType["Dismiss"] = "dismiss";
UserPromptHandlerType["Ignore"] = "ignore";
return UserPromptHandlerType;
}({});
_Session10.UserPromptHandlerType = UserPromptHandlerType;
})(Session || (exports.Session = Session = {}));
let Browser = exports.Browser = void 0;
let BrowsingContext = exports.BrowsingContext = void 0;
(function (_BrowsingContext10) {
let ReadinessState = /*#__PURE__*/function (ReadinessState) {
ReadinessState["None"] = "none";
ReadinessState["Interactive"] = "interactive";
ReadinessState["Complete"] = "complete";
return ReadinessState;
}({});
_BrowsingContext10.ReadinessState = ReadinessState;
})(BrowsingContext || (exports.BrowsingContext = BrowsingContext = {}));
(function (_BrowsingContext11) {
let UserPromptType = /*#__PURE__*/function (UserPromptType) {
UserPromptType["Alert"] = "alert";
UserPromptType["Beforeunload"] = "beforeunload";
UserPromptType["Confirm"] = "confirm";
UserPromptType["Prompt"] = "prompt";
return UserPromptType;
}({});
_BrowsingContext11.UserPromptType = UserPromptType;
})(BrowsingContext || (exports.BrowsingContext = BrowsingContext = {}));
(function (_BrowsingContext24) {
let CreateType = /*#__PURE__*/function (CreateType) {
CreateType["Tab"] = "tab";
CreateType["Window"] = "window";
return CreateType;
}({});
_BrowsingContext24.CreateType = CreateType;
})(BrowsingContext || (exports.BrowsingContext = BrowsingContext = {}));
let Network = exports.Network = void 0;
(function (_Network6) {
let SameSite = /*#__PURE__*/function (SameSite) {
SameSite["Strict"] = "strict";
SameSite["Lax"] = "lax";
SameSite["None"] = "none";
return SameSite;
}({});
_Network6.SameSite = SameSite;
})(Network || (exports.Network = Network = {}));
(function (_Network23) {
let InterceptPhase = /*#__PURE__*/function (InterceptPhase) {
InterceptPhase["BeforeRequestSent"] = "beforeRequestSent";
InterceptPhase["ResponseStarted"] = "responseStarted";
InterceptPhase["AuthRequired"] = "authRequired";
return InterceptPhase;
}({});
_Network23.InterceptPhase = InterceptPhase;
})(Network || (exports.Network = Network = {}));
let Script = exports.Script = void 0;
(function (_Script68) {
let ResultOwnership = /*#__PURE__*/function (ResultOwnership) {
ResultOwnership["Root"] = "root";
ResultOwnership["None"] = "none";
return ResultOwnership;
}({});
_Script68.ResultOwnership = ResultOwnership;
})(Script || (exports.Script = Script = {}));
let Storage = exports.Storage = void 0;
let Log = exports.Log = void 0;
(function (_Log7) {
let Level = /*#__PURE__*/function (Level) {
Level["Debug"] = "debug";
Level["Info"] = "info";
Level["Warn"] = "warn";
Level["Error"] = "error";
return Level;
}({});
_Log7.Level = Level;
})(Log || (exports.Log = Log = {}));
let Input = exports.Input = void 0;
(function (_Input9) {
let PointerType = /*#__PURE__*/function (PointerType) {
PointerType["Mouse"] = "mouse";
PointerType["Pen"] = "pen";
PointerType["Touch"] = "touch";
return PointerType;
}({});
_Input9.PointerType = PointerType;
})(Input || (exports.Input = Input = {}));

View File

@@ -0,0 +1,144 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isRegExp = exports.isPlainObject = exports.isDate = exports.BidiSerializer = void 0;
/**
* @license
* Copyright 2024 Google Inc.
* Modifications copyright (c) Microsoft Corporation.
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-disable curly, indent */
/**
* @internal
*/
class UnserializableError extends Error {}
/**
* @internal
*/
class BidiSerializer {
static serialize(arg) {
switch (typeof arg) {
case 'symbol':
case 'function':
throw new UnserializableError(`Unable to serializable ${typeof arg}`);
case 'object':
return BidiSerializer._serializeObject(arg);
case 'undefined':
return {
type: 'undefined'
};
case 'number':
return BidiSerializer._serializeNumber(arg);
case 'bigint':
return {
type: 'bigint',
value: arg.toString()
};
case 'string':
return {
type: 'string',
value: arg
};
case 'boolean':
return {
type: 'boolean',
value: arg
};
}
}
static _serializeNumber(arg) {
let value;
if (Object.is(arg, -0)) {
value = '-0';
} else if (Object.is(arg, Infinity)) {
value = 'Infinity';
} else if (Object.is(arg, -Infinity)) {
value = '-Infinity';
} else if (Object.is(arg, NaN)) {
value = 'NaN';
} else {
value = arg;
}
return {
type: 'number',
value
};
}
static _serializeObject(arg) {
if (arg === null) {
return {
type: 'null'
};
} else if (Array.isArray(arg)) {
const parsedArray = arg.map(subArg => {
return BidiSerializer.serialize(subArg);
});
return {
type: 'array',
value: parsedArray
};
} else if (isPlainObject(arg)) {
try {
JSON.stringify(arg);
} catch (error) {
if (error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON')) {
error.message += ' Recursive objects are not allowed.';
}
throw error;
}
const parsedObject = [];
for (const key in arg) {
parsedObject.push([BidiSerializer.serialize(key), BidiSerializer.serialize(arg[key])]);
}
return {
type: 'object',
value: parsedObject
};
} else if (isRegExp(arg)) {
return {
type: 'regexp',
value: {
pattern: arg.source,
flags: arg.flags
}
};
} else if (isDate(arg)) {
return {
type: 'date',
value: arg.toISOString()
};
}
throw new UnserializableError('Custom object serialization not possible. Use plain objects instead.');
}
}
/**
* @internal
*/
exports.BidiSerializer = BidiSerializer;
const isPlainObject = obj => {
return typeof obj === 'object' && (obj === null || obj === void 0 ? void 0 : obj.constructor) === Object;
};
/**
* @internal
*/
exports.isPlainObject = isPlainObject;
const isRegExp = obj => {
return typeof obj === 'object' && (obj === null || obj === void 0 ? void 0 : obj.constructor) === RegExp;
};
/**
* @internal
*/
exports.isRegExp = isRegExp;
const isDate = obj => {
return typeof obj === 'object' && (obj === null || obj === void 0 ? void 0 : obj.constructor) === Date;
};
exports.isDate = isDate;

View File

@@ -0,0 +1,237 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createProfile = createProfile;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* @license
* Copyright 2023 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/* eslint-disable curly, indent */
async function createProfile(options) {
if (!_fs.default.existsSync(options.path)) {
await _fs.default.promises.mkdir(options.path, {
recursive: true
});
}
await writePreferences({
preferences: {
...defaultProfilePreferences(options.preferences),
...options.preferences
},
path: options.path
});
}
function defaultProfilePreferences(extraPrefs) {
const server = 'dummy.test';
const defaultPrefs = {
// Make sure Shield doesn't hit the network.
'app.normandy.api_url': '',
// Disable Firefox old build background check
'app.update.checkInstallTime': false,
// Disable automatically upgrading Firefox
'app.update.disabledForTesting': true,
// Increase the APZ content response timeout to 1 minute
'apz.content_response_timeout': 60000,
// Prevent various error message on the console
// jest-puppeteer asserts that no error message is emitted by the console
'browser.contentblocking.features.standard': '-tp,tpPrivate,cookieBehavior0,-cm,-fp',
// Enable the dump function: which sends messages to the system
// console
// https://bugzilla.mozilla.org/show_bug.cgi?id=1543115
'browser.dom.window.dump.enabled': true,
// Make sure newtab weather doesn't hit the network to retrieve weather data.
'browser.newtabpage.activity-stream.discoverystream.region-weather-config': '',
// Make sure newtab wallpapers don't hit the network to retrieve wallpaper data.
'browser.newtabpage.activity-stream.newtabWallpapers.enabled': false,
'browser.newtabpage.activity-stream.newtabWallpapers.v2.enabled': false,
// Make sure Topsites doesn't hit the network to retrieve sponsored tiles.
'browser.newtabpage.activity-stream.showSponsoredTopSites': false,
// Disable topstories
'browser.newtabpage.activity-stream.feeds.system.topstories': false,
// Always display a blank page
'browser.newtabpage.enabled': false,
// Background thumbnails in particular cause grief: and disabling
// thumbnails in general cannot hurt
'browser.pagethumbnails.capturing_disabled': true,
// Disable safebrowsing components.
'browser.safebrowsing.blockedURIs.enabled': false,
'browser.safebrowsing.downloads.enabled': false,
'browser.safebrowsing.malware.enabled': false,
'browser.safebrowsing.phishing.enabled': false,
// Disable updates to search engines.
'browser.search.update': false,
// Do not restore the last open set of tabs if the browser has crashed
'browser.sessionstore.resume_from_crash': false,
// Skip check for default browser on startup
'browser.shell.checkDefaultBrowser': false,
// Disable newtabpage
'browser.startup.homepage': 'about:blank',
// Do not redirect user when a milstone upgrade of Firefox is detected
'browser.startup.homepage_override.mstone': 'ignore',
// Start with a blank page about:blank
'browser.startup.page': 0,
// Do not allow background tabs to be zombified on Android: otherwise for
// tests that open additional tabs: the test harness tab itself might get
// unloaded
'browser.tabs.disableBackgroundZombification': false,
// Do not warn when closing all other open tabs
'browser.tabs.warnOnCloseOtherTabs': false,
// Do not warn when multiple tabs will be opened
'browser.tabs.warnOnOpen': false,
// Do not automatically offer translations, as tests do not expect this.
'browser.translations.automaticallyPopup': false,
// Disable the UI tour.
'browser.uitour.enabled': false,
// Turn off search suggestions in the location bar so as not to trigger
// network connections.
'browser.urlbar.suggest.searches': false,
// Disable first run splash page on Windows 10
'browser.usedOnWindows10.introURL': '',
// Do not warn on quitting Firefox
'browser.warnOnQuit': false,
// Defensively disable data reporting systems
'datareporting.healthreport.documentServerURI': `http://${server}/dummy/healthreport/`,
'datareporting.healthreport.logging.consoleEnabled': false,
'datareporting.healthreport.service.enabled': false,
'datareporting.healthreport.service.firstRun': false,
'datareporting.healthreport.uploadEnabled': false,
// Do not show datareporting policy notifications which can interfere with tests
'datareporting.policy.dataSubmissionEnabled': false,
'datareporting.policy.dataSubmissionPolicyBypassNotification': true,
// DevTools JSONViewer sometimes fails to load dependencies with its require.js.
// This doesn't affect Puppeteer but spams console (Bug 1424372)
'devtools.jsonview.enabled': false,
// Disable popup-blocker
'dom.disable_open_during_load': false,
// Enable the support for File object creation in the content process
// Required for |Page.setFileInputFiles| protocol method.
'dom.file.createInChild': true,
// Disable the ProcessHangMonitor
'dom.ipc.reportProcessHangs': false,
// Disable slow script dialogues
'dom.max_chrome_script_run_time': 0,
'dom.max_script_run_time': 0,
// Disable background timer throttling to allow tests to run in parallel
// without a decrease in performance.
'dom.min_background_timeout_value': 0,
'dom.min_background_timeout_value_without_budget_throttling': 0,
'dom.timeout.enable_budget_timer_throttling': false,
// Disable HTTPS-First upgrades
'dom.security.https_first': false,
// Only load extensions from the application and user profile
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
'extensions.autoDisableScopes': 0,
'extensions.enabledScopes': 5,
// Disable metadata caching for installed add-ons by default
'extensions.getAddons.cache.enabled': false,
// Disable installing any distribution extensions or add-ons.
'extensions.installDistroAddons': false,
// Disabled screenshots extension
'extensions.screenshots.disabled': true,
// Turn off extension updates so they do not bother tests
'extensions.update.enabled': false,
// Turn off extension updates so they do not bother tests
'extensions.update.notifyUser': false,
// Make sure opening about:addons will not hit the network
'extensions.webservice.discoverURL': `http://${server}/dummy/discoveryURL`,
// Allow the application to have focus even it runs in the background
'focusmanager.testmode': true,
// Disable useragent updates
'general.useragent.updates.enabled': false,
// Always use network provider for geolocation tests so we bypass the
// macOS dialog raised by the corelocation provider
'geo.provider.testing': true,
// Do not scan Wifi
'geo.wifi.scan': false,
// No hang monitor
'hangmonitor.timeout': 0,
// Show chrome errors and warnings in the error console
'javascript.options.showInConsole': true,
// Do not throttle rendering (requestAnimationFrame) in background tabs
'layout.testing.top-level-always-active': true,
// Disable download and usage of OpenH264: and Widevine plugins
'media.gmp-manager.updateEnabled': false,
// Disable the GFX sanity window
'media.sanity-test.disabled': true,
// Disable connectivity service pings
'network.connectivity-service.enabled': false,
// Disable experimental feature that is only available in Nightly
'network.cookie.sameSite.laxByDefault': false,
// Do not prompt for temporary redirects
'network.http.prompt-temp-redirect': false,
// Disable speculative connections so they are not reported as leaking
// when they are hanging around
'network.http.speculative-parallel-limit': 0,
// Do not automatically switch between offline and online
'network.manage-offline-status': false,
// Make sure SNTP requests do not hit the network
'network.sntp.pools': server,
// Disable Flash.
'plugin.state.flash': 0,
'privacy.trackingprotection.enabled': false,
// Can be removed once Firefox 89 is no longer supported
// https://bugzilla.mozilla.org/show_bug.cgi?id=1710839
'remote.enabled': true,
// Don't do network connections for mitm priming
'security.certerrors.mitm.priming.enabled': false,
// Local documents have access to all other local documents,
// including directory listings
'security.fileuri.strict_origin_policy': false,
// Do not wait for the notification button security delay
'security.notification_enable_delay': 0,
// Do not automatically fill sign-in forms with known usernames and
// passwords
'signon.autofillForms': false,
// Disable password capture, so that tests that include forms are not
// influenced by the presence of the persistent doorhanger notification
'signon.rememberSignons': false,
// Disable first-run welcome page
'startup.homepage_welcome_url': 'about:blank',
// Disable first-run welcome page
'startup.homepage_welcome_url.additional': '',
// Disable browser animations (tabs, fullscreen, sliding alerts)
'toolkit.cosmeticAnimations.enabled': false,
// Prevent starting into safe mode after application crashes
'toolkit.startup.max_resumed_crashes': -1
};
return Object.assign(defaultPrefs, extraPrefs);
}
/**
* Populates the user.js file with custom preferences as needed to allow
* Firefox's CDP support to properly function. These preferences will be
* automatically copied over to prefs.js during startup of Firefox. To be
* able to restore the original values of preferences a backup of prefs.js
* will be created.
*
* @param prefs - List of preferences to add.
* @param profilePath - Firefox profile to write the preferences to.
*/
async function writePreferences(options) {
const prefsPath = _path.default.join(options.path, 'prefs.js');
const lines = Object.entries(options.preferences).map(([key, value]) => {
return `user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`;
});
// Use allSettled to prevent corruption
const result = await Promise.allSettled([_fs.default.promises.writeFile(_path.default.join(options.path, 'user.js'), lines.join('\n')),
// Create a backup of the preferences file if it already exitsts.
_fs.default.promises.access(prefsPath, _fs.default.constants.F_OK).then(async () => {
await _fs.default.promises.copyFile(prefsPath, _path.default.join(options.path, 'prefs.js.playwright'));
},
// Swallow only if file does not exist
() => {})]);
for (const command of result) {
if (command.status === 'rejected') {
throw command.reason;
}
}
}