browserContext.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  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.attribution.context = this;
  73. this._browser = browser;
  74. this._options = options;
  75. this._browserContextId = browserContextId;
  76. this._isPersistentContext = !browserContextId;
  77. this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
  78. this.fetchRequest = new _fetch.BrowserContextAPIRequestContext(this);
  79. if (this._options.recordHar) this._harRecorders.set('', new _harRecorder.HarRecorder(this, null, this._options.recordHar));
  80. this.tracing = new _tracing.Tracing(this, browser.options.tracesDir);
  81. }
  82. isPersistentContext() {
  83. return this._isPersistentContext;
  84. }
  85. setSelectors(selectors) {
  86. this._selectors = selectors;
  87. }
  88. selectors() {
  89. return this._selectors || this.attribution.playwright.selectors;
  90. }
  91. async _initialize() {
  92. if (this.attribution.playwright.options.isInternalPlaywright) return;
  93. // Debugger will pause execution upon page.pause in headed mode.
  94. this._debugger = new _debugger.Debugger(this);
  95. // When PWDEBUG=1, show inspector for each context.
  96. if ((0, _utils.debugMode)() === 'inspector') await _recorder.Recorder.show(this, {
  97. pauseOnNextStatement: true
  98. });
  99. // When paused, show inspector.
  100. if (this._debugger.isPaused()) _recorder.Recorder.showInspector(this);
  101. this._debugger.on(_debugger.Debugger.Events.PausedStateChanged, () => {
  102. _recorder.Recorder.showInspector(this);
  103. });
  104. if ((0, _utils.debugMode)() === 'console') await this.extendInjectedScript(consoleApiSource.source);
  105. if (this._options.serviceWorkers === 'block') await this.addInitScript(`\nnavigator.serviceWorker.register = async () => { console.warn('Service Worker registration blocked by Playwright'); };\n`);
  106. if (this._options.permissions) await this.grantPermissions(this._options.permissions);
  107. }
  108. debugger() {
  109. return this._debugger;
  110. }
  111. async _ensureVideosPath() {
  112. if (this._options.recordVideo) await (0, _fileUtils.mkdirIfNeeded)(_path.default.join(this._options.recordVideo.dir, 'dummy'));
  113. }
  114. canResetForReuse() {
  115. if (this._closedStatus !== 'open') return false;
  116. return true;
  117. }
  118. async stopPendingOperations() {
  119. for (const controller of this._activeProgressControllers) controller.abort(new Error(`Context was reset for reuse.`));
  120. }
  121. static reusableContextHash(params) {
  122. const paramsCopy = {
  123. ...params
  124. };
  125. for (const k of Object.keys(paramsCopy)) {
  126. const key = k;
  127. if (paramsCopy[key] === defaultNewContextParamValues[key]) delete paramsCopy[key];
  128. }
  129. for (const key of paramsThatAllowContextReuse) delete paramsCopy[key];
  130. return JSON.stringify(paramsCopy);
  131. }
  132. async resetForReuse(metadata, params) {
  133. var _page, _page2, _page3, _page4, _page5;
  134. this.setDefaultNavigationTimeout(undefined);
  135. this.setDefaultTimeout(undefined);
  136. this.tracing.resetForReuse();
  137. if (params) {
  138. for (const key of paramsThatAllowContextReuse) this._options[key] = params[key];
  139. }
  140. await this._cancelAllRoutesInFlight();
  141. // Close extra pages early.
  142. let page = this.pages()[0];
  143. const [, ...otherPages] = this.pages();
  144. for (const p of otherPages) await p.close(metadata);
  145. if (page && page._crashedPromise.isDone()) {
  146. await page.close(metadata);
  147. page = undefined;
  148. }
  149. // Unless dialogs are dismissed, setting extra http headers below does not respond.
  150. (_page = page) === null || _page === void 0 ? void 0 : _page._frameManager.setCloseAllOpeningDialogs(true);
  151. await ((_page2 = page) === null || _page2 === void 0 ? void 0 : _page2._frameManager.closeOpenDialogs());
  152. // Navigate to about:blank first to ensure no page scripts are running after this point.
  153. await ((_page3 = page) === null || _page3 === void 0 ? void 0 : _page3.mainFrame().goto(metadata, 'about:blank', {
  154. timeout: 0
  155. }));
  156. (_page4 = page) === null || _page4 === void 0 ? void 0 : _page4._frameManager.setCloseAllOpeningDialogs(false);
  157. await this._resetStorage();
  158. await this._removeExposedBindings();
  159. await this._removeInitScripts();
  160. // TODO: following can be optimized to not perform noops.
  161. if (this._options.permissions) await this.grantPermissions(this._options.permissions);else await this.clearPermissions();
  162. await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []);
  163. await this.setGeolocation(this._options.geolocation);
  164. await this.setOffline(!!this._options.offline);
  165. await this.setUserAgent(this._options.userAgent);
  166. await this.clearCache();
  167. await this._resetCookies();
  168. await ((_page5 = page) === null || _page5 === void 0 ? void 0 : _page5.resetForReuse(metadata));
  169. }
  170. _browserClosed() {
  171. for (const page of this.pages()) page._didClose();
  172. this._didCloseInternal();
  173. }
  174. _didCloseInternal() {
  175. if (this._closedStatus === 'closed') {
  176. // We can come here twice if we close browser context and browser
  177. // at the same time.
  178. return;
  179. }
  180. const gotClosedGracefully = this._closedStatus === 'closing';
  181. this._closedStatus = 'closed';
  182. if (!gotClosedGracefully) {
  183. this._deleteAllDownloads();
  184. this._downloads.clear();
  185. }
  186. this.tracing.dispose().catch(() => {});
  187. if (this._isPersistentContext) this.onClosePersistent();
  188. this._closePromiseFulfill(new Error('Context closed'));
  189. this.emit(BrowserContext.Events.Close);
  190. }
  191. // BrowserContext methods.
  192. async cookies(urls = []) {
  193. if (urls && !Array.isArray(urls)) urls = [urls];
  194. return await this.doGetCookies(urls);
  195. }
  196. setHTTPCredentials(httpCredentials) {
  197. return this.doSetHTTPCredentials(httpCredentials);
  198. }
  199. async exposeBinding(name, needsHandle, playwrightBinding) {
  200. if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
  201. for (const page of this.pages()) {
  202. if (page.getBinding(name)) throw new Error(`Function "${name}" has been already registered in one of the pages`);
  203. }
  204. const binding = new _page6.PageBinding(name, playwrightBinding, needsHandle);
  205. this._pageBindings.set(name, binding);
  206. await this.doExposeBinding(binding);
  207. }
  208. async _removeExposedBindings() {
  209. for (const key of this._pageBindings.keys()) {
  210. if (!key.startsWith('__pw')) this._pageBindings.delete(key);
  211. }
  212. await this.doRemoveExposedBindings();
  213. }
  214. async grantPermissions(permissions, origin) {
  215. let resolvedOrigin = '*';
  216. if (origin) {
  217. const url = new URL(origin);
  218. resolvedOrigin = url.origin;
  219. }
  220. const existing = new Set(this._permissions.get(resolvedOrigin) || []);
  221. permissions.forEach(p => existing.add(p));
  222. const list = [...existing.values()];
  223. this._permissions.set(resolvedOrigin, list);
  224. await this.doGrantPermissions(resolvedOrigin, list);
  225. }
  226. async clearPermissions() {
  227. this._permissions.clear();
  228. await this.doClearPermissions();
  229. }
  230. setDefaultNavigationTimeout(timeout) {
  231. this._timeoutSettings.setDefaultNavigationTimeout(timeout);
  232. }
  233. setDefaultTimeout(timeout) {
  234. this._timeoutSettings.setDefaultTimeout(timeout);
  235. }
  236. async _loadDefaultContextAsIs(progress) {
  237. if (!this.pages().length) {
  238. const waitForEvent = _helper.helper.waitForEvent(progress, this, BrowserContext.Events.Page);
  239. progress.cleanupWhenAborted(() => waitForEvent.dispose);
  240. const page = await waitForEvent.promise;
  241. if (page._pageIsError) throw page._pageIsError;
  242. }
  243. const pages = this.pages();
  244. if (pages[0]._pageIsError) throw pages[0]._pageIsError;
  245. await pages[0].mainFrame()._waitForLoadState(progress, 'load');
  246. return pages;
  247. }
  248. async _loadDefaultContext(progress) {
  249. const pages = await this._loadDefaultContextAsIs(progress);
  250. const browserName = this._browser.options.name;
  251. if (this._options.isMobile && browserName === 'chromium' || this._options.locale && browserName === 'webkit') {
  252. // Workaround for:
  253. // - chromium fails to change isMobile for existing page;
  254. // - webkit fails to change locale for existing page.
  255. const oldPage = pages[0];
  256. await this.newPage(progress.metadata);
  257. await oldPage.close(progress.metadata);
  258. }
  259. }
  260. _authenticateProxyViaHeader() {
  261. const proxy = this._options.proxy || this._browser.options.proxy || {
  262. username: undefined,
  263. password: undefined
  264. };
  265. const {
  266. username,
  267. password
  268. } = proxy;
  269. if (username) {
  270. this._options.httpCredentials = {
  271. username,
  272. password: password
  273. };
  274. const token = Buffer.from(`${username}:${password}`).toString('base64');
  275. this._options.extraHTTPHeaders = network.mergeHeaders([this._options.extraHTTPHeaders, network.singleHeader('Proxy-Authorization', `Basic ${token}`)]);
  276. }
  277. }
  278. _authenticateProxyViaCredentials() {
  279. const proxy = this._options.proxy || this._browser.options.proxy;
  280. if (!proxy) return;
  281. const {
  282. username,
  283. password
  284. } = proxy;
  285. if (username) this._options.httpCredentials = {
  286. username,
  287. password: password || ''
  288. };
  289. }
  290. async addInitScript(script) {
  291. this.initScripts.push(script);
  292. await this.doAddInitScript(script);
  293. }
  294. async _removeInitScripts() {
  295. this.initScripts.splice(0, this.initScripts.length);
  296. await this.doRemoveInitScripts();
  297. }
  298. async setRequestInterceptor(handler) {
  299. this._requestInterceptor = handler;
  300. await this.doUpdateRequestInterception();
  301. }
  302. isClosingOrClosed() {
  303. return this._closedStatus !== 'open';
  304. }
  305. async _deleteAllDownloads() {
  306. await Promise.all(Array.from(this._downloads).map(download => download.artifact.deleteOnContextClose()));
  307. }
  308. async _deleteAllTempDirs() {
  309. await Promise.all(this._tempDirs.map(async dir => await _fs.default.promises.unlink(dir).catch(e => {})));
  310. }
  311. setCustomCloseHandler(handler) {
  312. this._customCloseHandler = handler;
  313. }
  314. async close(metadata) {
  315. if (this._closedStatus === 'open') {
  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.dispose();
  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();
  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(internalMetadata));
  400. await page._setServerRequestInterceptor(handler => {
  401. handler.fulfill({
  402. body: '<html></html>',
  403. requestUrl: handler.request().url()
  404. }).catch(() => {});
  405. return true;
  406. });
  407. for (const origin of new Set([...oldOrigins, ...newOrigins.keys()])) {
  408. const frame = page.mainFrame();
  409. await frame.goto(internalMetadata, origin);
  410. await frame.resetStorageForCurrentOriginBestEffort(newOrigins.get(origin));
  411. }
  412. await page._setServerRequestInterceptor(undefined);
  413. this._origins = new Set([...newOrigins.keys()]);
  414. // It is safe to not restore the URL to about:blank since we are doing it in Page::resetForReuse.
  415. }
  416. async _resetCookies() {
  417. var _this$_options$storag3, _this$_options$storag4;
  418. await this.clearCookies();
  419. 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);
  420. }
  421. isSettingStorageState() {
  422. return this._settingStorageState;
  423. }
  424. async setStorageState(metadata, state) {
  425. this._settingStorageState = true;
  426. try {
  427. if (state.cookies) await this.addCookies(state.cookies);
  428. if (state.origins && state.origins.length) {
  429. const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
  430. const page = await this.newPage(internalMetadata);
  431. await page._setServerRequestInterceptor(handler => {
  432. handler.fulfill({
  433. body: '<html></html>',
  434. requestUrl: handler.request().url()
  435. }).catch(() => {});
  436. return true;
  437. });
  438. for (const originState of state.origins) {
  439. const frame = page.mainFrame();
  440. await frame.goto(metadata, originState.origin);
  441. await frame.evaluateExpression(`
  442. originState => {
  443. for (const { name, value } of (originState.localStorage || []))
  444. localStorage.setItem(name, value);
  445. }`, {
  446. isFunction: true,
  447. world: 'utility'
  448. }, originState);
  449. }
  450. await page.close(internalMetadata);
  451. }
  452. } finally {
  453. this._settingStorageState = false;
  454. }
  455. }
  456. async extendInjectedScript(source, arg) {
  457. const installInFrame = frame => frame.extendInjectedScript(source, arg).catch(() => {});
  458. const installInPage = page => {
  459. page.on(_page6.Page.Events.InternalFrameNavigatedToNewDocument, installInFrame);
  460. return Promise.all(page.frames().map(installInFrame));
  461. };
  462. this.on(BrowserContext.Events.Page, installInPage);
  463. return Promise.all(this.pages().map(installInPage));
  464. }
  465. async _harStart(page, options) {
  466. const harId = (0, _utils.createGuid)();
  467. this._harRecorders.set(harId, new _harRecorder.HarRecorder(this, page, options));
  468. return harId;
  469. }
  470. async _harExport(harId) {
  471. const recorder = this._harRecorders.get(harId || '');
  472. return recorder.export();
  473. }
  474. addRouteInFlight(route) {
  475. this._routesInFlight.add(route);
  476. }
  477. removeRouteInFlight(route) {
  478. this._routesInFlight.delete(route);
  479. }
  480. async _cancelAllRoutesInFlight() {
  481. await Promise.all([...this._routesInFlight].map(r => r.abort())).catch(() => {});
  482. this._routesInFlight.clear();
  483. }
  484. }
  485. exports.BrowserContext = BrowserContext;
  486. BrowserContext.Events = {
  487. Console: 'console',
  488. Close: 'close',
  489. Dialog: 'dialog',
  490. Page: 'page',
  491. Request: 'request',
  492. Response: 'response',
  493. RequestFailed: 'requestfailed',
  494. RequestFinished: 'requestfinished',
  495. RequestAborted: 'requestaborted',
  496. RequestFulfilled: 'requestfulfilled',
  497. RequestContinued: 'requestcontinued',
  498. BeforeClose: 'beforeclose',
  499. VideoStarted: 'videostarted'
  500. };
  501. function assertBrowserContextIsNotOwned(context) {
  502. for (const page of context.pages()) {
  503. if (page._ownedContext) throw new Error('Please use browser.newContext() for multi-page scripts that share the context.');
  504. }
  505. }
  506. function validateBrowserContextOptions(options, browserOptions) {
  507. if (options.noDefaultViewport && options.deviceScaleFactor !== undefined) throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
  508. if (options.noDefaultViewport && !!options.isMobile) throw new Error(`"isMobile" option is not supported with null "viewport"`);
  509. if (options.acceptDownloads === undefined) options.acceptDownloads = true;
  510. if (!options.viewport && !options.noDefaultViewport) options.viewport = {
  511. width: 1280,
  512. height: 720
  513. };
  514. if (options.recordVideo) {
  515. if (!options.recordVideo.size) {
  516. if (options.noDefaultViewport) {
  517. options.recordVideo.size = {
  518. width: 800,
  519. height: 600
  520. };
  521. } else {
  522. const size = options.viewport;
  523. const scale = Math.min(1, 800 / Math.max(size.width, size.height));
  524. options.recordVideo.size = {
  525. width: Math.floor(size.width * scale),
  526. height: Math.floor(size.height * scale)
  527. };
  528. }
  529. }
  530. // Make sure both dimensions are odd, this is required for vp8
  531. options.recordVideo.size.width &= ~1;
  532. options.recordVideo.size.height &= ~1;
  533. }
  534. if (options.proxy) {
  535. 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' } })"`);
  536. options.proxy = normalizeProxySettings(options.proxy);
  537. }
  538. verifyGeolocation(options.geolocation);
  539. }
  540. function verifyGeolocation(geolocation) {
  541. if (!geolocation) return;
  542. geolocation.accuracy = geolocation.accuracy || 0;
  543. const {
  544. longitude,
  545. latitude,
  546. accuracy
  547. } = geolocation;
  548. if (longitude < -180 || longitude > 180) throw new Error(`geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.`);
  549. if (latitude < -90 || latitude > 90) throw new Error(`geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed.`);
  550. if (accuracy < 0) throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
  551. }
  552. function normalizeProxySettings(proxy) {
  553. let {
  554. server,
  555. bypass
  556. } = proxy;
  557. let url;
  558. try {
  559. // new URL('127.0.0.1:8080') throws
  560. // new URL('localhost:8080') fails to parse host or protocol
  561. // In both of these cases, we need to try re-parse URL with `http://` prefix.
  562. url = new URL(server);
  563. if (!url.host || !url.protocol) url = new URL('http://' + server);
  564. } catch (e) {
  565. url = new URL('http://' + server);
  566. }
  567. if (url.protocol === 'socks4:' && (proxy.username || proxy.password)) throw new Error(`Socks4 proxy protocol does not support authentication`);
  568. if (url.protocol === 'socks5:' && (proxy.username || proxy.password)) throw new Error(`Browser does not support socks5 proxy authentication`);
  569. server = url.protocol + '//' + url.host;
  570. if (bypass) bypass = bypass.split(',').map(t => t.trim()).join(',');
  571. return {
  572. ...proxy,
  573. server,
  574. bypass
  575. };
  576. }
  577. const paramsThatAllowContextReuse = ['colorScheme', 'forcedColors', 'reducedMotion', 'screen', 'userAgent', 'viewport'];
  578. const defaultNewContextParamValues = {
  579. noDefaultViewport: false,
  580. ignoreHTTPSErrors: false,
  581. javaScriptEnabled: true,
  582. bypassCSP: false,
  583. offline: false,
  584. isMobile: false,
  585. hasTouch: false,
  586. acceptDownloads: true,
  587. strictSelectors: false,
  588. serviceWorkers: 'allow',
  589. locale: 'en-US'
  590. };