frames.js 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.NavigationAbortedError = exports.FrameManager = exports.Frame = void 0;
  6. var dom = _interopRequireWildcard(require("./dom"));
  7. var _helper = require("./helper");
  8. var _eventsHelper = require("../utils/eventsHelper");
  9. var js = _interopRequireWildcard(require("./javascript"));
  10. var network = _interopRequireWildcard(require("./network"));
  11. var _page = require("./page");
  12. var types = _interopRequireWildcard(require("./types"));
  13. var _browserContext = require("./browserContext");
  14. var _progress = require("./progress");
  15. var _utils = require("../utils");
  16. var _manualPromise = require("../utils/manualPromise");
  17. var _debugLogger = require("../common/debugLogger");
  18. var _instrumentation = require("./instrumentation");
  19. var _protocolError = require("./protocolError");
  20. var _selectorParser = require("../utils/isomorphic/selectorParser");
  21. var _locatorGenerators = require("../utils/isomorphic/locatorGenerators");
  22. var _frameSelectors = require("./frameSelectors");
  23. var _errors = require("./errors");
  24. var _fileUploadUtils = require("./fileUploadUtils");
  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 NavigationAbortedError extends Error {
  44. constructor(documentId, message) {
  45. super(message);
  46. this.documentId = void 0;
  47. this.documentId = documentId;
  48. }
  49. }
  50. exports.NavigationAbortedError = NavigationAbortedError;
  51. const kDummyFrameId = '<dummy>';
  52. class FrameManager {
  53. constructor(page) {
  54. this._page = void 0;
  55. this._frames = new Map();
  56. this._mainFrame = void 0;
  57. this._consoleMessageTags = new Map();
  58. this._signalBarriers = new Set();
  59. this._webSockets = new Map();
  60. this._openedDialogs = new Set();
  61. this._closeAllOpeningDialogs = false;
  62. this._page = page;
  63. this._mainFrame = undefined;
  64. }
  65. createDummyMainFrameIfNeeded() {
  66. if (!this._mainFrame) this.frameAttached(kDummyFrameId, null);
  67. }
  68. dispose() {
  69. for (const frame of this._frames.values()) {
  70. frame._stopNetworkIdleTimer();
  71. frame._invalidateNonStallingEvaluations('Target crashed');
  72. }
  73. }
  74. mainFrame() {
  75. return this._mainFrame;
  76. }
  77. frames() {
  78. const frames = [];
  79. collect(this._mainFrame);
  80. return frames;
  81. function collect(frame) {
  82. frames.push(frame);
  83. for (const subframe of frame.childFrames()) collect(subframe);
  84. }
  85. }
  86. frame(frameId) {
  87. return this._frames.get(frameId) || null;
  88. }
  89. frameAttached(frameId, parentFrameId) {
  90. const parentFrame = parentFrameId ? this._frames.get(parentFrameId) : null;
  91. if (!parentFrame) {
  92. if (this._mainFrame) {
  93. // Update frame id to retain frame identity on cross-process navigation.
  94. this._frames.delete(this._mainFrame._id);
  95. this._mainFrame._id = frameId;
  96. } else {
  97. (0, _utils.assert)(!this._frames.has(frameId));
  98. this._mainFrame = new Frame(this._page, frameId, parentFrame);
  99. }
  100. this._frames.set(frameId, this._mainFrame);
  101. return this._mainFrame;
  102. } else {
  103. (0, _utils.assert)(!this._frames.has(frameId));
  104. const frame = new Frame(this._page, frameId, parentFrame);
  105. this._frames.set(frameId, frame);
  106. this._page.emit(_page.Page.Events.FrameAttached, frame);
  107. return frame;
  108. }
  109. }
  110. async waitForSignalsCreatedBy(progress, noWaitAfter, action, source) {
  111. if (noWaitAfter) return action();
  112. const barrier = new SignalBarrier(progress);
  113. this._signalBarriers.add(barrier);
  114. if (progress) progress.cleanupWhenAborted(() => this._signalBarriers.delete(barrier));
  115. const result = await action();
  116. if (source === 'input') await this._page._delegate.inputActionEpilogue();
  117. await barrier.waitFor();
  118. this._signalBarriers.delete(barrier);
  119. // Resolve in the next task, after all waitForNavigations.
  120. await new Promise((0, _utils.makeWaitForNextTask)());
  121. return result;
  122. }
  123. frameWillPotentiallyRequestNavigation() {
  124. for (const barrier of this._signalBarriers) barrier.retain();
  125. }
  126. frameDidPotentiallyRequestNavigation() {
  127. for (const barrier of this._signalBarriers) barrier.release();
  128. }
  129. frameRequestedNavigation(frameId, documentId) {
  130. const frame = this._frames.get(frameId);
  131. if (!frame) return;
  132. for (const barrier of this._signalBarriers) barrier.addFrameNavigation(frame);
  133. if (frame.pendingDocument() && frame.pendingDocument().documentId === documentId) {
  134. // Do not override request with undefined.
  135. return;
  136. }
  137. const request = documentId ? Array.from(frame._inflightRequests).find(request => request._documentId === documentId) : undefined;
  138. frame.setPendingDocument({
  139. documentId,
  140. request
  141. });
  142. }
  143. frameCommittedNewDocumentNavigation(frameId, url, name, documentId, initial) {
  144. const frame = this._frames.get(frameId);
  145. this.removeChildFramesRecursively(frame);
  146. this.clearWebSockets(frame);
  147. frame._url = url;
  148. frame._name = name;
  149. let keepPending;
  150. const pendingDocument = frame.pendingDocument();
  151. if (pendingDocument) {
  152. if (pendingDocument.documentId === undefined) {
  153. // Pending with unknown documentId - assume it is the one being committed.
  154. pendingDocument.documentId = documentId;
  155. }
  156. if (pendingDocument.documentId === documentId) {
  157. // Committing a pending document.
  158. frame._currentDocument = pendingDocument;
  159. } else {
  160. // Sometimes, we already have a new pending when the old one commits.
  161. // An example would be Chromium error page followed by a new navigation request,
  162. // where the error page commit arrives after Network.requestWillBeSent for the
  163. // new navigation.
  164. // We commit, but keep the pending request since it's not done yet.
  165. keepPending = pendingDocument;
  166. frame._currentDocument = {
  167. documentId,
  168. request: undefined
  169. };
  170. }
  171. frame.setPendingDocument(undefined);
  172. } else {
  173. // No pending - just commit a new document.
  174. frame._currentDocument = {
  175. documentId,
  176. request: undefined
  177. };
  178. }
  179. frame._onClearLifecycle();
  180. const navigationEvent = {
  181. url,
  182. name,
  183. newDocument: frame._currentDocument,
  184. isPublic: true
  185. };
  186. this._fireInternalFrameNavigation(frame, navigationEvent);
  187. if (!initial) {
  188. _debugLogger.debugLogger.log('api', ` navigated to "${url}"`);
  189. this._page.frameNavigatedToNewDocument(frame);
  190. }
  191. // Restore pending if any - see comments above about keepPending.
  192. frame.setPendingDocument(keepPending);
  193. }
  194. frameCommittedSameDocumentNavigation(frameId, url) {
  195. const frame = this._frames.get(frameId);
  196. if (!frame) return;
  197. frame._url = url;
  198. const navigationEvent = {
  199. url,
  200. name: frame._name,
  201. isPublic: true
  202. };
  203. this._fireInternalFrameNavigation(frame, navigationEvent);
  204. _debugLogger.debugLogger.log('api', ` navigated to "${url}"`);
  205. }
  206. frameAbortedNavigation(frameId, errorText, documentId) {
  207. const frame = this._frames.get(frameId);
  208. if (!frame || !frame.pendingDocument()) return;
  209. if (documentId !== undefined && frame.pendingDocument().documentId !== documentId) return;
  210. const navigationEvent = {
  211. url: frame._url,
  212. name: frame._name,
  213. newDocument: frame.pendingDocument(),
  214. error: new NavigationAbortedError(documentId, errorText),
  215. isPublic: !(documentId && frame._redirectedNavigations.has(documentId))
  216. };
  217. frame.setPendingDocument(undefined);
  218. this._fireInternalFrameNavigation(frame, navigationEvent);
  219. }
  220. frameDetached(frameId) {
  221. const frame = this._frames.get(frameId);
  222. if (frame) {
  223. this._removeFramesRecursively(frame);
  224. this._page.mainFrame()._recalculateNetworkIdle();
  225. }
  226. }
  227. frameLifecycleEvent(frameId, event) {
  228. const frame = this._frames.get(frameId);
  229. if (frame) frame._onLifecycleEvent(event);
  230. }
  231. requestStarted(request, route) {
  232. const frame = request.frame();
  233. this._inflightRequestStarted(request);
  234. if (request._documentId) frame.setPendingDocument({
  235. documentId: request._documentId,
  236. request
  237. });
  238. if (request._isFavicon) {
  239. if (route) route.continue(request, {
  240. isFallback: true
  241. }).catch(() => {});
  242. return;
  243. }
  244. this._page.emitOnContext(_browserContext.BrowserContext.Events.Request, request);
  245. if (route) {
  246. var _this$_page$_serverRe, _this$_page, _this$_page$_clientRe, _this$_page2, _this$_page$_browserC, _this$_page$_browserC2;
  247. const r = new network.Route(request, route);
  248. if ((_this$_page$_serverRe = (_this$_page = this._page)._serverRequestInterceptor) !== null && _this$_page$_serverRe !== void 0 && _this$_page$_serverRe.call(_this$_page, r, request)) return;
  249. if ((_this$_page$_clientRe = (_this$_page2 = this._page)._clientRequestInterceptor) !== null && _this$_page$_clientRe !== void 0 && _this$_page$_clientRe.call(_this$_page2, r, request)) return;
  250. if ((_this$_page$_browserC = (_this$_page$_browserC2 = this._page._browserContext)._requestInterceptor) !== null && _this$_page$_browserC !== void 0 && _this$_page$_browserC.call(_this$_page$_browserC2, r, request)) return;
  251. r.continue({
  252. isFallback: true
  253. }).catch(() => {});
  254. }
  255. }
  256. requestReceivedResponse(response) {
  257. if (response.request()._isFavicon) return;
  258. this._page.emitOnContext(_browserContext.BrowserContext.Events.Response, response);
  259. }
  260. reportRequestFinished(request, response) {
  261. this._inflightRequestFinished(request);
  262. if (request._isFavicon) return;
  263. this._page.emitOnContext(_browserContext.BrowserContext.Events.RequestFinished, {
  264. request,
  265. response
  266. });
  267. }
  268. requestFailed(request, canceled) {
  269. const frame = request.frame();
  270. this._inflightRequestFinished(request);
  271. if (frame.pendingDocument() && frame.pendingDocument().request === request) {
  272. let errorText = request.failure().errorText;
  273. if (canceled) errorText += '; maybe frame was detached?';
  274. this.frameAbortedNavigation(frame._id, errorText, frame.pendingDocument().documentId);
  275. }
  276. if (request._isFavicon) return;
  277. this._page.emitOnContext(_browserContext.BrowserContext.Events.RequestFailed, request);
  278. }
  279. dialogDidOpen(dialog) {
  280. // Any ongoing evaluations will be stalled until the dialog is closed.
  281. for (const frame of this._frames.values()) frame._invalidateNonStallingEvaluations('JavaScript dialog interrupted evaluation');
  282. if (this._closeAllOpeningDialogs) dialog.close().then(() => {});else this._openedDialogs.add(dialog);
  283. }
  284. dialogWillClose(dialog) {
  285. this._openedDialogs.delete(dialog);
  286. }
  287. async closeOpenDialogs() {
  288. await Promise.all([...this._openedDialogs].map(dialog => dialog.close())).catch(() => {});
  289. this._openedDialogs.clear();
  290. }
  291. setCloseAllOpeningDialogs(closeDialogs) {
  292. this._closeAllOpeningDialogs = closeDialogs;
  293. }
  294. removeChildFramesRecursively(frame) {
  295. for (const child of frame.childFrames()) this._removeFramesRecursively(child);
  296. }
  297. _removeFramesRecursively(frame) {
  298. this.removeChildFramesRecursively(frame);
  299. frame._onDetached();
  300. this._frames.delete(frame._id);
  301. if (!this._page.isClosed()) this._page.emit(_page.Page.Events.FrameDetached, frame);
  302. }
  303. _inflightRequestFinished(request) {
  304. const frame = request.frame();
  305. if (request._isFavicon) return;
  306. if (!frame._inflightRequests.has(request)) return;
  307. frame._inflightRequests.delete(request);
  308. if (frame._inflightRequests.size === 0) frame._startNetworkIdleTimer();
  309. }
  310. _inflightRequestStarted(request) {
  311. const frame = request.frame();
  312. if (request._isFavicon) return;
  313. frame._inflightRequests.add(request);
  314. if (frame._inflightRequests.size === 1) frame._stopNetworkIdleTimer();
  315. }
  316. interceptConsoleMessage(message) {
  317. if (message.type() !== 'debug') return false;
  318. const tag = message.text();
  319. const handler = this._consoleMessageTags.get(tag);
  320. if (!handler) return false;
  321. this._consoleMessageTags.delete(tag);
  322. handler();
  323. return true;
  324. }
  325. clearWebSockets(frame) {
  326. // TODO: attribute sockets to frames.
  327. if (frame.parentFrame()) return;
  328. this._webSockets.clear();
  329. }
  330. onWebSocketCreated(requestId, url) {
  331. const ws = new network.WebSocket(this._page, url);
  332. this._webSockets.set(requestId, ws);
  333. }
  334. onWebSocketRequest(requestId) {
  335. const ws = this._webSockets.get(requestId);
  336. if (ws && ws.markAsNotified()) this._page.emit(_page.Page.Events.WebSocket, ws);
  337. }
  338. onWebSocketResponse(requestId, status, statusText) {
  339. const ws = this._webSockets.get(requestId);
  340. if (status < 400) return;
  341. if (ws) ws.error(`${statusText}: ${status}`);
  342. }
  343. onWebSocketFrameSent(requestId, opcode, data) {
  344. const ws = this._webSockets.get(requestId);
  345. if (ws) ws.frameSent(opcode, data);
  346. }
  347. webSocketFrameReceived(requestId, opcode, data) {
  348. const ws = this._webSockets.get(requestId);
  349. if (ws) ws.frameReceived(opcode, data);
  350. }
  351. webSocketClosed(requestId) {
  352. const ws = this._webSockets.get(requestId);
  353. if (ws) ws.closed();
  354. this._webSockets.delete(requestId);
  355. }
  356. webSocketError(requestId, errorMessage) {
  357. const ws = this._webSockets.get(requestId);
  358. if (ws) ws.error(errorMessage);
  359. }
  360. _fireInternalFrameNavigation(frame, event) {
  361. frame.emit(Frame.Events.InternalNavigation, event);
  362. }
  363. }
  364. exports.FrameManager = FrameManager;
  365. class Frame extends _instrumentation.SdkObject {
  366. constructor(page, id, parentFrame) {
  367. super(page, 'frame');
  368. this._id = void 0;
  369. this._firedLifecycleEvents = new Set();
  370. this._firedNetworkIdleSelf = false;
  371. this._currentDocument = void 0;
  372. this._pendingDocument = void 0;
  373. this._page = void 0;
  374. this._parentFrame = void 0;
  375. this._url = '';
  376. this._contextData = new Map();
  377. this._childFrames = new Set();
  378. this._name = '';
  379. this._inflightRequests = new Set();
  380. this._networkIdleTimer = void 0;
  381. this._setContentCounter = 0;
  382. this._detachedScope = new _utils.LongStandingScope();
  383. this._raceAgainstEvaluationStallingEventsPromises = new Set();
  384. this._redirectedNavigations = new Map();
  385. // documentId -> data
  386. this.selectors = void 0;
  387. this.attribution.frame = this;
  388. this._id = id;
  389. this._page = page;
  390. this._parentFrame = parentFrame;
  391. this._currentDocument = {
  392. documentId: undefined,
  393. request: undefined
  394. };
  395. this.selectors = new _frameSelectors.FrameSelectors(this);
  396. this._contextData.set('main', {
  397. contextPromise: new _manualPromise.ManualPromise(),
  398. context: null
  399. });
  400. this._contextData.set('utility', {
  401. contextPromise: new _manualPromise.ManualPromise(),
  402. context: null
  403. });
  404. this._setContext('main', null);
  405. this._setContext('utility', null);
  406. if (this._parentFrame) this._parentFrame._childFrames.add(this);
  407. this._firedLifecycleEvents.add('commit');
  408. if (id !== kDummyFrameId) this._startNetworkIdleTimer();
  409. }
  410. isDetached() {
  411. return this._detachedScope.isClosed();
  412. }
  413. _onLifecycleEvent(event) {
  414. if (this._firedLifecycleEvents.has(event)) return;
  415. this._firedLifecycleEvents.add(event);
  416. this.emit(Frame.Events.AddLifecycle, event);
  417. if (this === this._page.mainFrame() && this._url !== 'about:blank') _debugLogger.debugLogger.log('api', ` "${event}" event fired`);
  418. this._page.mainFrame()._recalculateNetworkIdle();
  419. }
  420. _onClearLifecycle() {
  421. for (const event of this._firedLifecycleEvents) this.emit(Frame.Events.RemoveLifecycle, event);
  422. this._firedLifecycleEvents.clear();
  423. // Keep the current navigation request if any.
  424. this._inflightRequests = new Set(Array.from(this._inflightRequests).filter(request => request === this._currentDocument.request));
  425. this._stopNetworkIdleTimer();
  426. if (this._inflightRequests.size === 0) this._startNetworkIdleTimer();
  427. this._page.mainFrame()._recalculateNetworkIdle(this);
  428. this._onLifecycleEvent('commit');
  429. }
  430. setPendingDocument(documentInfo) {
  431. this._pendingDocument = documentInfo;
  432. if (documentInfo) this._invalidateNonStallingEvaluations('Navigation interrupted the evaluation');
  433. }
  434. pendingDocument() {
  435. return this._pendingDocument;
  436. }
  437. _invalidateNonStallingEvaluations(message) {
  438. if (!this._raceAgainstEvaluationStallingEventsPromises.size) return;
  439. const error = new Error(message);
  440. for (const promise of this._raceAgainstEvaluationStallingEventsPromises) promise.reject(error);
  441. }
  442. async raceAgainstEvaluationStallingEvents(cb) {
  443. if (this._pendingDocument) throw new Error('Frame is currently attempting a navigation');
  444. if (this._page._frameManager._openedDialogs.size) throw new Error('Open JavaScript dialog prevents evaluation');
  445. const promise = new _manualPromise.ManualPromise();
  446. this._raceAgainstEvaluationStallingEventsPromises.add(promise);
  447. try {
  448. return await Promise.race([cb(), promise]);
  449. } finally {
  450. this._raceAgainstEvaluationStallingEventsPromises.delete(promise);
  451. }
  452. }
  453. nonStallingRawEvaluateInExistingMainContext(expression) {
  454. return this.raceAgainstEvaluationStallingEvents(() => {
  455. const context = this._existingMainContext();
  456. if (!context) throw new Error('Frame does not yet have a main execution context');
  457. return context.rawEvaluateJSON(expression);
  458. });
  459. }
  460. nonStallingEvaluateInExistingContext(expression, isFunction, world) {
  461. return this.raceAgainstEvaluationStallingEvents(() => {
  462. var _this$_contextData$ge;
  463. const context = (_this$_contextData$ge = this._contextData.get(world)) === null || _this$_contextData$ge === void 0 ? void 0 : _this$_contextData$ge.context;
  464. if (!context) throw new Error('Frame does not yet have the execution context');
  465. return context.evaluateExpression(expression, {
  466. isFunction
  467. });
  468. });
  469. }
  470. _recalculateNetworkIdle(frameThatAllowsRemovingNetworkIdle) {
  471. let isNetworkIdle = this._firedNetworkIdleSelf;
  472. for (const child of this._childFrames) {
  473. child._recalculateNetworkIdle(frameThatAllowsRemovingNetworkIdle);
  474. // We require networkidle event to be fired in the whole frame subtree, and then consider it done.
  475. if (!child._firedLifecycleEvents.has('networkidle')) isNetworkIdle = false;
  476. }
  477. if (isNetworkIdle && !this._firedLifecycleEvents.has('networkidle')) {
  478. this._firedLifecycleEvents.add('networkidle');
  479. this.emit(Frame.Events.AddLifecycle, 'networkidle');
  480. if (this === this._page.mainFrame() && this._url !== 'about:blank') _debugLogger.debugLogger.log('api', ` "networkidle" event fired`);
  481. }
  482. if (frameThatAllowsRemovingNetworkIdle !== this && this._firedLifecycleEvents.has('networkidle') && !isNetworkIdle) {
  483. // Usually, networkidle is fired once and not removed after that.
  484. // However, when we clear them right before a new commit, this is allowed for a particular frame.
  485. this._firedLifecycleEvents.delete('networkidle');
  486. this.emit(Frame.Events.RemoveLifecycle, 'networkidle');
  487. }
  488. }
  489. async raceNavigationAction(progress, options, action) {
  490. return _utils.LongStandingScope.raceMultiple([this._detachedScope, this._page.openScope], action().catch(e => {
  491. if (e instanceof NavigationAbortedError && e.documentId) {
  492. const data = this._redirectedNavigations.get(e.documentId);
  493. if (data) {
  494. progress.log(`waiting for redirected navigation to "${data.url}"`);
  495. return data.gotoPromise;
  496. }
  497. }
  498. throw e;
  499. }));
  500. }
  501. redirectNavigation(url, documentId, referer) {
  502. const controller = new _progress.ProgressController((0, _instrumentation.serverSideCallMetadata)(), this);
  503. const data = {
  504. url,
  505. gotoPromise: controller.run(progress => this._gotoAction(progress, url, {
  506. referer
  507. }), 0)
  508. };
  509. this._redirectedNavigations.set(documentId, data);
  510. data.gotoPromise.finally(() => this._redirectedNavigations.delete(documentId));
  511. }
  512. async goto(metadata, url, options = {}) {
  513. const constructedNavigationURL = (0, _utils.constructURLBasedOnBaseURL)(this._page._browserContext._options.baseURL, url);
  514. const controller = new _progress.ProgressController(metadata, this);
  515. return controller.run(progress => this._goto(progress, constructedNavigationURL, options), this._page._timeoutSettings.navigationTimeout(options));
  516. }
  517. async _goto(progress, url, options) {
  518. return this.raceNavigationAction(progress, options, async () => this._gotoAction(progress, url, options));
  519. }
  520. async _gotoAction(progress, url, options) {
  521. const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
  522. progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
  523. const headers = this._page.extraHTTPHeaders() || [];
  524. const refererHeader = headers.find(h => h.name.toLowerCase() === 'referer');
  525. let referer = refererHeader ? refererHeader.value : undefined;
  526. if (options.referer !== undefined) {
  527. if (referer !== undefined && referer !== options.referer) throw new Error('"referer" is already specified as extra HTTP header');
  528. referer = options.referer;
  529. }
  530. url = _helper.helper.completeUserURL(url);
  531. const sameDocument = _helper.helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, e => !e.newDocument);
  532. const navigateResult = await this._page._delegate.navigateFrame(this, url, referer);
  533. let event;
  534. if (navigateResult.newDocumentId) {
  535. sameDocument.dispose();
  536. event = await _helper.helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, event => {
  537. // We are interested either in this specific document, or any other document that
  538. // did commit and replaced the expected document.
  539. return event.newDocument && (event.newDocument.documentId === navigateResult.newDocumentId || !event.error);
  540. }).promise;
  541. if (event.newDocument.documentId !== navigateResult.newDocumentId) {
  542. // This is just a sanity check. In practice, new navigation should
  543. // cancel the previous one and report "request cancelled"-like error.
  544. throw new NavigationAbortedError(navigateResult.newDocumentId, `Navigation to "${url}" is interrupted by another navigation to "${event.url}"`);
  545. }
  546. if (event.error) throw event.error;
  547. } else {
  548. event = await sameDocument.promise;
  549. }
  550. if (!this._firedLifecycleEvents.has(waitUntil)) await _helper.helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, e => e === waitUntil).promise;
  551. const request = event.newDocument ? event.newDocument.request : undefined;
  552. const response = request ? request._finalRequest().response() : null;
  553. return response;
  554. }
  555. async _waitForNavigation(progress, requiresNewDocument, options) {
  556. const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
  557. progress.log(`waiting for navigation until "${waitUntil}"`);
  558. const navigationEvent = await _helper.helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, event => {
  559. // Any failed navigation results in a rejection.
  560. if (event.error) return true;
  561. if (requiresNewDocument && !event.newDocument) return false;
  562. progress.log(` navigated to "${this._url}"`);
  563. return true;
  564. }).promise;
  565. if (navigationEvent.error) throw navigationEvent.error;
  566. if (!this._firedLifecycleEvents.has(waitUntil)) await _helper.helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, e => e === waitUntil).promise;
  567. const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined;
  568. return request ? request._finalRequest().response() : null;
  569. }
  570. async _waitForLoadState(progress, state) {
  571. const waitUntil = verifyLifecycle('state', state);
  572. if (!this._firedLifecycleEvents.has(waitUntil)) await _helper.helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, e => e === waitUntil).promise;
  573. }
  574. async frameElement() {
  575. return this._page._delegate.getFrameElement(this);
  576. }
  577. _context(world) {
  578. return this._contextData.get(world).contextPromise.then(contextOrDestroyedReason => {
  579. if (contextOrDestroyedReason instanceof js.ExecutionContext) return contextOrDestroyedReason;
  580. throw new Error(contextOrDestroyedReason.destroyedReason);
  581. });
  582. }
  583. _mainContext() {
  584. return this._context('main');
  585. }
  586. _existingMainContext() {
  587. var _this$_contextData$ge2;
  588. return ((_this$_contextData$ge2 = this._contextData.get('main')) === null || _this$_contextData$ge2 === void 0 ? void 0 : _this$_contextData$ge2.context) || null;
  589. }
  590. _utilityContext() {
  591. return this._context('utility');
  592. }
  593. async evaluateExpression(expression, options = {}, arg) {
  594. var _options$world;
  595. const context = await this._context((_options$world = options.world) !== null && _options$world !== void 0 ? _options$world : 'main');
  596. const value = await context.evaluateExpression(expression, options, arg);
  597. return value;
  598. }
  599. async evaluateExpressionHandle(expression, options = {}, arg) {
  600. var _options$world2;
  601. const context = await this._context((_options$world2 = options.world) !== null && _options$world2 !== void 0 ? _options$world2 : 'main');
  602. const value = await context.evaluateExpressionHandle(expression, options, arg);
  603. return value;
  604. }
  605. async querySelector(selector, options) {
  606. _debugLogger.debugLogger.log('api', ` finding element using the selector "${selector}"`);
  607. return this.selectors.query(selector, options);
  608. }
  609. async waitForSelector(metadata, selector, options, scope) {
  610. const controller = new _progress.ProgressController(metadata, this);
  611. if (options.visibility) throw new Error('options.visibility is not supported, did you mean options.state?');
  612. if (options.waitFor && options.waitFor !== 'visible') throw new Error('options.waitFor is not supported, did you mean options.state?');
  613. const {
  614. state = 'visible'
  615. } = options;
  616. if (!['attached', 'detached', 'visible', 'hidden'].includes(state)) throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
  617. return controller.run(async progress => {
  618. progress.log(`waiting for ${this._asLocator(selector)}${state === 'attached' ? '' : ' to be ' + state}`);
  619. const promise = this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => {
  620. const resolved = await this.selectors.resolveInjectedForSelector(selector, options, scope);
  621. progress.throwIfAborted();
  622. if (!resolved) {
  623. if (state === 'hidden' || state === 'detached') return null;
  624. return continuePolling;
  625. }
  626. const result = await resolved.injected.evaluateHandle((injected, {
  627. info,
  628. root
  629. }) => {
  630. const elements = injected.querySelectorAll(info.parsed, root || document);
  631. const element = elements[0];
  632. const visible = element ? injected.isVisible(element) : false;
  633. let log = '';
  634. if (elements.length > 1) {
  635. if (info.strict) throw injected.strictModeViolationError(info.parsed, elements);
  636. log = ` locator resolved to ${elements.length} elements. Proceeding with the first one: ${injected.previewNode(elements[0])}`;
  637. } else if (element) {
  638. log = ` locator resolved to ${visible ? 'visible' : 'hidden'} ${injected.previewNode(element)}`;
  639. }
  640. return {
  641. log,
  642. element,
  643. visible,
  644. attached: !!element
  645. };
  646. }, {
  647. info: resolved.info,
  648. root: resolved.frame === this ? scope : undefined
  649. });
  650. const {
  651. log,
  652. visible,
  653. attached
  654. } = await result.evaluate(r => ({
  655. log: r.log,
  656. visible: r.visible,
  657. attached: r.attached
  658. }));
  659. if (log) progress.log(log);
  660. const success = {
  661. attached,
  662. detached: !attached,
  663. visible,
  664. hidden: !visible
  665. }[state];
  666. if (!success) {
  667. result.dispose();
  668. return continuePolling;
  669. }
  670. if (options.omitReturnValue) {
  671. result.dispose();
  672. return null;
  673. }
  674. const element = state === 'attached' || state === 'visible' ? await result.evaluateHandle(r => r.element) : null;
  675. result.dispose();
  676. if (!element) return null;
  677. if (options.__testHookBeforeAdoptNode) await options.__testHookBeforeAdoptNode();
  678. try {
  679. return await element._adoptTo(await resolved.frame._mainContext());
  680. } catch (e) {
  681. return continuePolling;
  682. }
  683. });
  684. return scope ? scope._context._raceAgainstContextDestroyed(promise) : promise;
  685. }, this._page._timeoutSettings.timeout(options));
  686. }
  687. async dispatchEvent(metadata, selector, type, eventInit = {}, options = {}, scope) {
  688. await this._callOnElementOnceMatches(metadata, selector, (injectedScript, element, data) => {
  689. injectedScript.dispatchEvent(element, data.type, data.eventInit);
  690. }, {
  691. type,
  692. eventInit
  693. }, {
  694. mainWorld: true,
  695. ...options
  696. }, scope);
  697. }
  698. async evalOnSelector(selector, strict, expression, isFunction, arg, scope) {
  699. const handle = await this.selectors.query(selector, {
  700. strict
  701. }, scope);
  702. if (!handle) throw new Error(`Failed to find element matching selector "${selector}"`);
  703. const result = await handle.evaluateExpression(expression, {
  704. isFunction
  705. }, arg);
  706. handle.dispose();
  707. return result;
  708. }
  709. async evalOnSelectorAll(selector, expression, isFunction, arg, scope) {
  710. const arrayHandle = await this.selectors.queryArrayInMainWorld(selector, scope);
  711. const result = await arrayHandle.evaluateExpression(expression, {
  712. isFunction
  713. }, arg);
  714. arrayHandle.dispose();
  715. return result;
  716. }
  717. async maskSelectors(selectors, color) {
  718. const context = await this._utilityContext();
  719. const injectedScript = await context.injectedScript();
  720. await injectedScript.evaluate((injected, {
  721. parsed,
  722. color
  723. }) => {
  724. injected.maskSelectors(parsed, color);
  725. }, {
  726. parsed: selectors,
  727. color: color
  728. });
  729. }
  730. async querySelectorAll(selector) {
  731. return this.selectors.queryAll(selector);
  732. }
  733. async queryCount(selector) {
  734. return await this.selectors.queryCount(selector);
  735. }
  736. async content() {
  737. try {
  738. const context = await this._utilityContext();
  739. return await context.evaluate(() => {
  740. let retVal = '';
  741. if (document.doctype) retVal = new XMLSerializer().serializeToString(document.doctype);
  742. if (document.documentElement) retVal += document.documentElement.outerHTML;
  743. return retVal;
  744. });
  745. } catch (e) {
  746. if (js.isJavaScriptErrorInEvaluate(e) || (0, _protocolError.isSessionClosedError)(e)) throw e;
  747. throw new Error(`Unable to retrieve content because the page is navigating and changing the content.`);
  748. }
  749. }
  750. async setContent(metadata, html, options = {}) {
  751. const controller = new _progress.ProgressController(metadata, this);
  752. return controller.run(async progress => {
  753. await this.raceNavigationAction(progress, options, async () => {
  754. const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
  755. progress.log(`setting frame content, waiting until "${waitUntil}"`);
  756. const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`;
  757. const context = await this._utilityContext();
  758. const lifecyclePromise = new Promise((resolve, reject) => {
  759. this._page._frameManager._consoleMessageTags.set(tag, () => {
  760. // Clear lifecycle right after document.open() - see 'tag' below.
  761. this._onClearLifecycle();
  762. this._waitForLoadState(progress, waitUntil).then(resolve).catch(reject);
  763. });
  764. });
  765. const contentPromise = context.evaluate(({
  766. html,
  767. tag
  768. }) => {
  769. document.open();
  770. console.debug(tag); // eslint-disable-line no-console
  771. document.write(html);
  772. document.close();
  773. }, {
  774. html,
  775. tag
  776. });
  777. await Promise.all([contentPromise, lifecyclePromise]);
  778. return null;
  779. });
  780. }, this._page._timeoutSettings.navigationTimeout(options));
  781. }
  782. name() {
  783. return this._name || '';
  784. }
  785. url() {
  786. return this._url;
  787. }
  788. parentFrame() {
  789. return this._parentFrame;
  790. }
  791. childFrames() {
  792. return Array.from(this._childFrames);
  793. }
  794. async addScriptTag(params) {
  795. const {
  796. url = null,
  797. content = null,
  798. type = ''
  799. } = params;
  800. if (!url && !content) throw new Error('Provide an object with a `url`, `path` or `content` property');
  801. const context = await this._mainContext();
  802. return this._raceWithCSPError(async () => {
  803. if (url !== null) return (await context.evaluateHandle(addScriptUrl, {
  804. url,
  805. type
  806. })).asElement();
  807. const result = (await context.evaluateHandle(addScriptContent, {
  808. content: content,
  809. type
  810. })).asElement();
  811. // Another round trip to the browser to ensure that we receive CSP error messages
  812. // (if any) logged asynchronously in a separate task on the content main thread.
  813. if (this._page._delegate.cspErrorsAsynchronousForInlineScipts) await context.evaluate(() => true);
  814. return result;
  815. });
  816. async function addScriptUrl(params) {
  817. const script = document.createElement('script');
  818. script.src = params.url;
  819. if (params.type) script.type = params.type;
  820. const promise = new Promise((res, rej) => {
  821. script.onload = res;
  822. script.onerror = e => rej(typeof e === 'string' ? new Error(e) : new Error(`Failed to load script at ${script.src}`));
  823. });
  824. document.head.appendChild(script);
  825. await promise;
  826. return script;
  827. }
  828. function addScriptContent(params) {
  829. const script = document.createElement('script');
  830. script.type = params.type || 'text/javascript';
  831. script.text = params.content;
  832. let error = null;
  833. script.onerror = e => error = e;
  834. document.head.appendChild(script);
  835. if (error) throw error;
  836. return script;
  837. }
  838. }
  839. async addStyleTag(params) {
  840. const {
  841. url = null,
  842. content = null
  843. } = params;
  844. if (!url && !content) throw new Error('Provide an object with a `url`, `path` or `content` property');
  845. const context = await this._mainContext();
  846. return this._raceWithCSPError(async () => {
  847. if (url !== null) return (await context.evaluateHandle(addStyleUrl, url)).asElement();
  848. return (await context.evaluateHandle(addStyleContent, content)).asElement();
  849. });
  850. async function addStyleUrl(url) {
  851. const link = document.createElement('link');
  852. link.rel = 'stylesheet';
  853. link.href = url;
  854. const promise = new Promise((res, rej) => {
  855. link.onload = res;
  856. link.onerror = rej;
  857. });
  858. document.head.appendChild(link);
  859. await promise;
  860. return link;
  861. }
  862. async function addStyleContent(content) {
  863. const style = document.createElement('style');
  864. style.type = 'text/css';
  865. style.appendChild(document.createTextNode(content));
  866. const promise = new Promise((res, rej) => {
  867. style.onload = res;
  868. style.onerror = rej;
  869. });
  870. document.head.appendChild(style);
  871. await promise;
  872. return style;
  873. }
  874. }
  875. async _raceWithCSPError(func) {
  876. const listeners = [];
  877. let result;
  878. let error;
  879. let cspMessage;
  880. const actionPromise = func().then(r => result = r).catch(e => error = e);
  881. const errorPromise = new Promise(resolve => {
  882. listeners.push(_eventsHelper.eventsHelper.addEventListener(this._page._browserContext, _browserContext.BrowserContext.Events.Console, message => {
  883. if (message.page() !== this._page || message.type() !== 'error') return;
  884. if (message.text().includes('Content-Security-Policy') || message.text().includes('Content Security Policy')) {
  885. cspMessage = message;
  886. resolve();
  887. }
  888. }));
  889. });
  890. await Promise.race([actionPromise, errorPromise]);
  891. _eventsHelper.eventsHelper.removeEventListeners(listeners);
  892. if (cspMessage) throw new Error(cspMessage.text());
  893. if (error) throw error;
  894. return result;
  895. }
  896. async retryWithProgressAndTimeouts(progress, timeouts, action) {
  897. const continuePolling = Symbol('continuePolling');
  898. timeouts = [0, ...timeouts];
  899. let timeoutIndex = 0;
  900. while (progress.isRunning()) {
  901. const timeout = timeouts[Math.min(timeoutIndex++, timeouts.length - 1)];
  902. if (timeout) {
  903. // Make sure we react immediately upon page close or frame detach.
  904. // We need this to show expected/received values in time.
  905. const actionPromise = new Promise(f => setTimeout(f, timeout));
  906. await _utils.LongStandingScope.raceMultiple([this._page.openScope, this._detachedScope], actionPromise);
  907. }
  908. progress.throwIfAborted();
  909. try {
  910. const result = await action(continuePolling);
  911. if (result === continuePolling) continue;
  912. return result;
  913. } catch (e) {
  914. if (this._isErrorThatCannotBeRetried(e)) throw e;
  915. continue;
  916. }
  917. }
  918. progress.throwIfAborted();
  919. return undefined;
  920. }
  921. _isErrorThatCannotBeRetried(e) {
  922. // Always fail on JavaScript errors or when the main connection is closed.
  923. if (js.isJavaScriptErrorInEvaluate(e) || (0, _protocolError.isSessionClosedError)(e)) return true;
  924. // Certain errors opt-out of the retries, throw.
  925. if (dom.isNonRecoverableDOMError(e) || (0, _selectorParser.isInvalidSelectorError)(e)) return true;
  926. // If the call is made on the detached frame - throw.
  927. if (this.isDetached()) return true;
  928. // Retry upon all other errors.
  929. return false;
  930. }
  931. async _retryWithProgressIfNotConnected(progress, selector, strict, action) {
  932. progress.log(`waiting for ${this._asLocator(selector)}`);
  933. return this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => {
  934. const resolved = await this.selectors.resolveInjectedForSelector(selector, {
  935. strict
  936. });
  937. progress.throwIfAborted();
  938. if (!resolved) return continuePolling;
  939. const result = await resolved.injected.evaluateHandle((injected, {
  940. info
  941. }) => {
  942. const elements = injected.querySelectorAll(info.parsed, document);
  943. const element = elements[0];
  944. let log = '';
  945. if (elements.length > 1) {
  946. if (info.strict) throw injected.strictModeViolationError(info.parsed, elements);
  947. log = ` locator resolved to ${elements.length} elements. Proceeding with the first one: ${injected.previewNode(elements[0])}`;
  948. } else if (element) {
  949. log = ` locator resolved to ${injected.previewNode(element)}`;
  950. }
  951. return {
  952. log,
  953. success: !!element,
  954. element
  955. };
  956. }, {
  957. info: resolved.info
  958. });
  959. const {
  960. log,
  961. success
  962. } = await result.evaluate(r => ({
  963. log: r.log,
  964. success: r.success
  965. }));
  966. if (log) progress.log(log);
  967. if (!success) {
  968. result.dispose();
  969. return continuePolling;
  970. }
  971. const element = await result.evaluateHandle(r => r.element);
  972. result.dispose();
  973. try {
  974. const result = await action(element);
  975. if (result === 'error:notconnected') {
  976. progress.log('element was detached from the DOM, retrying');
  977. return continuePolling;
  978. }
  979. return result;
  980. } finally {
  981. element === null || element === void 0 ? void 0 : element.dispose();
  982. }
  983. });
  984. }
  985. async rafrafTimeoutScreenshotElementWithProgress(progress, selector, timeout, options) {
  986. return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, async handle => {
  987. await handle._frame.rafrafTimeout(timeout);
  988. return await this._page._screenshotter.screenshotElement(progress, handle, options);
  989. });
  990. }
  991. async click(metadata, selector, options) {
  992. const controller = new _progress.ProgressController(metadata, this);
  993. return controller.run(async progress => {
  994. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._click(progress, options)));
  995. }, this._page._timeoutSettings.timeout(options));
  996. }
  997. async dblclick(metadata, selector, options = {}) {
  998. const controller = new _progress.ProgressController(metadata, this);
  999. return controller.run(async progress => {
  1000. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._dblclick(progress, options)));
  1001. }, this._page._timeoutSettings.timeout(options));
  1002. }
  1003. async dragAndDrop(metadata, source, target, options = {}) {
  1004. const controller = new _progress.ProgressController(metadata, this);
  1005. await controller.run(async progress => {
  1006. dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, options.strict, async handle => {
  1007. return handle._retryPointerAction(progress, 'move and down', false, async point => {
  1008. await this._page.mouse.move(point.x, point.y);
  1009. await this._page.mouse.down();
  1010. }, {
  1011. ...options,
  1012. position: options.sourcePosition,
  1013. timeout: progress.timeUntilDeadline()
  1014. });
  1015. }));
  1016. dom.assertDone(await this._retryWithProgressIfNotConnected(progress, target, options.strict, async handle => {
  1017. return handle._retryPointerAction(progress, 'move and up', false, async point => {
  1018. await this._page.mouse.move(point.x, point.y);
  1019. await this._page.mouse.up();
  1020. }, {
  1021. ...options,
  1022. position: options.targetPosition,
  1023. timeout: progress.timeUntilDeadline()
  1024. });
  1025. }));
  1026. }, this._page._timeoutSettings.timeout(options));
  1027. }
  1028. async tap(metadata, selector, options) {
  1029. if (!this._page._browserContext._options.hasTouch) throw new Error('The page does not support tap. Use hasTouch context option to enable touch support.');
  1030. const controller = new _progress.ProgressController(metadata, this);
  1031. return controller.run(async progress => {
  1032. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._tap(progress, options)));
  1033. }, this._page._timeoutSettings.timeout(options));
  1034. }
  1035. async fill(metadata, selector, value, options) {
  1036. const controller = new _progress.ProgressController(metadata, this);
  1037. return controller.run(async progress => {
  1038. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._fill(progress, value, options)));
  1039. }, this._page._timeoutSettings.timeout(options));
  1040. }
  1041. async focus(metadata, selector, options = {}) {
  1042. const controller = new _progress.ProgressController(metadata, this);
  1043. await controller.run(async progress => {
  1044. dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._focus(progress)));
  1045. }, this._page._timeoutSettings.timeout(options));
  1046. }
  1047. async blur(metadata, selector, options = {}) {
  1048. const controller = new _progress.ProgressController(metadata, this);
  1049. await controller.run(async progress => {
  1050. dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._blur(progress)));
  1051. }, this._page._timeoutSettings.timeout(options));
  1052. }
  1053. async textContent(metadata, selector, options = {}, scope) {
  1054. return this._callOnElementOnceMatches(metadata, selector, (injected, element) => element.textContent, undefined, options, scope);
  1055. }
  1056. async innerText(metadata, selector, options = {}, scope) {
  1057. return this._callOnElementOnceMatches(metadata, selector, (injectedScript, element) => {
  1058. if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml') throw injectedScript.createStacklessError('Node is not an HTMLElement');
  1059. return element.innerText;
  1060. }, undefined, options, scope);
  1061. }
  1062. async innerHTML(metadata, selector, options = {}, scope) {
  1063. return this._callOnElementOnceMatches(metadata, selector, (injected, element) => element.innerHTML, undefined, options, scope);
  1064. }
  1065. async getAttribute(metadata, selector, name, options = {}, scope) {
  1066. return this._callOnElementOnceMatches(metadata, selector, (injected, element, data) => element.getAttribute(data.name), {
  1067. name
  1068. }, options, scope);
  1069. }
  1070. async inputValue(metadata, selector, options = {}, scope) {
  1071. return this._callOnElementOnceMatches(metadata, selector, (injectedScript, node) => {
  1072. const element = injectedScript.retarget(node, 'follow-label');
  1073. if (!element || element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT') throw injectedScript.createStacklessError('Node is not an <input>, <textarea> or <select> element');
  1074. return element.value;
  1075. }, undefined, options, scope);
  1076. }
  1077. async highlight(selector) {
  1078. const resolved = await this.selectors.resolveInjectedForSelector(selector);
  1079. if (!resolved) return;
  1080. return await resolved.injected.evaluate((injected, {
  1081. info
  1082. }) => {
  1083. return injected.highlight(info.parsed);
  1084. }, {
  1085. info: resolved.info
  1086. });
  1087. }
  1088. async hideHighlight() {
  1089. return this.raceAgainstEvaluationStallingEvents(async () => {
  1090. const context = await this._utilityContext();
  1091. const injectedScript = await context.injectedScript();
  1092. return await injectedScript.evaluate(injected => {
  1093. return injected.hideHighlight();
  1094. });
  1095. });
  1096. }
  1097. async _elementState(metadata, selector, state, options = {}, scope) {
  1098. const result = await this._callOnElementOnceMatches(metadata, selector, (injected, element, data) => {
  1099. return injected.elementState(element, data.state);
  1100. }, {
  1101. state
  1102. }, options, scope);
  1103. return dom.throwRetargetableDOMError(result);
  1104. }
  1105. async isVisible(metadata, selector, options = {}, scope) {
  1106. const controller = new _progress.ProgressController(metadata, this);
  1107. return controller.run(async progress => {
  1108. progress.log(` checking visibility of ${this._asLocator(selector)}`);
  1109. const resolved = await this.selectors.resolveInjectedForSelector(selector, options, scope);
  1110. if (!resolved) return false;
  1111. return await resolved.injected.evaluate((injected, {
  1112. info,
  1113. root
  1114. }) => {
  1115. const element = injected.querySelector(info.parsed, root || document, info.strict);
  1116. const state = element ? injected.elementState(element, 'visible') : false;
  1117. return state === 'error:notconnected' ? false : state;
  1118. }, {
  1119. info: resolved.info,
  1120. root: resolved.frame === this ? scope : undefined
  1121. });
  1122. }, this._page._timeoutSettings.timeout({})).catch(e => {
  1123. if (js.isJavaScriptErrorInEvaluate(e) || (0, _selectorParser.isInvalidSelectorError)(e) || (0, _protocolError.isSessionClosedError)(e)) throw e;
  1124. return false;
  1125. });
  1126. }
  1127. async isHidden(metadata, selector, options = {}, scope) {
  1128. return !(await this.isVisible(metadata, selector, options, scope));
  1129. }
  1130. async isDisabled(metadata, selector, options = {}, scope) {
  1131. return this._elementState(metadata, selector, 'disabled', options, scope);
  1132. }
  1133. async isEnabled(metadata, selector, options = {}, scope) {
  1134. return this._elementState(metadata, selector, 'enabled', options, scope);
  1135. }
  1136. async isEditable(metadata, selector, options = {}, scope) {
  1137. return this._elementState(metadata, selector, 'editable', options, scope);
  1138. }
  1139. async isChecked(metadata, selector, options = {}, scope) {
  1140. return this._elementState(metadata, selector, 'checked', options, scope);
  1141. }
  1142. async hover(metadata, selector, options = {}) {
  1143. const controller = new _progress.ProgressController(metadata, this);
  1144. return controller.run(async progress => {
  1145. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._hover(progress, options)));
  1146. }, this._page._timeoutSettings.timeout(options));
  1147. }
  1148. async selectOption(metadata, selector, elements, values, options = {}) {
  1149. const controller = new _progress.ProgressController(metadata, this);
  1150. return controller.run(async progress => {
  1151. return await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._selectOption(progress, elements, values, options));
  1152. }, this._page._timeoutSettings.timeout(options));
  1153. }
  1154. async setInputFiles(metadata, selector, params) {
  1155. const inputFileItems = await (0, _fileUploadUtils.prepareFilesForUpload)(this, params);
  1156. const controller = new _progress.ProgressController(metadata, this);
  1157. return controller.run(async progress => {
  1158. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, params.strict, handle => handle._setInputFiles(progress, inputFileItems, params)));
  1159. }, this._page._timeoutSettings.timeout(params));
  1160. }
  1161. async type(metadata, selector, text, options = {}) {
  1162. const controller = new _progress.ProgressController(metadata, this);
  1163. return controller.run(async progress => {
  1164. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._type(progress, text, options)));
  1165. }, this._page._timeoutSettings.timeout(options));
  1166. }
  1167. async press(metadata, selector, key, options = {}) {
  1168. const controller = new _progress.ProgressController(metadata, this);
  1169. return controller.run(async progress => {
  1170. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._press(progress, key, options)));
  1171. }, this._page._timeoutSettings.timeout(options));
  1172. }
  1173. async check(metadata, selector, options = {}) {
  1174. const controller = new _progress.ProgressController(metadata, this);
  1175. return controller.run(async progress => {
  1176. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._setChecked(progress, true, options)));
  1177. }, this._page._timeoutSettings.timeout(options));
  1178. }
  1179. async uncheck(metadata, selector, options = {}) {
  1180. const controller = new _progress.ProgressController(metadata, this);
  1181. return controller.run(async progress => {
  1182. return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._setChecked(progress, false, options)));
  1183. }, this._page._timeoutSettings.timeout(options));
  1184. }
  1185. async waitForTimeout(metadata, timeout) {
  1186. const controller = new _progress.ProgressController(metadata, this);
  1187. return controller.run(async () => {
  1188. await new Promise(resolve => setTimeout(resolve, timeout));
  1189. });
  1190. }
  1191. async expect(metadata, selector, options) {
  1192. let timeout = this._page._timeoutSettings.timeout(options);
  1193. const start = timeout > 0 ? (0, _utils.monotonicTime)() : 0;
  1194. const lastIntermediateResult = {
  1195. isSet: false
  1196. };
  1197. const resultOneShot = await this._expectInternal(metadata, selector, options, true, timeout, lastIntermediateResult);
  1198. if (resultOneShot.matches !== options.isNot) return resultOneShot;
  1199. if (timeout > 0) {
  1200. const elapsed = (0, _utils.monotonicTime)() - start;
  1201. timeout -= elapsed;
  1202. }
  1203. if (timeout < 0) return {
  1204. matches: options.isNot,
  1205. log: metadata.log,
  1206. timedOut: true,
  1207. received: lastIntermediateResult.received
  1208. };
  1209. return await this._expectInternal(metadata, selector, options, false, timeout, lastIntermediateResult);
  1210. }
  1211. async _expectInternal(metadata, selector, options, oneShot, timeout, lastIntermediateResult) {
  1212. const controller = new _progress.ProgressController(metadata, this);
  1213. return controller.run(async progress => {
  1214. if (oneShot) {
  1215. progress.log(`${metadata.apiName}${timeout ? ` with timeout ${timeout}ms` : ''}`);
  1216. progress.log(`waiting for ${this._asLocator(selector)}`);
  1217. }
  1218. return await this.retryWithProgressAndTimeouts(progress, [100, 250, 500, 1000], async continuePolling => {
  1219. var _info$world;
  1220. const selectorInFrame = await this.selectors.resolveFrameForSelector(selector, {
  1221. strict: true
  1222. });
  1223. progress.throwIfAborted();
  1224. const {
  1225. frame,
  1226. info
  1227. } = selectorInFrame || {
  1228. frame: this,
  1229. info: undefined
  1230. };
  1231. const world = options.expression === 'to.have.property' ? 'main' : (_info$world = info === null || info === void 0 ? void 0 : info.world) !== null && _info$world !== void 0 ? _info$world : 'utility';
  1232. const context = await frame._context(world);
  1233. const injected = await context.injectedScript();
  1234. progress.throwIfAborted();
  1235. const {
  1236. log,
  1237. matches,
  1238. received,
  1239. missingRecevied
  1240. } = await injected.evaluate(async (injected, {
  1241. info,
  1242. options,
  1243. callId
  1244. }) => {
  1245. const elements = info ? injected.querySelectorAll(info.parsed, document) : [];
  1246. const isArray = options.expression === 'to.have.count' || options.expression.endsWith('.array');
  1247. let log = '';
  1248. if (isArray) log = ` locator resolved to ${elements.length} element${elements.length === 1 ? '' : 's'}`;else if (elements.length > 1) throw injected.strictModeViolationError(info.parsed, elements);else if (elements.length) log = ` locator resolved to ${injected.previewNode(elements[0])}`;
  1249. if (callId) injected.markTargetElements(new Set(elements), callId);
  1250. return {
  1251. log,
  1252. ...(await injected.expect(elements[0], options, elements))
  1253. };
  1254. }, {
  1255. info,
  1256. options,
  1257. callId: metadata.id
  1258. });
  1259. if (log) progress.log(log);
  1260. // Note: missingReceived avoids `unexpected value "undefined"` when element was not found.
  1261. if (matches === options.isNot && !missingRecevied) {
  1262. lastIntermediateResult.received = received;
  1263. lastIntermediateResult.isSet = true;
  1264. if (!Array.isArray(received)) progress.log(` unexpected value "${renderUnexpectedValue(options.expression, received)}"`);
  1265. }
  1266. if (!oneShot && matches === options.isNot) {
  1267. // Keep waiting in these cases:
  1268. // expect(locator).conditionThatDoesNotMatch
  1269. // expect(locator).not.conditionThatDoesMatch
  1270. return continuePolling;
  1271. }
  1272. return {
  1273. matches,
  1274. received
  1275. };
  1276. });
  1277. }, oneShot ? 0 : timeout).catch(e => {
  1278. // Q: Why not throw upon isSessionClosedError(e) as in other places?
  1279. // A: We want user to receive a friendly message containing the last intermediate result.
  1280. if (js.isJavaScriptErrorInEvaluate(e) || (0, _selectorParser.isInvalidSelectorError)(e)) throw e;
  1281. const result = {
  1282. matches: options.isNot,
  1283. log: metadata.log
  1284. };
  1285. if (lastIntermediateResult.isSet) result.received = lastIntermediateResult.received;
  1286. if (e instanceof _errors.TimeoutError) result.timedOut = true;
  1287. return result;
  1288. });
  1289. }
  1290. async _waitForFunctionExpression(metadata, expression, isFunction, arg, options, world = 'main') {
  1291. const controller = new _progress.ProgressController(metadata, this);
  1292. if (typeof options.pollingInterval === 'number') (0, _utils.assert)(options.pollingInterval > 0, 'Cannot poll with non-positive interval: ' + options.pollingInterval);
  1293. expression = js.normalizeEvaluationExpression(expression, isFunction);
  1294. return controller.run(async progress => {
  1295. return this.retryWithProgressAndTimeouts(progress, [100], async () => {
  1296. const context = world === 'main' ? await this._mainContext() : await this._utilityContext();
  1297. const injectedScript = await context.injectedScript();
  1298. const handle = await injectedScript.evaluateHandle((injected, {
  1299. expression,
  1300. isFunction,
  1301. polling,
  1302. arg
  1303. }) => {
  1304. const predicate = () => {
  1305. // NOTE: make sure to use `globalThis.eval` instead of `self.eval` due to a bug with sandbox isolation
  1306. // in firefox.
  1307. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1814898
  1308. let result = globalThis.eval(expression);
  1309. if (isFunction === true) {
  1310. result = result(arg);
  1311. } else if (isFunction === false) {
  1312. result = result;
  1313. } else {
  1314. // auto detect.
  1315. if (typeof result === 'function') result = result(arg);
  1316. }
  1317. return result;
  1318. };
  1319. let fulfill;
  1320. let reject;
  1321. let aborted = false;
  1322. const result = new Promise((f, r) => {
  1323. fulfill = f;
  1324. reject = r;
  1325. });
  1326. const next = () => {
  1327. if (aborted) return;
  1328. try {
  1329. const success = predicate();
  1330. if (success) {
  1331. fulfill(success);
  1332. return;
  1333. }
  1334. if (typeof polling !== 'number') requestAnimationFrame(next);else setTimeout(next, polling);
  1335. } catch (e) {
  1336. reject(e);
  1337. }
  1338. };
  1339. next();
  1340. return {
  1341. result,
  1342. abort: () => aborted = true
  1343. };
  1344. }, {
  1345. expression,
  1346. isFunction,
  1347. polling: options.pollingInterval,
  1348. arg
  1349. });
  1350. progress.cleanupWhenAborted(() => handle.evaluate(h => h.abort()).catch(() => {}));
  1351. return handle.evaluateHandle(h => h.result);
  1352. });
  1353. }, this._page._timeoutSettings.timeout(options));
  1354. }
  1355. async waitForFunctionValueInUtility(progress, pageFunction) {
  1356. const expression = `() => {
  1357. const result = (${pageFunction})();
  1358. if (!result)
  1359. return result;
  1360. return JSON.stringify(result);
  1361. }`;
  1362. const handle = await this._waitForFunctionExpression((0, _instrumentation.serverSideCallMetadata)(), expression, true, undefined, {
  1363. timeout: progress.timeUntilDeadline()
  1364. }, 'utility');
  1365. return JSON.parse(handle.rawValue());
  1366. }
  1367. async title() {
  1368. const context = await this._utilityContext();
  1369. return context.evaluate(() => document.title);
  1370. }
  1371. async rafrafTimeout(timeout) {
  1372. if (timeout === 0) return;
  1373. const context = await this._utilityContext();
  1374. await Promise.all([
  1375. // wait for double raf
  1376. context.evaluate(() => new Promise(x => {
  1377. requestAnimationFrame(() => {
  1378. requestAnimationFrame(x);
  1379. });
  1380. })), new Promise(fulfill => setTimeout(fulfill, timeout))]);
  1381. }
  1382. _onDetached() {
  1383. this._stopNetworkIdleTimer();
  1384. this._detachedScope.close(new Error('Frame was detached'));
  1385. for (const data of this._contextData.values()) {
  1386. if (data.context) data.context.contextDestroyed('Frame was detached');
  1387. data.contextPromise.resolve({
  1388. destroyedReason: 'Frame was detached'
  1389. });
  1390. }
  1391. if (this._parentFrame) this._parentFrame._childFrames.delete(this);
  1392. this._parentFrame = null;
  1393. }
  1394. async _callOnElementOnceMatches(metadata, selector, body, taskData, options = {}, scope) {
  1395. const callbackText = body.toString();
  1396. const controller = new _progress.ProgressController(metadata, this);
  1397. return controller.run(async progress => {
  1398. progress.log(`waiting for ${this._asLocator(selector)}`);
  1399. const promise = this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => {
  1400. const resolved = await this.selectors.resolveInjectedForSelector(selector, options, scope);
  1401. progress.throwIfAborted();
  1402. if (!resolved) return continuePolling;
  1403. const {
  1404. log,
  1405. success,
  1406. value
  1407. } = await resolved.injected.evaluate((injected, {
  1408. info,
  1409. callbackText,
  1410. taskData,
  1411. callId,
  1412. root
  1413. }) => {
  1414. const callback = injected.eval(callbackText);
  1415. const element = injected.querySelector(info.parsed, root || document, info.strict);
  1416. if (!element) return {
  1417. success: false
  1418. };
  1419. const log = ` locator resolved to ${injected.previewNode(element)}`;
  1420. if (callId) injected.markTargetElements(new Set([element]), callId);
  1421. return {
  1422. log,
  1423. success: true,
  1424. value: callback(injected, element, taskData)
  1425. };
  1426. }, {
  1427. info: resolved.info,
  1428. callbackText,
  1429. taskData,
  1430. callId: progress.metadata.id,
  1431. root: resolved.frame === this ? scope : undefined
  1432. });
  1433. if (log) progress.log(log);
  1434. if (!success) return continuePolling;
  1435. return value;
  1436. });
  1437. return scope ? scope._context._raceAgainstContextDestroyed(promise) : promise;
  1438. }, this._page._timeoutSettings.timeout(options));
  1439. }
  1440. _setContext(world, context) {
  1441. const data = this._contextData.get(world);
  1442. data.context = context;
  1443. if (context) data.contextPromise.resolve(context);else data.contextPromise = new _manualPromise.ManualPromise();
  1444. }
  1445. _contextCreated(world, context) {
  1446. const data = this._contextData.get(world);
  1447. // In case of multiple sessions to the same target, there's a race between
  1448. // connections so we might end up creating multiple isolated worlds.
  1449. // We can use either.
  1450. if (data.context) {
  1451. data.context.contextDestroyed('Execution context was destroyed, most likely because of a navigation');
  1452. this._setContext(world, null);
  1453. }
  1454. this._setContext(world, context);
  1455. }
  1456. _contextDestroyed(context) {
  1457. // Sometimes we get this after detach, in which case we should not reset
  1458. // our already destroyed contexts to something that will never resolve.
  1459. if (this._detachedScope.isClosed()) return;
  1460. context.contextDestroyed('Execution context was destroyed, most likely because of a navigation');
  1461. for (const [world, data] of this._contextData) {
  1462. if (data.context === context) this._setContext(world, null);
  1463. }
  1464. }
  1465. _startNetworkIdleTimer() {
  1466. (0, _utils.assert)(!this._networkIdleTimer);
  1467. // We should not start a timer and report networkidle in detached frames.
  1468. // This happens at least in Firefox for child frames, where we may get requestFinished
  1469. // after the frame was detached - probably a race in the Firefox itself.
  1470. if (this._firedLifecycleEvents.has('networkidle') || this._detachedScope.isClosed()) return;
  1471. this._networkIdleTimer = setTimeout(() => {
  1472. this._firedNetworkIdleSelf = true;
  1473. this._page.mainFrame()._recalculateNetworkIdle();
  1474. }, 500);
  1475. }
  1476. _stopNetworkIdleTimer() {
  1477. if (this._networkIdleTimer) clearTimeout(this._networkIdleTimer);
  1478. this._networkIdleTimer = undefined;
  1479. this._firedNetworkIdleSelf = false;
  1480. }
  1481. async extendInjectedScript(source, arg) {
  1482. const context = await this._context('main');
  1483. const injectedScriptHandle = await context.injectedScript();
  1484. return injectedScriptHandle.evaluateHandle((injectedScript, {
  1485. source,
  1486. arg
  1487. }) => {
  1488. return injectedScript.extend(source, arg);
  1489. }, {
  1490. source,
  1491. arg
  1492. });
  1493. }
  1494. async resetStorageForCurrentOriginBestEffort(newStorage) {
  1495. const context = await this._utilityContext();
  1496. await context.evaluate(async ({
  1497. ls
  1498. }) => {
  1499. // Clean DOMStorage.
  1500. sessionStorage.clear();
  1501. localStorage.clear();
  1502. // Add new DOM Storage values.
  1503. for (const entry of ls || []) localStorage[entry.name] = entry.value;
  1504. // Clean Service Workers
  1505. const registrations = navigator.serviceWorker ? await navigator.serviceWorker.getRegistrations() : [];
  1506. await Promise.all(registrations.map(async r => {
  1507. // Heuristic for service workers that stalled during main script fetch or importScripts:
  1508. // Waiting for them to finish unregistering takes ages so we do not await.
  1509. // However, they will unregister immediately after fetch finishes and should not affect next page load.
  1510. // Unfortunately, loading next page in Chromium still takes 5 seconds waiting for
  1511. // some operation on this bogus service worker to finish.
  1512. if (!r.installing && !r.waiting && !r.active) r.unregister().catch(() => {});else await r.unregister().catch(() => {});
  1513. }));
  1514. // Clean IndexedDB
  1515. for (const db of (await ((_indexedDB$databases = (_indexedDB = indexedDB).databases) === null || _indexedDB$databases === void 0 ? void 0 : _indexedDB$databases.call(_indexedDB))) || []) {
  1516. var _indexedDB$databases, _indexedDB;
  1517. // Do not wait for the callback - it is called on timer in Chromium (slow).
  1518. if (db.name) indexedDB.deleteDatabase(db.name);
  1519. }
  1520. }, {
  1521. ls: newStorage === null || newStorage === void 0 ? void 0 : newStorage.localStorage
  1522. }).catch(() => {});
  1523. }
  1524. _asLocator(selector) {
  1525. return (0, _locatorGenerators.asLocator)(this._page.attribution.playwright.options.sdkLanguage, selector);
  1526. }
  1527. }
  1528. exports.Frame = Frame;
  1529. Frame.Events = {
  1530. InternalNavigation: 'internalnavigation',
  1531. AddLifecycle: 'addlifecycle',
  1532. RemoveLifecycle: 'removelifecycle'
  1533. };
  1534. class SignalBarrier {
  1535. constructor(progress) {
  1536. this._progress = void 0;
  1537. this._protectCount = 0;
  1538. this._promise = new _manualPromise.ManualPromise();
  1539. this._progress = progress;
  1540. this.retain();
  1541. }
  1542. waitFor() {
  1543. this.release();
  1544. return this._promise;
  1545. }
  1546. async addFrameNavigation(frame) {
  1547. // Auto-wait top-level navigations only.
  1548. if (frame.parentFrame()) return;
  1549. this.retain();
  1550. const waiter = _helper.helper.waitForEvent(null, frame, Frame.Events.InternalNavigation, e => {
  1551. if (!e.isPublic) return false;
  1552. if (!e.error && this._progress) this._progress.log(` navigated to "${frame._url}"`);
  1553. return true;
  1554. });
  1555. await _utils.LongStandingScope.raceMultiple([frame._page.openScope, frame._detachedScope], waiter.promise).catch(() => {});
  1556. waiter.dispose();
  1557. this.release();
  1558. }
  1559. retain() {
  1560. ++this._protectCount;
  1561. }
  1562. release() {
  1563. --this._protectCount;
  1564. if (!this._protectCount) this._promise.resolve();
  1565. }
  1566. }
  1567. function verifyLifecycle(name, waitUntil) {
  1568. if (waitUntil === 'networkidle0') waitUntil = 'networkidle';
  1569. if (!types.kLifecycleEvents.has(waitUntil)) throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
  1570. return waitUntil;
  1571. }
  1572. function renderUnexpectedValue(expression, received) {
  1573. if (expression === 'to.be.checked') return received ? 'checked' : 'unchecked';
  1574. if (expression === 'to.be.unchecked') return received ? 'unchecked' : 'checked';
  1575. if (expression === 'to.be.visible') return received ? 'visible' : 'hidden';
  1576. if (expression === 'to.be.hidden') return received ? 'hidden' : 'visible';
  1577. if (expression === 'to.be.enabled') return received ? 'enabled' : 'disabled';
  1578. if (expression === 'to.be.disabled') return received ? 'disabled' : 'enabled';
  1579. if (expression === 'to.be.editable') return received ? 'editable' : 'readonly';
  1580. if (expression === 'to.be.readonly') return received ? 'readonly' : 'editable';
  1581. if (expression === 'to.be.empty') return received ? 'empty' : 'not empty';
  1582. if (expression === 'to.be.focused') return received ? 'focused' : 'not focused';
  1583. return received;
  1584. }