channelOwner.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.ChannelOwner = void 0;
  6. var _events = require("events");
  7. var _validator = require("../protocol/validator");
  8. var _debugLogger = require("../common/debugLogger");
  9. var _stackTrace = require("../utils/stackTrace");
  10. var _utils = require("../utils");
  11. var _zones = require("../utils/zones");
  12. /**
  13. * Copyright (c) Microsoft Corporation.
  14. *
  15. * Licensed under the Apache License, Version 2.0 (the 'License");
  16. * you may not use this file except in compliance with the License.
  17. * You may obtain a copy of the License at
  18. *
  19. * http://www.apache.org/licenses/LICENSE-2.0
  20. *
  21. * Unless required by applicable law or agreed to in writing, software
  22. * distributed under the License is distributed on an "AS IS" BASIS,
  23. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24. * See the License for the specific language governing permissions and
  25. * limitations under the License.
  26. */
  27. class ChannelOwner extends _events.EventEmitter {
  28. constructor(parent, type, guid, initializer) {
  29. super();
  30. this._connection = void 0;
  31. this._parent = void 0;
  32. this._objects = new Map();
  33. this._type = void 0;
  34. this._guid = void 0;
  35. this._channel = void 0;
  36. this._initializer = void 0;
  37. this._logger = void 0;
  38. this._instrumentation = void 0;
  39. this._eventToSubscriptionMapping = new Map();
  40. this._wasCollected = false;
  41. this.setMaxListeners(0);
  42. this._connection = parent instanceof ChannelOwner ? parent._connection : parent;
  43. this._type = type;
  44. this._guid = guid;
  45. this._parent = parent instanceof ChannelOwner ? parent : undefined;
  46. this._instrumentation = this._connection._instrumentation;
  47. this._connection._objects.set(guid, this);
  48. if (this._parent) {
  49. this._parent._objects.set(guid, this);
  50. this._logger = this._parent._logger;
  51. }
  52. this._channel = this._createChannel(new _events.EventEmitter());
  53. this._initializer = initializer;
  54. }
  55. _setEventToSubscriptionMapping(mapping) {
  56. this._eventToSubscriptionMapping = mapping;
  57. }
  58. _updateSubscription(event, enabled) {
  59. const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
  60. if (protocolEvent) {
  61. this._wrapApiCall(async () => {
  62. await this._channel.updateSubscription({
  63. event: protocolEvent,
  64. enabled
  65. });
  66. }, true).catch(() => {});
  67. }
  68. }
  69. on(event, listener) {
  70. if (!this.listenerCount(event)) this._updateSubscription(event, true);
  71. super.on(event, listener);
  72. return this;
  73. }
  74. addListener(event, listener) {
  75. if (!this.listenerCount(event)) this._updateSubscription(event, true);
  76. super.addListener(event, listener);
  77. return this;
  78. }
  79. prependListener(event, listener) {
  80. if (!this.listenerCount(event)) this._updateSubscription(event, true);
  81. super.prependListener(event, listener);
  82. return this;
  83. }
  84. off(event, listener) {
  85. super.off(event, listener);
  86. if (!this.listenerCount(event)) this._updateSubscription(event, false);
  87. return this;
  88. }
  89. removeListener(event, listener) {
  90. super.removeListener(event, listener);
  91. if (!this.listenerCount(event)) this._updateSubscription(event, false);
  92. return this;
  93. }
  94. _adopt(child) {
  95. child._parent._objects.delete(child._guid);
  96. this._objects.set(child._guid, child);
  97. child._parent = this;
  98. }
  99. _dispose(reason) {
  100. // Clean up from parent and connection.
  101. if (this._parent) this._parent._objects.delete(this._guid);
  102. this._connection._objects.delete(this._guid);
  103. this._wasCollected = reason === 'gc';
  104. // Dispose all children.
  105. for (const object of [...this._objects.values()]) object._dispose(reason);
  106. this._objects.clear();
  107. }
  108. _debugScopeState() {
  109. return {
  110. _guid: this._guid,
  111. objects: Array.from(this._objects.values()).map(o => o._debugScopeState())
  112. };
  113. }
  114. _createChannel(base) {
  115. const channel = new Proxy(base, {
  116. get: (obj, prop) => {
  117. if (typeof prop === 'string') {
  118. const validator = (0, _validator.maybeFindValidator)(this._type, prop, 'Params');
  119. if (validator) {
  120. return async params => {
  121. return await this._wrapApiCall(async apiZone => {
  122. const {
  123. apiName,
  124. frames,
  125. csi,
  126. callCookie,
  127. wallTime
  128. } = apiZone.reported ? {
  129. apiName: undefined,
  130. csi: undefined,
  131. callCookie: undefined,
  132. frames: [],
  133. wallTime: undefined
  134. } : apiZone;
  135. apiZone.reported = true;
  136. if (csi && apiName) csi.onApiCallBegin(apiName, params, frames, wallTime, callCookie);
  137. return await this._connection.sendMessageToServer(this, prop, validator(params, '', {
  138. tChannelImpl: tChannelImplToWire,
  139. binary: this._connection.rawBuffers() ? 'buffer' : 'toBase64'
  140. }), apiName, frames, wallTime);
  141. });
  142. };
  143. }
  144. }
  145. return obj[prop];
  146. }
  147. });
  148. channel._object = this;
  149. return channel;
  150. }
  151. async _wrapApiCall(func, isInternal = false) {
  152. const logger = this._logger;
  153. const stack = (0, _stackTrace.captureRawStack)();
  154. const apiZone = _zones.zones.zoneData('apiZone', stack);
  155. if (apiZone) return await func(apiZone);
  156. const stackTrace = (0, _stackTrace.captureLibraryStackTrace)(stack);
  157. let apiName = stackTrace.apiName;
  158. const frames = stackTrace.frames;
  159. isInternal = isInternal || this._type === 'LocalUtils';
  160. if (isInternal) apiName = undefined;
  161. // Enclosing zone could have provided the apiName and wallTime.
  162. const expectZone = _zones.zones.zoneData('expectZone', stack);
  163. const wallTime = expectZone ? expectZone.wallTime : Date.now();
  164. if (!isInternal && expectZone) apiName = expectZone.title;
  165. // If we are coming from the expectZone, there is no need to generate a new
  166. // step for the API call, since it will be generated by the expect itself.
  167. const csi = isInternal || expectZone ? undefined : this._instrumentation;
  168. const callCookie = {};
  169. try {
  170. logApiCall(logger, `=> ${apiName} started`, isInternal);
  171. const apiZone = {
  172. apiName,
  173. frames,
  174. isInternal,
  175. reported: false,
  176. csi,
  177. callCookie,
  178. wallTime
  179. };
  180. const result = await _zones.zones.run('apiZone', apiZone, async () => {
  181. return await func(apiZone);
  182. });
  183. csi === null || csi === void 0 ? void 0 : csi.onApiCallEnd(callCookie);
  184. logApiCall(logger, `<= ${apiName} succeeded`, isInternal);
  185. return result;
  186. } catch (e) {
  187. const innerError = (process.env.PWDEBUGIMPL || (0, _utils.isUnderTest)()) && e.stack ? '\n<inner error>\n' + e.stack : '';
  188. if (apiName && !apiName.includes('<anonymous>')) e.message = apiName + ': ' + e.message;
  189. const stackFrames = '\n' + (0, _stackTrace.stringifyStackFrames)(stackTrace.frames).join('\n') + innerError;
  190. if (stackFrames.trim()) e.stack = e.message + stackFrames;else e.stack = '';
  191. csi === null || csi === void 0 ? void 0 : csi.onApiCallEnd(callCookie, e);
  192. logApiCall(logger, `<= ${apiName} failed`, isInternal);
  193. throw e;
  194. }
  195. }
  196. _toImpl() {
  197. var _this$_connection$toI, _this$_connection;
  198. return (_this$_connection$toI = (_this$_connection = this._connection).toImpl) === null || _this$_connection$toI === void 0 ? void 0 : _this$_connection$toI.call(_this$_connection, this);
  199. }
  200. toJSON() {
  201. // Jest's expect library tries to print objects sometimes.
  202. // RPC objects can contain links to lots of other objects,
  203. // which can cause jest to crash. Let's help it out
  204. // by just returning the important values.
  205. return {
  206. _type: this._type,
  207. _guid: this._guid
  208. };
  209. }
  210. }
  211. exports.ChannelOwner = ChannelOwner;
  212. function logApiCall(logger, message, isNested) {
  213. if (isNested) return;
  214. if (logger && logger.isEnabled('api', 'info')) logger.log('api', 'info', message, [], {
  215. color: 'cyan'
  216. });
  217. _debugLogger.debugLogger.log('api', message);
  218. }
  219. function tChannelImplToWire(names, arg, path, context) {
  220. if (arg._object instanceof ChannelOwner && (names === '*' || names.includes(arg._object._type))) return {
  221. guid: arg._object._guid
  222. };
  223. throw new _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
  224. }