206 lines
9.4 KiB
JavaScript
206 lines
9.4 KiB
JavaScript
"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 = exports.unrollEvents = exports.cartesianProduct = void 0;
|
|
const protocol_js_1 = require("../../../protocol/protocol.js");
|
|
const events_js_1 = require("./events.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())));
|
|
}
|
|
exports.cartesianProduct = cartesianProduct;
|
|
/** 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.BrowsingContext:
|
|
addEvents(Object.values(protocol_js_1.ChromiumBidi.BrowsingContext.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;
|
|
default:
|
|
allEvents.add(event);
|
|
}
|
|
}
|
|
return [...allEvents.values()];
|
|
}
|
|
exports.unrollEvents = unrollEvents;
|
|
class SubscriptionManager {
|
|
#subscriptionPriority = 0;
|
|
// BrowsingContext `null` means the event has subscription across all the
|
|
// browsing contexts.
|
|
// Channel `null` means no `channel` should be added.
|
|
#channelToContextToEventMap = new Map();
|
|
#browsingContextStorage;
|
|
constructor(browsingContextStorage) {
|
|
this.#browsingContextStorage = browsingContextStorage;
|
|
}
|
|
getChannelsSubscribedToEvent(eventMethod, contextId) {
|
|
const prioritiesAndChannels = Array.from(this.#channelToContextToEventMap.keys())
|
|
.map((channel) => ({
|
|
priority: this.#getEventSubscriptionPriorityForChannel(eventMethod, contextId, channel),
|
|
channel,
|
|
}))
|
|
.filter(({ priority }) => priority !== null);
|
|
// Sort channels by priority.
|
|
return prioritiesAndChannels
|
|
.sort((a, b) => a.priority - b.priority)
|
|
.map(({ channel }) => channel);
|
|
}
|
|
#getEventSubscriptionPriorityForChannel(eventMethod, contextId, channel) {
|
|
const contextToEventMap = this.#channelToContextToEventMap.get(channel);
|
|
if (contextToEventMap === undefined) {
|
|
return null;
|
|
}
|
|
const maybeTopLevelContextId = this.#browsingContextStorage.findTopLevelContextId(contextId);
|
|
// `null` covers global subscription.
|
|
const relevantContexts = [...new Set([null, maybeTopLevelContextId])];
|
|
// Get all the subscription priorities.
|
|
const priorities = relevantContexts
|
|
.map((context) => {
|
|
// Get the priority for exact event name
|
|
const priority = contextToEventMap.get(context)?.get(eventMethod);
|
|
// For CDP we can't provide specific event name when subscribing
|
|
// to the module directly.
|
|
// Because of that we need to see event `cdp` exits in the map.
|
|
if ((0, events_js_1.isCdpEvent)(eventMethod)) {
|
|
const cdpPriority = contextToEventMap
|
|
.get(context)
|
|
?.get(protocol_js_1.ChromiumBidi.BiDiModule.Cdp);
|
|
// If we subscribe to the event directly and `cdp` module as well
|
|
// priority will be different we take minimal priority
|
|
return priority && cdpPriority
|
|
? Math.min(priority, cdpPriority)
|
|
: // At this point we know that we have subscribed
|
|
// to only one of the two
|
|
priority ?? cdpPriority;
|
|
}
|
|
return priority;
|
|
})
|
|
.filter((p) => p !== undefined);
|
|
if (priorities.length === 0) {
|
|
// Not subscribed, return null.
|
|
return null;
|
|
}
|
|
// Return minimal priority.
|
|
return Math.min(...priorities);
|
|
}
|
|
subscribe(event, contextId, channel) {
|
|
// All the subscriptions are handled on the top-level contexts.
|
|
contextId = this.#browsingContextStorage.findTopLevelContextId(contextId);
|
|
// Check if subscribed event is a whole module
|
|
switch (event) {
|
|
case protocol_js_1.ChromiumBidi.BiDiModule.BrowsingContext:
|
|
Object.values(protocol_js_1.ChromiumBidi.BrowsingContext.EventNames).map((specificEvent) => this.subscribe(specificEvent, contextId, channel));
|
|
return;
|
|
case protocol_js_1.ChromiumBidi.BiDiModule.Log:
|
|
Object.values(protocol_js_1.ChromiumBidi.Log.EventNames).map((specificEvent) => this.subscribe(specificEvent, contextId, channel));
|
|
return;
|
|
case protocol_js_1.ChromiumBidi.BiDiModule.Network:
|
|
Object.values(protocol_js_1.ChromiumBidi.Network.EventNames).map((specificEvent) => this.subscribe(specificEvent, contextId, channel));
|
|
return;
|
|
case protocol_js_1.ChromiumBidi.BiDiModule.Script:
|
|
Object.values(protocol_js_1.ChromiumBidi.Script.EventNames).map((specificEvent) => this.subscribe(specificEvent, contextId, channel));
|
|
return;
|
|
default:
|
|
// Intentionally left empty.
|
|
}
|
|
if (!this.#channelToContextToEventMap.has(channel)) {
|
|
this.#channelToContextToEventMap.set(channel, new Map());
|
|
}
|
|
const contextToEventMap = this.#channelToContextToEventMap.get(channel);
|
|
if (!contextToEventMap.has(contextId)) {
|
|
contextToEventMap.set(contextId, new Map());
|
|
}
|
|
const eventMap = contextToEventMap.get(contextId);
|
|
// Do not re-subscribe to events to keep the priority.
|
|
if (eventMap.has(event)) {
|
|
return;
|
|
}
|
|
eventMap.set(event, this.#subscriptionPriority++);
|
|
}
|
|
/**
|
|
* Unsubscribes atomically from all events in the given contexts and channel.
|
|
*/
|
|
unsubscribeAll(events, contextIds, channel) {
|
|
// Assert all contexts are known.
|
|
for (const contextId of contextIds) {
|
|
if (contextId !== null) {
|
|
this.#browsingContextStorage.getContext(contextId);
|
|
}
|
|
}
|
|
const eventContextPairs = cartesianProduct(unrollEvents(events), contextIds);
|
|
// Assert all unsubscriptions are valid.
|
|
// If any of the unsubscriptions are invalid, do not unsubscribe from anything.
|
|
eventContextPairs
|
|
.map(([event, contextId]) => this.#checkUnsubscribe(event, contextId, channel))
|
|
.forEach((unsubscribe) => unsubscribe());
|
|
}
|
|
/**
|
|
* Unsubscribes from the event in the given context and channel.
|
|
* Syntactic sugar for "unsubscribeAll".
|
|
*/
|
|
unsubscribe(eventName, contextId, channel) {
|
|
this.unsubscribeAll([eventName], [contextId], channel);
|
|
}
|
|
#checkUnsubscribe(event, contextId, channel) {
|
|
// All the subscriptions are handled on the top-level contexts.
|
|
contextId = this.#browsingContextStorage.findTopLevelContextId(contextId);
|
|
if (!this.#channelToContextToEventMap.has(channel)) {
|
|
throw new protocol_js_1.InvalidArgumentException(`Cannot unsubscribe from ${event}, ${contextId === null ? 'null' : contextId}. No subscription found.`);
|
|
}
|
|
const contextToEventMap = this.#channelToContextToEventMap.get(channel);
|
|
if (!contextToEventMap.has(contextId)) {
|
|
throw new protocol_js_1.InvalidArgumentException(`Cannot unsubscribe from ${event}, ${contextId === null ? 'null' : contextId}. No subscription found.`);
|
|
}
|
|
const eventMap = contextToEventMap.get(contextId);
|
|
if (!eventMap.has(event)) {
|
|
throw new protocol_js_1.InvalidArgumentException(`Cannot unsubscribe from ${event}, ${contextId === null ? 'null' : contextId}. No subscription found.`);
|
|
}
|
|
return () => {
|
|
eventMap.delete(event);
|
|
// Clean up maps if empty.
|
|
if (eventMap.size === 0) {
|
|
contextToEventMap.delete(event);
|
|
}
|
|
if (contextToEventMap.size === 0) {
|
|
this.#channelToContextToEventMap.delete(channel);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
exports.SubscriptionManager = SubscriptionManager;
|
|
//# sourceMappingURL=SubscriptionManager.js.map
|