chrome-devtools-frontend
Version:
Chrome DevTools UI
113 lines (102 loc) • 4.88 kB
text/typescript
// 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 SDK from '../../../core/sdk/sdk.js';
import {areOriginsEquivalent, extractContextOrigin} from '../AiOrigins.js';
/**
* Maximum number of target origins allowed in a single batch query to bound
* model iteration, memory usage, and CDP traffic.
*/
export const MAX_TARGET_ORIGINS = 100;
/**
* Resolves active DOM storage partitions from all DOMStorageModel instances
* matching origin constraints under the primary page target tree.
*
* In Chromium, storage is partitioned by StorageKey (origin + top-level site
* context). A single origin can have multiple distinct storage partitions
* (e.g. first-party page vs embedded third-party iframe). Furthermore, DevTools
* creates a separate DOMStorageModel per frame subtarget.
*
* This function:
* 1. Enables DOMStorageModel on-demand for subtargets in the primary page tree.
* 2. Deduplicates partitions across multiple subtargets sharing the same storageKey.
* 3. Preserves distinct partition keys for the same origin unless a specific storageKey is requested.
*
* @param origin The partition origin to match.
* @param type The DOM storage type ('localStorage' or 'sessionStorage') to filter for.
* @param targetManager The TargetManager instance to query for DOMStorageModels.
* @param primaryPageTarget The primary page target to ensure target-tree containment.
* @param storageKey Optional. If specified, resolves only the partition matching this unique key.
* @returns Array of unique DOMStorage partition handles matching constraints.
*/
export function resolveDOMStorages(
origin: string,
type: 'localStorage'|'sessionStorage',
targetManager: SDK.TargetManager.TargetManager,
primaryPageTarget: SDK.Target.Target,
storageKey?: string,
): SDK.DOMStorageModel.DOMStorage[] {
const resolvedStorages: SDK.DOMStorageModel.DOMStorage[] = [];
const seenStorageKeys = new Set<string>();
const isLocalStorage = type === 'localStorage';
const targetOrigin = extractContextOrigin(origin);
const domStorageModels = targetManager.models(SDK.DOMStorageModel.DOMStorageModel);
for (const domStorageModel of domStorageModels) {
// Scope models strictly to the primary page target tree (including subframes and OOPIFs)
// while ignoring unrelated targets like service workers, extensions, or other tabs.
if (domStorageModel.target().outermostTarget() !== primaryPageTarget) {
continue;
}
// DOMStorageModel has autostart: false and only populates its storages map when enabled.
// Ensure the model is enabled on-demand at the moment storage access is requested.
domStorageModel.enable();
for (const storage of domStorageModel.storages()) {
if (storage.isLocalStorage !== isLocalStorage) {
continue;
}
const currentStorageKey = storage.storageKey;
// In multi-frame or subtarget pages, multiple DOMStorageModel instances can
// report identical storage keys for the same underlying partition. Deduplicate
// by storageKey to prevent duplicate partition handles across models (which
// would cause double-counting storage usage and duplicate CDP requests).
if (!currentStorageKey || seenStorageKeys.has(currentStorageKey)) {
continue;
}
seenStorageKeys.add(currentStorageKey);
// If storageKey is specified, only match that exact partition key.
if (storageKey && storageKey !== currentStorageKey) {
continue;
}
// Parse the serialized StorageKey (e.g. "https://example.com/^0https://top.com") to
// extract the partition's origin for equivalence comparison.
const parsedKey = SDK.StorageKeyManager.parseStorageKey(currentStorageKey);
if (areOriginsEquivalent(parsedKey.origin, targetOrigin)) {
resolvedStorages.push(storage);
}
}
}
return resolvedStorages;
}
/**
* Calculates the total size in bytes of all key-value entries across the provided
* DOMStorage partitions.
*
* Transient CDP failures (e.g. target detachment during frame navigation) are caught
* gracefully to prevent failing the entire storage calculation.
*
* @param storages Array of DOMStorage partition handles to inspect.
* @returns Total storage size in bytes.
*/
export async function calculateDOMStoragesUsage(storages: SDK.DOMStorageModel.DOMStorage[]): Promise<number> {
const itemBatches = await Promise.all(storages.map(storage => storage.getItems().catch(() => null)));
let totalBytes = 0;
for (const items of itemBatches) {
if (items) {
for (const [key, value] of items) {
// UTF-16 encoded strings use 2 bytes per character.
totalBytes += (key.length + value.length) * 2;
}
}
}
return totalBytes;
}