UNPKG

chrome-devtools-frontend

Version:
143 lines (123 loc) 5.29 kB
// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../../core/common/common.js'; import * as Host from '../../../core/host/host.js'; import * as i18n from '../../../core/i18n/i18n.js'; import * as SDK from '../../../core/sdk/sdk.js'; import {areOriginsEquivalent, extractContextOrigin, isOpaqueOrigin} from '../AiOrigins.js'; import {MAX_TARGET_ORIGINS, resolveDOMStorages} from './DOMStorageUtils.js'; import { type BaseToolCapability, type DataHandlerResult, type DataTool, type OriginLockCapability, type ServerLoggingCapability, ToolAnnotation, type ToolArgs, ToolName, } from './Tool.js'; const lockedString = i18n.i18n.lockedString; export interface ListStorageKeysArgs extends ToolArgs { type: 'localStorage'|'sessionStorage'; origins: string[]; storageKey?: string; } export interface ListStorageKeysResult { storageKeysByOrigin: Record<string, { partitions: Array<{ storageKey: string, keys: string[], }>, }>; } export class ListStorageKeysTool implements DataTool<ListStorageKeysArgs, ListStorageKeysResult, BaseToolCapability&OriginLockCapability&ServerLoggingCapability> { readonly name = ToolName.LIST_STORAGE_KEYS; readonly description = 'Lists all keys for a given storage type for requested origins. Returns keys grouped by storage partition under their origin.'; readonly annotations = [ToolAnnotation.REDACT_FROM_HISTORY]; readonly parameters: Host.AidaClient.FunctionObjectParam<keyof ListStorageKeysArgs> = { type: Host.AidaClient.ParametersTypes.OBJECT, description: '', nullable: false, properties: { type: { type: Host.AidaClient.ParametersTypes.STRING, description: 'Storage type: localStorage or sessionStorage', nullable: false, }, origins: { type: Host.AidaClient.ParametersTypes.ARRAY, description: 'List of origins to list keys for.', items: {type: Host.AidaClient.ParametersTypes.STRING, description: 'An origin URL.'}, nullable: false, }, storageKey: { type: Host.AidaClient.ParametersTypes.STRING, description: 'Optional. Specific storageKey to list keys for. Only applies if single origin is provided.', nullable: true, }, }, required: ['type', 'origins'], }; displayInfoFromArgs(args: ListStorageKeysArgs): { title: string, action: string, } { return { title: lockedString('Reading storage keys'), action: `listStorageKeys('${args.type}', ${JSON.stringify(args.origins)})`, }; } async handler( args: ListStorageKeysArgs, context: BaseToolCapability&OriginLockCapability&ServerLoggingCapability, ): Promise<DataHandlerResult<ListStorageKeysResult>> { context.setLoggingEnabled(false); // eslint-disable-next-line @devtools/no-instance-of-migrated-singletons const targetManager = SDK.TargetManager.TargetManager.instance(); const primaryPageTarget = targetManager.primaryPageTarget(); const allowedOrigin = context.getEstablishedOrigin(); if (!allowedOrigin || isOpaqueOrigin(allowedOrigin)) { return {error: 'No origin available or not allowed.'}; } if (!primaryPageTarget) { return {error: 'No origin available or not allowed.'}; } const pageOrigin = Common.ParsedURL.ParsedURL.extractOrigin(primaryPageTarget.inspectedURL()); if (!pageOrigin || !areOriginsEquivalent(pageOrigin, allowedOrigin)) { return {error: 'No origin available or not allowed.'}; } const rawList = (args.origins && args.origins.length > 0) ? args.origins : [allowedOrigin]; const validOrigins = rawList.map(origin => extractContextOrigin(origin)) .filter(origin => areOriginsEquivalent(origin, allowedOrigin)); const targetOrigins = Array.from(new Set(validOrigins)).slice(0, MAX_TARGET_ORIGINS); if (targetOrigins.length === 0) { return {error: 'No valid origins found.'}; } const storageKey = (targetOrigins.length === 1 && args.storageKey) ? args.storageKey : undefined; const storageKeysByOrigin: ListStorageKeysResult['storageKeysByOrigin'] = {}; await Promise.all(targetOrigins.map(async origin => { const storages = resolveDOMStorages(origin, args.type, targetManager, primaryPageTarget, storageKey); const keyAndItems = await Promise.all(storages.map(async storage => { // Silently catch CDP errors (e.g. target navigation, detachment, or frame crash) // so that transient target loss does not fail the entire batch. const items = await storage.getItems().catch(() => null); return {storageKey: storage.storageKey, items}; })); const partitions = []; for (const {storageKey: partKey, items} of keyAndItems) { if (!items || !partKey) { continue; } const keys = items.map(([key]) => key); if (keys.length > 0) { partitions.push({storageKey: partKey, keys}); } } storageKeysByOrigin[origin] = {partitions}; })); return {result: {storageKeysByOrigin}}; } }