Skip to content

Commit

Permalink
feat: user contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
OrKoN committed Jan 24, 2024
1 parent 43ac4f1 commit 13dc8e6
Show file tree
Hide file tree
Showing 19 changed files with 513 additions and 55 deletions.
Empty file added compile_commands.json
Empty file.
22 changes: 22 additions & 0 deletions src/bidiMapper/BidiServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class BidiServer extends EventEmitter<BidiServerEvent> {
cdpConnection: CdpConnection,
browserCdpClient: CdpClient,
selfTargetId: string,
defaultUserContextId: string,
options?: MapperOptions,
parser?: BidiCommandParameterParser,
logger?: LoggerFn
Expand All @@ -92,6 +93,7 @@ export class BidiServer extends EventEmitter<BidiServerEvent> {
browserCdpClient,
this.#eventManager,
selfTargetId,
defaultUserContextId,
this.#browsingContextStorage,
new RealmStorage(),
options?.acceptInsecureCerts ?? false,
Expand Down Expand Up @@ -122,11 +124,31 @@ export class BidiServer extends EventEmitter<BidiServerEvent> {
parser?: BidiCommandParameterParser,
logger?: LoggerFn
): Promise<BidiServer> {
// The default context is not exposed in Target.getBrowserContexts but can
// be observed via Target.getTargets. To determine the default browser
// context, we check which one is mentioned in Target.getTargets and not in
// Target.getBrowserContexts.
const [{browserContextIds}, {targetInfos}] = await Promise.all([
browserCdpClient.sendCommand('Target.getBrowserContexts'),
browserCdpClient.sendCommand('Target.getTargets'),
]);
let defaultUserContextId = 'default';
for (const info of targetInfos) {
if (
info.browserContextId &&
!browserContextIds.includes(info.browserContextId)
) {
defaultUserContextId = info.browserContextId;
break;
}
}

const server = new BidiServer(
bidiTransport,
cdpConnection,
browserCdpClient,
selfTargetId,
defaultUserContextId,
options,
parser,
logger
Expand Down
10 changes: 10 additions & 0 deletions src/bidiMapper/CommandProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
browserCdpClient: CdpClient,
eventManager: EventManager,
selfTargetId: string,
defaultUserContextId: string,
browsingContextStorage: BrowsingContextStorage,
realmStorage: RealmStorage,
acceptInsecureCerts: boolean,
Expand Down Expand Up @@ -105,6 +106,7 @@ export class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
preloadScriptStorage,
acceptInsecureCerts,
sharedIdWithFrame,
defaultUserContextId,
logger
);
this.#cdpProcessor = new CdpProcessor(
Expand Down Expand Up @@ -149,6 +151,14 @@ export class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
// keep-sorted start block=yes
case 'browser.close':
return this.#browserProcessor.close();
case 'browser.createUserContext':
return await this.#browserProcessor.createUserContext();
case 'browser.getUserContexts':
return await this.#browserProcessor.getUserContexts();
case 'browser.removeUserContext':
return await this.#browserProcessor.removeUserContext(
command.params.userContext
);
// keep-sorted end

// Browsing Context domain
Expand Down
56 changes: 55 additions & 1 deletion src/bidiMapper/domains/browser/BrowserProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
* limitations under the License.
*/

import type {EmptyResult} from '../../../protocol/protocol.js';
import {
type EmptyResult,
type Browser,
InvalidArgumentException,
NoSuchUserContextException,
} from '../../../protocol/protocol.js';
import type {CdpClient} from '../../BidiMapper.js';

export class BrowserProcessor {
Expand All @@ -32,4 +37,53 @@ export class BrowserProcessor {

return {};
}

async createUserContext(): Promise<Browser.CreateUserContextResult> {
const context = await this.#browserCdpClient.sendCommand(
'Target.createBrowserContext'
);
return {
userContext: context.browserContextId,
};
}

async removeUserContext(
userContext: Browser.UserContext
): Promise<EmptyResult> {
if (userContext === 'default') {
throw new InvalidArgumentException(
'`default` user context cannot be removed'
);
}
try {
await this.#browserCdpClient.sendCommand('Target.disposeBrowserContext', {
browserContextId: userContext,
});
} catch (err) {
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/target_handler.cc;l=1424;drc=c686e8f4fd379312469fe018f5c390e9c8f20d0d
if ((err as Error).message.startsWith('Failed to find context with id')) {
throw new NoSuchUserContextException((err as Error).message);
}
throw err;
}
return {};
}

async getUserContexts(): Promise<Browser.GetUserContextsResult> {
const result = await this.#browserCdpClient.sendCommand(
'Target.getBrowserContexts'
);
return {
userContexts: [
{
userContext: 'default',
},
...result.browserContextIds.map((id) => {
return {
userContext: id,
};
}),
],
};
}
}
6 changes: 6 additions & 0 deletions src/bidiMapper/domains/context/BrowsingContextImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class BrowsingContextImpl {

/** The ID of this browsing context. */
readonly #id: BrowsingContext.BrowsingContext;
readonly userContext: string;

/**
* The ID of the parent browsing context.
Expand Down Expand Up @@ -83,6 +84,7 @@ export class BrowsingContextImpl {
realmStorage: RealmStorage,
id: BrowsingContext.BrowsingContext,
parentId: BrowsingContext.BrowsingContext | null,
userContext: string,
eventManager: EventManager,
browsingContextStorage: BrowsingContextStorage,
sharedIdWithFrame: boolean,
Expand All @@ -92,6 +94,7 @@ export class BrowsingContextImpl {
this.#realmStorage = realmStorage;
this.#id = id;
this.#parentId = parentId;
this.userContext = userContext;
this.#eventManager = eventManager;
this.#browsingContextStorage = browsingContextStorage;
this.#sharedIdWithFrame = sharedIdWithFrame;
Expand All @@ -103,6 +106,7 @@ export class BrowsingContextImpl {
realmStorage: RealmStorage,
id: BrowsingContext.BrowsingContext,
parentId: BrowsingContext.BrowsingContext | null,
userContext: string,
eventManager: EventManager,
browsingContextStorage: BrowsingContextStorage,
sharedIdWithFrame: boolean,
Expand All @@ -113,6 +117,7 @@ export class BrowsingContextImpl {
realmStorage,
id,
parentId,
userContext,
eventManager,
browsingContextStorage,
sharedIdWithFrame,
Expand Down Expand Up @@ -306,6 +311,7 @@ export class BrowsingContextImpl {
return {
context: this.#id,
url: this.url,
userContext: this.userContext,
children:
maxDepth > 0
? this.directChildren.map((c) =>
Expand Down
64 changes: 48 additions & 16 deletions src/bidiMapper/domains/context/BrowsingContextProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
BrowsingContext,
InvalidArgumentException,
type EmptyResult,
NoSuchUserContextException,
} from '../../../protocol/protocol.js';
import {CdpErrorConstants} from '../../../utils/CdpErrorConstants.js';
import {LogType, type LoggerFn} from '../../../utils/log.js';
Expand All @@ -48,6 +49,7 @@ export class BrowsingContextProcessor {
readonly #preloadScriptStorage: PreloadScriptStorage;
readonly #realmStorage: RealmStorage;

readonly #defaultUserContextId: string;
readonly #logger?: LoggerFn;

constructor(
Expand All @@ -61,6 +63,7 @@ export class BrowsingContextProcessor {
preloadScriptStorage: PreloadScriptStorage,
acceptInsecureCerts: boolean,
sharedIdWithFrame: boolean,
defaultUserContextId: string,
logger?: LoggerFn
) {
this.#acceptInsecureCerts = acceptInsecureCerts;
Expand All @@ -73,6 +76,7 @@ export class BrowsingContextProcessor {
this.#networkStorage = networkStorage;
this.#realmStorage = realmStorage;
this.#sharedIdWithFrame = sharedIdWithFrame;
this.#defaultUserContextId = defaultUserContextId;
this.#logger = logger;

this.#setEventListeners(browserCdpClient);
Expand All @@ -97,6 +101,7 @@ export class BrowsingContextProcessor {
params: BrowsingContext.CreateParameters
): Promise<BrowsingContext.CreateResult> {
let referenceContext: BrowsingContextImpl | undefined;
let userContext = params.userContext ?? 'default';
if (params.referenceContext !== undefined) {
referenceContext = this.#browsingContextStorage.getContext(
params.referenceContext
Expand All @@ -106,31 +111,53 @@ export class BrowsingContextProcessor {
`referenceContext should be a top-level context`
);
}
userContext = referenceContext.userContext;
}

let result: Protocol.Target.CreateTargetResponse;

let newWindow = false;
switch (params.type) {
case BrowsingContext.CreateType.Tab:
result = await this.#browserCdpClient.sendCommand(
'Target.createTarget',
{
url: 'about:blank',
newWindow: false,
}
);
newWindow = false;
break;
case BrowsingContext.CreateType.Window:
result = await this.#browserCdpClient.sendCommand(
'Target.createTarget',
{
url: 'about:blank',
newWindow: true,
}
);
newWindow = true;
break;
}

if (userContext !== 'default') {
const existingContexts = this.#browsingContextStorage
.getAllContexts()
.filter((context) => context.userContext === userContext);

if (!existingContexts.length) {
// If there are no contexts in the given user context, we need to set
// newWindow to true as newWindow=false will be rejected.
newWindow = true;
}
}

let result: Protocol.Target.CreateTargetResponse;

try {
result = await this.#browserCdpClient.sendCommand('Target.createTarget', {
url: 'about:blank',
newWindow,
browserContextId: userContext === 'default' ? undefined : userContext,
});
} catch (err) {
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/devtools/protocol/target_handler.cc;l=1;drc=e80392ac11e48a691f4309964cab83a3a59e01c8
if (
(err as Error).message.startsWith(
'Failed to find browser context with id'
)
) {
throw new NoSuchUserContextException(
`The context ${userContext} was not found`
);
}
throw err;
}

// Wait for the new tab to be loaded to avoid race conditions in the
// `browsingContext` events, when the `browsingContext.domContentLoaded` and
// `browsingContext.load` events from the initial `about:blank` navigation
Expand Down Expand Up @@ -319,6 +346,7 @@ export class BrowsingContextProcessor {
this.#realmStorage,
params.frameId,
params.parentFrameId,
parentBrowsingContext.userContext,
this.#eventManager,
this.#browsingContextStorage,
this.#sharedIdWithFrame,
Expand Down Expand Up @@ -382,6 +410,10 @@ export class BrowsingContextProcessor {
this.#realmStorage,
targetInfo.targetId,
null,
targetInfo.browserContextId &&
targetInfo.browserContextId !== this.#defaultUserContextId
? targetInfo.browserContextId
: 'default',
this.#eventManager,
this.#browsingContextStorage,
this.#sharedIdWithFrame,
Expand Down
Loading

0 comments on commit 13dc8e6

Please sign in to comment.