browserContext.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.BrowserContext = void 0;
  6. exports.assertBrowserContextIsNotOwned = assertBrowserContextIsNotOwned;
  7. exports.normalizeProxySettings = normalizeProxySettings;
  8. exports.validateBrowserContextOptions = validateBrowserContextOptions;
  9. exports.verifyGeolocation = verifyGeolocation;
  10. var os = _interopRequireWildcard(require("os"));
  11. var _timeoutSettings = require("../common/timeoutSettings");
  12. var _utils = require("../utils");
  13. var _fileUtils = require("../utils/fileUtils");
  14. var _helper = require("./helper");
  15. var network = _interopRequireWildcard(require("./network"));
  16. var _page6 = require("./page");
  17. var _path = _interopRequireDefault(require("path"));
  18. var _fs = _interopRequireDefault(require("fs"));
  19. var _instrumentation = require("./instrumentation");
  20. var _debugger = require("./debugger");
  21. var _tracing = require("./trace/recorder/tracing");
  22. var _harRecorder = require("./har/harRecorder");
  23. var _recorder = require("./recorder");
  24. var consoleApiSource = _interopRequireWildcard(require("../generated/consoleApiSource"));
  25. var _fetch = require("./fetch");
  26. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  27. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  28. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  29. /**
  30. * Copyright 2017 Google Inc. All rights reserved.
  31. * Modifications copyright (c) Microsoft Corporation.
  32. *
  33. * Licensed under the Apache License, Version 2.0 (the "License");
  34. * you may not use this file except in compliance with the License.
  35. * You may obtain a copy of the License at
  36. *
  37. * http://www.apache.org/licenses/LICENSE-2.0
  38. *
  39. * Unless required by applicable law or agreed to in writing, software
  40. * distributed under the License is distributed on an "AS IS" BASIS,
  41. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  42. * See the License for the specific language governing permissions and
  43. * limitations under the License.
  44. */
  45. class BrowserContext extends _instrumentation.SdkObject {
  46. constructor(browser, options, browserContextId) {
  47. super(browser, 'browser-context');
  48. this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
  49. this._pageBindings = new Map();
  50. this._activeProgressControllers = new Set();
  51. this._options = void 0;
  52. this._requestInterceptor = void 0;
  53. this._isPersistentContext = void 0;
  54. this._closedStatus = 'open';
  55. this._closePromise = void 0;
  56. this._closePromiseFulfill = void 0;
  57. this._permissions = new Map();
  58. this._downloads = new Set();
  59. this._browser = void 0;
  60. this._browserContextId = void 0;
  61. this._selectors = void 0;
  62. this._origins = new Set();
  63. this._harRecorders = new Map();
  64. this.tracing = void 0;
  65. this.fetchRequest = void 0;
  66. this._customCloseHandler = void 0;
  67. this._tempDirs = [];
  68. this._settingStorageState = false;
  69. this.initScripts = [];
  70. this._routesInFlight = new Set();
  71. this._debugger = void 0;
  72. this._closeReason = void 0;
  73. this.attribution.context = this;
  74. this._browser = browser;
  75. this._options = options;
  76. this._browserContextId = browserContextId;
  77. this._isPersistentContext = !browserContextId;
  78. this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
  79. this.fetchRequest = new _fetch.BrowserContextAPIRequestContext(this);
  80. if (this._options.recordHar) this._harRecorders.set('', new _harRecorder.HarRecorder(this, null, this._options.recordHar));
  81. this.tracing = new _tracing.Tracing(this, browser.options.tracesDir);
  82. }
  83. isPersistentContext() {
  84. return this._isPersistentContext;
  85. }
  86. setSelectors(selectors) {
  87. this._selectors = selectors;
  88. }
  89. selectors() {
  90. return this._selectors || this.attribution.playwright.selectors;
  91. }
  92. async _initialize() {
  93. if (this.attribution.playwright.options.isInternalPlaywright) return;
  94. // Debugger will pause execution upon page.pause in headed mode.
  95. this._debugger = new _debugger.Debugger(this);
  96. // When PWDEBUG=1, show inspector for each context.
  97. if ((0, _utils.debugMode)() === 'inspector') await _recorder.Recorder.show(this, {
  98. pauseOnNextStatement: true
  99. });
  100. // When paused, show inspector.
  101. if (this._debugger.isPaused()) _recorder.Recorder.showInspector(this);
  102. this._debugger.on(_debugger.Debugger.Events.PausedStateChanged, () => {
  103. _recorder.Recorder.showInspector(this);
  104. });
  105. if ((0, _utils.debugMode)() === 'console') await this.extendInjectedScript(consoleApiSource.source);
  106. if (this._options.serviceWorkers === 'block') await this.addInitScript(`\nnavigator.serviceWorker.register = async () => { console.warn('Service Worker registration blocked by Playwright'); };\n`);
  107. if (this._options.permissions) await this.grantPermissions(this._options.permissions);
  108. }
  109. debugger() {
  110. return this._debugger;
  111. }
  112. async _ensureVideosPath() {
  113. if (this._options.recordVideo) await (0, _fileUtils.mkdirIfNeeded)(_path.default.join(this._options.recordVideo.dir, 'dummy'));
  114. }
  115. canResetForReuse() {
  116. if (this._closedStatus !== 'open') return false;
  117. return true;
  118. }
  119. async stopPendingOperations(reason) {
  120. // When using context reuse, stop pending operations to gracefully terminate all the actions
  121. // with a user-friendly error message containing operation log.
  122. for (const controller of this._activeProgressControllers) controller.abort(new Error(reason));
  123. // Let rejections in microtask generate events before returning.
  124. await new Promise(f => setTimeout(f, 0));
  125. }
  126. static reusableContextHash(params) {
  127. const paramsCopy = {
  128. ...params
  129. };
  130. for (const k of Object.keys(paramsCopy)) {
  131. const key = k;
  132. if (paramsCopy[key] === defaultNewContextParamValues[key]) delete paramsCopy[key];
  133. }
  134. for (const key of paramsThatAllowContextReuse) delete paramsCopy[key];
  135. return JSON.stringify(paramsCopy);
  136. }
  137. async resetForReuse(metadata, params) {
  138. var _page, _page2, _page3, _page4, _page5;
  139. this.setDefaultNavigationTimeout(undefined);
  140. this.setDefaultTimeout(undefined);
  141. this.tracing.resetForReuse();
  142. if (params) {
  143. for (const key of paramsThatAllowContextReuse) this._options[key] = params[key];
  144. }
  145. await this._cancelAllRoutesInFlight();
  146. // Close extra pages early.
  147. let page = this.pages()[0];
  148. const [, ...otherPages] = this.pages();
  149. for (const p of otherPages) await p.close(metadata);
  150. if (page && page.hasCrashed()) {
  151. await page.close(metadata);
  152. page = undefined;
  153. }
  154. // Unless dialogs are dismissed, setting extra http headers below does not respond.
  155. (_page = page) === null || _page === void 0 ? void 0 : _page._frameManager.setCloseAllOpeningDialogs(true);
  156. await ((_page2 = page) === null || _page2 === void 0 ? void 0 : _page2._frameManager.closeOpenDialogs());
  157. // Navigate to about:blank first to ensure no page scripts are running after this point.
  158. await ((_page3 = page) === null || _page3 === void 0 ? void 0 : _page3.mainFrame().goto(metadata, 'about:blank', {
  159. timeout: 0
  160. }));
  161. (_page4 = page) === null || _page4 === void 0 ? void 0 : _page4._frameManager.setCloseAllOpeningDialogs(false);
  162. await this._resetStorage();
  163. await this._removeExposedBindings();
  164. await this._removeInitScripts();
  165. // TODO: following can be optimized to not perform noops.
  166. if (this._options.permissions) await this.grantPermissions(this._options.permissions);else await this.clearPermissions();
  167. await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []);
  168. await this.setGeolocation(this._options.geolocation);
  169. await this.setOffline(!!this._options.offline);
  170. await this.setUserAgent(this._options.userAgent);
  171. await this.clearCache();
  172. await this._resetCookies();
  173. await ((_page5 = page) === null || _page5 === void 0 ? void 0 : _page5.resetForReuse(metadata));
  174. }
  175. _browserClosed() {
  176. for (const page of this.pages()) page._didClose();
  177. this._didCloseInternal();
  178. }
  179. _didCloseInternal() {
  180. if (this._closedStatus === 'closed') {
  181. // We can come here twice if we close browser context and browser
  182. // at the same time.
  183. return;
  184. }
  185. this.tracing.abort();
  186. if (this._isPersistentContext) this.onClosePersistent();
  187. this._closePromiseFulfill(new Error('Context closed'));
  188. this.emit(BrowserContext.Events.Close);
  189. }
  190. // BrowserContext methods.
  191. async cookies(urls = []) {
  192. if (urls && !Array.isArray(urls)) urls = [urls];
  193. return await this.doGetCookies(urls);
  194. }
  195. setHTTPCredentials(httpCredentials) {
  196. return this.doSetHTTPCredentials(httpCredentials);
  197. }
  198. async exposeBinding(name, needsHandle, playwrightBinding) {
  199. if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
  200. for (const page of this.pages()) {
  201. if (page.getBinding(name)) throw new Error(`Function "${name}" has been already registered in one of the pages`);
  202. }
  203. const binding = new _page6.PageBinding(name, playwrightBinding, needsHandle);
  204. this._pageBindings.set(name, binding);
  205. await this.doExposeBinding(binding);
  206. }
  207. async _removeExposedBindings() {
  208. for (const key of this._pageBindings.keys()) {
  209. if (!key.startsWith('__pw')) this._pageBindings.delete(key);
  210. }
  211. await this.doRemoveExposedBindings();
  212. }
  213. async grantPermissions(permissions, origin) {
  214. let resolvedOrigin = '*';
  215. if (origin) {
  216. const url = new URL(origin);
  217. resolvedOrigin = url.origin;
  218. }
  219. const existing = new Set(this._permissions.get(resolvedOrigin) || []);
  220. permissions.forEach(p => existing.add(p));
  221. const list = [...existing.values()];
  222. this._permissions.set(resolvedOrigin, list);
  223. await this.doGrantPermissions(resolvedOrigin, list);
  224. }
  225. async clearPermissions() {
  226. this._permissions.clear();
  227. await this.doClearPermissions();
  228. }
  229. setDefaultNavigationTimeout(timeout) {
  230. this._timeoutSettings.setDefaultNavigationTimeout(timeout);
  231. }
  232. setDefaultTimeout(timeout) {
  233. this._timeoutSettings.setDefaultTimeout(timeout);
  234. }
  235. async _loadDefaultContextAsIs(progress) {
  236. if (!this.pages().length) {
  237. const waitForEvent = _helper.helper.waitForEvent(progress, this, BrowserContext.Events.Page);
  238. progress.cleanupWhenAborted(() => waitForEvent.dispose);
  239. const page = await waitForEvent.promise;
  240. if (page._pageIsError) throw page._pageIsError;
  241. }
  242. const pages = this.pages();
  243. if (pages[0]._pageIsError) throw pages[0]._pageIsError;
  244. await pages[0].mainFrame()._waitForLoadState(progress, 'load');
  245. return pages;
  246. }
  247. async _loadDefaultContext(progress) {
  248. const pages = await this._loadDefaultContextAsIs(progress);
  249. const browserName = this._browser.options.name;
  250. if (this._options.isMobile && browserName === 'chromium' || this._options.locale && browserName === 'webkit') {
  251. // Workaround for:
  252. // - chromium fails to change isMobile for existing page;
  253. // - webkit fails to change locale for existing page.
  254. const oldPage = pages[0];
  255. await this.newPage(progress.metadata);
  256. await oldPage.close(progress.metadata);
  257. }
  258. }
  259. _authenticateProxyViaHeader() {
  260. const proxy = this._options.proxy || this._browser.options.proxy || {
  261. username: undefined,
  262. password: undefined
  263. };
  264. const {
  265. username,
  266. password
  267. } = proxy;
  268. if (username) {
  269. this._options.httpCredentials = {
  270. username,
  271. password: password
  272. };
  273. const token = Buffer.from(`${username}:${password}`).toString('base64');
  274. this._options.extraHTTPHeaders = network.mergeHeaders([this._options.extraHTTPHeaders, network.singleHeader('Proxy-Authorization', `Basic ${token}`)]);
  275. }
  276. }
  277. _authenticateProxyViaCredentials() {
  278. const proxy = this._options.proxy || this._browser.options.proxy;
  279. if (!proxy) return;
  280. const {
  281. username,
  282. password
  283. } = proxy;
  284. if (username) this._options.httpCredentials = {
  285. username,
  286. password: password || ''
  287. };
  288. }
  289. async addInitScript(script) {
  290. this.initScripts.push(script);
  291. await this.doAddInitScript(script);
  292. }
  293. async _removeInitScripts() {
  294. this.initScripts.splice(0, this.initScripts.length);
  295. await this.doRemoveInitScripts();
  296. }
  297. async setRequestInterceptor(handler) {
  298. this._requestInterceptor = handler;
  299. await this.doUpdateRequestInterception();
  300. }
  301. isClosingOrClosed() {
  302. return this._closedStatus !== 'open';
  303. }
  304. async _deleteAllDownloads() {
  305. await Promise.all(Array.from(this._downloads).map(download => download.artifact.deleteOnContextClose()));
  306. }
  307. async _deleteAllTempDirs() {
  308. await Promise.all(this._tempDirs.map(async dir => await _fs.default.promises.unlink(dir).catch(e => {})));
  309. }
  310. setCustomCloseHandler(handler) {
  311. this._customCloseHandler = handler;
  312. }
  313. async close(options) {
  314. if (this._closedStatus === 'open') {
  315. if (options.reason) this._closeReason = options.reason;
  316. this.emit(BrowserContext.Events.BeforeClose);
  317. this._closedStatus = 'closing';
  318. for (const harRecorder of this._harRecorders.values()) await harRecorder.flush();
  319. await this.tracing.flush();
  320. // Cleanup.
  321. const promises = [];
  322. for (const {
  323. context,
  324. artifact
  325. } of this._browser._idToVideo.values()) {
  326. // Wait for the videos to finish.
  327. if (context === this) promises.push(artifact.finishedPromise());
  328. }
  329. if (this._customCloseHandler) {
  330. await this._customCloseHandler();
  331. } else {
  332. // Close the context.
  333. await this.doClose(options.reason);
  334. }
  335. // We delete downloads after context closure
  336. // so that browser does not write to the download file anymore.
  337. promises.push(this._deleteAllDownloads());
  338. promises.push(this._deleteAllTempDirs());
  339. await Promise.all(promises);
  340. // Custom handler should trigger didCloseInternal itself.
  341. if (!this._customCloseHandler) this._didCloseInternal();
  342. }
  343. await this._closePromise;
  344. }
  345. async newPage(metadata) {
  346. const pageDelegate = await this.newPageDelegate();
  347. if (metadata.isServerSide) pageDelegate.potentiallyUninitializedPage().markAsServerSideOnly();
  348. const pageOrError = await pageDelegate.pageOrError();
  349. if (pageOrError instanceof _page6.Page) {
  350. if (pageOrError.isClosed()) throw new Error('Page has been closed.');
  351. return pageOrError;
  352. }
  353. throw pageOrError;
  354. }
  355. addVisitedOrigin(origin) {
  356. this._origins.add(origin);
  357. }
  358. async storageState() {
  359. const result = {
  360. cookies: await this.cookies(),
  361. origins: []
  362. };
  363. if (this._origins.size) {
  364. const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
  365. const page = await this.newPage(internalMetadata);
  366. await page._setServerRequestInterceptor(handler => {
  367. handler.fulfill({
  368. body: '<html></html>',
  369. requestUrl: handler.request().url()
  370. }).catch(() => {});
  371. return true;
  372. });
  373. for (const origin of this._origins) {
  374. const originStorage = {
  375. origin,
  376. localStorage: []
  377. };
  378. const frame = page.mainFrame();
  379. await frame.goto(internalMetadata, origin);
  380. const storage = await frame.evaluateExpression(`({
  381. localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
  382. })`, {
  383. world: 'utility'
  384. });
  385. originStorage.localStorage = storage.localStorage;
  386. if (storage.localStorage.length) result.origins.push(originStorage);
  387. }
  388. await page.close(internalMetadata);
  389. }
  390. return result;
  391. }
  392. async _resetStorage() {
  393. var _this$_options$storag, _this$_options$storag2;
  394. const oldOrigins = this._origins;
  395. const newOrigins = new Map(((_this$_options$storag = this._options.storageState) === null || _this$_options$storag === void 0 ? void 0 : (_this$_options$storag2 = _this$_options$storag.origins) === null || _this$_options$storag2 === void 0 ? void 0 : _this$_options$storag2.map(p => [p.origin, p])) || []);
  396. if (!oldOrigins.size && !newOrigins.size) return;
  397. let page = this.pages()[0];
  398. const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
  399. page = page || (await this.newPage({
  400. ...internalMetadata,
  401. // Do not mark this page as internal, because we will leave it for later reuse
  402. // as a user-visible page.
  403. isServerSide: false
  404. }));
  405. await page._setServerRequestInterceptor(handler => {
  406. handler.fulfill({
  407. body: '<html></html>',
  408. requestUrl: handler.request().url()
  409. }).catch(() => {});
  410. return true;
  411. });
  412. for (const origin of new Set([...oldOrigins, ...newOrigins.keys()])) {
  413. const frame = page.mainFrame();
  414. await frame.goto(internalMetadata, origin);
  415. await frame.resetStorageForCurrentOriginBestEffort(newOrigins.get(origin));
  416. }
  417. await page._setServerRequestInterceptor(undefined);
  418. this._origins = new Set([...newOrigins.keys()]);
  419. // It is safe to not restore the URL to about:blank since we are doing it in Page::resetForReuse.
  420. }
  421. async _resetCookies() {
  422. var _this$_options$storag3, _this$_options$storag4;
  423. await this.clearCookies();
  424. if ((_this$_options$storag3 = this._options.storageState) !== null && _this$_options$storag3 !== void 0 && _this$_options$storag3.cookies) await this.addCookies((_this$_options$storag4 = this._options.storageState) === null || _this$_options$storag4 === void 0 ? void 0 : _this$_options$storag4.cookies);
  425. }
  426. isSettingStorageState() {
  427. return this._settingStorageState;
  428. }
  429. async setStorageState(metadata, state) {
  430. this._settingStorageState = true;
  431. try {
  432. if (state.cookies) await this.addCookies(state.cookies);
  433. if (state.origins && state.origins.length) {
  434. const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
  435. const page = await this.newPage(internalMetadata);
  436. await page._setServerRequestInterceptor(handler => {
  437. handler.fulfill({
  438. body: '<html></html>',
  439. requestUrl: handler.request().url()
  440. }).catch(() => {});
  441. return true;
  442. });
  443. for (const originState of state.origins) {
  444. const frame = page.mainFrame();
  445. await frame.goto(metadata, originState.origin);
  446. await frame.evaluateExpression(`
  447. originState => {
  448. for (const { name, value } of (originState.localStorage || []))
  449. localStorage.setItem(name, value);
  450. }`, {
  451. isFunction: true,
  452. world: 'utility'
  453. }, originState);
  454. }
  455. await page.close(internalMetadata);
  456. }
  457. } finally {
  458. this._settingStorageState = false;
  459. }
  460. }
  461. async extendInjectedScript(source, arg) {
  462. const installInFrame = frame => frame.extendInjectedScript(source, arg).catch(() => {});
  463. const installInPage = page => {
  464. page.on(_page6.Page.Events.InternalFrameNavigatedToNewDocument, installInFrame);
  465. return Promise.all(page.frames().map(installInFrame));
  466. };
  467. this.on(BrowserContext.Events.Page, installInPage);
  468. return Promise.all(this.pages().map(installInPage));
  469. }
  470. async _harStart(page, options) {
  471. const harId = (0, _utils.createGuid)();
  472. this._harRecorders.set(harId, new _harRecorder.HarRecorder(this, page, options));
  473. return harId;
  474. }
  475. async _harExport(harId) {
  476. const recorder = this._harRecorders.get(harId || '');
  477. return recorder.export();
  478. }
  479. addRouteInFlight(route) {
  480. this._routesInFlight.add(route);
  481. }
  482. removeRouteInFlight(route) {
  483. this._routesInFlight.delete(route);
  484. }
  485. async _cancelAllRoutesInFlight() {
  486. await Promise.all([...this._routesInFlight].map(r => r.abort())).catch(() => {});
  487. this._routesInFlight.clear();
  488. }
  489. }
  490. exports.BrowserContext = BrowserContext;
  491. BrowserContext.Events = {
  492. Console: 'console',
  493. Close: 'close',
  494. Dialog: 'dialog',
  495. Page: 'page',
  496. // Can't use just 'error' due to node.js special treatment of error events.
  497. // @see https://nodejs.org/api/events.html#events_error_events
  498. PageError: 'pageerror',
  499. Request: 'request',
  500. Response: 'response',
  501. RequestFailed: 'requestfailed',
  502. RequestFinished: 'requestfinished',
  503. RequestAborted: 'requestaborted',
  504. RequestFulfilled: 'requestfulfilled',
  505. RequestContinued: 'requestcontinued',
  506. BeforeClose: 'beforeclose',
  507. VideoStarted: 'videostarted'
  508. };
  509. function assertBrowserContextIsNotOwned(context) {
  510. for (const page of context.pages()) {
  511. if (page._ownedContext) throw new Error('Please use browser.newContext() for multi-page scripts that share the context.');
  512. }
  513. }
  514. function validateBrowserContextOptions(options, browserOptions) {
  515. if (options.noDefaultViewport && options.deviceScaleFactor !== undefined) throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
  516. if (options.noDefaultViewport && !!options.isMobile) throw new Error(`"isMobile" option is not supported with null "viewport"`);
  517. if (options.acceptDownloads === undefined) options.acceptDownloads = 'accept';
  518. if (!options.viewport && !options.noDefaultViewport) options.viewport = {
  519. width: 1280,
  520. height: 720
  521. };
  522. if (options.recordVideo) {
  523. if (!options.recordVideo.size) {
  524. if (options.noDefaultViewport) {
  525. options.recordVideo.size = {
  526. width: 800,
  527. height: 600
  528. };
  529. } else {
  530. const size = options.viewport;
  531. const scale = Math.min(1, 800 / Math.max(size.width, size.height));
  532. options.recordVideo.size = {
  533. width: Math.floor(size.width * scale),
  534. height: Math.floor(size.height * scale)
  535. };
  536. }
  537. }
  538. // Make sure both dimensions are odd, this is required for vp8
  539. options.recordVideo.size.width &= ~1;
  540. options.recordVideo.size.height &= ~1;
  541. }
  542. if (options.proxy) {
  543. if (!browserOptions.proxy && browserOptions.isChromium && os.platform() === 'win32') throw new Error(`Browser needs to be launched with the global proxy. If all contexts override the proxy, global proxy will be never used and can be any string, for example "launch({ proxy: { server: 'http://per-context' } })"`);
  544. options.proxy = normalizeProxySettings(options.proxy);
  545. }
  546. verifyGeolocation(options.geolocation);
  547. }
  548. function verifyGeolocation(geolocation) {
  549. if (!geolocation) return;
  550. geolocation.accuracy = geolocation.accuracy || 0;
  551. const {
  552. longitude,
  553. latitude,
  554. accuracy
  555. } = geolocation;
  556. if (longitude < -180 || longitude > 180) throw new Error(`geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.`);
  557. if (latitude < -90 || latitude > 90) throw new Error(`geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed.`);
  558. if (accuracy < 0) throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
  559. }
  560. function normalizeProxySettings(proxy) {
  561. let {
  562. server,
  563. bypass
  564. } = proxy;
  565. let url;
  566. try {
  567. // new URL('127.0.0.1:8080') throws
  568. // new URL('localhost:8080') fails to parse host or protocol
  569. // In both of these cases, we need to try re-parse URL with `http://` prefix.
  570. url = new URL(server);
  571. if (!url.host || !url.protocol) url = new URL('http://' + server);
  572. } catch (e) {
  573. url = new URL('http://' + server);
  574. }
  575. if (url.protocol === 'socks4:' && (proxy.username || proxy.password)) throw new Error(`Socks4 proxy protocol does not support authentication`);
  576. if (url.protocol === 'socks5:' && (proxy.username || proxy.password)) throw new Error(`Browser does not support socks5 proxy authentication`);
  577. server = url.protocol + '//' + url.host;
  578. if (bypass) bypass = bypass.split(',').map(t => t.trim()).join(',');
  579. return {
  580. ...proxy,
  581. server,
  582. bypass
  583. };
  584. }
  585. const paramsThatAllowContextReuse = ['colorScheme', 'forcedColors', 'reducedMotion', 'screen', 'userAgent', 'viewport'];
  586. const defaultNewContextParamValues = {
  587. noDefaultViewport: false,
  588. ignoreHTTPSErrors: false,
  589. javaScriptEnabled: true,
  590. bypassCSP: false,
  591. offline: false,
  592. isMobile: false,
  593. hasTouch: false,
  594. acceptDownloads: 'accept',
  595. strictSelectors: false,
  596. serviceWorkers: 'allow',
  597. locale: 'en-US'
  598. };