brave-real-playwright-core
Version:
Brave-optimized Playwright Core (v1.55.0) with comprehensive stealth patches and error stack sanitization
429 lines (428 loc) • 18.7 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var ffBrowser_exports = {};
__export(ffBrowser_exports, {
FFBrowser: () => FFBrowser,
FFBrowserContext: () => FFBrowserContext
});
module.exports = __toCommonJS(ffBrowser_exports);
var import_utils = require("../../utils");
var import_browser = require("../browser");
var import_browserContext = require("../browserContext");
var import_errors = require("../errors");
var network = __toESM(require("../network"));
var import_ffConnection = require("./ffConnection");
var import_ffPage = require("./ffPage");
var import_page = require("../page");
class FFBrowser extends import_browser.Browser {
constructor(parent, connection, options) {
super(parent, options);
this._version = "";
this._userAgent = "";
this._connection = connection;
this.session = connection.rootSession;
this._ffPages = /* @__PURE__ */ new Map();
this._contexts = /* @__PURE__ */ new Map();
this._connection.on(import_ffConnection.ConnectionEvents.Disconnected, () => this._onDisconnect());
this.session.on("Browser.attachedToTarget", this._onAttachedToTarget.bind(this));
this.session.on("Browser.detachedFromTarget", this._onDetachedFromTarget.bind(this));
this.session.on("Browser.downloadCreated", this._onDownloadCreated.bind(this));
this.session.on("Browser.downloadFinished", this._onDownloadFinished.bind(this));
this.session.on("Browser.videoRecordingFinished", this._onVideoRecordingFinished.bind(this));
}
static async connect(parent, transport, options) {
const connection = new import_ffConnection.FFConnection(transport, options.protocolLogger, options.browserLogsCollector);
const browser = new FFBrowser(parent, connection, options);
if (options.__testHookOnConnectToBrowser)
await options.__testHookOnConnectToBrowser();
let firefoxUserPrefs = options.originalLaunchOptions.firefoxUserPrefs ?? {};
if (Object.keys(kBandaidFirefoxUserPrefs).length)
firefoxUserPrefs = { ...kBandaidFirefoxUserPrefs, ...firefoxUserPrefs };
const promises = [
browser.session.send("Browser.enable", {
attachToDefaultContext: !!options.persistent,
userPrefs: Object.entries(firefoxUserPrefs).map(([name, value]) => ({ name, value }))
}),
browser._initVersion()
];
if (options.persistent) {
browser._defaultContext = new FFBrowserContext(browser, void 0, options.persistent);
promises.push(browser._defaultContext._initialize());
}
const proxy = options.originalLaunchOptions.proxyOverride || options.proxy;
if (proxy)
promises.push(browser.session.send("Browser.setBrowserProxy", toJugglerProxyOptions(proxy)));
await Promise.all(promises);
return browser;
}
async _initVersion() {
const result = await this.session.send("Browser.getInfo");
this._version = result.version.substring(result.version.indexOf("/") + 1);
this._userAgent = result.userAgent;
}
isConnected() {
return !this._connection._closed;
}
async doCreateNewContext(options) {
if (options.isMobile)
throw new Error("options.isMobile is not supported in Firefox");
const { browserContextId } = await this.session.send("Browser.createBrowserContext", { removeOnDetach: true });
const context = new FFBrowserContext(this, browserContextId, options);
await context._initialize();
this._contexts.set(browserContextId, context);
return context;
}
contexts() {
return Array.from(this._contexts.values());
}
version() {
return this._version;
}
userAgent() {
return this._userAgent;
}
_onDetachedFromTarget(payload) {
const ffPage = this._ffPages.get(payload.targetId);
this._ffPages.delete(payload.targetId);
ffPage.didClose();
}
_onAttachedToTarget(payload) {
const { targetId, browserContextId, openerId, type } = payload.targetInfo;
(0, import_utils.assert)(type === "page");
const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext;
(0, import_utils.assert)(context, `Unknown context id:${browserContextId}, _defaultContext: ${this._defaultContext}`);
const session = this._connection.createSession(payload.sessionId);
const opener = openerId ? this._ffPages.get(openerId) : null;
const ffPage = new import_ffPage.FFPage(session, context, opener);
this._ffPages.set(targetId, ffPage);
}
_onDownloadCreated(payload) {
const ffPage = this._ffPages.get(payload.pageTargetId);
if (!ffPage)
return;
ffPage._page.frameManager.frameAbortedNavigation(payload.frameId, "Download is starting");
let originPage = ffPage._page.initializedOrUndefined();
if (!originPage) {
ffPage._markAsError(new Error("Starting new page download"));
if (ffPage._opener)
originPage = ffPage._opener._page.initializedOrUndefined();
}
if (!originPage)
return;
this._downloadCreated(originPage, payload.uuid, payload.url, payload.suggestedFileName);
}
_onDownloadFinished(payload) {
const error = payload.canceled ? "canceled" : payload.error;
this._downloadFinished(payload.uuid, error);
}
_onVideoRecordingFinished(payload) {
this._takeVideo(payload.screencastId)?.reportFinished();
}
_onDisconnect() {
for (const video of this._idToVideo.values())
video.artifact.reportFinished(new import_errors.TargetClosedError());
this._idToVideo.clear();
for (const ffPage of this._ffPages.values())
ffPage.didClose();
this._ffPages.clear();
this._didClose();
}
}
class FFBrowserContext extends import_browserContext.BrowserContext {
constructor(browser, browserContextId, options) {
super(browser, options, browserContextId);
}
async _initialize() {
(0, import_utils.assert)(!this._ffPages().length);
const browserContextId = this._browserContextId;
const promises = [
super._initialize(),
this._updateInitScripts()
];
if (this._options.acceptDownloads !== "internal-browser-default") {
promises.push(this._browser.session.send("Browser.setDownloadOptions", {
browserContextId,
downloadOptions: {
behavior: this._options.acceptDownloads === "accept" ? "saveToDisk" : "cancel",
downloadsDir: this._browser.options.downloadsPath
}
}));
}
promises.push(this.doUpdateDefaultViewport());
if (this._options.hasTouch)
promises.push(this._browser.session.send("Browser.setTouchOverride", { browserContextId, hasTouch: true }));
if (this._options.userAgent)
promises.push(this._browser.session.send("Browser.setUserAgentOverride", { browserContextId, userAgent: this._options.userAgent }));
if (this._options.bypassCSP)
promises.push(this._browser.session.send("Browser.setBypassCSP", { browserContextId, bypassCSP: true }));
if (this._options.ignoreHTTPSErrors || this._options.internalIgnoreHTTPSErrors)
promises.push(this._browser.session.send("Browser.setIgnoreHTTPSErrors", { browserContextId, ignoreHTTPSErrors: true }));
if (this._options.javaScriptEnabled === false)
promises.push(this._browser.session.send("Browser.setJavaScriptDisabled", { browserContextId, javaScriptDisabled: true }));
if (this._options.locale)
promises.push(this._browser.session.send("Browser.setLocaleOverride", { browserContextId, locale: this._options.locale }));
if (this._options.timezoneId)
promises.push(this._browser.session.send("Browser.setTimezoneOverride", { browserContextId, timezoneId: this._options.timezoneId }));
if (this._options.extraHTTPHeaders || this._options.locale)
promises.push(this.doUpdateExtraHTTPHeaders());
if (this._options.httpCredentials)
promises.push(this.setHTTPCredentials(this._options.httpCredentials));
if (this._options.geolocation)
promises.push(this.setGeolocation(this._options.geolocation));
if (this._options.offline)
promises.push(this.doUpdateOffline());
promises.push(this.doUpdateDefaultEmulatedMedia());
if (this._options.recordVideo) {
promises.push(this._ensureVideosPath().then(() => {
return this._browser.session.send("Browser.setVideoRecordingOptions", {
// validateBrowserContextOptions ensures correct video size.
options: {
...this._options.recordVideo.size,
dir: this._options.recordVideo.dir
},
browserContextId: this._browserContextId
});
}));
}
const proxy = this._options.proxyOverride || this._options.proxy;
if (proxy) {
promises.push(this._browser.session.send("Browser.setContextProxy", {
browserContextId: this._browserContextId,
...toJugglerProxyOptions(proxy)
}));
}
await Promise.all(promises);
}
_ffPages() {
return Array.from(this._browser._ffPages.values()).filter((ffPage) => ffPage._browserContext === this);
}
possiblyUninitializedPages() {
return this._ffPages().map((ffPage) => ffPage._page);
}
async doCreateNewPage() {
const { targetId } = await this._browser.session.send("Browser.newPage", {
browserContextId: this._browserContextId
}).catch((e) => {
if (e.message.includes("Failed to override timezone"))
throw new Error(`Invalid timezone ID: ${this._options.timezoneId}`);
throw e;
});
return this._browser._ffPages.get(targetId)._page;
}
async doGetCookies(urls) {
const { cookies } = await this._browser.session.send("Browser.getCookies", { browserContextId: this._browserContextId });
return network.filterCookies(cookies.map((c) => {
const { name, value, domain, path, expires, httpOnly, secure, sameSite } = c;
return {
name,
value,
domain,
path,
expires,
httpOnly,
secure,
sameSite
};
}), urls);
}
async addCookies(cookies) {
const cc = network.rewriteCookies(cookies).map((c) => {
const { name, value, url, domain, path, expires, httpOnly, secure, sameSite } = c;
return {
name,
value,
url,
domain,
path,
expires: expires === -1 ? void 0 : expires,
httpOnly,
secure,
sameSite
};
});
await this._browser.session.send("Browser.setCookies", { browserContextId: this._browserContextId, cookies: cc });
}
async doClearCookies() {
await this._browser.session.send("Browser.clearCookies", { browserContextId: this._browserContextId });
}
async doGrantPermissions(origin, permissions) {
const webPermissionToProtocol = /* @__PURE__ */ new Map([
["geolocation", "geo"],
["persistent-storage", "persistent-storage"],
["push", "push"],
["notifications", "desktop-notification"]
]);
const filtered = permissions.map((permission) => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission)
throw new Error("Unknown permission: " + permission);
return protocolPermission;
});
await this._browser.session.send("Browser.grantPermissions", { origin, browserContextId: this._browserContextId, permissions: filtered });
}
async doClearPermissions() {
await this._browser.session.send("Browser.resetPermissions", { browserContextId: this._browserContextId });
}
async setGeolocation(geolocation) {
(0, import_browserContext.verifyGeolocation)(geolocation);
this._options.geolocation = geolocation;
await this._browser.session.send("Browser.setGeolocationOverride", { browserContextId: this._browserContextId, geolocation: geolocation || null });
}
async doUpdateExtraHTTPHeaders() {
let allHeaders = this._options.extraHTTPHeaders || [];
if (this._options.locale)
allHeaders = network.mergeHeaders([allHeaders, network.singleHeader("Accept-Language", this._options.locale)]);
await this._browser.session.send("Browser.setExtraHTTPHeaders", { browserContextId: this._browserContextId, headers: allHeaders });
}
async setUserAgent(userAgent) {
await this._browser.session.send("Browser.setUserAgentOverride", { browserContextId: this._browserContextId, userAgent: userAgent || null });
}
async doUpdateOffline() {
await this._browser.session.send("Browser.setOnlineOverride", { browserContextId: this._browserContextId, override: this._options.offline ? "offline" : "online" });
}
async doSetHTTPCredentials(httpCredentials) {
this._options.httpCredentials = httpCredentials;
let credentials = null;
if (httpCredentials) {
const { username, password, origin } = httpCredentials;
credentials = { username, password, origin };
}
await this._browser.session.send("Browser.setHTTPCredentials", { browserContextId: this._browserContextId, credentials });
}
async doAddInitScript(initScript) {
await this._updateInitScripts();
}
async doRemoveInitScripts(initScripts) {
await this._updateInitScripts();
}
async _updateInitScripts() {
const bindingScripts = [...this._pageBindings.values()].map((binding) => binding.initScript.source);
if (this.bindingsInitScript)
bindingScripts.unshift(this.bindingsInitScript.source);
const initScripts = this.initScripts.map((script) => script.source);
await this._browser.session.send("Browser.setInitScripts", { browserContextId: this._browserContextId, scripts: [...bindingScripts, ...initScripts].map((script) => ({ script })) });
}
async doUpdateRequestInterception() {
await Promise.all([
this._browser.session.send("Browser.setRequestInterception", { browserContextId: this._browserContextId, enabled: this.requestInterceptors.length > 0 }),
this._browser.session.send("Browser.setCacheDisabled", { browserContextId: this._browserContextId, cacheDisabled: this.requestInterceptors.length > 0 })
]);
}
async doUpdateDefaultViewport() {
if (!this._options.viewport)
return;
const viewport = {
viewportSize: { width: this._options.viewport.width, height: this._options.viewport.height },
deviceScaleFactor: this._options.deviceScaleFactor || 1
};
await this._browser.session.send("Browser.setDefaultViewport", { browserContextId: this._browserContextId, viewport });
}
async doUpdateDefaultEmulatedMedia() {
if (this._options.colorScheme !== "no-override") {
await this._browser.session.send("Browser.setColorScheme", {
browserContextId: this._browserContextId,
colorScheme: this._options.colorScheme !== void 0 ? this._options.colorScheme : "light"
});
}
if (this._options.reducedMotion !== "no-override") {
await this._browser.session.send("Browser.setReducedMotion", {
browserContextId: this._browserContextId,
reducedMotion: this._options.reducedMotion !== void 0 ? this._options.reducedMotion : "no-preference"
});
}
if (this._options.forcedColors !== "no-override") {
await this._browser.session.send("Browser.setForcedColors", {
browserContextId: this._browserContextId,
forcedColors: this._options.forcedColors !== void 0 ? this._options.forcedColors : "none"
});
}
if (this._options.contrast !== "no-override") {
await this._browser.session.send("Browser.setContrast", {
browserContextId: this._browserContextId,
contrast: this._options.contrast !== void 0 ? this._options.contrast : "no-preference"
});
}
}
async doExposePlaywrightBinding() {
this._browser.session.send("Browser.addBinding", { browserContextId: this._browserContextId, name: import_page.PageBinding.kBindingName, script: "" });
}
onClosePersistent() {
}
async clearCache() {
await this._browser.session.send("Browser.clearCache");
}
async doClose(reason) {
if (!this._browserContextId) {
if (this._options.recordVideo) {
await this._browser.session.send("Browser.setVideoRecordingOptions", {
options: void 0,
browserContextId: this._browserContextId
});
}
await this._browser.close({ reason });
} else {
await this._browser.session.send("Browser.removeBrowserContext", { browserContextId: this._browserContextId });
this._browser._contexts.delete(this._browserContextId);
}
}
async cancelDownload(uuid) {
await this._browser.session.send("Browser.cancelDownload", { uuid });
}
}
function toJugglerProxyOptions(proxy) {
const proxyServer = new URL(proxy.server);
let port = parseInt(proxyServer.port, 10);
let type = "http";
if (proxyServer.protocol === "socks5:")
type = "socks";
else if (proxyServer.protocol === "socks4:")
type = "socks4";
else if (proxyServer.protocol === "https:")
type = "https";
if (proxyServer.port === "") {
if (proxyServer.protocol === "http:")
port = 80;
else if (proxyServer.protocol === "https:")
port = 443;
}
return {
type,
bypass: proxy.bypass ? proxy.bypass.split(",").map((domain) => domain.trim()) : [],
host: proxyServer.hostname,
port,
username: proxy.username,
password: proxy.password
};
}
const kBandaidFirefoxUserPrefs = {};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FFBrowser,
FFBrowserContext
});