dispatcher.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.dispatcherSymbol = exports.RootDispatcher = exports.DispatcherConnection = exports.Dispatcher = void 0;
  6. exports.existingDispatcher = existingDispatcher;
  7. exports.setMaxDispatchersForTest = setMaxDispatchersForTest;
  8. var _events = require("events");
  9. var _validator = require("../../protocol/validator");
  10. var _utils = require("../../utils");
  11. var _errors = require("../errors");
  12. var _instrumentation = require("../instrumentation");
  13. var _eventsHelper = require("../..//utils/eventsHelper");
  14. var _protocolError = require("../protocolError");
  15. /**
  16. * Copyright (c) Microsoft Corporation.
  17. *
  18. * Licensed under the Apache License, Version 2.0 (the "License");
  19. * you may not use this file except in compliance with the License.
  20. * You may obtain a copy of the License at
  21. *
  22. * http://www.apache.org/licenses/LICENSE-2.0
  23. *
  24. * Unless required by applicable law or agreed to in writing, software
  25. * distributed under the License is distributed on an "AS IS" BASIS,
  26. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  27. * See the License for the specific language governing permissions and
  28. * limitations under the License.
  29. */
  30. const dispatcherSymbol = exports.dispatcherSymbol = Symbol('dispatcher');
  31. const metadataValidator = (0, _validator.createMetadataValidator)();
  32. function existingDispatcher(object) {
  33. return object[dispatcherSymbol];
  34. }
  35. let maxDispatchersOverride;
  36. function setMaxDispatchersForTest(value) {
  37. maxDispatchersOverride = value;
  38. }
  39. function maxDispatchersForBucket(gcBucket) {
  40. var _ref, _maxDispatchersOverri;
  41. return (_ref = (_maxDispatchersOverri = maxDispatchersOverride) !== null && _maxDispatchersOverri !== void 0 ? _maxDispatchersOverri : {
  42. 'JSHandle': 100000,
  43. 'ElementHandle': 100000
  44. }[gcBucket]) !== null && _ref !== void 0 ? _ref : 10000;
  45. }
  46. class Dispatcher extends _events.EventEmitter {
  47. constructor(parent, object, type, initializer, gcBucket) {
  48. super();
  49. this._connection = void 0;
  50. // Parent is always "isScope".
  51. this._parent = void 0;
  52. // Only "isScope" channel owners have registered dispatchers inside.
  53. this._dispatchers = new Map();
  54. this._disposed = false;
  55. this._eventListeners = [];
  56. this._guid = void 0;
  57. this._type = void 0;
  58. this._gcBucket = void 0;
  59. this._object = void 0;
  60. this._openScope = new _utils.LongStandingScope();
  61. this._connection = parent instanceof DispatcherConnection ? parent : parent._connection;
  62. this._parent = parent instanceof DispatcherConnection ? undefined : parent;
  63. const guid = object.guid;
  64. this._guid = guid;
  65. this._type = type;
  66. this._object = object;
  67. this._gcBucket = gcBucket !== null && gcBucket !== void 0 ? gcBucket : type;
  68. object[dispatcherSymbol] = this;
  69. this._connection.registerDispatcher(this);
  70. if (this._parent) {
  71. (0, _utils.assert)(!this._parent._dispatchers.has(guid));
  72. this._parent._dispatchers.set(guid, this);
  73. }
  74. if (this._parent) this._connection.sendCreate(this._parent, type, guid, initializer, this._parent._object);
  75. this._connection.maybeDisposeStaleDispatchers(this._gcBucket);
  76. }
  77. parentScope() {
  78. return this._parent;
  79. }
  80. addObjectListener(eventName, handler) {
  81. this._eventListeners.push(_eventsHelper.eventsHelper.addEventListener(this._object, eventName, handler));
  82. }
  83. adopt(child) {
  84. if (child._parent === this) return;
  85. const oldParent = child._parent;
  86. oldParent._dispatchers.delete(child._guid);
  87. this._dispatchers.set(child._guid, child);
  88. child._parent = this;
  89. this._connection.sendAdopt(this, child);
  90. }
  91. async _handleCommand(callMetadata, method, validParams) {
  92. const commandPromise = this[method](validParams, callMetadata);
  93. try {
  94. return await this._openScope.race(commandPromise);
  95. } catch (e) {
  96. if (callMetadata.potentiallyClosesScope && (0, _errors.isTargetClosedError)(e)) return await commandPromise;
  97. throw e;
  98. }
  99. }
  100. _dispatchEvent(method, params) {
  101. if (this._disposed) {
  102. if ((0, _utils.isUnderTest)()) throw new Error(`${this._guid} is sending "${String(method)}" event after being disposed`);
  103. // Just ignore this event outside of tests.
  104. return;
  105. }
  106. const sdkObject = this._object instanceof _instrumentation.SdkObject ? this._object : undefined;
  107. this._connection.sendEvent(this, method, params, sdkObject);
  108. }
  109. _dispose(reason) {
  110. this._disposeRecursively(new _errors.TargetClosedError());
  111. this._connection.sendDispose(this, reason);
  112. }
  113. _onDispose() {}
  114. _disposeRecursively(error) {
  115. var _this$_parent;
  116. (0, _utils.assert)(!this._disposed, `${this._guid} is disposed more than once`);
  117. this._onDispose();
  118. this._disposed = true;
  119. _eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
  120. // Clean up from parent and connection.
  121. (_this$_parent = this._parent) === null || _this$_parent === void 0 ? void 0 : _this$_parent._dispatchers.delete(this._guid);
  122. const list = this._connection._dispatchersByBucket.get(this._gcBucket);
  123. list === null || list === void 0 ? void 0 : list.delete(this._guid);
  124. this._connection._dispatchers.delete(this._guid);
  125. // Dispose all children.
  126. for (const dispatcher of [...this._dispatchers.values()]) dispatcher._disposeRecursively(error);
  127. this._dispatchers.clear();
  128. delete this._object[dispatcherSymbol];
  129. this._openScope.close(error);
  130. }
  131. _debugScopeState() {
  132. return {
  133. _guid: this._guid,
  134. objects: Array.from(this._dispatchers.values()).map(o => o._debugScopeState())
  135. };
  136. }
  137. async waitForEventInfo() {
  138. // Instrumentation takes care of this.
  139. }
  140. }
  141. exports.Dispatcher = Dispatcher;
  142. class RootDispatcher extends Dispatcher {
  143. constructor(connection, createPlaywright) {
  144. super(connection, {
  145. guid: ''
  146. }, 'Root', {});
  147. this._initialized = false;
  148. this.createPlaywright = createPlaywright;
  149. }
  150. async initialize(params) {
  151. (0, _utils.assert)(this.createPlaywright);
  152. (0, _utils.assert)(!this._initialized);
  153. this._initialized = true;
  154. return {
  155. playwright: await this.createPlaywright(this, params)
  156. };
  157. }
  158. }
  159. exports.RootDispatcher = RootDispatcher;
  160. class DispatcherConnection {
  161. constructor(isLocal) {
  162. this._dispatchers = new Map();
  163. this._dispatchersByBucket = new Map();
  164. this.onmessage = message => {};
  165. this._waitOperations = new Map();
  166. this._isLocal = void 0;
  167. this._isLocal = !!isLocal;
  168. }
  169. sendEvent(dispatcher, event, params, sdkObject) {
  170. const validator = (0, _validator.findValidator)(dispatcher._type, event, 'Event');
  171. params = validator(params, '', {
  172. tChannelImpl: this._tChannelImplToWire.bind(this),
  173. binary: this._isLocal ? 'buffer' : 'toBase64'
  174. });
  175. this._sendMessageToClient(dispatcher._guid, dispatcher._type, event, params, sdkObject);
  176. }
  177. sendCreate(parent, type, guid, initializer, sdkObject) {
  178. const validator = (0, _validator.findValidator)(type, '', 'Initializer');
  179. initializer = validator(initializer, '', {
  180. tChannelImpl: this._tChannelImplToWire.bind(this),
  181. binary: this._isLocal ? 'buffer' : 'toBase64'
  182. });
  183. this._sendMessageToClient(parent._guid, type, '__create__', {
  184. type,
  185. initializer,
  186. guid
  187. }, sdkObject);
  188. }
  189. sendAdopt(parent, dispatcher) {
  190. this._sendMessageToClient(parent._guid, dispatcher._type, '__adopt__', {
  191. guid: dispatcher._guid
  192. });
  193. }
  194. sendDispose(dispatcher, reason) {
  195. this._sendMessageToClient(dispatcher._guid, dispatcher._type, '__dispose__', {
  196. reason
  197. });
  198. }
  199. _sendMessageToClient(guid, type, method, params, sdkObject) {
  200. if (sdkObject) {
  201. var _sdkObject$attributio, _sdkObject$attributio2, _sdkObject$instrument;
  202. const event = {
  203. type: 'event',
  204. class: type,
  205. method,
  206. params: params || {},
  207. time: (0, _utils.monotonicTime)(),
  208. pageId: sdkObject === null || sdkObject === void 0 ? void 0 : (_sdkObject$attributio = sdkObject.attribution) === null || _sdkObject$attributio === void 0 ? void 0 : (_sdkObject$attributio2 = _sdkObject$attributio.page) === null || _sdkObject$attributio2 === void 0 ? void 0 : _sdkObject$attributio2.guid
  209. };
  210. (_sdkObject$instrument = sdkObject.instrumentation) === null || _sdkObject$instrument === void 0 ? void 0 : _sdkObject$instrument.onEvent(sdkObject, event);
  211. }
  212. this.onmessage({
  213. guid,
  214. method,
  215. params
  216. });
  217. }
  218. _tChannelImplFromWire(names, arg, path, context) {
  219. if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
  220. const guid = arg.guid;
  221. const dispatcher = this._dispatchers.get(guid);
  222. if (!dispatcher) throw new _validator.ValidationError(`${path}: no object with guid ${guid}`);
  223. if (names !== '*' && !names.includes(dispatcher._type)) throw new _validator.ValidationError(`${path}: object with guid ${guid} has type ${dispatcher._type}, expected ${names.toString()}`);
  224. return dispatcher;
  225. }
  226. throw new _validator.ValidationError(`${path}: expected guid for ${names.toString()}`);
  227. }
  228. _tChannelImplToWire(names, arg, path, context) {
  229. if (arg instanceof Dispatcher) {
  230. if (names !== '*' && !names.includes(arg._type)) throw new _validator.ValidationError(`${path}: dispatcher with guid ${arg._guid} has type ${arg._type}, expected ${names.toString()}`);
  231. return {
  232. guid: arg._guid
  233. };
  234. }
  235. throw new _validator.ValidationError(`${path}: expected dispatcher ${names.toString()}`);
  236. }
  237. registerDispatcher(dispatcher) {
  238. (0, _utils.assert)(!this._dispatchers.has(dispatcher._guid));
  239. this._dispatchers.set(dispatcher._guid, dispatcher);
  240. let list = this._dispatchersByBucket.get(dispatcher._gcBucket);
  241. if (!list) {
  242. list = new Set();
  243. this._dispatchersByBucket.set(dispatcher._gcBucket, list);
  244. }
  245. list.add(dispatcher._guid);
  246. }
  247. maybeDisposeStaleDispatchers(gcBucket) {
  248. const maxDispatchers = maxDispatchersForBucket(gcBucket);
  249. const list = this._dispatchersByBucket.get(gcBucket);
  250. if (!list || list.size <= maxDispatchers) return;
  251. const dispatchersArray = [...list];
  252. const disposeCount = maxDispatchers / 10 | 0;
  253. this._dispatchersByBucket.set(gcBucket, new Set(dispatchersArray.slice(disposeCount)));
  254. for (let i = 0; i < disposeCount; ++i) {
  255. const d = this._dispatchers.get(dispatchersArray[i]);
  256. if (!d) continue;
  257. d._dispose('gc');
  258. }
  259. }
  260. async dispatch(message) {
  261. var _sdkObject$attributio3, _sdkObject$attributio4, _sdkObject$attributio5, _sdkObject$attributio6, _params$info;
  262. const {
  263. id,
  264. guid,
  265. method,
  266. params,
  267. metadata
  268. } = message;
  269. const dispatcher = this._dispatchers.get(guid);
  270. if (!dispatcher) {
  271. this.onmessage({
  272. id,
  273. error: (0, _errors.serializeError)(new _errors.TargetClosedError())
  274. });
  275. return;
  276. }
  277. let validParams;
  278. let validMetadata;
  279. try {
  280. const validator = (0, _validator.findValidator)(dispatcher._type, method, 'Params');
  281. validParams = validator(params, '', {
  282. tChannelImpl: this._tChannelImplFromWire.bind(this),
  283. binary: this._isLocal ? 'buffer' : 'fromBase64'
  284. });
  285. validMetadata = metadataValidator(metadata, '', {
  286. tChannelImpl: this._tChannelImplFromWire.bind(this),
  287. binary: this._isLocal ? 'buffer' : 'fromBase64'
  288. });
  289. if (typeof dispatcher[method] !== 'function') throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`);
  290. } catch (e) {
  291. this.onmessage({
  292. id,
  293. error: (0, _errors.serializeError)(e)
  294. });
  295. return;
  296. }
  297. const sdkObject = dispatcher._object instanceof _instrumentation.SdkObject ? dispatcher._object : undefined;
  298. const callMetadata = {
  299. id: `call@${id}`,
  300. wallTime: validMetadata.wallTime || Date.now(),
  301. location: validMetadata.location,
  302. apiName: validMetadata.apiName,
  303. internal: validMetadata.internal,
  304. objectId: sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.guid,
  305. pageId: sdkObject === null || sdkObject === void 0 ? void 0 : (_sdkObject$attributio3 = sdkObject.attribution) === null || _sdkObject$attributio3 === void 0 ? void 0 : (_sdkObject$attributio4 = _sdkObject$attributio3.page) === null || _sdkObject$attributio4 === void 0 ? void 0 : _sdkObject$attributio4.guid,
  306. frameId: sdkObject === null || sdkObject === void 0 ? void 0 : (_sdkObject$attributio5 = sdkObject.attribution) === null || _sdkObject$attributio5 === void 0 ? void 0 : (_sdkObject$attributio6 = _sdkObject$attributio5.frame) === null || _sdkObject$attributio6 === void 0 ? void 0 : _sdkObject$attributio6.guid,
  307. startTime: (0, _utils.monotonicTime)(),
  308. endTime: 0,
  309. type: dispatcher._type,
  310. method,
  311. params: params || {},
  312. log: []
  313. };
  314. if (sdkObject && params !== null && params !== void 0 && (_params$info = params.info) !== null && _params$info !== void 0 && _params$info.waitId) {
  315. // Process logs for waitForNavigation/waitForLoadState/etc.
  316. const info = params.info;
  317. switch (info.phase) {
  318. case 'before':
  319. {
  320. this._waitOperations.set(info.waitId, callMetadata);
  321. await sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata);
  322. this.onmessage({
  323. id
  324. });
  325. return;
  326. }
  327. case 'log':
  328. {
  329. const originalMetadata = this._waitOperations.get(info.waitId);
  330. originalMetadata.log.push(info.message);
  331. sdkObject.instrumentation.onCallLog(sdkObject, originalMetadata, 'api', info.message);
  332. this.onmessage({
  333. id
  334. });
  335. return;
  336. }
  337. case 'after':
  338. {
  339. const originalMetadata = this._waitOperations.get(info.waitId);
  340. originalMetadata.endTime = (0, _utils.monotonicTime)();
  341. originalMetadata.error = info.error ? {
  342. error: {
  343. name: 'Error',
  344. message: info.error
  345. }
  346. } : undefined;
  347. this._waitOperations.delete(info.waitId);
  348. await sdkObject.instrumentation.onAfterCall(sdkObject, originalMetadata);
  349. this.onmessage({
  350. id
  351. });
  352. return;
  353. }
  354. }
  355. }
  356. await (sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata));
  357. try {
  358. const result = await dispatcher._handleCommand(callMetadata, method, validParams);
  359. const validator = (0, _validator.findValidator)(dispatcher._type, method, 'Result');
  360. callMetadata.result = validator(result, '', {
  361. tChannelImpl: this._tChannelImplToWire.bind(this),
  362. binary: this._isLocal ? 'buffer' : 'toBase64'
  363. });
  364. } catch (e) {
  365. if ((0, _errors.isTargetClosedError)(e) && sdkObject) {
  366. const reason = closeReason(sdkObject);
  367. if (reason) (0, _utils.rewriteErrorMessage)(e, reason);
  368. } else if ((0, _protocolError.isProtocolError)(e)) {
  369. if (e.type === 'closed') {
  370. const reason = sdkObject ? closeReason(sdkObject) : undefined;
  371. e = new _errors.TargetClosedError(reason, e.browserLogMessage());
  372. } else if (e.type === 'crashed') {
  373. (0, _utils.rewriteErrorMessage)(e, 'Target crashed ' + e.browserLogMessage());
  374. }
  375. }
  376. callMetadata.error = (0, _errors.serializeError)(e);
  377. } finally {
  378. callMetadata.endTime = (0, _utils.monotonicTime)();
  379. await (sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.instrumentation.onAfterCall(sdkObject, callMetadata));
  380. }
  381. const response = {
  382. id
  383. };
  384. if (callMetadata.result) response.result = callMetadata.result;
  385. if (callMetadata.error) {
  386. response.error = callMetadata.error;
  387. response.log = callMetadata.log;
  388. }
  389. this.onmessage(response);
  390. }
  391. }
  392. exports.DispatcherConnection = DispatcherConnection;
  393. function closeReason(sdkObject) {
  394. var _sdkObject$attributio7, _sdkObject$attributio8, _sdkObject$attributio9;
  395. return ((_sdkObject$attributio7 = sdkObject.attribution.page) === null || _sdkObject$attributio7 === void 0 ? void 0 : _sdkObject$attributio7._closeReason) || ((_sdkObject$attributio8 = sdkObject.attribution.context) === null || _sdkObject$attributio8 === void 0 ? void 0 : _sdkObject$attributio8._closeReason) || ((_sdkObject$attributio9 = sdkObject.attribution.browser) === null || _sdkObject$attributio9 === void 0 ? void 0 : _sdkObject$attributio9._closeReason);
  396. }