Skip to content

Commit

Permalink
feat: implement user context subscriptions (GoogleChromeLabs#3039)
Browse files Browse the repository at this point in the history
- Introduces UserContextStorage to abstract and re-use operations on
user contexts. For now, only the get operation is moved.

Issue GoogleChromeLabs#2983
  • Loading branch information
OrKoN authored and anshikavashistha committed Jan 31, 2025
1 parent cb16a32 commit 33c49cb
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 62 deletions.
8 changes: 7 additions & 1 deletion src/bidiMapper/BidiServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {BidiCommandParameterParser} from './BidiParser.js';
import type {BidiTransport} from './BidiTransport.js';
import {CommandProcessor, CommandProcessorEvents} from './CommandProcessor.js';
import {BluetoothProcessor} from './modules/bluetooth/BluetoothProcessor.js';
import {UserContextStorage} from './modules/browser/UserContextStorage.js';
import {CdpTargetManager} from './modules/cdp/CdpTargetManager.js';
import {BrowsingContextStorage} from './modules/context/BrowsingContextStorage.js';
import {NetworkStorage} from './modules/network/NetworkStorage.js';
Expand Down Expand Up @@ -91,7 +92,11 @@ export class BidiServer extends EventEmitter<BidiServerEvent> {
);
this.#transport = bidiTransport;
this.#transport.setOnMessage(this.#handleIncomingMessage);
this.#eventManager = new EventManager(this.#browsingContextStorage);
const userUserContextStorage = new UserContextStorage(browserCdpClient);
this.#eventManager = new EventManager(
this.#browsingContextStorage,
userUserContextStorage,
);
const networkStorage = new NetworkStorage(
this.#eventManager,
this.#browsingContextStorage,
Expand All @@ -111,6 +116,7 @@ export class BidiServer extends EventEmitter<BidiServerEvent> {
this.#preloadScriptStorage,
networkStorage,
this.#bluetoothProcessor,
userUserContextStorage,
parser,
async (options: MapperOptions) => {
// This is required to ignore certificate errors when service worker is fetched.
Expand Down
3 changes: 3 additions & 0 deletions src/bidiMapper/CommandProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {BidiCommandParameterParser} from './BidiParser.js';
import type {MapperOptions} from './BidiServer.js';
import type {BluetoothProcessor} from './modules/bluetooth/BluetoothProcessor.js';
import {BrowserProcessor} from './modules/browser/BrowserProcessor.js';
import type {UserContextStorage} from './modules/browser/UserContextStorage.js';
import {CdpProcessor} from './modules/cdp/CdpProcessor.js';
import {BrowsingContextProcessor} from './modules/context/BrowsingContextProcessor.js';
import type {BrowsingContextStorage} from './modules/context/BrowsingContextStorage.js';
Expand Down Expand Up @@ -85,6 +86,7 @@ export class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
preloadScriptStorage: PreloadScriptStorage,
networkStorage: NetworkStorage,
bluetoothProcessor: BluetoothProcessor,
userContextStorage: UserContextStorage,
parser: BidiCommandParameterParser = new BidiNoOpParser(),
initConnection: (options: MapperOptions) => Promise<void>,
logger?: LoggerFn,
Expand All @@ -99,6 +101,7 @@ export class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
this.#browserProcessor = new BrowserProcessor(
browserCdpClient,
browsingContextStorage,
userContextStorage,
);
this.#browsingContextProcessor = new BrowsingContextProcessor(
browserCdpClient,
Expand Down
19 changes: 6 additions & 13 deletions src/bidiMapper/modules/browser/BrowserProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@ import {
import type {CdpClient} from '../../BidiMapper.js';
import type {BrowsingContextStorage} from '../context/BrowsingContextStorage.js';

import type {UserContextStorage} from './UserContextStorage.js';

export class BrowserProcessor {
readonly #browserCdpClient: CdpClient;
readonly #browsingContextStorage: BrowsingContextStorage;
readonly #userContextStorage: UserContextStorage;

constructor(
browserCdpClient: CdpClient,
browsingContextStorage: BrowsingContextStorage,
userContextStorage: UserContextStorage,
) {
this.#browserCdpClient = browserCdpClient;
this.#browsingContextStorage = browsingContextStorage;
this.#userContextStorage = userContextStorage;
}

close(): EmptyResult {
Expand Down Expand Up @@ -90,20 +95,8 @@ export class BrowserProcessor {
}

async getUserContexts(): Promise<Browser.GetUserContextsResult> {
const result = await this.#browserCdpClient.sendCommand(
'Target.getBrowserContexts',
);
return {
userContexts: [
{
userContext: 'default',
},
...result.browserContextIds.map((id) => {
return {
userContext: id,
};
}),
],
userContexts: await this.#userContextStorage.getUserContexts(),
};
}

Expand Down
45 changes: 45 additions & 0 deletions src/bidiMapper/modules/browser/UserContextStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright 2025 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.
*/

import type {CdpClient} from '../../../cdp/CdpClient.js';
import type {Browser} from '../../../protocol/protocol.js';

export class UserContextStorage {
#browserClient: CdpClient;

constructor(browserClient: CdpClient) {
this.#browserClient = browserClient;
}

async getUserContexts(): Promise<
[Browser.UserContextInfo, ...Browser.UserContextInfo[]]
> {
const result = await this.#browserClient.sendCommand(
'Target.getBrowserContexts',
);
return [
{
userContext: 'default',
},
...result.browserContextIds.map((id) => {
return {
userContext: id,
};
}),
];
}
}
5 changes: 4 additions & 1 deletion src/bidiMapper/modules/network/NetworkStorage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {CdpClient} from '../../../cdp/CdpClient.js';
import {ChromiumBidi, Network} from '../../../protocol/protocol.js';
import {ProcessingQueue} from '../../../utils/ProcessingQueue.js';
import type {OutgoingMessage} from '../../OutgoingMessage.js';
import {UserContextStorage} from '../browser/UserContextStorage.js';
import type {CdpTarget} from '../cdp/CdpTarget.js';
import type {BrowsingContextImpl} from '../context/BrowsingContextImpl.js';
import {BrowsingContextStorage} from '../context/BrowsingContextStorage.js';
Expand Down Expand Up @@ -75,9 +76,10 @@ describe('NetworkStorage', () => {
id: MockCdpNetworkEvents.defaultFrameId,
} as unknown as BrowsingContextImpl;
cdpClient = cdpTarget.cdpClient;
const userContextStorage = new UserContextStorage(cdpClient);
// We need to add it the storage to emit properly
browsingContextStorage.addContext(browsingContext);
eventManager = new EventManager(browsingContextStorage);
eventManager = new EventManager(browsingContextStorage, userContextStorage);
processingQueue = new ProcessingQueue<OutgoingMessage>(
async ({message}) => {
if (message.type === 'event') {
Expand All @@ -93,6 +95,7 @@ describe('NetworkStorage', () => {
// Verify that the Request send the message
// To the correct context
[MockCdpNetworkEvents.defaultFrameId],
[],
{},
);
eventManager.on(EventManagerEvents.Event, ({message, event}) => {
Expand Down
34 changes: 33 additions & 1 deletion src/bidiMapper/modules/session/EventManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

import type {BidiPlusChannel} from '../../../protocol/chromium-bidi.js';
import {
type Browser,
ChromiumBidi,
InvalidArgumentException,
NoSuchUserContextException,
type BrowsingContext,
} from '../../../protocol/protocol.js';
import {Buffer} from '../../../utils/Buffer.js';
Expand All @@ -27,6 +29,7 @@ import {EventEmitter} from '../../../utils/EventEmitter.js';
import {IdWrapper} from '../../../utils/IdWrapper.js';
import type {Result} from '../../../utils/result.js';
import {OutgoingMessage} from '../../OutgoingMessage.js';
import type {UserContextStorage} from '../browser/UserContextStorage.js';
import type {BrowsingContextStorage} from '../context/BrowsingContextStorage.js';

import {assertSupportedEvent} from './events.js';
Expand Down Expand Up @@ -118,9 +121,15 @@ export class EventManager extends EventEmitter<EventManagerEventsMap> {
((contextId: BrowsingContext.BrowsingContext) => void)[]
>;

constructor(browsingContextStorage: BrowsingContextStorage) {
#userContextStorage: UserContextStorage;

constructor(
browsingContextStorage: BrowsingContextStorage,
userContextStorage: UserContextStorage,
) {
super();
this.#browsingContextStorage = browsingContextStorage;
this.#userContextStorage = userContextStorage;
this.#subscriptionManager = new SubscriptionManager(browsingContextStorage);
this.#subscribeHooks = new DefaultMap(() => []);
}
Expand Down Expand Up @@ -213,12 +222,19 @@ export class EventManager extends EventEmitter<EventManagerEventsMap> {
async subscribe(
eventNames: ChromiumBidi.EventNames[],
contextIds: BrowsingContext.BrowsingContext[],
userContextIds: Browser.UserContext[],
channel: BidiPlusChannel,
): Promise<string> {
for (const name of eventNames) {
assertSupportedEvent(name);
}

if (userContextIds.length && contextIds.length) {
throw new InvalidArgumentException(
'Both userContexts and contexts cannot be specified.',
);
}

// First check if all the contexts are known.
for (const contextId of contextIds) {
if (contextId !== null) {
Expand All @@ -227,6 +243,21 @@ export class EventManager extends EventEmitter<EventManagerEventsMap> {
}
}

// Validate user contexts.
if (userContextIds.length) {
const userContexts = await this.#userContextStorage.getUserContexts();
const knownUserContextIds = new Set(
userContexts.map((userContext) => userContext.userContext),
);
for (const userContextId of userContextIds) {
if (!knownUserContextIds.has(userContextId)) {
throw new NoSuchUserContextException(
`User context ${userContextId} not found`,
);
}
}
}

const unrolledEventNames = new Set(unrollEvents(eventNames));
const subscribeStepEvents = new Map<ChromiumBidi.EventNames, Set<string>>();
const subscriptionNavigableIds = new Set(
Expand Down Expand Up @@ -260,6 +291,7 @@ export class EventManager extends EventEmitter<EventManagerEventsMap> {
const subscription = this.#subscriptionManager.subscribe(
eventNames,
contextIds,
userContextIds,
channel,
);

Expand Down
1 change: 1 addition & 0 deletions src/bidiMapper/modules/session/SessionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export class SessionProcessor {
const subscription = await this.#eventManager.subscribe(
params.events as ChromiumBidi.EventNames[],
params.contexts ?? [],
params.userContexts ?? [],
channel,
);
return {
Expand Down
Loading

0 comments on commit 33c49cb

Please sign in to comment.