"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.dispatcherSymbol = exports.RootDispatcher = exports.DispatcherConnection = exports.Dispatcher = void 0; exports.existingDispatcher = existingDispatcher; exports.setMaxDispatchersForTest = setMaxDispatchersForTest; var _events = require("events"); var _validator = require("../../protocol/validator"); var _utils = require("../../utils"); var _errors = require("../errors"); var _instrumentation = require("../instrumentation"); var _eventsHelper = require("../..//utils/eventsHelper"); var _protocolError = require("../protocolError"); /** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const dispatcherSymbol = exports.dispatcherSymbol = Symbol('dispatcher'); const metadataValidator = (0, _validator.createMetadataValidator)(); function existingDispatcher(object) { return object[dispatcherSymbol]; } let maxDispatchersOverride; function setMaxDispatchersForTest(value) { maxDispatchersOverride = value; } function maxDispatchersForBucket(gcBucket) { var _ref, _maxDispatchersOverri; return (_ref = (_maxDispatchersOverri = maxDispatchersOverride) !== null && _maxDispatchersOverri !== void 0 ? _maxDispatchersOverri : { 'JSHandle': 100000, 'ElementHandle': 100000 }[gcBucket]) !== null && _ref !== void 0 ? _ref : 10000; } class Dispatcher extends _events.EventEmitter { constructor(parent, object, type, initializer, gcBucket) { super(); this._connection = void 0; // Parent is always "isScope". this._parent = void 0; // Only "isScope" channel owners have registered dispatchers inside. this._dispatchers = new Map(); this._disposed = false; this._eventListeners = []; this._guid = void 0; this._type = void 0; this._gcBucket = void 0; this._object = void 0; this._openScope = new _utils.LongStandingScope(); this._connection = parent instanceof DispatcherConnection ? parent : parent._connection; this._parent = parent instanceof DispatcherConnection ? undefined : parent; const guid = object.guid; this._guid = guid; this._type = type; this._object = object; this._gcBucket = gcBucket !== null && gcBucket !== void 0 ? gcBucket : type; object[dispatcherSymbol] = this; this._connection.registerDispatcher(this); if (this._parent) { (0, _utils.assert)(!this._parent._dispatchers.has(guid)); this._parent._dispatchers.set(guid, this); } if (this._parent) this._connection.sendCreate(this._parent, type, guid, initializer, this._parent._object); this._connection.maybeDisposeStaleDispatchers(this._gcBucket); } parentScope() { return this._parent; } addObjectListener(eventName, handler) { this._eventListeners.push(_eventsHelper.eventsHelper.addEventListener(this._object, eventName, handler)); } adopt(child) { if (child._parent === this) return; const oldParent = child._parent; oldParent._dispatchers.delete(child._guid); this._dispatchers.set(child._guid, child); child._parent = this; this._connection.sendAdopt(this, child); } async _handleCommand(callMetadata, method, validParams) { const commandPromise = this[method](validParams, callMetadata); try { return await this._openScope.race(commandPromise); } catch (e) { if (callMetadata.potentiallyClosesScope && (0, _errors.isTargetClosedError)(e)) return await commandPromise; throw e; } } _dispatchEvent(method, params) { if (this._disposed) { if ((0, _utils.isUnderTest)()) throw new Error(`${this._guid} is sending "${String(method)}" event after being disposed`); // Just ignore this event outside of tests. return; } const sdkObject = this._object instanceof _instrumentation.SdkObject ? this._object : undefined; this._connection.sendEvent(this, method, params, sdkObject); } _dispose(reason) { this._disposeRecursively(new _errors.TargetClosedError()); this._connection.sendDispose(this, reason); } _onDispose() {} _disposeRecursively(error) { var _this$_parent; (0, _utils.assert)(!this._disposed, `${this._guid} is disposed more than once`); this._onDispose(); this._disposed = true; _eventsHelper.eventsHelper.removeEventListeners(this._eventListeners); // Clean up from parent and connection. (_this$_parent = this._parent) === null || _this$_parent === void 0 ? void 0 : _this$_parent._dispatchers.delete(this._guid); const list = this._connection._dispatchersByBucket.get(this._gcBucket); list === null || list === void 0 ? void 0 : list.delete(this._guid); this._connection._dispatchers.delete(this._guid); // Dispose all children. for (const dispatcher of [...this._dispatchers.values()]) dispatcher._disposeRecursively(error); this._dispatchers.clear(); delete this._object[dispatcherSymbol]; this._openScope.close(error); } _debugScopeState() { return { _guid: this._guid, objects: Array.from(this._dispatchers.values()).map(o => o._debugScopeState()) }; } async waitForEventInfo() { // Instrumentation takes care of this. } } exports.Dispatcher = Dispatcher; class RootDispatcher extends Dispatcher { constructor(connection, createPlaywright) { super(connection, { guid: '' }, 'Root', {}); this._initialized = false; this.createPlaywright = createPlaywright; } async initialize(params) { (0, _utils.assert)(this.createPlaywright); (0, _utils.assert)(!this._initialized); this._initialized = true; return { playwright: await this.createPlaywright(this, params) }; } } exports.RootDispatcher = RootDispatcher; class DispatcherConnection { constructor(isLocal) { this._dispatchers = new Map(); this._dispatchersByBucket = new Map(); this.onmessage = message => {}; this._waitOperations = new Map(); this._isLocal = void 0; this._isLocal = !!isLocal; } sendEvent(dispatcher, event, params, sdkObject) { const validator = (0, _validator.findValidator)(dispatcher._type, event, 'Event'); params = validator(params, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); this._sendMessageToClient(dispatcher._guid, dispatcher._type, event, params, sdkObject); } sendCreate(parent, type, guid, initializer, sdkObject) { const validator = (0, _validator.findValidator)(type, '', 'Initializer'); initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); this._sendMessageToClient(parent._guid, type, '__create__', { type, initializer, guid }, sdkObject); } sendAdopt(parent, dispatcher) { this._sendMessageToClient(parent._guid, dispatcher._type, '__adopt__', { guid: dispatcher._guid }); } sendDispose(dispatcher, reason) { this._sendMessageToClient(dispatcher._guid, dispatcher._type, '__dispose__', { reason }); } _sendMessageToClient(guid, type, method, params, sdkObject) { if (sdkObject) { var _sdkObject$attributio, _sdkObject$attributio2, _sdkObject$instrument; const event = { type: 'event', class: type, method, params: params || {}, time: (0, _utils.monotonicTime)(), 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 }; (_sdkObject$instrument = sdkObject.instrumentation) === null || _sdkObject$instrument === void 0 ? void 0 : _sdkObject$instrument.onEvent(sdkObject, event); } this.onmessage({ guid, method, params }); } _tChannelImplFromWire(names, arg, path, context) { if (arg && typeof arg === 'object' && typeof arg.guid === 'string') { const guid = arg.guid; const dispatcher = this._dispatchers.get(guid); if (!dispatcher) throw new _validator.ValidationError(`${path}: no object with guid ${guid}`); if (names !== '*' && !names.includes(dispatcher._type)) throw new _validator.ValidationError(`${path}: object with guid ${guid} has type ${dispatcher._type}, expected ${names.toString()}`); return dispatcher; } throw new _validator.ValidationError(`${path}: expected guid for ${names.toString()}`); } _tChannelImplToWire(names, arg, path, context) { if (arg instanceof Dispatcher) { if (names !== '*' && !names.includes(arg._type)) throw new _validator.ValidationError(`${path}: dispatcher with guid ${arg._guid} has type ${arg._type}, expected ${names.toString()}`); return { guid: arg._guid }; } throw new _validator.ValidationError(`${path}: expected dispatcher ${names.toString()}`); } registerDispatcher(dispatcher) { (0, _utils.assert)(!this._dispatchers.has(dispatcher._guid)); this._dispatchers.set(dispatcher._guid, dispatcher); let list = this._dispatchersByBucket.get(dispatcher._gcBucket); if (!list) { list = new Set(); this._dispatchersByBucket.set(dispatcher._gcBucket, list); } list.add(dispatcher._guid); } maybeDisposeStaleDispatchers(gcBucket) { const maxDispatchers = maxDispatchersForBucket(gcBucket); const list = this._dispatchersByBucket.get(gcBucket); if (!list || list.size <= maxDispatchers) return; const dispatchersArray = [...list]; const disposeCount = maxDispatchers / 10 | 0; this._dispatchersByBucket.set(gcBucket, new Set(dispatchersArray.slice(disposeCount))); for (let i = 0; i < disposeCount; ++i) { const d = this._dispatchers.get(dispatchersArray[i]); if (!d) continue; d._dispose('gc'); } } async dispatch(message) { var _sdkObject$attributio3, _sdkObject$attributio4, _sdkObject$attributio5, _sdkObject$attributio6, _params$info; const { id, guid, method, params, metadata } = message; const dispatcher = this._dispatchers.get(guid); if (!dispatcher) { this.onmessage({ id, error: (0, _errors.serializeError)(new _errors.TargetClosedError()) }); return; } let validParams; let validMetadata; try { const validator = (0, _validator.findValidator)(dispatcher._type, method, 'Params'); validParams = validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' }); validMetadata = metadataValidator(metadata, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' }); if (typeof dispatcher[method] !== 'function') throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`); } catch (e) { this.onmessage({ id, error: (0, _errors.serializeError)(e) }); return; } const sdkObject = dispatcher._object instanceof _instrumentation.SdkObject ? dispatcher._object : undefined; const callMetadata = { id: `call@${id}`, wallTime: validMetadata.wallTime || Date.now(), location: validMetadata.location, apiName: validMetadata.apiName, internal: validMetadata.internal, objectId: sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.guid, 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, 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, startTime: (0, _utils.monotonicTime)(), endTime: 0, type: dispatcher._type, method, params: params || {}, log: [] }; if (sdkObject && params !== null && params !== void 0 && (_params$info = params.info) !== null && _params$info !== void 0 && _params$info.waitId) { // Process logs for waitForNavigation/waitForLoadState/etc. const info = params.info; switch (info.phase) { case 'before': { this._waitOperations.set(info.waitId, callMetadata); await sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata); this.onmessage({ id }); return; } case 'log': { const originalMetadata = this._waitOperations.get(info.waitId); originalMetadata.log.push(info.message); sdkObject.instrumentation.onCallLog(sdkObject, originalMetadata, 'api', info.message); this.onmessage({ id }); return; } case 'after': { const originalMetadata = this._waitOperations.get(info.waitId); originalMetadata.endTime = (0, _utils.monotonicTime)(); originalMetadata.error = info.error ? { error: { name: 'Error', message: info.error } } : undefined; this._waitOperations.delete(info.waitId); await sdkObject.instrumentation.onAfterCall(sdkObject, originalMetadata); this.onmessage({ id }); return; } } } await (sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata)); try { const result = await dispatcher._handleCommand(callMetadata, method, validParams); const validator = (0, _validator.findValidator)(dispatcher._type, method, 'Result'); callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); } catch (e) { if ((0, _errors.isTargetClosedError)(e) && sdkObject) { const reason = closeReason(sdkObject); if (reason) (0, _utils.rewriteErrorMessage)(e, reason); } else if ((0, _protocolError.isProtocolError)(e)) { if (e.type === 'closed') { const reason = sdkObject ? closeReason(sdkObject) : undefined; e = new _errors.TargetClosedError(reason, e.browserLogMessage()); } else if (e.type === 'crashed') { (0, _utils.rewriteErrorMessage)(e, 'Target crashed ' + e.browserLogMessage()); } } callMetadata.error = (0, _errors.serializeError)(e); } finally { callMetadata.endTime = (0, _utils.monotonicTime)(); await (sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.instrumentation.onAfterCall(sdkObject, callMetadata)); } const response = { id }; if (callMetadata.result) response.result = callMetadata.result; if (callMetadata.error) { response.error = callMetadata.error; response.log = callMetadata.log; } this.onmessage(response); } } exports.DispatcherConnection = DispatcherConnection; function closeReason(sdkObject) { var _sdkObject$attributio7, _sdkObject$attributio8, _sdkObject$attributio9; 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); }