crPage.js 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.CRPage = void 0;
  6. var _path = _interopRequireDefault(require("path"));
  7. var _eventsHelper = require("../../utils/eventsHelper");
  8. var _registry = require("../registry");
  9. var _stackTrace = require("../../utils/stackTrace");
  10. var _utils = require("../../utils");
  11. var dialog = _interopRequireWildcard(require("../dialog"));
  12. var dom = _interopRequireWildcard(require("../dom"));
  13. var frames = _interopRequireWildcard(require("../frames"));
  14. var _helper = require("../helper");
  15. var network = _interopRequireWildcard(require("../network"));
  16. var _page = require("../page");
  17. var _crAccessibility = require("./crAccessibility");
  18. var _crBrowser = require("./crBrowser");
  19. var _crCoverage = require("./crCoverage");
  20. var _crDragDrop = require("./crDragDrop");
  21. var _crExecutionContext = require("./crExecutionContext");
  22. var _crInput = require("./crInput");
  23. var _crNetworkManager = require("./crNetworkManager");
  24. var _crPdf = require("./crPdf");
  25. var _crProtocolHelper = require("./crProtocolHelper");
  26. var _defaultFontFamilies = require("./defaultFontFamilies");
  27. var _videoRecorder = require("./videoRecorder");
  28. var _browserContext = require("../browserContext");
  29. var _errors = require("../errors");
  30. var _protocolError = require("../protocolError");
  31. 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); }
  32. 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; }
  33. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  34. /**
  35. * Copyright 2017 Google Inc. All rights reserved.
  36. * Modifications copyright (c) Microsoft Corporation.
  37. *
  38. * Licensed under the Apache License, Version 2.0 (the "License");
  39. * you may not use this file except in compliance with the License.
  40. * You may obtain a copy of the License at
  41. *
  42. * http://www.apache.org/licenses/LICENSE-2.0
  43. *
  44. * Unless required by applicable law or agreed to in writing, software
  45. * distributed under the License is distributed on an "AS IS" BASIS,
  46. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  47. * See the License for the specific language governing permissions and
  48. * limitations under the License.
  49. */
  50. const UTILITY_WORLD_NAME = '__playwright_utility_world__';
  51. class CRPage {
  52. static mainFrameSession(page) {
  53. const crPage = page._delegate;
  54. return crPage._mainFrameSession;
  55. }
  56. constructor(client, targetId, browserContext, opener, bits) {
  57. this._mainFrameSession = void 0;
  58. this._sessions = new Map();
  59. this._page = void 0;
  60. this.rawMouse = void 0;
  61. this.rawKeyboard = void 0;
  62. this.rawTouchscreen = void 0;
  63. this._targetId = void 0;
  64. this._opener = void 0;
  65. this._pdf = void 0;
  66. this._coverage = void 0;
  67. this._browserContext = void 0;
  68. this._pagePromise = void 0;
  69. this._initializedPage = null;
  70. this._isBackgroundPage = void 0;
  71. // Holds window features for the next popup being opened via window.open,
  72. // until the popup target arrives. This could be racy if two oopifs
  73. // simultaneously call window.open with window features: the order
  74. // of their Page.windowOpen events is not guaranteed to match the order
  75. // of new popup targets.
  76. this._nextWindowOpenPopupFeatures = [];
  77. this._targetId = targetId;
  78. this._opener = opener;
  79. this._isBackgroundPage = bits.isBackgroundPage;
  80. const dragManager = new _crDragDrop.DragManager(this);
  81. this.rawKeyboard = new _crInput.RawKeyboardImpl(client, browserContext._browser._platform() === 'mac', dragManager);
  82. this.rawMouse = new _crInput.RawMouseImpl(this, client, dragManager);
  83. this.rawTouchscreen = new _crInput.RawTouchscreenImpl(client);
  84. this._pdf = new _crPdf.CRPDF(client);
  85. this._coverage = new _crCoverage.CRCoverage(client);
  86. this._browserContext = browserContext;
  87. this._page = new _page.Page(this, browserContext);
  88. this._mainFrameSession = new FrameSession(this, client, targetId, null);
  89. this._sessions.set(targetId, this._mainFrameSession);
  90. if (opener && !browserContext._options.noDefaultViewport) {
  91. const features = opener._nextWindowOpenPopupFeatures.shift() || [];
  92. const viewportSize = _helper.helper.getViewportSizeFromWindowFeatures(features);
  93. if (viewportSize) this._page._emulatedSize = {
  94. viewport: viewportSize,
  95. screen: viewportSize
  96. };
  97. }
  98. // Note: it is important to call |reportAsNew| before resolving pageOrError promise,
  99. // so that anyone who awaits pageOrError got a ready and reported page.
  100. this._pagePromise = this._mainFrameSession._initialize(bits.hasUIWindow).then(async r => {
  101. await this._page.initOpener(this._opener);
  102. return r;
  103. }).catch(async e => {
  104. await this._page.initOpener(this._opener);
  105. throw e;
  106. }).then(() => {
  107. this._initializedPage = this._page;
  108. this._reportAsNew();
  109. return this._page;
  110. }).catch(e => {
  111. this._reportAsNew(e);
  112. return e;
  113. });
  114. }
  115. potentiallyUninitializedPage() {
  116. return this._page;
  117. }
  118. _reportAsNew(error) {
  119. this._page.reportAsNew(error, this._isBackgroundPage ? _crBrowser.CRBrowserContext.CREvents.BackgroundPage : _browserContext.BrowserContext.Events.Page);
  120. }
  121. async _forAllFrameSessions(cb) {
  122. const frameSessions = Array.from(this._sessions.values());
  123. await Promise.all(frameSessions.map(frameSession => {
  124. if (frameSession._isMainFrame()) return cb(frameSession);
  125. return cb(frameSession).catch(e => {
  126. // Broadcasting a message to the closed iframe should be a noop.
  127. if ((0, _protocolError.isSessionClosedError)(e)) return;
  128. throw e;
  129. });
  130. }));
  131. }
  132. _sessionForFrame(frame) {
  133. // Frame id equals target id.
  134. while (!this._sessions.has(frame._id)) {
  135. const parent = frame.parentFrame();
  136. if (!parent) throw new Error(`Frame has been detached.`);
  137. frame = parent;
  138. }
  139. return this._sessions.get(frame._id);
  140. }
  141. _sessionForHandle(handle) {
  142. const frame = handle._context.frame;
  143. return this._sessionForFrame(frame);
  144. }
  145. willBeginDownload() {
  146. this._mainFrameSession._willBeginDownload();
  147. }
  148. async pageOrError() {
  149. return this._pagePromise;
  150. }
  151. didClose() {
  152. for (const session of this._sessions.values()) session.dispose();
  153. this._page._didClose();
  154. }
  155. async navigateFrame(frame, url, referrer) {
  156. return this._sessionForFrame(frame)._navigate(frame, url, referrer);
  157. }
  158. async exposeBinding(binding) {
  159. await this._forAllFrameSessions(frame => frame._initBinding(binding));
  160. await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(binding.source).catch(e => {})));
  161. }
  162. async removeExposedBindings() {
  163. await this._forAllFrameSessions(frame => frame._removeExposedBindings());
  164. }
  165. async updateExtraHTTPHeaders() {
  166. await this._forAllFrameSessions(frame => frame._updateExtraHTTPHeaders(false));
  167. }
  168. async updateGeolocation() {
  169. await this._forAllFrameSessions(frame => frame._updateGeolocation(false));
  170. }
  171. async updateOffline() {
  172. await this._forAllFrameSessions(frame => frame._updateOffline(false));
  173. }
  174. async updateHttpCredentials() {
  175. await this._forAllFrameSessions(frame => frame._updateHttpCredentials(false));
  176. }
  177. async updateEmulatedViewportSize(preserveWindowBoundaries) {
  178. await this._mainFrameSession._updateViewport(preserveWindowBoundaries);
  179. }
  180. async bringToFront() {
  181. await this._mainFrameSession._client.send('Page.bringToFront');
  182. }
  183. async updateEmulateMedia() {
  184. await this._forAllFrameSessions(frame => frame._updateEmulateMedia());
  185. }
  186. async updateUserAgent() {
  187. await this._forAllFrameSessions(frame => frame._updateUserAgent());
  188. }
  189. async updateRequestInterception() {
  190. await this._forAllFrameSessions(frame => frame._updateRequestInterception());
  191. }
  192. async updateFileChooserInterception() {
  193. await this._forAllFrameSessions(frame => frame._updateFileChooserInterception(false));
  194. }
  195. async reload() {
  196. await this._mainFrameSession._client.send('Page.reload');
  197. }
  198. async _go(delta) {
  199. const history = await this._mainFrameSession._client.send('Page.getNavigationHistory');
  200. const entry = history.entries[history.currentIndex + delta];
  201. if (!entry) return false;
  202. await this._mainFrameSession._client.send('Page.navigateToHistoryEntry', {
  203. entryId: entry.id
  204. });
  205. return true;
  206. }
  207. goBack() {
  208. return this._go(-1);
  209. }
  210. goForward() {
  211. return this._go(+1);
  212. }
  213. async addInitScript(source, world = 'main') {
  214. await this._forAllFrameSessions(frame => frame._evaluateOnNewDocument(source, world));
  215. }
  216. async removeInitScripts() {
  217. await this._forAllFrameSessions(frame => frame._removeEvaluatesOnNewDocument());
  218. }
  219. async closePage(runBeforeUnload) {
  220. if (runBeforeUnload) await this._mainFrameSession._client.send('Page.close');else await this._browserContext._browser._closePage(this);
  221. }
  222. async setBackgroundColor(color) {
  223. await this._mainFrameSession._client.send('Emulation.setDefaultBackgroundColorOverride', {
  224. color
  225. });
  226. }
  227. async takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, scale) {
  228. const {
  229. visualViewport
  230. } = await this._mainFrameSession._client.send('Page.getLayoutMetrics');
  231. if (!documentRect) {
  232. documentRect = {
  233. x: visualViewport.pageX + viewportRect.x,
  234. y: visualViewport.pageY + viewportRect.y,
  235. ..._helper.helper.enclosingIntSize({
  236. width: viewportRect.width / visualViewport.scale,
  237. height: viewportRect.height / visualViewport.scale
  238. })
  239. };
  240. }
  241. // When taking screenshots with documentRect (based on the page content, not viewport),
  242. // ignore current page scale.
  243. const clip = {
  244. ...documentRect,
  245. scale: viewportRect ? visualViewport.scale : 1
  246. };
  247. if (scale === 'css') {
  248. const deviceScaleFactor = this._browserContext._options.deviceScaleFactor || 1;
  249. clip.scale /= deviceScaleFactor;
  250. }
  251. progress.throwIfAborted();
  252. const result = await this._mainFrameSession._client.send('Page.captureScreenshot', {
  253. format,
  254. quality,
  255. clip,
  256. captureBeyondViewport: !fitsViewport
  257. });
  258. return Buffer.from(result.data, 'base64');
  259. }
  260. async getContentFrame(handle) {
  261. return this._sessionForHandle(handle)._getContentFrame(handle);
  262. }
  263. async getOwnerFrame(handle) {
  264. return this._sessionForHandle(handle)._getOwnerFrame(handle);
  265. }
  266. isElementHandle(remoteObject) {
  267. return remoteObject.subtype === 'node';
  268. }
  269. async getBoundingBox(handle) {
  270. return this._sessionForHandle(handle)._getBoundingBox(handle);
  271. }
  272. async scrollRectIntoViewIfNeeded(handle, rect) {
  273. return this._sessionForHandle(handle)._scrollRectIntoViewIfNeeded(handle, rect);
  274. }
  275. async setScreencastOptions(options) {
  276. if (options) {
  277. await this._mainFrameSession._startScreencast(this, {
  278. format: 'jpeg',
  279. quality: options.quality,
  280. maxWidth: options.width,
  281. maxHeight: options.height
  282. });
  283. } else {
  284. await this._mainFrameSession._stopScreencast(this);
  285. }
  286. }
  287. rafCountForStablePosition() {
  288. return 1;
  289. }
  290. async getContentQuads(handle) {
  291. return this._sessionForHandle(handle)._getContentQuads(handle);
  292. }
  293. async setInputFiles(handle, files) {
  294. await handle.evaluateInUtility(([injected, node, files]) => injected.setInputFiles(node, files), files);
  295. }
  296. async setInputFilePaths(progress, handle, files) {
  297. const frame = await handle.ownerFrame();
  298. if (!frame) throw new Error('Cannot set input files to detached input element');
  299. const parentSession = this._sessionForFrame(frame);
  300. await parentSession._client.send('DOM.setFileInputFiles', {
  301. objectId: handle._objectId,
  302. files
  303. });
  304. }
  305. async adoptElementHandle(handle, to) {
  306. return this._sessionForHandle(handle)._adoptElementHandle(handle, to);
  307. }
  308. async getAccessibilityTree(needle) {
  309. return (0, _crAccessibility.getAccessibilityTree)(this._mainFrameSession._client, needle);
  310. }
  311. async inputActionEpilogue() {
  312. await this._mainFrameSession._client.send('Page.enable').catch(e => {});
  313. }
  314. async resetForReuse() {}
  315. async pdf(options) {
  316. return this._pdf.generate(options);
  317. }
  318. coverage() {
  319. return this._coverage;
  320. }
  321. async getFrameElement(frame) {
  322. let parent = frame.parentFrame();
  323. if (!parent) throw new Error('Frame has been detached.');
  324. const parentSession = this._sessionForFrame(parent);
  325. const {
  326. backendNodeId
  327. } = await parentSession._client.send('DOM.getFrameOwner', {
  328. frameId: frame._id
  329. }).catch(e => {
  330. if (e instanceof Error && e.message.includes('Frame with the given id was not found.')) (0, _stackTrace.rewriteErrorMessage)(e, 'Frame has been detached.');
  331. throw e;
  332. });
  333. parent = frame.parentFrame();
  334. if (!parent) throw new Error('Frame has been detached.');
  335. return parentSession._adoptBackendNodeId(backendNodeId, await parent._mainContext());
  336. }
  337. shouldToggleStyleSheetToSyncAnimations() {
  338. return false;
  339. }
  340. }
  341. exports.CRPage = CRPage;
  342. class FrameSession {
  343. constructor(crPage, client, targetId, parentSession) {
  344. this._client = void 0;
  345. this._crPage = void 0;
  346. this._page = void 0;
  347. this._networkManager = void 0;
  348. this._parentSession = void 0;
  349. this._childSessions = new Set();
  350. this._contextIdToContext = new Map();
  351. this._eventListeners = [];
  352. this._targetId = void 0;
  353. this._firstNonInitialNavigationCommittedPromise = void 0;
  354. this._firstNonInitialNavigationCommittedFulfill = () => {};
  355. this._firstNonInitialNavigationCommittedReject = e => {};
  356. this._windowId = void 0;
  357. // Marks the oopif session that remote -> local transition has happened in the parent.
  358. // See Target.detachedFromTarget handler for details.
  359. this._swappedIn = false;
  360. this._videoRecorder = null;
  361. this._screencastId = null;
  362. this._screencastClients = new Set();
  363. this._evaluateOnNewDocumentIdentifiers = [];
  364. this._exposedBindingNames = [];
  365. this._metricsOverride = void 0;
  366. this._workerSessions = new Map();
  367. this._client = client;
  368. this._crPage = crPage;
  369. this._page = crPage._page;
  370. this._targetId = targetId;
  371. this._networkManager = new _crNetworkManager.CRNetworkManager(client, this._page, null, parentSession ? parentSession._networkManager : null);
  372. this._parentSession = parentSession;
  373. if (parentSession) parentSession._childSessions.add(this);
  374. this._firstNonInitialNavigationCommittedPromise = new Promise((f, r) => {
  375. this._firstNonInitialNavigationCommittedFulfill = f;
  376. this._firstNonInitialNavigationCommittedReject = r;
  377. });
  378. }
  379. _isMainFrame() {
  380. return this._targetId === this._crPage._targetId;
  381. }
  382. _addRendererListeners() {
  383. this._eventListeners.push(...[_eventsHelper.eventsHelper.addEventListener(this._client, 'Log.entryAdded', event => this._onLogEntryAdded(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId, event.reason)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.javascriptDialogOpening', event => this._onDialog(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.bindingCalled', event => this._onBindingCalled(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.consoleAPICalled', event => this._onConsoleAPI(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()), _eventsHelper.eventsHelper.addEventListener(this._client, 'Target.attachedToTarget', event => this._onAttachedToTarget(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Target.detachedFromTarget', event => this._onDetachedFromTarget(event))]);
  384. }
  385. _addBrowserListeners() {
  386. this._eventListeners.push(...[_eventsHelper.eventsHelper.addEventListener(this._client, 'Inspector.targetCrashed', event => this._onTargetCrashed()), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.screencastFrame', event => this._onScreencastFrame(event)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Page.windowOpen', event => this._onWindowOpen(event))]);
  387. }
  388. async _initialize(hasUIWindow) {
  389. const isSettingStorageState = this._page._browserContext.isSettingStorageState();
  390. if (!isSettingStorageState && hasUIWindow && !this._crPage._browserContext._browser.isClank() && !this._crPage._browserContext._options.noDefaultViewport) {
  391. const {
  392. windowId
  393. } = await this._client.send('Browser.getWindowForTarget');
  394. this._windowId = windowId;
  395. }
  396. let screencastOptions;
  397. if (!isSettingStorageState && this._isMainFrame() && this._crPage._browserContext._options.recordVideo && hasUIWindow) {
  398. const screencastId = (0, _utils.createGuid)();
  399. const outputFile = _path.default.join(this._crPage._browserContext._options.recordVideo.dir, screencastId + '.webm');
  400. screencastOptions = {
  401. // validateBrowserContextOptions ensures correct video size.
  402. ...this._crPage._browserContext._options.recordVideo.size,
  403. outputFile
  404. };
  405. await this._crPage._browserContext._ensureVideosPath();
  406. // Note: it is important to start video recorder before sending Page.startScreencast,
  407. // and it is equally important to send Page.startScreencast before sending Runtime.runIfWaitingForDebugger.
  408. await this._createVideoRecorder(screencastId, screencastOptions);
  409. this._crPage.pageOrError().then(p => {
  410. if (p instanceof Error) this._stopVideoRecording().catch(() => {});
  411. });
  412. }
  413. let lifecycleEventsEnabled;
  414. if (!this._isMainFrame()) this._addRendererListeners();
  415. this._addBrowserListeners();
  416. const promises = [this._client.send('Page.enable'), this._client.send('Page.getFrameTree').then(({
  417. frameTree
  418. }) => {
  419. if (this._isMainFrame()) {
  420. this._handleFrameTree(frameTree);
  421. this._addRendererListeners();
  422. }
  423. const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)];
  424. for (const frame of localFrames) {
  425. // Note: frames might be removed before we send these.
  426. this._client._sendMayFail('Page.createIsolatedWorld', {
  427. frameId: frame._id,
  428. grantUniveralAccess: true,
  429. worldName: UTILITY_WORLD_NAME
  430. });
  431. for (const binding of this._crPage._browserContext._pageBindings.values()) frame.evaluateExpression(binding.source).catch(e => {});
  432. for (const source of this._crPage._browserContext.initScripts) frame.evaluateExpression(source).catch(e => {});
  433. }
  434. const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':';
  435. if (isInitialEmptyPage) {
  436. // Ignore lifecycle events for the initial empty page. It is never the final page
  437. // hence we are going to get more lifecycle updates after the actual navigation has
  438. // started (even if the target url is about:blank).
  439. lifecycleEventsEnabled.catch(e => {}).then(() => {
  440. this._eventListeners.push(_eventsHelper.eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
  441. });
  442. } else {
  443. this._firstNonInitialNavigationCommittedFulfill();
  444. this._eventListeners.push(_eventsHelper.eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
  445. }
  446. }), this._client.send('Log.enable', {}), lifecycleEventsEnabled = this._client.send('Page.setLifecycleEventsEnabled', {
  447. enabled: true
  448. }), this._client.send('Runtime.enable', {}), this._client.send('Page.addScriptToEvaluateOnNewDocument', {
  449. source: '',
  450. worldName: UTILITY_WORLD_NAME
  451. }), this._networkManager.initialize(), this._client.send('Target.setAutoAttach', {
  452. autoAttach: true,
  453. waitForDebuggerOnStart: true,
  454. flatten: true
  455. })];
  456. if (!isSettingStorageState) {
  457. if (this._isMainFrame()) promises.push(this._client.send('Emulation.setFocusEmulationEnabled', {
  458. enabled: true
  459. }));
  460. const options = this._crPage._browserContext._options;
  461. if (options.bypassCSP) promises.push(this._client.send('Page.setBypassCSP', {
  462. enabled: true
  463. }));
  464. if (options.ignoreHTTPSErrors) promises.push(this._client.send('Security.setIgnoreCertificateErrors', {
  465. ignore: true
  466. }));
  467. if (this._isMainFrame()) promises.push(this._updateViewport());
  468. if (options.hasTouch) promises.push(this._client.send('Emulation.setTouchEmulationEnabled', {
  469. enabled: true
  470. }));
  471. if (options.javaScriptEnabled === false) promises.push(this._client.send('Emulation.setScriptExecutionDisabled', {
  472. value: true
  473. }));
  474. if (options.userAgent || options.locale) promises.push(this._updateUserAgent());
  475. if (options.locale) promises.push(emulateLocale(this._client, options.locale));
  476. if (options.timezoneId) promises.push(emulateTimezone(this._client, options.timezoneId));
  477. if (!this._crPage._browserContext._browser.options.headful) promises.push(this._setDefaultFontFamilies(this._client));
  478. promises.push(this._updateGeolocation(true));
  479. promises.push(this._updateExtraHTTPHeaders(true));
  480. promises.push(this._updateRequestInterception());
  481. promises.push(this._updateOffline(true));
  482. promises.push(this._updateHttpCredentials(true));
  483. promises.push(this._updateEmulateMedia());
  484. promises.push(this._updateFileChooserInterception(true));
  485. for (const binding of this._crPage._page.allBindings()) promises.push(this._initBinding(binding));
  486. for (const source of this._crPage._browserContext.initScripts) promises.push(this._evaluateOnNewDocument(source, 'main'));
  487. for (const source of this._crPage._page.initScripts) promises.push(this._evaluateOnNewDocument(source, 'main'));
  488. if (screencastOptions) promises.push(this._startVideoRecording(screencastOptions));
  489. }
  490. promises.push(this._client.send('Runtime.runIfWaitingForDebugger'));
  491. promises.push(this._firstNonInitialNavigationCommittedPromise);
  492. await Promise.all(promises);
  493. }
  494. dispose() {
  495. this._firstNonInitialNavigationCommittedReject(new _errors.TargetClosedError());
  496. for (const childSession of this._childSessions) childSession.dispose();
  497. if (this._parentSession) this._parentSession._childSessions.delete(this);
  498. _eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
  499. this._networkManager.dispose();
  500. this._crPage._sessions.delete(this._targetId);
  501. this._client.dispose();
  502. }
  503. async _navigate(frame, url, referrer) {
  504. const response = await this._client.send('Page.navigate', {
  505. url,
  506. referrer,
  507. frameId: frame._id,
  508. referrerPolicy: 'unsafeUrl'
  509. });
  510. if (response.errorText) throw new frames.NavigationAbortedError(response.loaderId, `${response.errorText} at ${url}`);
  511. return {
  512. newDocumentId: response.loaderId
  513. };
  514. }
  515. _onLifecycleEvent(event) {
  516. if (this._eventBelongsToStaleFrame(event.frameId)) return;
  517. if (event.name === 'load') this._page._frameManager.frameLifecycleEvent(event.frameId, 'load');else if (event.name === 'DOMContentLoaded') this._page._frameManager.frameLifecycleEvent(event.frameId, 'domcontentloaded');
  518. }
  519. _handleFrameTree(frameTree) {
  520. this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId || null);
  521. this._onFrameNavigated(frameTree.frame, true);
  522. if (!frameTree.childFrames) return;
  523. for (const child of frameTree.childFrames) this._handleFrameTree(child);
  524. }
  525. _eventBelongsToStaleFrame(frameId) {
  526. const frame = this._page._frameManager.frame(frameId);
  527. // Subtree may be already gone because some ancestor navigation destroyed the oopif.
  528. if (!frame) return true;
  529. // When frame goes remote, parent process may still send some events
  530. // related to the local frame before it sends frameDetached.
  531. // In this case, we already have a new session for this frame, so events
  532. // in the old session should be ignored.
  533. const session = this._crPage._sessionForFrame(frame);
  534. return session && session !== this && !session._swappedIn;
  535. }
  536. _onFrameAttached(frameId, parentFrameId) {
  537. const frameSession = this._crPage._sessions.get(frameId);
  538. if (frameSession && frameId !== this._targetId) {
  539. // This is a remote -> local frame transition.
  540. frameSession._swappedIn = true;
  541. const frame = this._page._frameManager.frame(frameId);
  542. // Frame or even a whole subtree may be already gone, because some ancestor did navigate.
  543. if (frame) this._page._frameManager.removeChildFramesRecursively(frame);
  544. return;
  545. }
  546. if (parentFrameId && !this._page._frameManager.frame(parentFrameId)) {
  547. // Parent frame may be gone already because some ancestor frame navigated and
  548. // destroyed the whole subtree of some oopif, while oopif's process is still sending us events.
  549. // Be careful to not confuse this with "main frame navigated cross-process" scenario
  550. // where parentFrameId is null.
  551. return;
  552. }
  553. this._page._frameManager.frameAttached(frameId, parentFrameId);
  554. }
  555. _onFrameNavigated(framePayload, initial) {
  556. if (this._eventBelongsToStaleFrame(framePayload.id)) return;
  557. this._page._frameManager.frameCommittedNewDocumentNavigation(framePayload.id, framePayload.url + (framePayload.urlFragment || ''), framePayload.name || '', framePayload.loaderId, initial);
  558. if (!initial) this._firstNonInitialNavigationCommittedFulfill();
  559. }
  560. _onFrameRequestedNavigation(payload) {
  561. if (this._eventBelongsToStaleFrame(payload.frameId)) return;
  562. if (payload.disposition === 'currentTab') this._page._frameManager.frameRequestedNavigation(payload.frameId);
  563. }
  564. _onFrameNavigatedWithinDocument(frameId, url) {
  565. if (this._eventBelongsToStaleFrame(frameId)) return;
  566. this._page._frameManager.frameCommittedSameDocumentNavigation(frameId, url);
  567. }
  568. _onFrameDetached(frameId, reason) {
  569. if (this._crPage._sessions.has(frameId)) {
  570. // This is a local -> remote frame transition, where
  571. // Page.frameDetached arrives after Target.attachedToTarget.
  572. // We've already handled the new target and frame reattach - nothing to do here.
  573. return;
  574. }
  575. if (reason === 'swap') {
  576. // This is a local -> remote frame transtion, where
  577. // Page.frameDetached arrives before Target.attachedToTarget.
  578. // We should keep the frame in the tree, and it will be used for the new target.
  579. const frame = this._page._frameManager.frame(frameId);
  580. if (frame) this._page._frameManager.removeChildFramesRecursively(frame);
  581. return;
  582. }
  583. // Just a regular frame detach.
  584. this._page._frameManager.frameDetached(frameId);
  585. }
  586. _onExecutionContextCreated(contextPayload) {
  587. const frame = contextPayload.auxData ? this._page._frameManager.frame(contextPayload.auxData.frameId) : null;
  588. if (!frame || this._eventBelongsToStaleFrame(frame._id)) return;
  589. const delegate = new _crExecutionContext.CRExecutionContext(this._client, contextPayload);
  590. let worldName = null;
  591. if (contextPayload.auxData && !!contextPayload.auxData.isDefault) worldName = 'main';else if (contextPayload.name === UTILITY_WORLD_NAME) worldName = 'utility';
  592. const context = new dom.FrameExecutionContext(delegate, frame, worldName);
  593. context[contextDelegateSymbol] = delegate;
  594. if (worldName) frame._contextCreated(worldName, context);
  595. this._contextIdToContext.set(contextPayload.id, context);
  596. }
  597. _onExecutionContextDestroyed(executionContextId) {
  598. const context = this._contextIdToContext.get(executionContextId);
  599. if (!context) return;
  600. this._contextIdToContext.delete(executionContextId);
  601. context.frame._contextDestroyed(context);
  602. }
  603. _onExecutionContextsCleared() {
  604. for (const contextId of Array.from(this._contextIdToContext.keys())) this._onExecutionContextDestroyed(contextId);
  605. }
  606. _onAttachedToTarget(event) {
  607. var _this$_page$_frameMan;
  608. const session = this._client.createChildSession(event.sessionId);
  609. if (event.targetInfo.type === 'iframe') {
  610. // Frame id equals target id.
  611. const targetId = event.targetInfo.targetId;
  612. const frame = this._page._frameManager.frame(targetId);
  613. if (!frame) return; // Subtree may be already gone due to renderer/browser race.
  614. this._page._frameManager.removeChildFramesRecursively(frame);
  615. const frameSession = new FrameSession(this._crPage, session, targetId, this);
  616. this._crPage._sessions.set(targetId, frameSession);
  617. frameSession._initialize(false).catch(e => e);
  618. return;
  619. }
  620. if (event.targetInfo.type !== 'worker') {
  621. session.detach().catch(() => {});
  622. return;
  623. }
  624. const url = event.targetInfo.url;
  625. const worker = new _page.Worker(this._page, url);
  626. this._page._addWorker(event.sessionId, worker);
  627. this._workerSessions.set(event.sessionId, session);
  628. session.once('Runtime.executionContextCreated', async event => {
  629. worker._createExecutionContext(new _crExecutionContext.CRExecutionContext(session, event.context));
  630. });
  631. // This might fail if the target is closed before we initialize.
  632. session._sendMayFail('Runtime.enable');
  633. session._sendMayFail('Network.enable');
  634. session._sendMayFail('Runtime.runIfWaitingForDebugger');
  635. session._sendMayFail('Target.setAutoAttach', {
  636. autoAttach: true,
  637. waitForDebuggerOnStart: true,
  638. flatten: true
  639. });
  640. session.on('Target.attachedToTarget', event => this._onAttachedToTarget(event));
  641. session.on('Target.detachedFromTarget', event => this._onDetachedFromTarget(event));
  642. session.on('Runtime.consoleAPICalled', event => {
  643. const args = event.args.map(o => worker._existingExecutionContext.createHandle(o));
  644. this._page._addConsoleMessage(event.type, args, (0, _crProtocolHelper.toConsoleMessageLocation)(event.stackTrace));
  645. });
  646. session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized(_browserContext.BrowserContext.Events.PageError, (0, _crProtocolHelper.exceptionToError)(exception.exceptionDetails), this._page));
  647. // TODO: attribute workers to the right frame.
  648. this._networkManager.instrumentNetworkEvents({
  649. session,
  650. workerFrame: (_this$_page$_frameMan = this._page._frameManager.frame(this._targetId)) !== null && _this$_page$_frameMan !== void 0 ? _this$_page$_frameMan : undefined
  651. });
  652. }
  653. _onDetachedFromTarget(event) {
  654. // This might be a worker...
  655. const workerSession = this._workerSessions.get(event.sessionId);
  656. if (workerSession) {
  657. workerSession.dispose();
  658. this._page._removeWorker(event.sessionId);
  659. return;
  660. }
  661. // ... or an oopif.
  662. const childFrameSession = this._crPage._sessions.get(event.targetId);
  663. if (!childFrameSession) return;
  664. // Usually, we get frameAttached in this session first and mark child as swappedIn.
  665. if (childFrameSession._swappedIn) {
  666. childFrameSession.dispose();
  667. return;
  668. }
  669. // However, sometimes we get detachedFromTarget before frameAttached.
  670. // In this case we don't know whether this is a remote frame detach,
  671. // or just a remote -> local transition. In the latter case, frameAttached
  672. // is already inflight, so let's make a safe roundtrip to ensure it arrives.
  673. this._client.send('Page.enable').catch(e => null).then(() => {
  674. // Child was not swapped in - that means frameAttached did not happen and
  675. // this is remote detach rather than remote -> local swap.
  676. if (!childFrameSession._swappedIn) this._page._frameManager.frameDetached(event.targetId);
  677. childFrameSession.dispose();
  678. });
  679. }
  680. _onWindowOpen(event) {
  681. this._crPage._nextWindowOpenPopupFeatures.push(event.windowFeatures);
  682. }
  683. async _onConsoleAPI(event) {
  684. if (event.executionContextId === 0) {
  685. // DevTools protocol stores the last 1000 console messages. These
  686. // messages are always reported even for removed execution contexts. In
  687. // this case, they are marked with executionContextId = 0 and are
  688. // reported upon enabling Runtime agent.
  689. //
  690. // Ignore these messages since:
  691. // - there's no execution context we can use to operate with message
  692. // arguments
  693. // - these messages are reported before Playwright clients can subscribe
  694. // to the 'console'
  695. // page event.
  696. //
  697. // @see https://github.com/GoogleChrome/puppeteer/issues/3865
  698. return;
  699. }
  700. const context = this._contextIdToContext.get(event.executionContextId);
  701. if (!context) return;
  702. const values = event.args.map(arg => context.createHandle(arg));
  703. this._page._addConsoleMessage(event.type, values, (0, _crProtocolHelper.toConsoleMessageLocation)(event.stackTrace));
  704. }
  705. async _initBinding(binding) {
  706. const [, response] = await Promise.all([this._client.send('Runtime.addBinding', {
  707. name: binding.name
  708. }), this._client.send('Page.addScriptToEvaluateOnNewDocument', {
  709. source: binding.source
  710. })]);
  711. this._exposedBindingNames.push(binding.name);
  712. if (!binding.name.startsWith('__pw')) this._evaluateOnNewDocumentIdentifiers.push(response.identifier);
  713. }
  714. async _removeExposedBindings() {
  715. const toRetain = [];
  716. const toRemove = [];
  717. for (const name of this._exposedBindingNames) (name.startsWith('__pw_') ? toRetain : toRemove).push(name);
  718. this._exposedBindingNames = toRetain;
  719. await Promise.all(toRemove.map(name => this._client.send('Runtime.removeBinding', {
  720. name
  721. })));
  722. }
  723. async _onBindingCalled(event) {
  724. const pageOrError = await this._crPage.pageOrError();
  725. if (!(pageOrError instanceof Error)) {
  726. const context = this._contextIdToContext.get(event.executionContextId);
  727. if (context) await this._page._onBindingCalled(event.payload, context);
  728. }
  729. }
  730. _onDialog(event) {
  731. if (!this._page._frameManager.frame(this._targetId)) return; // Our frame/subtree may be gone already.
  732. this._page.emitOnContext(_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog(this._page, event.type, event.message, async (accept, promptText) => {
  733. await this._client.send('Page.handleJavaScriptDialog', {
  734. accept,
  735. promptText
  736. });
  737. }, event.defaultPrompt));
  738. }
  739. _handleException(exceptionDetails) {
  740. this._page.emitOnContextOnceInitialized(_browserContext.BrowserContext.Events.PageError, (0, _crProtocolHelper.exceptionToError)(exceptionDetails), this._page);
  741. }
  742. async _onTargetCrashed() {
  743. this._client._markAsCrashed();
  744. this._page._didCrash();
  745. }
  746. _onLogEntryAdded(event) {
  747. const {
  748. level,
  749. text,
  750. args,
  751. source,
  752. url,
  753. lineNumber
  754. } = event.entry;
  755. if (args) args.map(arg => (0, _crProtocolHelper.releaseObject)(this._client, arg.objectId));
  756. if (source !== 'worker') {
  757. const location = {
  758. url: url || '',
  759. lineNumber: lineNumber || 0,
  760. columnNumber: 0
  761. };
  762. this._page._addConsoleMessage(level, [], location, text);
  763. }
  764. }
  765. async _onFileChooserOpened(event) {
  766. if (!event.backendNodeId) return;
  767. const frame = this._page._frameManager.frame(event.frameId);
  768. if (!frame) return;
  769. let handle;
  770. try {
  771. const utilityContext = await frame._utilityContext();
  772. handle = await this._adoptBackendNodeId(event.backendNodeId, utilityContext);
  773. } catch (e) {
  774. // During async processing, frame/context may go away. We should not throw.
  775. return;
  776. }
  777. await this._page._onFileChooserOpened(handle);
  778. }
  779. _willBeginDownload() {
  780. const originPage = this._crPage._initializedPage;
  781. if (!originPage) {
  782. // Resume the page creation with an error. The page will automatically close right
  783. // after the download begins.
  784. this._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
  785. }
  786. }
  787. _onScreencastFrame(payload) {
  788. this._page.throttleScreencastFrameAck(() => {
  789. this._client.send('Page.screencastFrameAck', {
  790. sessionId: payload.sessionId
  791. }).catch(() => {});
  792. });
  793. const buffer = Buffer.from(payload.data, 'base64');
  794. this._page.emit(_page.Page.Events.ScreencastFrame, {
  795. buffer,
  796. timestamp: payload.metadata.timestamp,
  797. width: payload.metadata.deviceWidth,
  798. height: payload.metadata.deviceHeight
  799. });
  800. }
  801. async _createVideoRecorder(screencastId, options) {
  802. (0, _utils.assert)(!this._screencastId);
  803. const ffmpegPath = _registry.registry.findExecutable('ffmpeg').executablePathOrDie(this._page.attribution.playwright.options.sdkLanguage);
  804. this._videoRecorder = await _videoRecorder.VideoRecorder.launch(this._crPage._page, ffmpegPath, options);
  805. this._screencastId = screencastId;
  806. }
  807. async _startVideoRecording(options) {
  808. const screencastId = this._screencastId;
  809. (0, _utils.assert)(screencastId);
  810. this._page.once(_page.Page.Events.Close, () => this._stopVideoRecording().catch(() => {}));
  811. const gotFirstFrame = new Promise(f => this._client.once('Page.screencastFrame', f));
  812. await this._startScreencast(this._videoRecorder, {
  813. format: 'jpeg',
  814. quality: 90,
  815. maxWidth: options.width,
  816. maxHeight: options.height
  817. });
  818. // Wait for the first frame before reporting video to the client.
  819. gotFirstFrame.then(() => {
  820. this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage.pageOrError());
  821. });
  822. }
  823. async _stopVideoRecording() {
  824. if (!this._screencastId) return;
  825. const screencastId = this._screencastId;
  826. this._screencastId = null;
  827. const recorder = this._videoRecorder;
  828. this._videoRecorder = null;
  829. await this._stopScreencast(recorder);
  830. await recorder.stop().catch(() => {});
  831. // Keep the video artifact in the map until encoding is fully finished, if the context
  832. // starts closing before the video is fully written to disk it will wait for it.
  833. const video = this._crPage._browserContext._browser._takeVideo(screencastId);
  834. video === null || video === void 0 ? void 0 : video.reportFinished();
  835. }
  836. async _startScreencast(client, options = {}) {
  837. this._screencastClients.add(client);
  838. if (this._screencastClients.size === 1) await this._client.send('Page.startScreencast', options);
  839. }
  840. async _stopScreencast(client) {
  841. this._screencastClients.delete(client);
  842. if (!this._screencastClients.size) await this._client._sendMayFail('Page.stopScreencast');
  843. }
  844. async _updateExtraHTTPHeaders(initial) {
  845. const headers = network.mergeHeaders([this._crPage._browserContext._options.extraHTTPHeaders, this._page.extraHTTPHeaders()]);
  846. if (!initial || headers.length) await this._client.send('Network.setExtraHTTPHeaders', {
  847. headers: (0, _utils.headersArrayToObject)(headers, false /* lowerCase */)
  848. });
  849. }
  850. async _updateGeolocation(initial) {
  851. const geolocation = this._crPage._browserContext._options.geolocation;
  852. if (!initial || geolocation) await this._client.send('Emulation.setGeolocationOverride', geolocation || {});
  853. }
  854. async _updateOffline(initial) {
  855. const offline = !!this._crPage._browserContext._options.offline;
  856. if (!initial || offline) await this._networkManager.setOffline(offline);
  857. }
  858. async _updateHttpCredentials(initial) {
  859. const credentials = this._crPage._browserContext._options.httpCredentials || null;
  860. if (!initial || credentials) await this._networkManager.authenticate(credentials);
  861. }
  862. async _updateViewport(preserveWindowBoundaries) {
  863. if (this._crPage._browserContext._browser.isClank()) return;
  864. (0, _utils.assert)(this._isMainFrame());
  865. const options = this._crPage._browserContext._options;
  866. const emulatedSize = this._page.emulatedSize();
  867. if (emulatedSize === null) return;
  868. const viewportSize = emulatedSize.viewport;
  869. const screenSize = emulatedSize.screen;
  870. const isLandscape = screenSize.width > screenSize.height;
  871. const metricsOverride = {
  872. mobile: !!options.isMobile,
  873. width: viewportSize.width,
  874. height: viewportSize.height,
  875. screenWidth: screenSize.width,
  876. screenHeight: screenSize.height,
  877. deviceScaleFactor: options.deviceScaleFactor || 1,
  878. screenOrientation: !!options.isMobile ? isLandscape ? {
  879. angle: 90,
  880. type: 'landscapePrimary'
  881. } : {
  882. angle: 0,
  883. type: 'portraitPrimary'
  884. } : {
  885. angle: 0,
  886. type: 'landscapePrimary'
  887. },
  888. dontSetVisibleSize: preserveWindowBoundaries
  889. };
  890. if (JSON.stringify(this._metricsOverride) === JSON.stringify(metricsOverride)) return;
  891. const promises = [this._client.send('Emulation.setDeviceMetricsOverride', metricsOverride)];
  892. if (!preserveWindowBoundaries && this._windowId) {
  893. let insets = {
  894. width: 0,
  895. height: 0
  896. };
  897. if (this._crPage._browserContext._browser.options.headful) {
  898. // TODO: popup windows have their own insets.
  899. insets = {
  900. width: 24,
  901. height: 88
  902. };
  903. if (process.platform === 'win32') insets = {
  904. width: 16,
  905. height: 88
  906. };else if (process.platform === 'linux') insets = {
  907. width: 8,
  908. height: 85
  909. };else if (process.platform === 'darwin') insets = {
  910. width: 2,
  911. height: 80
  912. };
  913. if (this._crPage._browserContext.isPersistentContext()) {
  914. // FIXME: Chrome bug: OOPIF router is confused when hit target is
  915. // outside browser window.
  916. // Account for the infobar here to work around the bug.
  917. insets.height += 46;
  918. }
  919. }
  920. promises.push(this.setWindowBounds({
  921. width: viewportSize.width + insets.width,
  922. height: viewportSize.height + insets.height
  923. }));
  924. }
  925. await Promise.all(promises);
  926. this._metricsOverride = metricsOverride;
  927. }
  928. async windowBounds() {
  929. const {
  930. bounds
  931. } = await this._client.send('Browser.getWindowBounds', {
  932. windowId: this._windowId
  933. });
  934. return bounds;
  935. }
  936. async setWindowBounds(bounds) {
  937. return await this._client.send('Browser.setWindowBounds', {
  938. windowId: this._windowId,
  939. bounds
  940. });
  941. }
  942. async _updateEmulateMedia() {
  943. const emulatedMedia = this._page.emulatedMedia();
  944. // Empty string disables the override.
  945. const media = emulatedMedia.media === 'no-override' ? '' : emulatedMedia.media;
  946. const colorScheme = emulatedMedia.colorScheme === 'no-override' ? '' : emulatedMedia.colorScheme;
  947. const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? '' : emulatedMedia.reducedMotion;
  948. const forcedColors = emulatedMedia.forcedColors === 'no-override' ? '' : emulatedMedia.forcedColors;
  949. const features = [{
  950. name: 'prefers-color-scheme',
  951. value: colorScheme
  952. }, {
  953. name: 'prefers-reduced-motion',
  954. value: reducedMotion
  955. }, {
  956. name: 'forced-colors',
  957. value: forcedColors
  958. }];
  959. await this._client.send('Emulation.setEmulatedMedia', {
  960. media,
  961. features
  962. });
  963. }
  964. async _updateUserAgent() {
  965. const options = this._crPage._browserContext._options;
  966. await this._client.send('Emulation.setUserAgentOverride', {
  967. userAgent: options.userAgent || '',
  968. acceptLanguage: options.locale
  969. });
  970. }
  971. async _setDefaultFontFamilies(session) {
  972. const fontFamilies = _defaultFontFamilies.platformToFontFamilies[this._crPage._browserContext._browser._platform()];
  973. await session.send('Page.setFontFamilies', fontFamilies);
  974. }
  975. async _updateRequestInterception() {
  976. await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
  977. }
  978. async _updateFileChooserInterception(initial) {
  979. const enabled = this._page.fileChooserIntercepted();
  980. if (initial && !enabled) return;
  981. await this._client.send('Page.setInterceptFileChooserDialog', {
  982. enabled
  983. }).catch(() => {}); // target can be closed.
  984. }
  985. async _evaluateOnNewDocument(source, world) {
  986. const worldName = world === 'utility' ? UTILITY_WORLD_NAME : undefined;
  987. const {
  988. identifier
  989. } = await this._client.send('Page.addScriptToEvaluateOnNewDocument', {
  990. source,
  991. worldName
  992. });
  993. this._evaluateOnNewDocumentIdentifiers.push(identifier);
  994. }
  995. async _removeEvaluatesOnNewDocument() {
  996. const identifiers = this._evaluateOnNewDocumentIdentifiers;
  997. this._evaluateOnNewDocumentIdentifiers = [];
  998. await Promise.all(identifiers.map(identifier => this._client.send('Page.removeScriptToEvaluateOnNewDocument', {
  999. identifier
  1000. })));
  1001. }
  1002. async _getContentFrame(handle) {
  1003. const nodeInfo = await this._client.send('DOM.describeNode', {
  1004. objectId: handle._objectId
  1005. });
  1006. if (!nodeInfo || typeof nodeInfo.node.frameId !== 'string') return null;
  1007. return this._page._frameManager.frame(nodeInfo.node.frameId);
  1008. }
  1009. async _getOwnerFrame(handle) {
  1010. // document.documentElement has frameId of the owner frame.
  1011. const documentElement = await handle.evaluateHandle(node => {
  1012. const doc = node;
  1013. if (doc.documentElement && doc.documentElement.ownerDocument === doc) return doc.documentElement;
  1014. return node.ownerDocument ? node.ownerDocument.documentElement : null;
  1015. });
  1016. if (!documentElement) return null;
  1017. if (!documentElement._objectId) return null;
  1018. const nodeInfo = await this._client.send('DOM.describeNode', {
  1019. objectId: documentElement._objectId
  1020. });
  1021. const frameId = nodeInfo && typeof nodeInfo.node.frameId === 'string' ? nodeInfo.node.frameId : null;
  1022. documentElement.dispose();
  1023. return frameId;
  1024. }
  1025. async _getBoundingBox(handle) {
  1026. const result = await this._client._sendMayFail('DOM.getBoxModel', {
  1027. objectId: handle._objectId
  1028. });
  1029. if (!result) return null;
  1030. const quad = result.model.border;
  1031. const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
  1032. const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
  1033. const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
  1034. const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
  1035. const position = await this._framePosition();
  1036. if (!position) return null;
  1037. return {
  1038. x: x + position.x,
  1039. y: y + position.y,
  1040. width,
  1041. height
  1042. };
  1043. }
  1044. async _framePosition() {
  1045. const frame = this._page._frameManager.frame(this._targetId);
  1046. if (!frame) return null;
  1047. if (frame === this._page.mainFrame()) return {
  1048. x: 0,
  1049. y: 0
  1050. };
  1051. const element = await frame.frameElement();
  1052. const box = await element.boundingBox();
  1053. return box;
  1054. }
  1055. async _scrollRectIntoViewIfNeeded(handle, rect) {
  1056. return await this._client.send('DOM.scrollIntoViewIfNeeded', {
  1057. objectId: handle._objectId,
  1058. rect
  1059. }).then(() => 'done').catch(e => {
  1060. if (e instanceof Error && e.message.includes('Node does not have a layout object')) return 'error:notvisible';
  1061. if (e instanceof Error && e.message.includes('Node is detached from document')) return 'error:notconnected';
  1062. throw e;
  1063. });
  1064. }
  1065. async _getContentQuads(handle) {
  1066. const result = await this._client._sendMayFail('DOM.getContentQuads', {
  1067. objectId: handle._objectId
  1068. });
  1069. if (!result) return null;
  1070. const position = await this._framePosition();
  1071. if (!position) return null;
  1072. return result.quads.map(quad => [{
  1073. x: quad[0] + position.x,
  1074. y: quad[1] + position.y
  1075. }, {
  1076. x: quad[2] + position.x,
  1077. y: quad[3] + position.y
  1078. }, {
  1079. x: quad[4] + position.x,
  1080. y: quad[5] + position.y
  1081. }, {
  1082. x: quad[6] + position.x,
  1083. y: quad[7] + position.y
  1084. }]);
  1085. }
  1086. async _adoptElementHandle(handle, to) {
  1087. const nodeInfo = await this._client.send('DOM.describeNode', {
  1088. objectId: handle._objectId
  1089. });
  1090. return this._adoptBackendNodeId(nodeInfo.node.backendNodeId, to);
  1091. }
  1092. async _adoptBackendNodeId(backendNodeId, to) {
  1093. const result = await this._client._sendMayFail('DOM.resolveNode', {
  1094. backendNodeId,
  1095. executionContextId: to[contextDelegateSymbol]._contextId
  1096. });
  1097. if (!result || result.object.subtype === 'null') throw new Error(dom.kUnableToAdoptErrorMessage);
  1098. return to.createHandle(result.object).asElement();
  1099. }
  1100. }
  1101. async function emulateLocale(session, locale) {
  1102. try {
  1103. await session.send('Emulation.setLocaleOverride', {
  1104. locale
  1105. });
  1106. } catch (exception) {
  1107. // All pages in the same renderer share locale. All such pages belong to the same
  1108. // context and if locale is overridden for one of them its value is the same as
  1109. // we are trying to set so it's not a problem.
  1110. if (exception.message.includes('Another locale override is already in effect')) return;
  1111. throw exception;
  1112. }
  1113. }
  1114. async function emulateTimezone(session, timezoneId) {
  1115. try {
  1116. await session.send('Emulation.setTimezoneOverride', {
  1117. timezoneId: timezoneId
  1118. });
  1119. } catch (exception) {
  1120. if (exception.message.includes('Timezone override is already in effect')) return;
  1121. if (exception.message.includes('Invalid timezone')) throw new Error(`Invalid timezone ID: ${timezoneId}`);
  1122. throw exception;
  1123. }
  1124. }
  1125. const contextDelegateSymbol = Symbol('delegate');