Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
267 lines
No EOL
13 KiB
Text
267 lines
No EOL
13 KiB
Text
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.BrowsingContextProcessor = void 0;
|
|
const protocol_js_1 = require("../../../protocol/protocol.js");
|
|
class BrowsingContextProcessor {
|
|
#browserCdpClient;
|
|
#browsingContextStorage;
|
|
#contextConfigStorage;
|
|
#eventManager;
|
|
#userContextStorage;
|
|
constructor(browserCdpClient, browsingContextStorage, userContextStorage, contextConfigStorage, eventManager) {
|
|
this.#contextConfigStorage = contextConfigStorage;
|
|
this.#userContextStorage = userContextStorage;
|
|
this.#browserCdpClient = browserCdpClient;
|
|
this.#browsingContextStorage = browsingContextStorage;
|
|
this.#eventManager = eventManager;
|
|
this.#eventManager.addSubscribeHook(protocol_js_1.ChromiumBidi.BrowsingContext.EventNames.ContextCreated, this.#onContextCreatedSubscribeHook.bind(this));
|
|
}
|
|
getTree(params) {
|
|
const resultContexts = params.root === undefined
|
|
? this.#browsingContextStorage.getTopLevelContexts()
|
|
: [this.#browsingContextStorage.getContext(params.root)];
|
|
return {
|
|
contexts: resultContexts.map((c) => c.serializeToBidiValue(params.maxDepth ?? Number.MAX_VALUE)),
|
|
};
|
|
}
|
|
async create(params) {
|
|
let referenceContext;
|
|
let userContext = 'default';
|
|
if (params.referenceContext !== undefined) {
|
|
referenceContext = this.#browsingContextStorage.getContext(params.referenceContext);
|
|
if (!referenceContext.isTopLevelContext()) {
|
|
throw new protocol_js_1.InvalidArgumentException(`referenceContext should be a top-level context`);
|
|
}
|
|
userContext = referenceContext.userContext;
|
|
}
|
|
if (params.userContext !== undefined) {
|
|
userContext = params.userContext;
|
|
}
|
|
const existingContexts = this.#browsingContextStorage
|
|
.getAllContexts()
|
|
.filter((context) => context.userContext === userContext);
|
|
let newWindow = false;
|
|
switch (params.type) {
|
|
case "tab" /* BrowsingContext.CreateType.Tab */:
|
|
newWindow = false;
|
|
break;
|
|
case "window" /* BrowsingContext.CreateType.Window */:
|
|
newWindow = true;
|
|
break;
|
|
}
|
|
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;
|
|
try {
|
|
result = await this.#browserCdpClient.sendCommand('Target.createTarget', {
|
|
url: 'about:blank',
|
|
newWindow,
|
|
browserContextId: userContext === 'default' ? undefined : userContext,
|
|
background: params.background === true,
|
|
});
|
|
}
|
|
catch (err) {
|
|
if (
|
|
// See https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/devtools/protocol/target_handler.cc;l=90;drc=e80392ac11e48a691f4309964cab83a3a59e01c8
|
|
err.message.startsWith('Failed to find browser context with id') ||
|
|
// See https://source.chromium.org/chromium/chromium/src/+/main:headless/lib/browser/protocol/target_handler.cc;l=49;drc=e80392ac11e48a691f4309964cab83a3a59e01c8
|
|
err.message === 'browserContextId') {
|
|
throw new protocol_js_1.NoSuchUserContextException(`The context ${userContext} was not found`);
|
|
}
|
|
throw err;
|
|
}
|
|
// Wait for the new target to be attached and to be added to the browsing context
|
|
// storage.
|
|
const context = await this.#browsingContextStorage.waitForContext(result.targetId);
|
|
// 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
|
|
// are emitted after the next navigation is started.
|
|
// Details: https://github.com/web-platform-tests/wpt/issues/35846
|
|
await context.lifecycleLoaded();
|
|
return { context: context.id };
|
|
}
|
|
navigate(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
return context.navigate(params.url, params.wait ?? "none" /* BrowsingContext.ReadinessState.None */);
|
|
}
|
|
reload(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
return context.reload(params.ignoreCache ?? false, params.wait ?? "none" /* BrowsingContext.ReadinessState.None */);
|
|
}
|
|
async activate(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
if (!context.isTopLevelContext()) {
|
|
throw new protocol_js_1.InvalidArgumentException('Activation is only supported on the top-level context');
|
|
}
|
|
await context.activate();
|
|
return {};
|
|
}
|
|
async captureScreenshot(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
return await context.captureScreenshot(params);
|
|
}
|
|
async print(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
return await context.print(params);
|
|
}
|
|
async setViewport(params) {
|
|
// Check the The viewport size limits is not checked by protocol parser, so we need to validate
|
|
// it manually:
|
|
// https://crsrc.org/c/content/browser/devtools/protocol/emulation_handler.cc;drc=f49e23d8e2bd190b42ec62284b8be10dcccd0446;l=660
|
|
const maxDimensionSize = 10_000_000;
|
|
if ((params.viewport?.height ?? 0) > maxDimensionSize ||
|
|
(params.viewport?.width ?? 0) > maxDimensionSize) {
|
|
throw new protocol_js_1.UnsupportedOperationException(`Viewport dimension over ${maxDimensionSize} are not supported`);
|
|
}
|
|
const config = {};
|
|
// `undefined` means no changes should be done to the config.
|
|
if (params.devicePixelRatio !== undefined) {
|
|
config.devicePixelRatio = params.devicePixelRatio;
|
|
}
|
|
if (params.viewport !== undefined) {
|
|
config.viewport = params.viewport;
|
|
}
|
|
const impactedTopLevelContexts = await this.#getRelatedTopLevelBrowsingContexts(params.context, params.userContexts);
|
|
for (const userContextId of params.userContexts ?? []) {
|
|
this.#contextConfigStorage.updateUserContextConfig(userContextId, config);
|
|
}
|
|
if (params.context !== undefined) {
|
|
this.#contextConfigStorage.updateBrowsingContextConfig(params.context, config);
|
|
}
|
|
await Promise.all(impactedTopLevelContexts.map(async (context) => {
|
|
const config = this.#contextConfigStorage.getActiveConfig(context.id, context.userContext);
|
|
await context.setViewport(config.viewport ?? null, config.devicePixelRatio ?? null, config.screenOrientation ?? null);
|
|
}));
|
|
return {};
|
|
}
|
|
/**
|
|
* Returns a list of top-level browsing context ids.
|
|
*/
|
|
async #getRelatedTopLevelBrowsingContexts(browsingContextId, userContextIds) {
|
|
if (browsingContextId === undefined && userContextIds === undefined) {
|
|
throw new protocol_js_1.InvalidArgumentException('Either userContexts or context must be provided');
|
|
}
|
|
if (browsingContextId !== undefined && userContextIds !== undefined) {
|
|
throw new protocol_js_1.InvalidArgumentException('userContexts and context are mutually exclusive');
|
|
}
|
|
if (browsingContextId !== undefined) {
|
|
const context = this.#browsingContextStorage.getContext(browsingContextId);
|
|
if (!context.isTopLevelContext()) {
|
|
throw new protocol_js_1.InvalidArgumentException('Emulating viewport is only supported on the top-level context');
|
|
}
|
|
return [context];
|
|
}
|
|
// Verify that all user contexts exist.
|
|
await this.#userContextStorage.verifyUserContextIdList(userContextIds);
|
|
const result = [];
|
|
for (const userContextId of userContextIds) {
|
|
const topLevelBrowsingContexts = this.#browsingContextStorage
|
|
.getTopLevelContexts()
|
|
.filter((browsingContext) => browsingContext.userContext === userContextId);
|
|
result.push(...topLevelBrowsingContexts);
|
|
}
|
|
// Remove duplicates. Compare `BrowsingContextImpl` by reference is correct here, as
|
|
// `browsingContextStorage` returns the same instance for the same id.
|
|
return [...new Set(result).values()];
|
|
}
|
|
async traverseHistory(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
if (!context) {
|
|
throw new protocol_js_1.InvalidArgumentException(`No browsing context with id ${params.context}`);
|
|
}
|
|
if (!context.isTopLevelContext()) {
|
|
throw new protocol_js_1.InvalidArgumentException('Traversing history is only supported on the top-level context');
|
|
}
|
|
await context.traverseHistory(params.delta);
|
|
return {};
|
|
}
|
|
async handleUserPrompt(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
try {
|
|
await context.handleUserPrompt(params.accept, params.userText);
|
|
}
|
|
catch (error) {
|
|
// Heuristically determine the error
|
|
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/page_handler.cc;l=1085?q=%22No%20dialog%20is%20showing%22&ss=chromium
|
|
if (error.message?.includes('No dialog is showing')) {
|
|
throw new protocol_js_1.NoSuchAlertException('No dialog is showing');
|
|
}
|
|
throw error;
|
|
}
|
|
return {};
|
|
}
|
|
async close(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
if (!context.isTopLevelContext()) {
|
|
throw new protocol_js_1.InvalidArgumentException(`Non top-level browsing context ${context.id} cannot be closed.`);
|
|
}
|
|
// Parent session of a page target session can be a `browser` or a `tab` session.
|
|
const parentCdpClient = context.cdpTarget.parentCdpClient;
|
|
try {
|
|
const detachedFromTargetPromise = new Promise((resolve) => {
|
|
const onContextDestroyed = (event) => {
|
|
if (event.targetId === params.context) {
|
|
parentCdpClient.off('Target.detachedFromTarget', onContextDestroyed);
|
|
resolve();
|
|
}
|
|
};
|
|
parentCdpClient.on('Target.detachedFromTarget', onContextDestroyed);
|
|
});
|
|
try {
|
|
if (params.promptUnload) {
|
|
await context.close();
|
|
}
|
|
else {
|
|
await parentCdpClient.sendCommand('Target.closeTarget', {
|
|
targetId: params.context,
|
|
});
|
|
}
|
|
}
|
|
catch (error) {
|
|
// Swallow error that arise from the session being destroyed. Rely on the
|
|
// `detachedFromTargetPromise` event to be resolved.
|
|
if (!parentCdpClient.isCloseError(error)) {
|
|
throw error;
|
|
}
|
|
}
|
|
// Sometimes CDP command finishes before `detachedFromTarget` event,
|
|
// sometimes after. Wait for the CDP command to be finished, and then wait
|
|
// for `detachedFromTarget` if it hasn't emitted.
|
|
await detachedFromTargetPromise;
|
|
}
|
|
catch (error) {
|
|
// Swallow error that arise from the page being destroyed
|
|
// Example is navigating to faulty SSL certificate
|
|
if (!(error.code === -32000 /* CdpErrorConstants.GENERIC_ERROR */ &&
|
|
error.message === 'Not attached to an active page')) {
|
|
throw error;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
async locateNodes(params) {
|
|
const context = this.#browsingContextStorage.getContext(params.context);
|
|
return await context.locateNodes(params);
|
|
}
|
|
#onContextCreatedSubscribeHook(contextId) {
|
|
const context = this.#browsingContextStorage.getContext(contextId);
|
|
const contextsToReport = [
|
|
context,
|
|
...this.#browsingContextStorage.getContext(contextId).allChildren,
|
|
];
|
|
contextsToReport.forEach((context) => {
|
|
this.#eventManager.registerEvent({
|
|
type: 'event',
|
|
method: protocol_js_1.ChromiumBidi.BrowsingContext.EventNames.ContextCreated,
|
|
params: context.serializeToBidiValue(),
|
|
}, context.id);
|
|
});
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
exports.BrowsingContextProcessor = BrowsingContextProcessor;
|
|
//# sourceMappingURL=BrowsingContextProcessor.js.map |