| "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. |
| */ |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.SubscriptionManager = void 0; |
| exports.cartesianProduct = cartesianProduct; |
| exports.unrollEvents = unrollEvents; |
| exports.difference = difference; |
| const protocol_js_1 = require("../../../protocol/protocol.js"); |
| const uuid_js_1 = require("../../../utils/uuid.js"); |
| /** |
| * Returns the cartesian product of the given arrays. |
| * |
| * Example: |
| * cartesian([1, 2], ['a', 'b']); => [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']] |
| */ |
| function cartesianProduct(...a) { |
| return a.reduce((a, b) => a.flatMap((d) => b.map((e) => [d, e].flat()))); |
| } |
| /** Expands "AllEvents" events into atomic events. */ |
| function unrollEvents(events) { |
| const allEvents = new Set(); |
| function addEvents(events) { |
| for (const event of events) { |
| allEvents.add(event); |
| } |
| } |
| for (const event of events) { |
| switch (event) { |
| case protocol_js_1.ChromiumBidi.BiDiModule.Bluetooth: |
| addEvents(Object.values(protocol_js_1.ChromiumBidi.Bluetooth.EventNames)); |
| break; |
| case protocol_js_1.ChromiumBidi.BiDiModule.BrowsingContext: |
| addEvents(Object.values(protocol_js_1.ChromiumBidi.BrowsingContext.EventNames)); |
| break; |
| case protocol_js_1.ChromiumBidi.BiDiModule.Input: |
| addEvents(Object.values(protocol_js_1.ChromiumBidi.Input.EventNames)); |
| break; |
| case protocol_js_1.ChromiumBidi.BiDiModule.Log: |
| addEvents(Object.values(protocol_js_1.ChromiumBidi.Log.EventNames)); |
| break; |
| case protocol_js_1.ChromiumBidi.BiDiModule.Network: |
| addEvents(Object.values(protocol_js_1.ChromiumBidi.Network.EventNames)); |
| break; |
| case protocol_js_1.ChromiumBidi.BiDiModule.Script: |
| addEvents(Object.values(protocol_js_1.ChromiumBidi.Script.EventNames)); |
| break; |
| case protocol_js_1.ChromiumBidi.BiDiModule.Speculation: |
| addEvents(Object.values(protocol_js_1.ChromiumBidi.Speculation.EventNames)); |
| break; |
| default: |
| allEvents.add(event); |
| } |
| } |
| return allEvents.values(); |
| } |
| class SubscriptionManager { |
| #subscriptions = []; |
| #knownSubscriptionIds = new Set(); |
| #browsingContextStorage; |
| constructor(browsingContextStorage) { |
| this.#browsingContextStorage = browsingContextStorage; |
| } |
| getGoogChannelsSubscribedToEvent(eventName, contextId) { |
| const googChannels = new Set(); |
| for (const subscription of this.#subscriptions) { |
| if (this.#isSubscribedTo(subscription, eventName, contextId)) { |
| googChannels.add(subscription.googChannel); |
| } |
| } |
| return Array.from(googChannels); |
| } |
| getGoogChannelsSubscribedToEventGlobally(eventName) { |
| const googChannels = new Set(); |
| for (const subscription of this.#subscriptions) { |
| if (this.#isSubscribedTo(subscription, eventName)) { |
| googChannels.add(subscription.googChannel); |
| } |
| } |
| return Array.from(googChannels); |
| } |
| #isSubscribedTo(subscription, moduleOrEvent, browsingContextId) { |
| let includesEvent = false; |
| for (const eventName of subscription.eventNames) { |
| // This also covers the `goog:cdp` case where |
| // we don't unroll the event names |
| if ( |
| // Event explicitly subscribed |
| eventName === moduleOrEvent || |
| // Event subscribed via module |
| eventName === moduleOrEvent.split('.').at(0) || |
| // Event explicitly subscribed compared to module |
| eventName.split('.').at(0) === moduleOrEvent) { |
| includesEvent = true; |
| break; |
| } |
| } |
| if (!includesEvent) { |
| return false; |
| } |
| // user context subscription. |
| if (subscription.userContextIds.size !== 0) { |
| if (!browsingContextId) { |
| return false; |
| } |
| const context = this.#browsingContextStorage.findContext(browsingContextId); |
| if (!context) { |
| return false; |
| } |
| return subscription.userContextIds.has(context.userContext); |
| } |
| // context subscription. |
| if (subscription.topLevelTraversableIds.size !== 0) { |
| if (!browsingContextId) { |
| return false; |
| } |
| const topLevelContext = this.#browsingContextStorage.findTopLevelContextId(browsingContextId); |
| return (topLevelContext !== null && |
| subscription.topLevelTraversableIds.has(topLevelContext)); |
| } |
| // global subscription. |
| return true; |
| } |
| isSubscribedTo(moduleOrEvent, contextId) { |
| for (const subscription of this.#subscriptions) { |
| if (this.#isSubscribedTo(subscription, moduleOrEvent, contextId)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| /** |
| * Subscribes to event in the given context and goog:channel. |
| * @return {SubscriptionItem[]} List of |
| * subscriptions. If the event is a whole module, it will return all the specific |
| * events. If the contextId is null, it will return all the top-level contexts which were |
| * not subscribed before the command. |
| */ |
| subscribe(eventNames, contextIds, userContextIds, googChannel) { |
| // All the subscriptions are handled on the top-level contexts. |
| const subscription = { |
| id: (0, uuid_js_1.uuidv4)(), |
| eventNames: new Set(unrollEvents(eventNames)), |
| topLevelTraversableIds: new Set(contextIds.map((contextId) => { |
| const topLevelContext = this.#browsingContextStorage.findTopLevelContextId(contextId); |
| if (!topLevelContext) { |
| throw new protocol_js_1.NoSuchFrameException(`Top-level navigable not found for context id ${contextId}`); |
| } |
| return topLevelContext; |
| })), |
| userContextIds: new Set(userContextIds), |
| googChannel, |
| }; |
| this.#subscriptions.push(subscription); |
| this.#knownSubscriptionIds.add(subscription.id); |
| return subscription; |
| } |
| /** |
| * Unsubscribes atomically from all events in the given contexts and channel. |
| * |
| * This is a legacy spec branch to unsubscribe by attributes. |
| */ |
| unsubscribe(inputEventNames, googChannel) { |
| const eventNames = new Set(unrollEvents(inputEventNames)); |
| const newSubscriptions = []; |
| const eventsMatched = new Set(); |
| for (const subscription of this.#subscriptions) { |
| if (subscription.googChannel !== googChannel) { |
| newSubscriptions.push(subscription); |
| continue; |
| } |
| // Skip user context subscriptions. |
| if (subscription.userContextIds.size !== 0) { |
| newSubscriptions.push(subscription); |
| continue; |
| } |
| // Skip subscriptions when none of the event names match. |
| if (intersection(subscription.eventNames, eventNames).size === 0) { |
| newSubscriptions.push(subscription); |
| continue; |
| } |
| // Skip non-global subscriptions. |
| if (subscription.topLevelTraversableIds.size !== 0) { |
| newSubscriptions.push(subscription); |
| continue; |
| } |
| const subscriptionEventNames = new Set(subscription.eventNames); |
| for (const eventName of eventNames) { |
| if (subscriptionEventNames.has(eventName)) { |
| eventsMatched.add(eventName); |
| subscriptionEventNames.delete(eventName); |
| } |
| } |
| if (subscriptionEventNames.size !== 0) { |
| newSubscriptions.push({ |
| ...subscription, |
| eventNames: subscriptionEventNames, |
| }); |
| } |
| } |
| // If some events did not match, it is an invalid request. |
| if (!equal(eventsMatched, eventNames)) { |
| throw new protocol_js_1.InvalidArgumentException('No subscription found'); |
| } |
| // Committing the new subscriptions. |
| this.#subscriptions = newSubscriptions; |
| } |
| /** |
| * Unsubscribes by subscriptionId. |
| */ |
| unsubscribeById(subscriptionIds) { |
| const subscriptionIdsSet = new Set(subscriptionIds); |
| const unknownIds = difference(subscriptionIdsSet, this.#knownSubscriptionIds); |
| if (unknownIds.size !== 0) { |
| throw new protocol_js_1.InvalidArgumentException('No subscription found'); |
| } |
| this.#subscriptions = this.#subscriptions.filter((subscription) => { |
| return !subscriptionIdsSet.has(subscription.id); |
| }); |
| this.#knownSubscriptionIds = difference(this.#knownSubscriptionIds, subscriptionIdsSet); |
| } |
| } |
| exports.SubscriptionManager = SubscriptionManager; |
| /** |
| * Replace with Set.prototype.intersection once Node 20 is dropped. |
| */ |
| function intersection(setA, setB) { |
| const result = new Set(); |
| for (const a of setA) { |
| if (setB.has(a)) { |
| result.add(a); |
| } |
| } |
| return result; |
| } |
| /** |
| * Replace with Set.prototype.difference once Node 20 is dropped. |
| */ |
| function difference(setA, setB) { |
| const result = new Set(); |
| for (const a of setA) { |
| if (!setB.has(a)) { |
| result.add(a); |
| } |
| } |
| return result; |
| } |
| function equal(setA, setB) { |
| if (setA.size !== setB.size) { |
| return false; |
| } |
| for (const a of setA) { |
| if (!setB.has(a)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| //# sourceMappingURL=SubscriptionManager.js.map |