| "use strict"; |
| /** |
| * Copyright 2022 Google LLC. |
| * 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. |
| */ |
| var _a; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.EventManager = void 0; |
| const protocol_js_1 = require("../../../protocol/protocol.js"); |
| const Buffer_js_1 = require("../../../utils/Buffer.js"); |
| const DefaultMap_js_1 = require("../../../utils/DefaultMap.js"); |
| const EventEmitter_js_1 = require("../../../utils/EventEmitter.js"); |
| const IdWrapper_js_1 = require("../../../utils/IdWrapper.js"); |
| const OutgoingMessage_js_1 = require("../../OutgoingMessage.js"); |
| const events_js_1 = require("./events.js"); |
| const SubscriptionManager_js_1 = require("./SubscriptionManager.js"); |
| class EventWrapper { |
| #idWrapper = new IdWrapper_js_1.IdWrapper(); |
| #contextId; |
| #event; |
| constructor(event, contextId) { |
| this.#event = event; |
| this.#contextId = contextId; |
| } |
| get id() { |
| return this.#idWrapper.id; |
| } |
| get contextId() { |
| return this.#contextId; |
| } |
| get event() { |
| return this.#event; |
| } |
| } |
| /** |
| * Maps event name to a desired buffer length. |
| */ |
| const eventBufferLength = new Map([[protocol_js_1.ChromiumBidi.Log.EventNames.LogEntryAdded, 100]]); |
| class EventManager extends EventEmitter_js_1.EventEmitter { |
| /** |
| * Maps event name to a set of contexts where this event already happened. |
| * Needed for getting buffered events from all the contexts in case of |
| * subscripting to all contexts. |
| */ |
| #eventToContextsMap = new DefaultMap_js_1.DefaultMap(() => new Set()); |
| /** |
| * Maps `eventName` + `browsingContext` to buffer. Used to get buffered events |
| * during subscription. Channel-agnostic. |
| */ |
| #eventBuffers = new Map(); |
| /** |
| * Maps `eventName` + `browsingContext` to Map of goog:channel to last id. |
| * Used to avoid sending duplicated events when user |
| * subscribes -> unsubscribes -> subscribes. |
| */ |
| #lastMessageSent = new Map(); |
| #subscriptionManager; |
| #browsingContextStorage; |
| /** |
| * Map of event name to hooks to be called when client is subscribed to the event. |
| */ |
| #subscribeHooks; |
| #userContextStorage; |
| constructor(browsingContextStorage, userContextStorage) { |
| super(); |
| this.#browsingContextStorage = browsingContextStorage; |
| this.#userContextStorage = userContextStorage; |
| this.#subscriptionManager = new SubscriptionManager_js_1.SubscriptionManager(browsingContextStorage); |
| this.#subscribeHooks = new DefaultMap_js_1.DefaultMap(() => []); |
| } |
| get subscriptionManager() { |
| return this.#subscriptionManager; |
| } |
| /** |
| * Returns consistent key to be used to access value maps. |
| */ |
| static #getMapKey(eventName, browsingContext) { |
| return JSON.stringify({ eventName, browsingContext }); |
| } |
| addSubscribeHook(event, hook) { |
| this.#subscribeHooks.get(event).push(hook); |
| } |
| registerEvent(event, contextId) { |
| this.registerPromiseEvent(Promise.resolve({ |
| kind: 'success', |
| value: event, |
| }), contextId, event.method); |
| } |
| registerGlobalEvent(event) { |
| this.registerGlobalPromiseEvent(Promise.resolve({ |
| kind: 'success', |
| value: event, |
| }), event.method); |
| } |
| registerPromiseEvent(event, contextId, eventName) { |
| const eventWrapper = new EventWrapper(event, contextId); |
| const sortedGoogChannels = this.#subscriptionManager.getGoogChannelsSubscribedToEvent(eventName, contextId); |
| this.#bufferEvent(eventWrapper, eventName); |
| // Send events to channels in the subscription priority. |
| for (const googChannel of sortedGoogChannels) { |
| this.emit("event" /* EventManagerEvents.Event */, { |
| message: OutgoingMessage_js_1.OutgoingMessage.createFromPromise(event, googChannel), |
| event: eventName, |
| }); |
| this.#markEventSent(eventWrapper, googChannel, eventName); |
| } |
| } |
| registerGlobalPromiseEvent(event, eventName) { |
| const eventWrapper = new EventWrapper(event, null); |
| const sortedGoogChannels = this.#subscriptionManager.getGoogChannelsSubscribedToEventGlobally(eventName); |
| this.#bufferEvent(eventWrapper, eventName); |
| // Send events to goog:channels in the subscription priority. |
| for (const googChannel of sortedGoogChannels) { |
| this.emit("event" /* EventManagerEvents.Event */, { |
| message: OutgoingMessage_js_1.OutgoingMessage.createFromPromise(event, googChannel), |
| event: eventName, |
| }); |
| this.#markEventSent(eventWrapper, googChannel, eventName); |
| } |
| } |
| async subscribe(eventNames, contextIds, userContextIds, googChannel) { |
| for (const name of eventNames) { |
| (0, events_js_1.assertSupportedEvent)(name); |
| } |
| if (userContextIds.length && contextIds.length) { |
| throw new protocol_js_1.InvalidArgumentException('Both userContexts and contexts cannot be specified.'); |
| } |
| // First check if all the contexts are known. |
| this.#browsingContextStorage.verifyContextsList(contextIds); |
| // Validate user contexts. |
| await this.#userContextStorage.verifyUserContextIdList(userContextIds); |
| const unrolledEventNames = new Set((0, SubscriptionManager_js_1.unrollEvents)(eventNames)); |
| const subscribeStepEvents = new Map(); |
| const subscriptionNavigableIds = new Set(contextIds.length |
| ? contextIds.map((contextId) => { |
| const id = this.#browsingContextStorage.findTopLevelContextId(contextId); |
| if (!id) { |
| throw new protocol_js_1.InvalidArgumentException('Invalid context id'); |
| } |
| return id; |
| }) |
| : this.#browsingContextStorage.getTopLevelContexts().map((c) => c.id)); |
| for (const eventName of unrolledEventNames) { |
| const subscribedNavigableIds = new Set(this.#browsingContextStorage |
| .getTopLevelContexts() |
| .map((c) => c.id) |
| .filter((id) => { |
| return this.#subscriptionManager.isSubscribedTo(eventName, id); |
| })); |
| subscribeStepEvents.set(eventName, (0, SubscriptionManager_js_1.difference)(subscriptionNavigableIds, subscribedNavigableIds)); |
| } |
| const subscription = this.#subscriptionManager.subscribe(eventNames, contextIds, userContextIds, googChannel); |
| for (const eventName of subscription.eventNames) { |
| for (const contextId of subscriptionNavigableIds) { |
| for (const eventWrapper of this.#getBufferedEvents(eventName, contextId, googChannel)) { |
| // The order of the events is important. |
| this.emit("event" /* EventManagerEvents.Event */, { |
| message: OutgoingMessage_js_1.OutgoingMessage.createFromPromise(eventWrapper.event, googChannel), |
| event: eventName, |
| }); |
| this.#markEventSent(eventWrapper, googChannel, eventName); |
| } |
| } |
| } |
| for (const [eventName, contextIds] of subscribeStepEvents) { |
| for (const contextId of contextIds) { |
| this.#subscribeHooks.get(eventName).forEach((hook) => hook(contextId)); |
| } |
| } |
| await this.toggleModulesIfNeeded(); |
| return subscription.id; |
| } |
| async unsubscribe(eventNames, googChannel) { |
| for (const name of eventNames) { |
| (0, events_js_1.assertSupportedEvent)(name); |
| } |
| this.#subscriptionManager.unsubscribe(eventNames, googChannel); |
| await this.toggleModulesIfNeeded(); |
| } |
| async unsubscribeByIds(subscriptionIds) { |
| this.#subscriptionManager.unsubscribeById(subscriptionIds); |
| await this.toggleModulesIfNeeded(); |
| } |
| async toggleModulesIfNeeded() { |
| // TODO(1): Only update changed subscribers |
| // TODO(2): Enable for Worker Targets |
| await Promise.all(this.#browsingContextStorage.getAllContexts().map(async (context) => { |
| return await context.toggleModulesIfNeeded(); |
| })); |
| } |
| clearBufferedEvents(contextId) { |
| for (const eventName of eventBufferLength.keys()) { |
| const bufferMapKey = _a.#getMapKey(eventName, contextId); |
| this.#eventBuffers.delete(bufferMapKey); |
| } |
| } |
| /** |
| * If the event is buffer-able, put it in the buffer. |
| */ |
| #bufferEvent(eventWrapper, eventName) { |
| if (!eventBufferLength.has(eventName)) { |
| // Do nothing if the event is no buffer-able. |
| return; |
| } |
| const bufferMapKey = _a.#getMapKey(eventName, eventWrapper.contextId); |
| if (!this.#eventBuffers.has(bufferMapKey)) { |
| this.#eventBuffers.set(bufferMapKey, new Buffer_js_1.Buffer(eventBufferLength.get(eventName))); |
| } |
| this.#eventBuffers.get(bufferMapKey).add(eventWrapper); |
| // Add the context to the list of contexts having `eventName` events. |
| this.#eventToContextsMap.get(eventName).add(eventWrapper.contextId); |
| } |
| /** |
| * If the event is buffer-able, mark it as sent to the given contextId and goog:channel. |
| */ |
| #markEventSent(eventWrapper, googChannel, eventName) { |
| if (!eventBufferLength.has(eventName)) { |
| // Do nothing if the event is no buffer-able. |
| return; |
| } |
| const lastSentMapKey = _a.#getMapKey(eventName, eventWrapper.contextId); |
| const lastId = Math.max(this.#lastMessageSent.get(lastSentMapKey)?.get(googChannel) ?? 0, eventWrapper.id); |
| const googChannelMap = this.#lastMessageSent.get(lastSentMapKey); |
| if (googChannelMap) { |
| googChannelMap.set(googChannel, lastId); |
| } |
| else { |
| this.#lastMessageSent.set(lastSentMapKey, new Map([[googChannel, lastId]])); |
| } |
| } |
| /** |
| * Returns events which are buffered and not yet sent to the given goog:channel events. |
| */ |
| #getBufferedEvents(eventName, contextId, googChannel) { |
| const bufferMapKey = _a.#getMapKey(eventName, contextId); |
| const lastSentMessageId = this.#lastMessageSent.get(bufferMapKey)?.get(googChannel) ?? -Infinity; |
| const result = this.#eventBuffers |
| .get(bufferMapKey) |
| ?.get() |
| .filter((wrapper) => wrapper.id > lastSentMessageId) ?? []; |
| if (contextId === null) { |
| // For global subscriptions, events buffered in each context should be sent back. |
| Array.from(this.#eventToContextsMap.get(eventName).keys()) |
| .filter((_contextId) => |
| // Events without context are already in the result. |
| _contextId !== null && |
| // Events from deleted contexts should not be sent. |
| this.#browsingContextStorage.hasContext(_contextId)) |
| .map((_contextId) => this.#getBufferedEvents(eventName, _contextId, googChannel)) |
| .forEach((events) => result.push(...events)); |
| } |
| return result.sort((e1, e2) => e1.id - e2.id); |
| } |
| } |
| exports.EventManager = EventManager; |
| _a = EventManager; |
| //# sourceMappingURL=EventManager.js.map |