crConnection.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.kBrowserCloseMessageId = exports.ConnectionEvents = exports.CRSession = exports.CRConnection = exports.CDPSession = void 0;
  6. var _utils = require("../../utils");
  7. var _events = require("events");
  8. var _debugLogger = require("../../common/debugLogger");
  9. var _helper = require("../helper");
  10. var _protocolError = require("../protocolError");
  11. /**
  12. * Copyright 2017 Google Inc. All rights reserved.
  13. * Modifications 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. const ConnectionEvents = exports.ConnectionEvents = {
  28. Disconnected: Symbol('ConnectionEvents.Disconnected')
  29. };
  30. // CRPlaywright uses this special id to issue Browser.close command which we
  31. // should ignore.
  32. const kBrowserCloseMessageId = exports.kBrowserCloseMessageId = -9999;
  33. class CRConnection extends _events.EventEmitter {
  34. constructor(transport, protocolLogger, browserLogsCollector) {
  35. super();
  36. this._lastId = 0;
  37. this._transport = void 0;
  38. this._sessions = new Map();
  39. this._protocolLogger = void 0;
  40. this._browserLogsCollector = void 0;
  41. this._browserDisconnectedLogs = void 0;
  42. this.rootSession = void 0;
  43. this._closed = false;
  44. this.setMaxListeners(0);
  45. this._transport = transport;
  46. this._protocolLogger = protocolLogger;
  47. this._browserLogsCollector = browserLogsCollector;
  48. this.rootSession = new CRSession(this, null, '');
  49. this._sessions.set('', this.rootSession);
  50. this._transport.onmessage = this._onMessage.bind(this);
  51. // onclose should be set last, since it can be immediately called.
  52. this._transport.onclose = this._onClose.bind(this);
  53. }
  54. _rawSend(sessionId, method, params) {
  55. const id = ++this._lastId;
  56. const message = {
  57. id,
  58. method,
  59. params
  60. };
  61. if (sessionId) message.sessionId = sessionId;
  62. this._protocolLogger('send', message);
  63. this._transport.send(message);
  64. return id;
  65. }
  66. async _onMessage(message) {
  67. this._protocolLogger('receive', message);
  68. if (message.id === kBrowserCloseMessageId) return;
  69. const session = this._sessions.get(message.sessionId || '');
  70. if (session) session._onMessage(message);
  71. }
  72. _onClose() {
  73. this._closed = true;
  74. this._transport.onmessage = undefined;
  75. this._transport.onclose = undefined;
  76. this._browserDisconnectedLogs = _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
  77. this.rootSession.dispose();
  78. Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
  79. }
  80. close() {
  81. if (!this._closed) this._transport.close();
  82. }
  83. async createBrowserSession() {
  84. const {
  85. sessionId
  86. } = await this.rootSession.send('Target.attachToBrowserTarget');
  87. return new CDPSession(this.rootSession, sessionId);
  88. }
  89. }
  90. exports.CRConnection = CRConnection;
  91. class CRSession extends _events.EventEmitter {
  92. constructor(connection, parentSession, sessionId, eventListener) {
  93. super();
  94. this._connection = void 0;
  95. this._eventListener = void 0;
  96. this._callbacks = new Map();
  97. this._sessionId = void 0;
  98. this._parentSession = void 0;
  99. this._crashed = false;
  100. this._closed = false;
  101. this.on = void 0;
  102. this.addListener = void 0;
  103. this.off = void 0;
  104. this.removeListener = void 0;
  105. this.once = void 0;
  106. this.setMaxListeners(0);
  107. this._connection = connection;
  108. this._parentSession = parentSession;
  109. this._sessionId = sessionId;
  110. this._eventListener = eventListener;
  111. this.on = super.on;
  112. this.addListener = super.addListener;
  113. this.off = super.removeListener;
  114. this.removeListener = super.removeListener;
  115. this.once = super.once;
  116. }
  117. _markAsCrashed() {
  118. this._crashed = true;
  119. }
  120. createChildSession(sessionId, eventListener) {
  121. const session = new CRSession(this._connection, this, sessionId, eventListener);
  122. this._connection._sessions.set(sessionId, session);
  123. return session;
  124. }
  125. async send(method, params) {
  126. if (this._crashed || this._closed || this._connection._closed || this._connection._browserDisconnectedLogs) throw new _protocolError.ProtocolError(this._crashed ? 'crashed' : 'closed', undefined, this._connection._browserDisconnectedLogs);
  127. const id = this._connection._rawSend(this._sessionId, method, params);
  128. return new Promise((resolve, reject) => {
  129. this._callbacks.set(id, {
  130. resolve,
  131. reject,
  132. error: new _protocolError.ProtocolError('error', method)
  133. });
  134. });
  135. }
  136. _sendMayFail(method, params) {
  137. return this.send(method, params).catch(error => _debugLogger.debugLogger.log('error', error));
  138. }
  139. _onMessage(object) {
  140. var _object$error;
  141. if (object.id && this._callbacks.has(object.id)) {
  142. const callback = this._callbacks.get(object.id);
  143. this._callbacks.delete(object.id);
  144. if (object.error) {
  145. callback.error.setMessage(object.error.message);
  146. callback.reject(callback.error);
  147. } else {
  148. callback.resolve(object.result);
  149. }
  150. } else if (object.id && ((_object$error = object.error) === null || _object$error === void 0 ? void 0 : _object$error.code) === -32001) {
  151. // Message to a closed session, just ignore it.
  152. } else {
  153. var _object$error2;
  154. (0, _utils.assert)(!object.id, (object === null || object === void 0 ? void 0 : (_object$error2 = object.error) === null || _object$error2 === void 0 ? void 0 : _object$error2.message) || undefined);
  155. Promise.resolve().then(() => {
  156. if (this._eventListener) this._eventListener(object.method, object.params);
  157. this.emit(object.method, object.params);
  158. });
  159. }
  160. }
  161. async detach() {
  162. if (this._closed) throw new Error(`Session already detached. Most likely the page has been closed.`);
  163. if (!this._parentSession) throw new Error('Root session cannot be closed');
  164. // Ideally, detaching should resume any target, but there is a bug in the backend,
  165. // so we must Runtime.runIfWaitingForDebugger first.
  166. await this._sendMayFail('Runtime.runIfWaitingForDebugger');
  167. await this._parentSession.send('Target.detachFromTarget', {
  168. sessionId: this._sessionId
  169. });
  170. this.dispose();
  171. }
  172. dispose() {
  173. this._closed = true;
  174. this._connection._sessions.delete(this._sessionId);
  175. for (const callback of this._callbacks.values()) {
  176. callback.error.setMessage(`Internal server error, session closed.`);
  177. callback.error.type = this._crashed ? 'crashed' : 'closed';
  178. callback.error.logs = this._connection._browserDisconnectedLogs;
  179. callback.reject(callback.error);
  180. }
  181. this._callbacks.clear();
  182. }
  183. }
  184. exports.CRSession = CRSession;
  185. class CDPSession extends _events.EventEmitter {
  186. constructor(parentSession, sessionId) {
  187. super();
  188. this.guid = void 0;
  189. this._session = void 0;
  190. this._listeners = [];
  191. this.guid = `cdp-session@${sessionId}`;
  192. this._session = parentSession.createChildSession(sessionId, (method, params) => this.emit(CDPSession.Events.Event, {
  193. method,
  194. params
  195. }));
  196. this._listeners = [_utils.eventsHelper.addEventListener(parentSession, 'Target.detachedFromTarget', event => {
  197. if (event.sessionId === sessionId) this._onClose();
  198. })];
  199. }
  200. async send(method, params) {
  201. return await this._session.send(method, params);
  202. }
  203. async detach() {
  204. return await this._session.detach();
  205. }
  206. async attachToTarget(targetId) {
  207. const {
  208. sessionId
  209. } = await this.send('Target.attachToTarget', {
  210. targetId,
  211. flatten: true
  212. });
  213. return new CDPSession(this._session, sessionId);
  214. }
  215. _onClose() {
  216. _utils.eventsHelper.removeEventListeners(this._listeners);
  217. this._session.dispose();
  218. this.emit(CDPSession.Events.Closed);
  219. }
  220. }
  221. exports.CDPSession = CDPSession;
  222. CDPSession.Events = {
  223. Event: 'event',
  224. Closed: 'close'
  225. };