page.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.Worker = exports.PageBinding = exports.Page = void 0;
  6. var frames = _interopRequireWildcard(require("./frames"));
  7. var input = _interopRequireWildcard(require("./input"));
  8. var js = _interopRequireWildcard(require("./javascript"));
  9. var network = _interopRequireWildcard(require("./network"));
  10. var _screenshotter = require("./screenshotter");
  11. var _timeoutSettings = require("../common/timeoutSettings");
  12. var _browserContext = require("./browserContext");
  13. var _console = require("./console");
  14. var accessibility = _interopRequireWildcard(require("./accessibility"));
  15. var _fileChooser = require("./fileChooser");
  16. var _progress = require("./progress");
  17. var _utils = require("../utils");
  18. var _manualPromise = require("../utils/manualPromise");
  19. var _debugLogger = require("../common/debugLogger");
  20. var _comparators = require("../utils/comparators");
  21. var _instrumentation = require("./instrumentation");
  22. var _selectorParser = require("../utils/isomorphic/selectorParser");
  23. var _utilityScriptSerializers = require("./isomorphic/utilityScriptSerializers");
  24. var _errors = require("./errors");
  25. 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); }
  26. 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; }
  27. /**
  28. * Copyright 2017 Google Inc. All rights reserved.
  29. * Modifications copyright (c) Microsoft Corporation.
  30. *
  31. * Licensed under the Apache License, Version 2.0 (the "License");
  32. * you may not use this file except in compliance with the License.
  33. * You may obtain a copy of the License at
  34. *
  35. * http://www.apache.org/licenses/LICENSE-2.0
  36. *
  37. * Unless required by applicable law or agreed to in writing, software
  38. * distributed under the License is distributed on an "AS IS" BASIS,
  39. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  40. * See the License for the specific language governing permissions and
  41. * limitations under the License.
  42. */
  43. class Page extends _instrumentation.SdkObject {
  44. constructor(delegate, browserContext) {
  45. super(browserContext, 'page');
  46. this._closedState = 'open';
  47. this._closedPromise = new _manualPromise.ManualPromise();
  48. this._initialized = false;
  49. this._eventsToEmitAfterInitialized = [];
  50. this._crashed = false;
  51. this.openScope = new _utils.LongStandingScope();
  52. this._browserContext = void 0;
  53. this.keyboard = void 0;
  54. this.mouse = void 0;
  55. this.touchscreen = void 0;
  56. this._timeoutSettings = void 0;
  57. this._delegate = void 0;
  58. this._emulatedSize = void 0;
  59. this._extraHTTPHeaders = void 0;
  60. this._emulatedMedia = {};
  61. this._interceptFileChooser = false;
  62. this._pageBindings = new Map();
  63. this.initScripts = [];
  64. this._screenshotter = void 0;
  65. this._frameManager = void 0;
  66. this.accessibility = void 0;
  67. this._workers = new Map();
  68. this.pdf = void 0;
  69. this.coverage = void 0;
  70. this._clientRequestInterceptor = void 0;
  71. this._serverRequestInterceptor = void 0;
  72. this._ownedContext = void 0;
  73. this._pageIsError = void 0;
  74. this._video = null;
  75. this._opener = void 0;
  76. this._isServerSideOnly = false;
  77. // Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
  78. // When throttling for tracing, 200ms between frames, except for 10 frames around the action.
  79. this._frameThrottler = new FrameThrottler(10, 35, 200);
  80. this._closeReason = void 0;
  81. this.attribution.page = this;
  82. this._delegate = delegate;
  83. this._browserContext = browserContext;
  84. this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));
  85. this.keyboard = new input.Keyboard(delegate.rawKeyboard, this);
  86. this.mouse = new input.Mouse(delegate.rawMouse, this);
  87. this.touchscreen = new input.Touchscreen(delegate.rawTouchscreen, this);
  88. this._timeoutSettings = new _timeoutSettings.TimeoutSettings(browserContext._timeoutSettings);
  89. this._screenshotter = new _screenshotter.Screenshotter(this);
  90. this._frameManager = new frames.FrameManager(this);
  91. if (delegate.pdf) this.pdf = delegate.pdf.bind(delegate);
  92. this.coverage = delegate.coverage ? delegate.coverage() : null;
  93. }
  94. async initOpener(opener) {
  95. if (!opener) return;
  96. const openerPage = await opener.pageOrError();
  97. if (openerPage instanceof Page && !openerPage.isClosed()) this._opener = openerPage;
  98. }
  99. reportAsNew(error = undefined, contextEvent = _browserContext.BrowserContext.Events.Page) {
  100. if (error) {
  101. // Initialization error could have happened because of
  102. // context/browser closure. Just ignore the page.
  103. if (this._browserContext.isClosingOrClosed()) return;
  104. this._setIsError(error);
  105. }
  106. this._initialized = true;
  107. this.emitOnContext(contextEvent, this);
  108. for (const {
  109. event,
  110. args
  111. } of this._eventsToEmitAfterInitialized) this._browserContext.emit(event, ...args);
  112. this._eventsToEmitAfterInitialized = [];
  113. // It may happen that page initialization finishes after Close event has already been sent,
  114. // in that case we fire another Close event to ensure that each reported Page will have
  115. // corresponding Close event after it is reported on the context.
  116. if (this.isClosed()) this.emit(Page.Events.Close);else this.instrumentation.onPageOpen(this);
  117. }
  118. initializedOrUndefined() {
  119. return this._initialized ? this : undefined;
  120. }
  121. emitOnContext(event, ...args) {
  122. if (this._isServerSideOnly) return;
  123. this._browserContext.emit(event, ...args);
  124. }
  125. emitOnContextOnceInitialized(event, ...args) {
  126. if (this._isServerSideOnly) return;
  127. // Some events, like console messages, may come before page is ready.
  128. // In this case, postpone the event until page is initialized,
  129. // and dispatch it to the client later, either on the live Page,
  130. // or on the "errored" Page.
  131. if (this._initialized) this._browserContext.emit(event, ...args);else this._eventsToEmitAfterInitialized.push({
  132. event,
  133. args
  134. });
  135. }
  136. async resetForReuse(metadata) {
  137. this.setDefaultNavigationTimeout(undefined);
  138. this.setDefaultTimeout(undefined);
  139. await this._removeExposedBindings();
  140. await this._removeInitScripts();
  141. await this.setClientRequestInterceptor(undefined);
  142. await this._setServerRequestInterceptor(undefined);
  143. await this.setFileChooserIntercepted(false);
  144. // Re-navigate once init scripts are gone.
  145. await this.mainFrame().goto(metadata, 'about:blank');
  146. this._emulatedSize = undefined;
  147. this._emulatedMedia = {};
  148. this._extraHTTPHeaders = undefined;
  149. this._interceptFileChooser = false;
  150. await Promise.all([this._delegate.updateEmulatedViewportSize(), this._delegate.updateEmulateMedia(), this._delegate.updateFileChooserInterception()]);
  151. await this._delegate.resetForReuse();
  152. }
  153. _didClose() {
  154. this._frameManager.dispose();
  155. this._frameThrottler.dispose();
  156. (0, _utils.assert)(this._closedState !== 'closed', 'Page closed twice');
  157. this._closedState = 'closed';
  158. this.emit(Page.Events.Close);
  159. this._closedPromise.resolve();
  160. this.instrumentation.onPageClose(this);
  161. this.openScope.close(new _errors.TargetClosedError());
  162. }
  163. _didCrash() {
  164. this._frameManager.dispose();
  165. this._frameThrottler.dispose();
  166. this.emit(Page.Events.Crash);
  167. this._crashed = true;
  168. this.instrumentation.onPageClose(this);
  169. this.openScope.close(new Error('Page crashed'));
  170. }
  171. async _onFileChooserOpened(handle) {
  172. let multiple;
  173. try {
  174. multiple = await handle.evaluate(element => !!element.multiple);
  175. } catch (e) {
  176. // Frame/context may be gone during async processing. Do not throw.
  177. return;
  178. }
  179. if (!this.listenerCount(Page.Events.FileChooser)) {
  180. handle.dispose();
  181. return;
  182. }
  183. const fileChooser = new _fileChooser.FileChooser(this, handle, multiple);
  184. this.emit(Page.Events.FileChooser, fileChooser);
  185. }
  186. context() {
  187. return this._browserContext;
  188. }
  189. opener() {
  190. return this._opener;
  191. }
  192. mainFrame() {
  193. return this._frameManager.mainFrame();
  194. }
  195. frames() {
  196. return this._frameManager.frames();
  197. }
  198. setDefaultNavigationTimeout(timeout) {
  199. this._timeoutSettings.setDefaultNavigationTimeout(timeout);
  200. }
  201. setDefaultTimeout(timeout) {
  202. this._timeoutSettings.setDefaultTimeout(timeout);
  203. }
  204. async exposeBinding(name, needsHandle, playwrightBinding) {
  205. if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
  206. if (this._browserContext._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered in the browser context`);
  207. const binding = new PageBinding(name, playwrightBinding, needsHandle);
  208. this._pageBindings.set(name, binding);
  209. await this._delegate.exposeBinding(binding);
  210. }
  211. async _removeExposedBindings() {
  212. for (const key of this._pageBindings.keys()) {
  213. if (!key.startsWith('__pw')) this._pageBindings.delete(key);
  214. }
  215. await this._delegate.removeExposedBindings();
  216. }
  217. setExtraHTTPHeaders(headers) {
  218. this._extraHTTPHeaders = headers;
  219. return this._delegate.updateExtraHTTPHeaders();
  220. }
  221. extraHTTPHeaders() {
  222. return this._extraHTTPHeaders;
  223. }
  224. async _onBindingCalled(payload, context) {
  225. if (this._closedState === 'closed') return;
  226. await PageBinding.dispatch(this, payload, context);
  227. }
  228. _addConsoleMessage(type, args, location, text) {
  229. const message = new _console.ConsoleMessage(this, type, text, args, location);
  230. const intercepted = this._frameManager.interceptConsoleMessage(message);
  231. if (intercepted) {
  232. args.forEach(arg => arg.dispose());
  233. return;
  234. }
  235. this.emitOnContextOnceInitialized(_browserContext.BrowserContext.Events.Console, message);
  236. }
  237. async reload(metadata, options) {
  238. const controller = new _progress.ProgressController(metadata, this);
  239. return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
  240. // Note: waitForNavigation may fail before we get response to reload(),
  241. // so we should await it immediately.
  242. const [response] = await Promise.all([
  243. // Reload must be a new document, and should not be confused with a stray pushState.
  244. this.mainFrame()._waitForNavigation(progress, true /* requiresNewDocument */, options), this._delegate.reload()]);
  245. return response;
  246. }), this._timeoutSettings.navigationTimeout(options));
  247. }
  248. async goBack(metadata, options) {
  249. const controller = new _progress.ProgressController(metadata, this);
  250. return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
  251. // Note: waitForNavigation may fail before we get response to goBack,
  252. // so we should catch it immediately.
  253. let error;
  254. const waitPromise = this.mainFrame()._waitForNavigation(progress, false /* requiresNewDocument */, options).catch(e => {
  255. error = e;
  256. return null;
  257. });
  258. const result = await this._delegate.goBack();
  259. if (!result) return null;
  260. const response = await waitPromise;
  261. if (error) throw error;
  262. return response;
  263. }), this._timeoutSettings.navigationTimeout(options));
  264. }
  265. async goForward(metadata, options) {
  266. const controller = new _progress.ProgressController(metadata, this);
  267. return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
  268. // Note: waitForNavigation may fail before we get response to goForward,
  269. // so we should catch it immediately.
  270. let error;
  271. const waitPromise = this.mainFrame()._waitForNavigation(progress, false /* requiresNewDocument */, options).catch(e => {
  272. error = e;
  273. return null;
  274. });
  275. const result = await this._delegate.goForward();
  276. if (!result) return null;
  277. const response = await waitPromise;
  278. if (error) throw error;
  279. return response;
  280. }), this._timeoutSettings.navigationTimeout(options));
  281. }
  282. async emulateMedia(options) {
  283. if (options.media !== undefined) this._emulatedMedia.media = options.media;
  284. if (options.colorScheme !== undefined) this._emulatedMedia.colorScheme = options.colorScheme;
  285. if (options.reducedMotion !== undefined) this._emulatedMedia.reducedMotion = options.reducedMotion;
  286. if (options.forcedColors !== undefined) this._emulatedMedia.forcedColors = options.forcedColors;
  287. await this._delegate.updateEmulateMedia();
  288. }
  289. emulatedMedia() {
  290. var _contextOptions$color, _contextOptions$reduc, _contextOptions$force;
  291. const contextOptions = this._browserContext._options;
  292. return {
  293. media: this._emulatedMedia.media || 'no-override',
  294. colorScheme: this._emulatedMedia.colorScheme !== undefined ? this._emulatedMedia.colorScheme : (_contextOptions$color = contextOptions.colorScheme) !== null && _contextOptions$color !== void 0 ? _contextOptions$color : 'light',
  295. reducedMotion: this._emulatedMedia.reducedMotion !== undefined ? this._emulatedMedia.reducedMotion : (_contextOptions$reduc = contextOptions.reducedMotion) !== null && _contextOptions$reduc !== void 0 ? _contextOptions$reduc : 'no-preference',
  296. forcedColors: this._emulatedMedia.forcedColors !== undefined ? this._emulatedMedia.forcedColors : (_contextOptions$force = contextOptions.forcedColors) !== null && _contextOptions$force !== void 0 ? _contextOptions$force : 'none'
  297. };
  298. }
  299. async setViewportSize(viewportSize) {
  300. this._emulatedSize = {
  301. viewport: {
  302. ...viewportSize
  303. },
  304. screen: {
  305. ...viewportSize
  306. }
  307. };
  308. await this._delegate.updateEmulatedViewportSize();
  309. }
  310. viewportSize() {
  311. var _this$emulatedSize;
  312. return ((_this$emulatedSize = this.emulatedSize()) === null || _this$emulatedSize === void 0 ? void 0 : _this$emulatedSize.viewport) || null;
  313. }
  314. emulatedSize() {
  315. if (this._emulatedSize) return this._emulatedSize;
  316. const contextOptions = this._browserContext._options;
  317. return contextOptions.viewport ? {
  318. viewport: contextOptions.viewport,
  319. screen: contextOptions.screen || contextOptions.viewport
  320. } : null;
  321. }
  322. async bringToFront() {
  323. await this._delegate.bringToFront();
  324. }
  325. async addInitScript(source) {
  326. this.initScripts.push(source);
  327. await this._delegate.addInitScript(source);
  328. }
  329. async _removeInitScripts() {
  330. this.initScripts.splice(0, this.initScripts.length);
  331. await this._delegate.removeInitScripts();
  332. }
  333. needsRequestInterception() {
  334. return !!this._clientRequestInterceptor || !!this._serverRequestInterceptor || !!this._browserContext._requestInterceptor;
  335. }
  336. async setClientRequestInterceptor(handler) {
  337. this._clientRequestInterceptor = handler;
  338. await this._delegate.updateRequestInterception();
  339. }
  340. async _setServerRequestInterceptor(handler) {
  341. this._serverRequestInterceptor = handler;
  342. await this._delegate.updateRequestInterception();
  343. }
  344. async expectScreenshot(metadata, options = {}) {
  345. const locator = options.locator;
  346. const rafrafScreenshot = locator ? async (progress, timeout) => {
  347. return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options.screenshotOptions || {});
  348. } : async (progress, timeout) => {
  349. await this.mainFrame().rafrafTimeout(timeout);
  350. return await this._screenshotter.screenshotPage(progress, options.screenshotOptions || {});
  351. };
  352. const comparator = (0, _comparators.getComparator)('image/png');
  353. const controller = new _progress.ProgressController(metadata, this);
  354. if (!options.expected && options.isNot) return {
  355. errorMessage: '"not" matcher requires expected result'
  356. };
  357. try {
  358. const format = (0, _screenshotter.validateScreenshotOptions)(options.screenshotOptions || {});
  359. if (format !== 'png') throw new Error('Only PNG screenshots are supported');
  360. } catch (error) {
  361. return {
  362. errorMessage: error.message
  363. };
  364. }
  365. let intermediateResult = undefined;
  366. const areEqualScreenshots = (actual, expected, previous) => {
  367. const comparatorResult = actual && expected ? comparator(actual, expected, options.comparatorOptions) : undefined;
  368. if (comparatorResult !== undefined && !!comparatorResult === !!options.isNot) return true;
  369. if (comparatorResult) intermediateResult = {
  370. errorMessage: comparatorResult.errorMessage,
  371. diff: comparatorResult.diff,
  372. actual,
  373. previous
  374. };
  375. return false;
  376. };
  377. const callTimeout = this._timeoutSettings.timeout(options);
  378. return controller.run(async progress => {
  379. let actual;
  380. let previous;
  381. const pollIntervals = [0, 100, 250, 500];
  382. progress.log(`${metadata.apiName}${callTimeout ? ` with timeout ${callTimeout}ms` : ''}`);
  383. if (options.expected) progress.log(` verifying given screenshot expectation`);else progress.log(` generating new stable screenshot expectation`);
  384. let isFirstIteration = true;
  385. while (true) {
  386. var _pollIntervals$shift;
  387. progress.throwIfAborted();
  388. if (this.isClosed()) throw new Error('The page has closed');
  389. const screenshotTimeout = (_pollIntervals$shift = pollIntervals.shift()) !== null && _pollIntervals$shift !== void 0 ? _pollIntervals$shift : 1000;
  390. if (screenshotTimeout) progress.log(`waiting ${screenshotTimeout}ms before taking screenshot`);
  391. previous = actual;
  392. actual = await rafrafScreenshot(progress, screenshotTimeout).catch(e => {
  393. progress.log(`failed to take screenshot - ` + e.message);
  394. return undefined;
  395. });
  396. if (!actual) continue;
  397. // Compare against expectation for the first iteration.
  398. const expectation = options.expected && isFirstIteration ? options.expected : previous;
  399. if (areEqualScreenshots(actual, expectation, previous)) break;
  400. if (intermediateResult) progress.log(intermediateResult.errorMessage);
  401. isFirstIteration = false;
  402. }
  403. if (!isFirstIteration) progress.log(`captured a stable screenshot`);
  404. if (!options.expected) return {
  405. actual
  406. };
  407. if (isFirstIteration) {
  408. progress.log(`screenshot matched expectation`);
  409. return {};
  410. }
  411. if (areEqualScreenshots(actual, options.expected, previous)) {
  412. progress.log(`screenshot matched expectation`);
  413. return {};
  414. }
  415. throw new Error(intermediateResult.errorMessage);
  416. }, callTimeout).catch(e => {
  417. // Q: Why not throw upon isSessionClosedError(e) as in other places?
  418. // A: We want user to receive a friendly diff between actual and expected/previous.
  419. if (js.isJavaScriptErrorInEvaluate(e) || (0, _selectorParser.isInvalidSelectorError)(e)) throw e;
  420. return {
  421. log: e.message ? [...metadata.log, e.message] : metadata.log,
  422. ...intermediateResult,
  423. errorMessage: e.message
  424. };
  425. });
  426. }
  427. async screenshot(metadata, options = {}) {
  428. const controller = new _progress.ProgressController(metadata, this);
  429. return controller.run(progress => this._screenshotter.screenshotPage(progress, options), this._timeoutSettings.timeout(options));
  430. }
  431. async close(metadata, options = {}) {
  432. if (this._closedState === 'closed') return;
  433. if (options.reason) this._closeReason = options.reason;
  434. const runBeforeUnload = !!options.runBeforeUnload;
  435. if (this._closedState !== 'closing') {
  436. this._closedState = 'closing';
  437. // This might throw if the browser context containing the page closes
  438. // while we are trying to close the page.
  439. await this._delegate.closePage(runBeforeUnload).catch(e => _debugLogger.debugLogger.log('error', e));
  440. }
  441. if (!runBeforeUnload) await this._closedPromise;
  442. if (this._ownedContext) await this._ownedContext.close(options);
  443. }
  444. _setIsError(error) {
  445. this._pageIsError = error;
  446. this._frameManager.createDummyMainFrameIfNeeded();
  447. }
  448. isClosed() {
  449. return this._closedState === 'closed';
  450. }
  451. hasCrashed() {
  452. return this._crashed;
  453. }
  454. isClosedOrClosingOrCrashed() {
  455. return this._closedState !== 'open' || this._crashed;
  456. }
  457. _addWorker(workerId, worker) {
  458. this._workers.set(workerId, worker);
  459. this.emit(Page.Events.Worker, worker);
  460. }
  461. _removeWorker(workerId) {
  462. const worker = this._workers.get(workerId);
  463. if (!worker) return;
  464. worker.didClose();
  465. this._workers.delete(workerId);
  466. }
  467. _clearWorkers() {
  468. for (const [workerId, worker] of this._workers) {
  469. worker.didClose();
  470. this._workers.delete(workerId);
  471. }
  472. }
  473. async setFileChooserIntercepted(enabled) {
  474. this._interceptFileChooser = enabled;
  475. await this._delegate.updateFileChooserInterception();
  476. }
  477. fileChooserIntercepted() {
  478. return this._interceptFileChooser;
  479. }
  480. frameNavigatedToNewDocument(frame) {
  481. this.emit(Page.Events.InternalFrameNavigatedToNewDocument, frame);
  482. const url = frame.url();
  483. if (!url.startsWith('http')) return;
  484. const purl = network.parsedURL(url);
  485. if (purl) this._browserContext.addVisitedOrigin(purl.origin);
  486. }
  487. allBindings() {
  488. return [...this._browserContext._pageBindings.values(), ...this._pageBindings.values()];
  489. }
  490. getBinding(name) {
  491. return this._pageBindings.get(name) || this._browserContext._pageBindings.get(name);
  492. }
  493. setScreencastOptions(options) {
  494. this._delegate.setScreencastOptions(options).catch(e => _debugLogger.debugLogger.log('error', e));
  495. this._frameThrottler.setThrottlingEnabled(!!options);
  496. }
  497. throttleScreencastFrameAck(ack) {
  498. // Don't ack immediately, tracing has smart throttling logic that is implemented here.
  499. this._frameThrottler.ack(ack);
  500. }
  501. temporarilyDisableTracingScreencastThrottling() {
  502. this._frameThrottler.recharge();
  503. }
  504. async hideHighlight() {
  505. await Promise.all(this.frames().map(frame => frame.hideHighlight().catch(() => {})));
  506. }
  507. markAsServerSideOnly() {
  508. this._isServerSideOnly = true;
  509. }
  510. }
  511. exports.Page = Page;
  512. Page.Events = {
  513. Close: 'close',
  514. Crash: 'crash',
  515. Download: 'download',
  516. FileChooser: 'filechooser',
  517. FrameAttached: 'frameattached',
  518. FrameDetached: 'framedetached',
  519. InternalFrameNavigatedToNewDocument: 'internalframenavigatedtonewdocument',
  520. ScreencastFrame: 'screencastframe',
  521. Video: 'video',
  522. WebSocket: 'websocket',
  523. Worker: 'worker'
  524. };
  525. class Worker extends _instrumentation.SdkObject {
  526. constructor(parent, url) {
  527. super(parent, 'worker');
  528. this._url = void 0;
  529. this._executionContextPromise = void 0;
  530. this._executionContextCallback = void 0;
  531. this._existingExecutionContext = null;
  532. this.openScope = new _utils.LongStandingScope();
  533. this._url = url;
  534. this._executionContextCallback = () => {};
  535. this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
  536. }
  537. _createExecutionContext(delegate) {
  538. this._existingExecutionContext = new js.ExecutionContext(this, delegate, 'worker');
  539. this._executionContextCallback(this._existingExecutionContext);
  540. }
  541. url() {
  542. return this._url;
  543. }
  544. didClose() {
  545. if (this._existingExecutionContext) this._existingExecutionContext.contextDestroyed('Worker was closed');
  546. this.emit(Worker.Events.Close, this);
  547. this.openScope.close(new Error('Worker closed'));
  548. }
  549. async evaluateExpression(expression, isFunction, arg) {
  550. return js.evaluateExpression(await this._executionContextPromise, expression, {
  551. returnByValue: true,
  552. isFunction
  553. }, arg);
  554. }
  555. async evaluateExpressionHandle(expression, isFunction, arg) {
  556. return js.evaluateExpression(await this._executionContextPromise, expression, {
  557. returnByValue: false,
  558. isFunction
  559. }, arg);
  560. }
  561. }
  562. exports.Worker = Worker;
  563. Worker.Events = {
  564. Close: 'close'
  565. };
  566. class PageBinding {
  567. constructor(name, playwrightFunction, needsHandle) {
  568. this.name = void 0;
  569. this.playwrightFunction = void 0;
  570. this.source = void 0;
  571. this.needsHandle = void 0;
  572. this.name = name;
  573. this.playwrightFunction = playwrightFunction;
  574. this.source = `(${addPageBinding.toString()})(${JSON.stringify(name)}, ${needsHandle}, (${_utilityScriptSerializers.source})())`;
  575. this.needsHandle = needsHandle;
  576. }
  577. static async dispatch(page, payload, context) {
  578. const {
  579. name,
  580. seq,
  581. serializedArgs
  582. } = JSON.parse(payload);
  583. try {
  584. (0, _utils.assert)(context.world);
  585. const binding = page.getBinding(name);
  586. let result;
  587. if (binding.needsHandle) {
  588. const handle = await context.evaluateHandle(takeHandle, {
  589. name,
  590. seq
  591. }).catch(e => null);
  592. result = await binding.playwrightFunction({
  593. frame: context.frame,
  594. page,
  595. context: page._browserContext
  596. }, handle);
  597. } else {
  598. const args = serializedArgs.map(a => (0, _utilityScriptSerializers.parseEvaluationResultValue)(a));
  599. result = await binding.playwrightFunction({
  600. frame: context.frame,
  601. page,
  602. context: page._browserContext
  603. }, ...args);
  604. }
  605. context.evaluate(deliverResult, {
  606. name,
  607. seq,
  608. result
  609. }).catch(e => _debugLogger.debugLogger.log('error', e));
  610. } catch (error) {
  611. if ((0, _utils.isError)(error)) context.evaluate(deliverError, {
  612. name,
  613. seq,
  614. message: error.message,
  615. stack: error.stack
  616. }).catch(e => _debugLogger.debugLogger.log('error', e));else context.evaluate(deliverErrorValue, {
  617. name,
  618. seq,
  619. error
  620. }).catch(e => _debugLogger.debugLogger.log('error', e));
  621. }
  622. function takeHandle(arg) {
  623. const handle = globalThis[arg.name]['handles'].get(arg.seq);
  624. globalThis[arg.name]['handles'].delete(arg.seq);
  625. return handle;
  626. }
  627. function deliverResult(arg) {
  628. globalThis[arg.name]['callbacks'].get(arg.seq).resolve(arg.result);
  629. globalThis[arg.name]['callbacks'].delete(arg.seq);
  630. }
  631. function deliverError(arg) {
  632. const error = new Error(arg.message);
  633. error.stack = arg.stack;
  634. globalThis[arg.name]['callbacks'].get(arg.seq).reject(error);
  635. globalThis[arg.name]['callbacks'].delete(arg.seq);
  636. }
  637. function deliverErrorValue(arg) {
  638. globalThis[arg.name]['callbacks'].get(arg.seq).reject(arg.error);
  639. globalThis[arg.name]['callbacks'].delete(arg.seq);
  640. }
  641. }
  642. }
  643. exports.PageBinding = PageBinding;
  644. function addPageBinding(bindingName, needsHandle, utilityScriptSerializers) {
  645. const binding = globalThis[bindingName];
  646. if (binding.__installed) return;
  647. globalThis[bindingName] = (...args) => {
  648. const me = globalThis[bindingName];
  649. if (needsHandle && args.slice(1).some(arg => arg !== undefined)) throw new Error(`exposeBindingHandle supports a single argument, ${args.length} received`);
  650. let callbacks = me['callbacks'];
  651. if (!callbacks) {
  652. callbacks = new Map();
  653. me['callbacks'] = callbacks;
  654. }
  655. const seq = (me['lastSeq'] || 0) + 1;
  656. me['lastSeq'] = seq;
  657. let handles = me['handles'];
  658. if (!handles) {
  659. handles = new Map();
  660. me['handles'] = handles;
  661. }
  662. const promise = new Promise((resolve, reject) => callbacks.set(seq, {
  663. resolve,
  664. reject
  665. }));
  666. let payload;
  667. if (needsHandle) {
  668. handles.set(seq, args[0]);
  669. payload = {
  670. name: bindingName,
  671. seq
  672. };
  673. } else {
  674. const serializedArgs = [];
  675. for (let i = 0; i < args.length; i++) {
  676. serializedArgs[i] = utilityScriptSerializers.serializeAsCallArgument(args[i], v => {
  677. return {
  678. fallThrough: v
  679. };
  680. });
  681. }
  682. payload = {
  683. name: bindingName,
  684. seq,
  685. serializedArgs
  686. };
  687. }
  688. binding(JSON.stringify(payload));
  689. return promise;
  690. };
  691. globalThis[bindingName].__installed = true;
  692. }
  693. class FrameThrottler {
  694. constructor(nonThrottledFrames, defaultInterval, throttlingInterval) {
  695. this._acks = [];
  696. this._defaultInterval = void 0;
  697. this._throttlingInterval = void 0;
  698. this._nonThrottledFrames = void 0;
  699. this._budget = void 0;
  700. this._throttlingEnabled = false;
  701. this._timeoutId = void 0;
  702. this._nonThrottledFrames = nonThrottledFrames;
  703. this._budget = nonThrottledFrames;
  704. this._defaultInterval = defaultInterval;
  705. this._throttlingInterval = throttlingInterval;
  706. this._tick();
  707. }
  708. dispose() {
  709. if (this._timeoutId) {
  710. clearTimeout(this._timeoutId);
  711. this._timeoutId = undefined;
  712. }
  713. }
  714. setThrottlingEnabled(enabled) {
  715. this._throttlingEnabled = enabled;
  716. }
  717. recharge() {
  718. // Send all acks, reset budget.
  719. for (const ack of this._acks) ack();
  720. this._acks = [];
  721. this._budget = this._nonThrottledFrames;
  722. if (this._timeoutId) {
  723. clearTimeout(this._timeoutId);
  724. this._tick();
  725. }
  726. }
  727. ack(ack) {
  728. if (!this._timeoutId) {
  729. // Already disposed.
  730. ack();
  731. return;
  732. }
  733. this._acks.push(ack);
  734. }
  735. _tick() {
  736. const ack = this._acks.shift();
  737. if (ack) {
  738. --this._budget;
  739. ack();
  740. }
  741. if (this._throttlingEnabled && this._budget <= 0) {
  742. // Non-throttled frame budget is exceeded. Next ack will be throttled.
  743. this._timeoutId = setTimeout(() => this._tick(), this._throttlingInterval);
  744. } else {
  745. // Either not throttling, or still under budget. Next ack will be after the default timeout.
  746. this._timeoutId = setTimeout(() => this._tick(), this._defaultInterval);
  747. }
  748. }
  749. }