ffPage.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.UTILITY_WORLD_NAME = exports.FFPage = void 0;
  6. var dialog = _interopRequireWildcard(require("../dialog"));
  7. var dom = _interopRequireWildcard(require("../dom"));
  8. var _eventsHelper = require("../../utils/eventsHelper");
  9. var _page = require("../page");
  10. var _ffAccessibility = require("./ffAccessibility");
  11. var _ffConnection = require("./ffConnection");
  12. var _ffExecutionContext = require("./ffExecutionContext");
  13. var _ffInput = require("./ffInput");
  14. var _ffNetworkManager = require("./ffNetworkManager");
  15. var _stackTrace = require("../../utils/stackTrace");
  16. var _debugLogger = require("../../common/debugLogger");
  17. var _manualPromise = require("../../utils/manualPromise");
  18. var _browserContext = require("../browserContext");
  19. var _errors = require("../errors");
  20. 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); }
  21. 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; }
  22. /**
  23. * Copyright 2019 Google Inc. All rights reserved.
  24. * Modifications copyright (c) Microsoft Corporation.
  25. *
  26. * Licensed under the Apache License, Version 2.0 (the "License");
  27. * you may not use this file except in compliance with the License.
  28. * You may obtain a copy of the License at
  29. *
  30. * http://www.apache.org/licenses/LICENSE-2.0
  31. *
  32. * Unless required by applicable law or agreed to in writing, software
  33. * distributed under the License is distributed on an "AS IS" BASIS,
  34. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  35. * See the License for the specific language governing permissions and
  36. * limitations under the License.
  37. */
  38. const UTILITY_WORLD_NAME = exports.UTILITY_WORLD_NAME = '__playwright_utility_world__';
  39. class FFPage {
  40. constructor(session, browserContext, opener) {
  41. this.cspErrorsAsynchronousForInlineScipts = true;
  42. this.rawMouse = void 0;
  43. this.rawKeyboard = void 0;
  44. this.rawTouchscreen = void 0;
  45. this._session = void 0;
  46. this._page = void 0;
  47. this._networkManager = void 0;
  48. this._browserContext = void 0;
  49. this._pagePromise = new _manualPromise.ManualPromise();
  50. this._initializedPage = null;
  51. this._initializationFailed = false;
  52. this._opener = void 0;
  53. this._contextIdToContext = void 0;
  54. this._eventListeners = void 0;
  55. this._workers = new Map();
  56. this._screencastId = void 0;
  57. this._initScripts = [];
  58. this._session = session;
  59. this._opener = opener;
  60. this.rawKeyboard = new _ffInput.RawKeyboardImpl(session);
  61. this.rawMouse = new _ffInput.RawMouseImpl(session);
  62. this.rawTouchscreen = new _ffInput.RawTouchscreenImpl(session);
  63. this._contextIdToContext = new Map();
  64. this._browserContext = browserContext;
  65. this._page = new _page.Page(this, browserContext);
  66. this.rawMouse.setPage(this._page);
  67. this._networkManager = new _ffNetworkManager.FFNetworkManager(session, this._page);
  68. this._page.on(_page.Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame));
  69. // TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
  70. this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.linkClicked', event => this._onLinkClicked(event.phase)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', this._onFileChooserOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerCreated', this._onWorkerCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerDestroyed', this._onWorkerDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.crashed', this._onCrashed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.videoRecordingStarted', this._onVideoRecordingStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketCreated', this._onWebSocketCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketClosed', this._onWebSocketClosed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this))];
  71. this._session.once('Page.ready', async () => {
  72. await this._page.initOpener(this._opener);
  73. if (this._initializationFailed) return;
  74. // Note: it is important to call |reportAsNew| before resolving pageOrError promise,
  75. // so that anyone who awaits pageOrError got a ready and reported page.
  76. this._initializedPage = this._page;
  77. this._page.reportAsNew();
  78. this._pagePromise.resolve(this._page);
  79. });
  80. // Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
  81. // Therefore, we can end up with an initialized page without utility world, although very unlikely.
  82. this.addInitScript('', UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
  83. }
  84. potentiallyUninitializedPage() {
  85. return this._page;
  86. }
  87. async _markAsError(error) {
  88. // Same error may be report twice: channer disconnected and session.send fails.
  89. if (this._initializationFailed) return;
  90. this._initializationFailed = true;
  91. if (!this._initializedPage) {
  92. await this._page.initOpener(this._opener);
  93. this._page.reportAsNew(error);
  94. this._pagePromise.resolve(error);
  95. }
  96. }
  97. async pageOrError() {
  98. return this._pagePromise;
  99. }
  100. _onWebSocketCreated(event) {
  101. this._page._frameManager.onWebSocketCreated(webSocketId(event.frameId, event.wsid), event.requestURL);
  102. this._page._frameManager.onWebSocketRequest(webSocketId(event.frameId, event.wsid));
  103. }
  104. _onWebSocketClosed(event) {
  105. if (event.error) this._page._frameManager.webSocketError(webSocketId(event.frameId, event.wsid), event.error);
  106. this._page._frameManager.webSocketClosed(webSocketId(event.frameId, event.wsid));
  107. }
  108. _onWebSocketFrameReceived(event) {
  109. this._page._frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
  110. }
  111. _onWebSocketFrameSent(event) {
  112. this._page._frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
  113. }
  114. _onExecutionContextCreated(payload) {
  115. const {
  116. executionContextId,
  117. auxData
  118. } = payload;
  119. const frame = this._page._frameManager.frame(auxData.frameId);
  120. if (!frame) return;
  121. const delegate = new _ffExecutionContext.FFExecutionContext(this._session, executionContextId);
  122. let worldName = null;
  123. if (auxData.name === UTILITY_WORLD_NAME) worldName = 'utility';else if (!auxData.name) worldName = 'main';
  124. const context = new dom.FrameExecutionContext(delegate, frame, worldName);
  125. context[contextDelegateSymbol] = delegate;
  126. if (worldName) frame._contextCreated(worldName, context);
  127. this._contextIdToContext.set(executionContextId, context);
  128. }
  129. _onExecutionContextDestroyed(payload) {
  130. const {
  131. executionContextId
  132. } = payload;
  133. const context = this._contextIdToContext.get(executionContextId);
  134. if (!context) return;
  135. this._contextIdToContext.delete(executionContextId);
  136. context.frame._contextDestroyed(context);
  137. }
  138. _onExecutionContextsCleared() {
  139. for (const executionContextId of Array.from(this._contextIdToContext.keys())) this._onExecutionContextDestroyed({
  140. executionContextId
  141. });
  142. }
  143. _removeContextsForFrame(frame) {
  144. for (const [contextId, context] of this._contextIdToContext) {
  145. if (context.frame === frame) this._contextIdToContext.delete(contextId);
  146. }
  147. }
  148. _onLinkClicked(phase) {
  149. if (phase === 'before') this._page._frameManager.frameWillPotentiallyRequestNavigation();else this._page._frameManager.frameDidPotentiallyRequestNavigation();
  150. }
  151. _onNavigationStarted(params) {
  152. this._page._frameManager.frameRequestedNavigation(params.frameId, params.navigationId);
  153. }
  154. _onNavigationAborted(params) {
  155. this._page._frameManager.frameAbortedNavigation(params.frameId, params.errorText, params.navigationId);
  156. }
  157. _onNavigationCommitted(params) {
  158. for (const [workerId, worker] of this._workers) {
  159. if (worker.frameId === params.frameId) this._onWorkerDestroyed({
  160. workerId
  161. });
  162. }
  163. this._page._frameManager.frameCommittedNewDocumentNavigation(params.frameId, params.url, params.name || '', params.navigationId || '', false);
  164. }
  165. _onSameDocumentNavigation(params) {
  166. this._page._frameManager.frameCommittedSameDocumentNavigation(params.frameId, params.url);
  167. }
  168. _onFrameAttached(params) {
  169. this._page._frameManager.frameAttached(params.frameId, params.parentFrameId);
  170. }
  171. _onFrameDetached(params) {
  172. this._page._frameManager.frameDetached(params.frameId);
  173. }
  174. _onEventFired(payload) {
  175. const {
  176. frameId,
  177. name
  178. } = payload;
  179. if (name === 'load') this._page._frameManager.frameLifecycleEvent(frameId, 'load');
  180. if (name === 'DOMContentLoaded') this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded');
  181. }
  182. _onUncaughtError(params) {
  183. const {
  184. name,
  185. message
  186. } = (0, _stackTrace.splitErrorMessage)(params.message);
  187. const error = new Error(message);
  188. error.stack = params.message + '\n' + params.stack.split('\n').filter(Boolean).map(a => a.replace(/([^@]*)@(.*)/, ' at $1 ($2)')).join('\n');
  189. error.name = name;
  190. this._page.emitOnContextOnceInitialized(_browserContext.BrowserContext.Events.PageError, error, this._page);
  191. }
  192. _onConsole(payload) {
  193. const {
  194. type,
  195. args,
  196. executionContextId,
  197. location
  198. } = payload;
  199. const context = this._contextIdToContext.get(executionContextId);
  200. if (!context) return;
  201. this._page._addConsoleMessage(type, args.map(arg => context.createHandle(arg)), location);
  202. }
  203. _onDialogOpened(params) {
  204. this._page.emitOnContext(_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog(this._page, params.type, params.message, async (accept, promptText) => {
  205. await this._session.sendMayFail('Page.handleDialog', {
  206. dialogId: params.dialogId,
  207. accept,
  208. promptText
  209. });
  210. }, params.defaultValue));
  211. }
  212. async _onBindingCalled(event) {
  213. const pageOrError = await this.pageOrError();
  214. if (!(pageOrError instanceof Error)) {
  215. const context = this._contextIdToContext.get(event.executionContextId);
  216. if (context) await this._page._onBindingCalled(event.payload, context);
  217. }
  218. }
  219. async _onFileChooserOpened(payload) {
  220. const {
  221. executionContextId,
  222. element
  223. } = payload;
  224. const context = this._contextIdToContext.get(executionContextId);
  225. if (!context) return;
  226. const handle = context.createHandle(element).asElement();
  227. await this._page._onFileChooserOpened(handle);
  228. }
  229. async _onWorkerCreated(event) {
  230. const workerId = event.workerId;
  231. const worker = new _page.Worker(this._page, event.url);
  232. const workerSession = new _ffConnection.FFSession(this._session._connection, workerId, message => {
  233. this._session.send('Page.sendMessageToWorker', {
  234. frameId: event.frameId,
  235. workerId: workerId,
  236. message: JSON.stringify(message)
  237. }).catch(e => {
  238. workerSession.dispatchMessage({
  239. id: message.id,
  240. method: '',
  241. params: {},
  242. error: {
  243. message: e.message,
  244. data: undefined
  245. }
  246. });
  247. });
  248. });
  249. this._workers.set(workerId, {
  250. session: workerSession,
  251. frameId: event.frameId
  252. });
  253. this._page._addWorker(workerId, worker);
  254. workerSession.once('Runtime.executionContextCreated', event => {
  255. worker._createExecutionContext(new _ffExecutionContext.FFExecutionContext(workerSession, event.executionContextId));
  256. });
  257. workerSession.on('Runtime.console', event => {
  258. const {
  259. type,
  260. args,
  261. location
  262. } = event;
  263. const context = worker._existingExecutionContext;
  264. this._page._addConsoleMessage(type, args.map(arg => context.createHandle(arg)), location);
  265. });
  266. // Note: we receive worker exceptions directly from the page.
  267. }
  268. _onWorkerDestroyed(event) {
  269. const workerId = event.workerId;
  270. const worker = this._workers.get(workerId);
  271. if (!worker) return;
  272. worker.session.dispose();
  273. this._workers.delete(workerId);
  274. this._page._removeWorker(workerId);
  275. }
  276. async _onDispatchMessageFromWorker(event) {
  277. const worker = this._workers.get(event.workerId);
  278. if (!worker) return;
  279. worker.session.dispatchMessage(JSON.parse(event.message));
  280. }
  281. async _onCrashed(event) {
  282. this._session.markAsCrashed();
  283. this._page._didCrash();
  284. }
  285. _onVideoRecordingStarted(event) {
  286. this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this.pageOrError());
  287. }
  288. async exposeBinding(binding) {
  289. await this._session.send('Page.addBinding', {
  290. name: binding.name,
  291. script: binding.source
  292. });
  293. }
  294. async removeExposedBindings() {
  295. // TODO: implement me.
  296. }
  297. didClose() {
  298. this._markAsError(new _errors.TargetClosedError());
  299. this._session.dispose();
  300. _eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
  301. this._networkManager.dispose();
  302. this._page._didClose();
  303. }
  304. async navigateFrame(frame, url, referer) {
  305. const response = await this._session.send('Page.navigate', {
  306. url,
  307. referer,
  308. frameId: frame._id
  309. });
  310. return {
  311. newDocumentId: response.navigationId || undefined
  312. };
  313. }
  314. async updateExtraHTTPHeaders() {
  315. await this._session.send('Network.setExtraHTTPHeaders', {
  316. headers: this._page.extraHTTPHeaders() || []
  317. });
  318. }
  319. async updateEmulatedViewportSize() {
  320. const viewportSize = this._page.viewportSize();
  321. await this._session.send('Page.setViewportSize', {
  322. viewportSize
  323. });
  324. }
  325. async bringToFront() {
  326. await this._session.send('Page.bringToFront', {});
  327. }
  328. async updateEmulateMedia() {
  329. const emulatedMedia = this._page.emulatedMedia();
  330. const colorScheme = emulatedMedia.colorScheme === 'no-override' ? undefined : emulatedMedia.colorScheme;
  331. const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? undefined : emulatedMedia.reducedMotion;
  332. const forcedColors = emulatedMedia.forcedColors === 'no-override' ? undefined : emulatedMedia.forcedColors;
  333. await this._session.send('Page.setEmulatedMedia', {
  334. // Empty string means reset.
  335. type: emulatedMedia.media === 'no-override' ? '' : emulatedMedia.media,
  336. colorScheme,
  337. reducedMotion,
  338. forcedColors
  339. });
  340. }
  341. async updateRequestInterception() {
  342. await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
  343. }
  344. async updateFileChooserInterception() {
  345. const enabled = this._page.fileChooserIntercepted();
  346. await this._session.send('Page.setInterceptFileChooserDialog', {
  347. enabled
  348. }).catch(() => {}); // target can be closed.
  349. }
  350. async reload() {
  351. await this._session.send('Page.reload');
  352. }
  353. async goBack() {
  354. const {
  355. success
  356. } = await this._session.send('Page.goBack', {
  357. frameId: this._page.mainFrame()._id
  358. });
  359. return success;
  360. }
  361. async goForward() {
  362. const {
  363. success
  364. } = await this._session.send('Page.goForward', {
  365. frameId: this._page.mainFrame()._id
  366. });
  367. return success;
  368. }
  369. async addInitScript(script, worldName) {
  370. this._initScripts.push({
  371. script,
  372. worldName
  373. });
  374. await this._session.send('Page.setInitScripts', {
  375. scripts: this._initScripts
  376. });
  377. }
  378. async removeInitScripts() {
  379. this._initScripts = [];
  380. await this._session.send('Page.setInitScripts', {
  381. scripts: []
  382. });
  383. }
  384. async closePage(runBeforeUnload) {
  385. await this._session.send('Page.close', {
  386. runBeforeUnload
  387. });
  388. }
  389. async setBackgroundColor(color) {
  390. if (color) throw new Error('Not implemented');
  391. }
  392. async takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, scale) {
  393. if (!documentRect) {
  394. const scrollOffset = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({
  395. x: window.scrollX,
  396. y: window.scrollY
  397. }));
  398. documentRect = {
  399. x: viewportRect.x + scrollOffset.x,
  400. y: viewportRect.y + scrollOffset.y,
  401. width: viewportRect.width,
  402. height: viewportRect.height
  403. };
  404. }
  405. progress.throwIfAborted();
  406. const {
  407. data
  408. } = await this._session.send('Page.screenshot', {
  409. mimeType: 'image/' + format,
  410. clip: documentRect,
  411. quality,
  412. omitDeviceScaleFactor: scale === 'css'
  413. });
  414. return Buffer.from(data, 'base64');
  415. }
  416. async getContentFrame(handle) {
  417. const {
  418. contentFrameId
  419. } = await this._session.send('Page.describeNode', {
  420. frameId: handle._context.frame._id,
  421. objectId: handle._objectId
  422. });
  423. if (!contentFrameId) return null;
  424. return this._page._frameManager.frame(contentFrameId);
  425. }
  426. async getOwnerFrame(handle) {
  427. const {
  428. ownerFrameId
  429. } = await this._session.send('Page.describeNode', {
  430. frameId: handle._context.frame._id,
  431. objectId: handle._objectId
  432. });
  433. return ownerFrameId || null;
  434. }
  435. isElementHandle(remoteObject) {
  436. return remoteObject.subtype === 'node';
  437. }
  438. async getBoundingBox(handle) {
  439. const quads = await this.getContentQuads(handle);
  440. if (!quads || !quads.length) return null;
  441. let minX = Infinity;
  442. let maxX = -Infinity;
  443. let minY = Infinity;
  444. let maxY = -Infinity;
  445. for (const quad of quads) {
  446. for (const point of quad) {
  447. minX = Math.min(minX, point.x);
  448. maxX = Math.max(maxX, point.x);
  449. minY = Math.min(minY, point.y);
  450. maxY = Math.max(maxY, point.y);
  451. }
  452. }
  453. return {
  454. x: minX,
  455. y: minY,
  456. width: maxX - minX,
  457. height: maxY - minY
  458. };
  459. }
  460. async scrollRectIntoViewIfNeeded(handle, rect) {
  461. return await this._session.send('Page.scrollIntoViewIfNeeded', {
  462. frameId: handle._context.frame._id,
  463. objectId: handle._objectId,
  464. rect
  465. }).then(() => 'done').catch(e => {
  466. if (e instanceof Error && e.message.includes('Node is detached from document')) return 'error:notconnected';
  467. if (e instanceof Error && e.message.includes('Node does not have a layout object')) return 'error:notvisible';
  468. throw e;
  469. });
  470. }
  471. async setScreencastOptions(options) {
  472. if (options) {
  473. const {
  474. screencastId
  475. } = await this._session.send('Page.startScreencast', options);
  476. this._screencastId = screencastId;
  477. } else {
  478. await this._session.send('Page.stopScreencast');
  479. }
  480. }
  481. _onScreencastFrame(event) {
  482. if (!this._screencastId) return;
  483. const screencastId = this._screencastId;
  484. this._page.throttleScreencastFrameAck(() => {
  485. this._session.send('Page.screencastFrameAck', {
  486. screencastId
  487. }).catch(e => _debugLogger.debugLogger.log('error', e));
  488. });
  489. const buffer = Buffer.from(event.data, 'base64');
  490. this._page.emit(_page.Page.Events.ScreencastFrame, {
  491. buffer,
  492. width: event.deviceWidth,
  493. height: event.deviceHeight
  494. });
  495. }
  496. rafCountForStablePosition() {
  497. return 1;
  498. }
  499. async getContentQuads(handle) {
  500. const result = await this._session.sendMayFail('Page.getContentQuads', {
  501. frameId: handle._context.frame._id,
  502. objectId: handle._objectId
  503. });
  504. if (!result) return null;
  505. return result.quads.map(quad => [quad.p1, quad.p2, quad.p3, quad.p4]);
  506. }
  507. async setInputFiles(handle, files) {
  508. await handle.evaluateInUtility(([injected, node, files]) => injected.setInputFiles(node, files), files);
  509. }
  510. async setInputFilePaths(progress, handle, files) {
  511. await Promise.all([this._session.send('Page.setFileInputFiles', {
  512. frameId: handle._context.frame._id,
  513. objectId: handle._objectId,
  514. files
  515. }), handle.dispatchEvent(progress.metadata, 'input'), handle.dispatchEvent(progress.metadata, 'change')]);
  516. }
  517. async adoptElementHandle(handle, to) {
  518. const result = await this._session.send('Page.adoptNode', {
  519. frameId: handle._context.frame._id,
  520. objectId: handle._objectId,
  521. executionContextId: to[contextDelegateSymbol]._executionContextId
  522. });
  523. if (!result.remoteObject) throw new Error(dom.kUnableToAdoptErrorMessage);
  524. return to.createHandle(result.remoteObject);
  525. }
  526. async getAccessibilityTree(needle) {
  527. return (0, _ffAccessibility.getAccessibilityTree)(this._session, needle);
  528. }
  529. async inputActionEpilogue() {}
  530. async resetForReuse() {
  531. // Firefox sometimes keeps the last mouse position in the page,
  532. // which affects things like hovered state.
  533. // See https://github.com/microsoft/playwright/issues/22432.
  534. // Move mouse to (-1, -1) to avoid anything being hovered.
  535. await this.rawMouse.move(-1, -1, 'none', new Set(), new Set(), false);
  536. }
  537. async getFrameElement(frame) {
  538. const parent = frame.parentFrame();
  539. if (!parent) throw new Error('Frame has been detached.');
  540. const context = await parent._mainContext();
  541. const result = await this._session.send('Page.adoptNode', {
  542. frameId: frame._id,
  543. executionContextId: context[contextDelegateSymbol]._executionContextId
  544. });
  545. if (!result.remoteObject) throw new Error('Frame has been detached.');
  546. return context.createHandle(result.remoteObject);
  547. }
  548. shouldToggleStyleSheetToSyncAnimations() {
  549. return false;
  550. }
  551. }
  552. exports.FFPage = FFPage;
  553. function webSocketId(frameId, wsid) {
  554. return `${frameId}---${wsid}`;
  555. }
  556. const contextDelegateSymbol = Symbol('delegate');