browserContext.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. "use strict";
  2. var __create = Object.create;
  3. var __defProp = Object.defineProperty;
  4. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  5. var __getOwnPropNames = Object.getOwnPropertyNames;
  6. var __getProtoOf = Object.getPrototypeOf;
  7. var __hasOwnProp = Object.prototype.hasOwnProperty;
  8. var __export = (target, all) => {
  9. for (var name in all)
  10. __defProp(target, name, { get: all[name], enumerable: true });
  11. };
  12. var __copyProps = (to, from, except, desc) => {
  13. if (from && typeof from === "object" || typeof from === "function") {
  14. for (let key of __getOwnPropNames(from))
  15. if (!__hasOwnProp.call(to, key) && key !== except)
  16. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  17. }
  18. return to;
  19. };
  20. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  21. // If the importer is in node compatibility mode or this is not an ESM
  22. // file that has been converted to a CommonJS file using a Babel-
  23. // compatible transform (i.e. "__esModule" has not been set), then set
  24. // "default" to the CommonJS "module.exports" for node compatibility.
  25. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  26. mod
  27. ));
  28. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  29. var browserContext_exports = {};
  30. __export(browserContext_exports, {
  31. BrowserContext: () => BrowserContext,
  32. prepareBrowserContextParams: () => prepareBrowserContextParams,
  33. toClientCertificatesProtocol: () => toClientCertificatesProtocol
  34. });
  35. module.exports = __toCommonJS(browserContext_exports);
  36. var import_artifact = require("./artifact");
  37. var import_cdpSession = require("./cdpSession");
  38. var import_channelOwner = require("./channelOwner");
  39. var import_clientHelper = require("./clientHelper");
  40. var import_clock = require("./clock");
  41. var import_consoleMessage = require("./consoleMessage");
  42. var import_dialog = require("./dialog");
  43. var import_errors = require("./errors");
  44. var import_events = require("./events");
  45. var import_fetch = require("./fetch");
  46. var import_frame = require("./frame");
  47. var import_harRouter = require("./harRouter");
  48. var network = __toESM(require("./network"));
  49. var import_page = require("./page");
  50. var import_tracing = require("./tracing");
  51. var import_waiter = require("./waiter");
  52. var import_webError = require("./webError");
  53. var import_worker = require("./worker");
  54. var import_timeoutSettings = require("./timeoutSettings");
  55. var import_fileUtils = require("./fileUtils");
  56. var import_headers = require("../utils/isomorphic/headers");
  57. var import_urlMatch = require("../utils/isomorphic/urlMatch");
  58. var import_rtti = require("../utils/isomorphic/rtti");
  59. var import_stackTrace = require("../utils/isomorphic/stackTrace");
  60. class BrowserContext extends import_channelOwner.ChannelOwner {
  61. constructor(parent, type, guid, initializer) {
  62. super(parent, type, guid, initializer);
  63. this._pages = /* @__PURE__ */ new Set();
  64. this._routes = [];
  65. this._webSocketRoutes = [];
  66. // Browser is null for browser contexts created outside of normal browser, e.g. android or electron.
  67. this._browser = null;
  68. this._bindings = /* @__PURE__ */ new Map();
  69. this._forReuse = false;
  70. this._serviceWorkers = /* @__PURE__ */ new Set();
  71. this._harRecorders = /* @__PURE__ */ new Map();
  72. this._closingStatus = "none";
  73. this._harRouters = [];
  74. this._options = initializer.options;
  75. this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
  76. this.tracing = import_tracing.Tracing.from(initializer.tracing);
  77. this.request = import_fetch.APIRequestContext.from(initializer.requestContext);
  78. this.request._timeoutSettings = this._timeoutSettings;
  79. this.request._checkUrlAllowed = (url) => this._checkUrlAllowed(url);
  80. this.clock = new import_clock.Clock(this);
  81. this._channel.on("bindingCall", ({ binding }) => this._onBinding(import_page.BindingCall.from(binding)));
  82. this._channel.on("close", () => this._onClose());
  83. this._channel.on("page", ({ page }) => this._onPage(import_page.Page.from(page)));
  84. this._channel.on("route", ({ route }) => this._onRoute(network.Route.from(route)));
  85. this._channel.on("webSocketRoute", ({ webSocketRoute }) => this._onWebSocketRoute(network.WebSocketRoute.from(webSocketRoute)));
  86. this._channel.on("serviceWorker", ({ worker }) => {
  87. const serviceWorker = import_worker.Worker.from(worker);
  88. serviceWorker._context = this;
  89. this._serviceWorkers.add(serviceWorker);
  90. this.emit(import_events.Events.BrowserContext.ServiceWorker, serviceWorker);
  91. });
  92. this._channel.on("console", (event) => {
  93. const worker = import_worker.Worker.fromNullable(event.worker);
  94. const page = import_page.Page.fromNullable(event.page);
  95. const consoleMessage = new import_consoleMessage.ConsoleMessage(this._platform, event, page, worker);
  96. worker?.emit(import_events.Events.Worker.Console, consoleMessage);
  97. page?.emit(import_events.Events.Page.Console, consoleMessage);
  98. if (worker && this._serviceWorkers.has(worker)) {
  99. const scope = this._serviceWorkerScope(worker);
  100. for (const page2 of this._pages) {
  101. if (scope && page2.url().startsWith(scope))
  102. page2.emit(import_events.Events.Page.Console, consoleMessage);
  103. }
  104. }
  105. this.emit(import_events.Events.BrowserContext.Console, consoleMessage);
  106. });
  107. this._channel.on("pageError", ({ error, page }) => {
  108. const pageObject = import_page.Page.from(page);
  109. const parsedError = (0, import_errors.parseError)(error);
  110. this.emit(import_events.Events.BrowserContext.WebError, new import_webError.WebError(pageObject, parsedError));
  111. if (pageObject)
  112. pageObject.emit(import_events.Events.Page.PageError, parsedError);
  113. });
  114. this._channel.on("dialog", ({ dialog }) => {
  115. const dialogObject = import_dialog.Dialog.from(dialog);
  116. let hasListeners = this.emit(import_events.Events.BrowserContext.Dialog, dialogObject);
  117. const page = dialogObject.page();
  118. if (page)
  119. hasListeners = page.emit(import_events.Events.Page.Dialog, dialogObject) || hasListeners;
  120. if (!hasListeners) {
  121. if (dialogObject.type() === "beforeunload")
  122. dialog.accept({}).catch(() => {
  123. });
  124. else
  125. dialog.dismiss().catch(() => {
  126. });
  127. }
  128. });
  129. this._channel.on("request", ({ request, page }) => this._onRequest(network.Request.from(request), import_page.Page.fromNullable(page)));
  130. this._channel.on("requestFailed", ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, import_page.Page.fromNullable(page)));
  131. this._channel.on("requestFinished", (params) => this._onRequestFinished(params));
  132. this._channel.on("response", ({ response, page }) => this._onResponse(network.Response.from(response), import_page.Page.fromNullable(page)));
  133. this._channel.on("recorderEvent", ({ event, data, page, code }) => {
  134. if (event === "actionAdded")
  135. this._onRecorderEventSink?.actionAdded?.(import_page.Page.from(page), data, code);
  136. else if (event === "actionUpdated")
  137. this._onRecorderEventSink?.actionUpdated?.(import_page.Page.from(page), data, code);
  138. else if (event === "signalAdded")
  139. this._onRecorderEventSink?.signalAdded?.(import_page.Page.from(page), data);
  140. });
  141. this._closedPromise = new Promise((f) => this.once(import_events.Events.BrowserContext.Close, f));
  142. this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
  143. [import_events.Events.BrowserContext.Console, "console"],
  144. [import_events.Events.BrowserContext.Dialog, "dialog"],
  145. [import_events.Events.BrowserContext.Request, "request"],
  146. [import_events.Events.BrowserContext.Response, "response"],
  147. [import_events.Events.BrowserContext.RequestFinished, "requestFinished"],
  148. [import_events.Events.BrowserContext.RequestFailed, "requestFailed"]
  149. ]));
  150. }
  151. static from(context) {
  152. return context._object;
  153. }
  154. static fromNullable(context) {
  155. return context ? BrowserContext.from(context) : null;
  156. }
  157. async _initializeHarFromOptions(recordHar) {
  158. if (!recordHar)
  159. return;
  160. const defaultContent = recordHar.path.endsWith(".zip") ? "attach" : "embed";
  161. await this._recordIntoHAR(recordHar.path, null, {
  162. url: recordHar.urlFilter,
  163. updateContent: recordHar.content ?? (recordHar.omitContent ? "omit" : defaultContent),
  164. updateMode: recordHar.mode ?? "full"
  165. });
  166. }
  167. _onPage(page) {
  168. this._pages.add(page);
  169. this.emit(import_events.Events.BrowserContext.Page, page);
  170. if (page._opener && !page._opener.isClosed())
  171. page._opener.emit(import_events.Events.Page.Popup, page);
  172. }
  173. _onRequest(request, page) {
  174. this.emit(import_events.Events.BrowserContext.Request, request);
  175. if (page)
  176. page.emit(import_events.Events.Page.Request, request);
  177. }
  178. _onResponse(response, page) {
  179. this.emit(import_events.Events.BrowserContext.Response, response);
  180. if (page)
  181. page.emit(import_events.Events.Page.Response, response);
  182. }
  183. _onRequestFailed(request, responseEndTiming, failureText, page) {
  184. request._failureText = failureText || null;
  185. request._setResponseEndTiming(responseEndTiming);
  186. this.emit(import_events.Events.BrowserContext.RequestFailed, request);
  187. if (page)
  188. page.emit(import_events.Events.Page.RequestFailed, request);
  189. }
  190. _onRequestFinished(params) {
  191. const { responseEndTiming } = params;
  192. const request = network.Request.from(params.request);
  193. const response = network.Response.fromNullable(params.response);
  194. const page = import_page.Page.fromNullable(params.page);
  195. request._setResponseEndTiming(responseEndTiming);
  196. this.emit(import_events.Events.BrowserContext.RequestFinished, request);
  197. if (page)
  198. page.emit(import_events.Events.Page.RequestFinished, request);
  199. if (response)
  200. response._finishedPromise.resolve(null);
  201. }
  202. async _onRoute(route) {
  203. route._context = this;
  204. const page = route.request()._safePage();
  205. const routeHandlers = this._routes.slice();
  206. for (const routeHandler of routeHandlers) {
  207. if (page?._closeWasCalled || this._closingStatus !== "none")
  208. return;
  209. if (!routeHandler.matches(route.request().url()))
  210. continue;
  211. const index = this._routes.indexOf(routeHandler);
  212. if (index === -1)
  213. continue;
  214. if (routeHandler.willExpire())
  215. this._routes.splice(index, 1);
  216. const handled = await routeHandler.handle(route);
  217. if (!this._routes.length)
  218. this._updateInterceptionPatterns({ internal: true }).catch(() => {
  219. });
  220. if (handled)
  221. return;
  222. }
  223. await route._innerContinue(
  224. true
  225. /* isFallback */
  226. ).catch(() => {
  227. });
  228. }
  229. async _onWebSocketRoute(webSocketRoute) {
  230. const routeHandler = this._webSocketRoutes.find((route) => route.matches(webSocketRoute.url()));
  231. if (routeHandler)
  232. await routeHandler.handle(webSocketRoute);
  233. else
  234. webSocketRoute.connectToServer();
  235. }
  236. async _onBinding(bindingCall) {
  237. const func = this._bindings.get(bindingCall._initializer.name);
  238. if (!func)
  239. return;
  240. await bindingCall.call(func);
  241. }
  242. _serviceWorkerScope(serviceWorker) {
  243. try {
  244. let url = new URL(".", serviceWorker.url()).href;
  245. if (!url.endsWith("/"))
  246. url += "/";
  247. return url;
  248. } catch {
  249. return null;
  250. }
  251. }
  252. setDefaultNavigationTimeout(timeout) {
  253. this._timeoutSettings.setDefaultNavigationTimeout(timeout);
  254. }
  255. setDefaultTimeout(timeout) {
  256. this._timeoutSettings.setDefaultTimeout(timeout);
  257. }
  258. browser() {
  259. return this._browser;
  260. }
  261. pages() {
  262. return [...this._pages];
  263. }
  264. async newPage() {
  265. if (this._ownerPage)
  266. throw new Error("Please use browser.newContext()");
  267. return import_page.Page.from((await this._channel.newPage()).page);
  268. }
  269. async cookies(urls) {
  270. if (!urls)
  271. urls = [];
  272. if (urls && typeof urls === "string")
  273. urls = [urls];
  274. return (await this._channel.cookies({ urls })).cookies;
  275. }
  276. async addCookies(cookies) {
  277. await this._channel.addCookies({ cookies });
  278. }
  279. async clearCookies(options = {}) {
  280. await this._channel.clearCookies({
  281. name: (0, import_rtti.isString)(options.name) ? options.name : void 0,
  282. nameRegexSource: (0, import_rtti.isRegExp)(options.name) ? options.name.source : void 0,
  283. nameRegexFlags: (0, import_rtti.isRegExp)(options.name) ? options.name.flags : void 0,
  284. domain: (0, import_rtti.isString)(options.domain) ? options.domain : void 0,
  285. domainRegexSource: (0, import_rtti.isRegExp)(options.domain) ? options.domain.source : void 0,
  286. domainRegexFlags: (0, import_rtti.isRegExp)(options.domain) ? options.domain.flags : void 0,
  287. path: (0, import_rtti.isString)(options.path) ? options.path : void 0,
  288. pathRegexSource: (0, import_rtti.isRegExp)(options.path) ? options.path.source : void 0,
  289. pathRegexFlags: (0, import_rtti.isRegExp)(options.path) ? options.path.flags : void 0
  290. });
  291. }
  292. async grantPermissions(permissions, options) {
  293. await this._channel.grantPermissions({ permissions, ...options });
  294. }
  295. async clearPermissions() {
  296. await this._channel.clearPermissions();
  297. }
  298. async setGeolocation(geolocation) {
  299. await this._channel.setGeolocation({ geolocation: geolocation || void 0 });
  300. }
  301. async setExtraHTTPHeaders(headers) {
  302. network.validateHeaders(headers);
  303. await this._channel.setExtraHTTPHeaders({ headers: (0, import_headers.headersObjectToArray)(headers) });
  304. }
  305. async setOffline(offline) {
  306. await this._channel.setOffline({ offline });
  307. }
  308. async setHTTPCredentials(httpCredentials) {
  309. await this._channel.setHTTPCredentials({ httpCredentials: httpCredentials || void 0 });
  310. }
  311. async addInitScript(script, arg) {
  312. const source = await (0, import_clientHelper.evaluationScript)(this._platform, script, arg);
  313. await this._channel.addInitScript({ source });
  314. }
  315. async exposeBinding(name, callback, options = {}) {
  316. await this._channel.exposeBinding({ name, needsHandle: options.handle });
  317. this._bindings.set(name, callback);
  318. }
  319. async exposeFunction(name, callback) {
  320. await this._channel.exposeBinding({ name });
  321. const binding = (source, ...args) => callback(...args);
  322. this._bindings.set(name, binding);
  323. }
  324. async route(url, handler, options = {}) {
  325. this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
  326. await this._updateInterceptionPatterns({ title: "Route requests" });
  327. }
  328. async routeWebSocket(url, handler) {
  329. this._webSocketRoutes.unshift(new network.WebSocketRouteHandler(this._options.baseURL, url, handler));
  330. await this._updateWebSocketInterceptionPatterns({ title: "Route WebSockets" });
  331. }
  332. async _recordIntoHAR(har, page, options = {}) {
  333. const { harId } = await this._channel.harStart({
  334. page: page?._channel,
  335. options: {
  336. zip: har.endsWith(".zip"),
  337. content: options.updateContent ?? "attach",
  338. urlGlob: (0, import_rtti.isString)(options.url) ? options.url : void 0,
  339. urlRegexSource: (0, import_rtti.isRegExp)(options.url) ? options.url.source : void 0,
  340. urlRegexFlags: (0, import_rtti.isRegExp)(options.url) ? options.url.flags : void 0,
  341. mode: options.updateMode ?? "minimal"
  342. }
  343. });
  344. this._harRecorders.set(harId, { path: har, content: options.updateContent ?? "attach" });
  345. }
  346. async routeFromHAR(har, options = {}) {
  347. const localUtils = this._connection.localUtils();
  348. if (!localUtils)
  349. throw new Error("Route from har is not supported in thin clients");
  350. if (options.update) {
  351. await this._recordIntoHAR(har, null, options);
  352. return;
  353. }
  354. const harRouter = await import_harRouter.HarRouter.create(localUtils, har, options.notFound || "abort", { urlMatch: options.url });
  355. this._harRouters.push(harRouter);
  356. await harRouter.addContextRoute(this);
  357. }
  358. _disposeHarRouters() {
  359. this._harRouters.forEach((router) => router.dispose());
  360. this._harRouters = [];
  361. }
  362. async unrouteAll(options) {
  363. await this._unrouteInternal(this._routes, [], options?.behavior);
  364. this._disposeHarRouters();
  365. }
  366. async unroute(url, handler) {
  367. const removed = [];
  368. const remaining = [];
  369. for (const route of this._routes) {
  370. if ((0, import_urlMatch.urlMatchesEqual)(route.url, url) && (!handler || route.handler === handler))
  371. removed.push(route);
  372. else
  373. remaining.push(route);
  374. }
  375. await this._unrouteInternal(removed, remaining, "default");
  376. }
  377. async _unrouteInternal(removed, remaining, behavior) {
  378. this._routes = remaining;
  379. if (behavior && behavior !== "default") {
  380. const promises = removed.map((routeHandler) => routeHandler.stop(behavior));
  381. await Promise.all(promises);
  382. }
  383. await this._updateInterceptionPatterns({ title: "Unroute requests" });
  384. }
  385. async _updateInterceptionPatterns(options) {
  386. const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes);
  387. await this._wrapApiCall(() => this._channel.setNetworkInterceptionPatterns({ patterns }), options);
  388. }
  389. async _updateWebSocketInterceptionPatterns(options) {
  390. const patterns = network.WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes);
  391. await this._wrapApiCall(() => this._channel.setWebSocketInterceptionPatterns({ patterns }), options);
  392. }
  393. _effectiveCloseReason() {
  394. return this._closeReason || this._browser?._closeReason;
  395. }
  396. async waitForEvent(event, optionsOrPredicate = {}) {
  397. return await this._wrapApiCall(async () => {
  398. const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
  399. const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
  400. const waiter = import_waiter.Waiter.createForEvent(this, event);
  401. waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
  402. if (event !== import_events.Events.BrowserContext.Close)
  403. waiter.rejectOnEvent(this, import_events.Events.BrowserContext.Close, () => new import_errors.TargetClosedError(this._effectiveCloseReason()));
  404. const result = await waiter.waitForEvent(this, event, predicate);
  405. waiter.dispose();
  406. return result;
  407. });
  408. }
  409. async storageState(options = {}) {
  410. const state = await this._channel.storageState({ indexedDB: options.indexedDB });
  411. if (options.path) {
  412. await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
  413. await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
  414. }
  415. return state;
  416. }
  417. backgroundPages() {
  418. return [];
  419. }
  420. serviceWorkers() {
  421. return [...this._serviceWorkers];
  422. }
  423. async newCDPSession(page) {
  424. if (!(page instanceof import_page.Page) && !(page instanceof import_frame.Frame))
  425. throw new Error("page: expected Page or Frame");
  426. const result = await this._channel.newCDPSession(page instanceof import_page.Page ? { page: page._channel } : { frame: page._channel });
  427. return import_cdpSession.CDPSession.from(result.session);
  428. }
  429. _onClose() {
  430. this._closingStatus = "closed";
  431. this._browser?._contexts.delete(this);
  432. this._browser?._browserType._contexts.delete(this);
  433. this._browser?._browserType._playwright.selectors._contextsForSelectors.delete(this);
  434. this._disposeHarRouters();
  435. this.tracing._resetStackCounter();
  436. this.emit(import_events.Events.BrowserContext.Close, this);
  437. }
  438. async [Symbol.asyncDispose]() {
  439. await this.close();
  440. }
  441. async close(options = {}) {
  442. if (this._closingStatus !== "none")
  443. return;
  444. this._closeReason = options.reason;
  445. this._closingStatus = "closing";
  446. await this.request.dispose(options);
  447. await this._instrumentation.runBeforeCloseBrowserContext(this);
  448. await this._wrapApiCall(async () => {
  449. for (const [harId, harParams] of this._harRecorders) {
  450. const har = await this._channel.harExport({ harId });
  451. const artifact = import_artifact.Artifact.from(har.artifact);
  452. const isCompressed = harParams.content === "attach" || harParams.path.endsWith(".zip");
  453. const needCompressed = harParams.path.endsWith(".zip");
  454. if (isCompressed && !needCompressed) {
  455. const localUtils = this._connection.localUtils();
  456. if (!localUtils)
  457. throw new Error("Uncompressed har is not supported in thin clients");
  458. await artifact.saveAs(harParams.path + ".tmp");
  459. await localUtils.harUnzip({ zipFile: harParams.path + ".tmp", harFile: harParams.path });
  460. } else {
  461. await artifact.saveAs(harParams.path);
  462. }
  463. await artifact.delete();
  464. }
  465. }, { internal: true });
  466. await this._channel.close(options);
  467. await this._closedPromise;
  468. }
  469. async _enableRecorder(params, eventSink) {
  470. if (eventSink)
  471. this._onRecorderEventSink = eventSink;
  472. await this._channel.enableRecorder(params);
  473. }
  474. async _disableRecorder() {
  475. this._onRecorderEventSink = void 0;
  476. await this._channel.disableRecorder();
  477. }
  478. async _exposeConsoleApi() {
  479. await this._channel.exposeConsoleApi();
  480. }
  481. _setAllowedProtocols(protocols) {
  482. this._allowedProtocols = protocols;
  483. }
  484. _checkUrlAllowed(url) {
  485. if (!this._allowedProtocols)
  486. return;
  487. let parsedURL;
  488. try {
  489. parsedURL = new URL(url);
  490. } catch (e) {
  491. throw new Error(`Access to ${url} is blocked. Invalid URL: ${e.message}`);
  492. }
  493. if (!this._allowedProtocols.includes(parsedURL.protocol))
  494. throw new Error(`Access to "${parsedURL.protocol}" URL is blocked. Allowed protocols: ${this._allowedProtocols.join(", ")}. Attempted URL: ${url}`);
  495. }
  496. _setAllowedDirectories(rootDirectories) {
  497. this._allowedDirectories = rootDirectories;
  498. }
  499. _checkFileAccess(filePath) {
  500. if (!this._allowedDirectories)
  501. return;
  502. const path = this._platform.path().resolve(filePath);
  503. const isInsideDir = (container, child) => {
  504. const path2 = this._platform.path();
  505. const rel = path2.relative(container, child);
  506. return !!rel && !rel.startsWith("..") && !path2.isAbsolute(rel);
  507. };
  508. if (this._allowedDirectories.some((root) => isInsideDir(root, path)))
  509. return;
  510. throw new Error(`File access denied: ${filePath} is outside allowed roots. Allowed roots: ${this._allowedDirectories.length ? this._allowedDirectories.join(", ") : "none"}`);
  511. }
  512. }
  513. async function prepareStorageState(platform, storageState) {
  514. if (typeof storageState !== "string")
  515. return storageState;
  516. try {
  517. return JSON.parse(await platform.fs().promises.readFile(storageState, "utf8"));
  518. } catch (e) {
  519. (0, import_stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${storageState}:
  520. ` + e.message);
  521. throw e;
  522. }
  523. }
  524. async function prepareBrowserContextParams(platform, options) {
  525. if (options.videoSize && !options.videosPath)
  526. throw new Error(`"videoSize" option requires "videosPath" to be specified`);
  527. if (options.extraHTTPHeaders)
  528. network.validateHeaders(options.extraHTTPHeaders);
  529. const contextParams = {
  530. ...options,
  531. viewport: options.viewport === null ? void 0 : options.viewport,
  532. noDefaultViewport: options.viewport === null,
  533. extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
  534. storageState: options.storageState ? await prepareStorageState(platform, options.storageState) : void 0,
  535. serviceWorkers: options.serviceWorkers,
  536. colorScheme: options.colorScheme === null ? "no-override" : options.colorScheme,
  537. reducedMotion: options.reducedMotion === null ? "no-override" : options.reducedMotion,
  538. forcedColors: options.forcedColors === null ? "no-override" : options.forcedColors,
  539. contrast: options.contrast === null ? "no-override" : options.contrast,
  540. acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
  541. clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates)
  542. };
  543. if (!contextParams.recordVideo && options.videosPath) {
  544. contextParams.recordVideo = {
  545. dir: options.videosPath,
  546. size: options.videoSize
  547. };
  548. }
  549. if (contextParams.recordVideo && contextParams.recordVideo.dir)
  550. contextParams.recordVideo.dir = platform.path().resolve(contextParams.recordVideo.dir);
  551. return contextParams;
  552. }
  553. function toAcceptDownloadsProtocol(acceptDownloads) {
  554. if (acceptDownloads === void 0)
  555. return void 0;
  556. if (acceptDownloads)
  557. return "accept";
  558. return "deny";
  559. }
  560. async function toClientCertificatesProtocol(platform, certs) {
  561. if (!certs)
  562. return void 0;
  563. const bufferizeContent = async (value, path) => {
  564. if (value)
  565. return value;
  566. if (path)
  567. return await platform.fs().promises.readFile(path);
  568. };
  569. return await Promise.all(certs.map(async (cert) => ({
  570. origin: cert.origin,
  571. cert: await bufferizeContent(cert.cert, cert.certPath),
  572. key: await bufferizeContent(cert.key, cert.keyPath),
  573. pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
  574. passphrase: cert.passphrase
  575. })));
  576. }
  577. // Annotate the CommonJS export names for ESM import in node:
  578. 0 && (module.exports = {
  579. BrowserContext,
  580. prepareBrowserContextParams,
  581. toClientCertificatesProtocol
  582. });