EventManager.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. "use strict";
  2. /**
  3. * Copyright 2022 Google LLC.
  4. * Copyright (c) Microsoft Corporation.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. exports.EventManager = void 0;
  20. const protocol_js_1 = require("../../../protocol/protocol.js");
  21. const buffer_js_1 = require("../../../utils/buffer.js");
  22. const idWrapper_js_1 = require("../../../utils/idWrapper.js");
  23. const OutgoingBidiMessage_js_1 = require("../../OutgoingBidiMessage.js");
  24. const DefaultMap_js_1 = require("../../../utils/DefaultMap.js");
  25. const SubscriptionManager_js_1 = require("./SubscriptionManager.js");
  26. class EventWrapper {
  27. #idWrapper = new idWrapper_js_1.IdWrapper();
  28. #contextId;
  29. #event;
  30. constructor(event, contextId) {
  31. this.#event = event;
  32. this.#contextId = contextId;
  33. }
  34. get id() {
  35. return this.#idWrapper.id;
  36. }
  37. get contextId() {
  38. return this.#contextId;
  39. }
  40. get event() {
  41. return this.#event;
  42. }
  43. }
  44. /**
  45. * Maps event name to a desired buffer length.
  46. */
  47. const eventBufferLength = new Map([
  48. [protocol_js_1.Log.EventNames.LogEntryAddedEvent, 100],
  49. ]);
  50. class EventManager {
  51. static #NETWORK_DOMAIN_PREFIX = 'network';
  52. /**
  53. * Maps event name to a set of contexts where this event already happened.
  54. * Needed for getting buffered events from all the contexts in case of
  55. * subscripting to all contexts.
  56. */
  57. #eventToContextsMap = new DefaultMap_js_1.DefaultMap(() => new Set());
  58. /**
  59. * Maps `eventName` + `browsingContext` to buffer. Used to get buffered events
  60. * during subscription. Channel-agnostic.
  61. */
  62. #eventBuffers = new Map();
  63. /**
  64. * Maps `eventName` + `browsingContext` + `channel` to last sent event id.
  65. * Used to avoid sending duplicated events when user
  66. * subscribes -> unsubscribes -> subscribes.
  67. */
  68. #lastMessageSent = new Map();
  69. #subscriptionManager;
  70. #bidiServer;
  71. #isNetworkDomainEnabled;
  72. constructor(bidiServer) {
  73. this.#bidiServer = bidiServer;
  74. this.#subscriptionManager = new SubscriptionManager_js_1.SubscriptionManager(bidiServer.getBrowsingContextStorage());
  75. this.#isNetworkDomainEnabled = false;
  76. }
  77. get isNetworkDomainEnabled() {
  78. return this.#isNetworkDomainEnabled;
  79. }
  80. /**
  81. * Returns consistent key to be used to access value maps.
  82. */
  83. static #getMapKey(eventName, browsingContext, channel) {
  84. return JSON.stringify({ eventName, browsingContext, channel });
  85. }
  86. registerEvent(event, contextId) {
  87. this.registerPromiseEvent(Promise.resolve(event), contextId, event.method);
  88. }
  89. registerPromiseEvent(event, contextId, eventName) {
  90. const eventWrapper = new EventWrapper(event, contextId);
  91. const sortedChannels = this.#subscriptionManager.getChannelsSubscribedToEvent(eventName, contextId);
  92. this.#bufferEvent(eventWrapper, eventName);
  93. // Send events to channels in the subscription priority.
  94. for (const channel of sortedChannels) {
  95. this.#bidiServer.emitOutgoingMessage(OutgoingBidiMessage_js_1.OutgoingBidiMessage.createFromPromise(event, channel));
  96. this.#markEventSent(eventWrapper, channel, eventName);
  97. }
  98. }
  99. async subscribe(eventNames, contextIds, channel) {
  100. // First check if all the contexts are known.
  101. for (const contextId of contextIds) {
  102. if (contextId !== null) {
  103. // Assert the context is known. Throw exception otherwise.
  104. this.#bidiServer.getBrowsingContextStorage().getContext(contextId);
  105. }
  106. }
  107. for (const eventName of eventNames) {
  108. for (const contextId of contextIds) {
  109. await this.#handleDomains(eventName, contextId);
  110. this.#subscriptionManager.subscribe(eventName, contextId, channel);
  111. for (const eventWrapper of this.#getBufferedEvents(eventName, contextId, channel)) {
  112. // The order of the events is important.
  113. this.#bidiServer.emitOutgoingMessage(OutgoingBidiMessage_js_1.OutgoingBidiMessage.createFromPromise(eventWrapper.event, channel));
  114. this.#markEventSent(eventWrapper, channel, eventName);
  115. }
  116. }
  117. }
  118. }
  119. /**
  120. * Enables domains for the subscribed event in the required contexts or
  121. * globally.
  122. */
  123. async #handleDomains(eventName, contextId) {
  124. // Enable network domain if user subscribed to any of network events.
  125. if (eventName.startsWith(EventManager.#NETWORK_DOMAIN_PREFIX)) {
  126. // Enable for all the contexts.
  127. if (contextId === null) {
  128. this.#isNetworkDomainEnabled = true;
  129. await Promise.all(this.#bidiServer
  130. .getBrowsingContextStorage()
  131. .getAllContexts()
  132. .map((context) => context.cdpTarget.enableNetworkDomain()));
  133. }
  134. else {
  135. await this.#bidiServer
  136. .getBrowsingContextStorage()
  137. .getContext(contextId)
  138. .cdpTarget.enableNetworkDomain();
  139. }
  140. }
  141. }
  142. unsubscribe(eventNames, contextIds, channel) {
  143. this.#subscriptionManager.unsubscribeAll(eventNames, contextIds, channel);
  144. }
  145. /**
  146. * If the event is buffer-able, put it in the buffer.
  147. */
  148. #bufferEvent(eventWrapper, eventName) {
  149. if (!eventBufferLength.has(eventName)) {
  150. // Do nothing if the event is no buffer-able.
  151. return;
  152. }
  153. const bufferMapKey = EventManager.#getMapKey(eventName, eventWrapper.contextId);
  154. if (!this.#eventBuffers.has(bufferMapKey)) {
  155. this.#eventBuffers.set(bufferMapKey, new buffer_js_1.Buffer(eventBufferLength.get(eventName)));
  156. }
  157. this.#eventBuffers.get(bufferMapKey).add(eventWrapper);
  158. // Add the context to the list of contexts having `eventName` events.
  159. this.#eventToContextsMap.get(eventName).add(eventWrapper.contextId);
  160. }
  161. /**
  162. * If the event is buffer-able, mark it as sent to the given contextId and channel.
  163. */
  164. #markEventSent(eventWrapper, channel, eventName) {
  165. if (!eventBufferLength.has(eventName)) {
  166. // Do nothing if the event is no buffer-able.
  167. return;
  168. }
  169. const lastSentMapKey = EventManager.#getMapKey(eventName, eventWrapper.contextId, channel);
  170. this.#lastMessageSent.set(lastSentMapKey, Math.max(this.#lastMessageSent.get(lastSentMapKey) ?? 0, eventWrapper.id));
  171. }
  172. /**
  173. * Returns events which are buffered and not yet sent to the given channel events.
  174. */
  175. #getBufferedEvents(eventName, contextId, channel) {
  176. const bufferMapKey = EventManager.#getMapKey(eventName, contextId);
  177. const lastSentMapKey = EventManager.#getMapKey(eventName, contextId, channel);
  178. const lastSentMessageId = this.#lastMessageSent.get(lastSentMapKey) ?? -Infinity;
  179. const result = this.#eventBuffers
  180. .get(bufferMapKey)
  181. ?.get()
  182. .filter((wrapper) => wrapper.id > lastSentMessageId) ?? [];
  183. if (contextId === null) {
  184. // For global subscriptions, events buffered in each context should be sent back.
  185. Array.from(this.#eventToContextsMap.get(eventName).keys())
  186. .filter((_contextId) =>
  187. // Events without context are already in the result.
  188. _contextId !== null &&
  189. // Events from deleted contexts should not be sent.
  190. this.#bidiServer.getBrowsingContextStorage().hasContext(_contextId))
  191. .map((_contextId) => this.#getBufferedEvents(eventName, _contextId, channel))
  192. .forEach((events) => result.push(...events));
  193. }
  194. return result.sort((e1, e2) => e1.id - e2.id);
  195. }
  196. }
  197. exports.EventManager = EventManager;
  198. //# sourceMappingURL=EventManager.js.map