blob: 0876cddec5255cacb4ce89d2484e447600681b82 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://resources/js/jstemplate_compiled.js';
import {assert} from 'chrome://resources/js/assert_ts.js';
import {getRequiredElement} from 'chrome://resources/js/util_ts.js';
import {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
import {Time} from 'chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
import {Origin} from 'chrome://resources/mojo/url/mojom/origin.mojom-webui.js';
import {BucketId} from './bucket_id.mojom-webui.js';
import {IdbTransactionMode, IdbTransactionState} from './indexed_db_bucket_types.mojom-webui.js';
import {IdbInternalsHandler, IdbInternalsHandlerInterface, IdbPartitionMetadata} from './indexed_db_internals.mojom-webui.js';
import {SchemefulSite} from './schemeful_site.mojom-webui.js';
// TODO: This comes from components/flags_ui/resources/flags.ts. It should be
// extracted into a tools/typescript/definitions/jstemplate.d.ts file, and
// include that as part of build_webui()'s ts_definitions, instead of copying it
// here.
declare global {
class JsEvalContext {
constructor(data: any);
}
function jstProcess(context: JsEvalContext, template: HTMLElement): void;
function jstGetTemplate(templateName: string): HTMLElement;
}
// Methods to convert mojo values to strings or to objects with readable
// toString values. Accessible to jstemplate html code.
const stringifyMojo = {
time(mojoTime: Time): Date {
// The JS Date() is based off of the number of milliseconds since
// the UNIX epoch (1970-01-01 00::00:00 UTC), while |internalValue|
// of the base::Time (represented in mojom.Time) represents the
// number of microseconds since the Windows FILETIME epoch
// (1601-01-01 00:00:00 UTC). This computes the final JS time by
// computing the epoch delta and the conversion from microseconds to
// milliseconds.
const windowsEpoch = Date.UTC(1601, 0, 1, 0, 0, 0, 0);
const unixEpoch = Date.UTC(1970, 0, 1, 0, 0, 0, 0);
// |epochDeltaInMs| equals to
// base::Time::kTimeTToMicrosecondsOffset.
const epochDeltaInMs = unixEpoch - windowsEpoch;
const timeInMs = Number(mojoTime.internalValue) / 1000;
return new Date(timeInMs - epochDeltaInMs);
},
string16(mojoString16: String16): string {
return String.fromCharCode(...mojoString16.data);
},
scope(mojoScope: String16[]): string {
return `[${mojoScope.map(s => stringifyMojo.string16(s)).join(', ')}]`;
},
origin(mojoOrigin: Origin): string {
const {scheme, host, port} = mojoOrigin;
const portSuf = (port === 0 ? '' : `:${port}`);
return `${scheme}://${host}${portSuf}`;
},
schemefulSite(mojoSite: SchemefulSite): string {
return stringifyMojo.origin(mojoSite.siteAsOrigin);
},
transactionState(mojoState: IdbTransactionState): string {
switch (mojoState) {
case IdbTransactionState.kBlocked:
return 'Blocked';
case IdbTransactionState.kRunning:
return 'Running';
case IdbTransactionState.kStarted:
return 'Started';
case IdbTransactionState.kCommitting:
return 'Comitting';
case IdbTransactionState.kFinished:
return 'Finished';
}
},
transactionMode(mojoMode: IdbTransactionMode): string {
switch (mojoMode) {
case IdbTransactionMode.kReadOnly:
return 'ReadOnly';
case IdbTransactionMode.kReadWrite:
return 'ReadWrite';
case IdbTransactionMode.kVersionChange:
return 'VersionChange';
}
},
partitionBucketCount(mojoPartition: IdbPartitionMetadata): number {
let count = 0;
mojoPartition.originList.forEach(
origin => origin.storageKeys.forEach(
storageKey => count += storageKey.buckets.length));
return count;
},
};
interface MojomResponse<T> {
error: string|null;
[key: string]: T|string|null;
}
function promisifyMojoResult<T>(
remotePromise: Promise<MojomResponse<T>>,
valueProp: keyof MojomResponse<T>): Promise<T> {
return new Promise((resolve, reject) => {
remotePromise.then((response: MojomResponse<T>) => {
if (response.error !== null) {
reject(response.error);
} else {
resolve(response[valueProp] as T);
}
});
});
}
class IdbInternalsRemote {
private handler: IdbInternalsHandlerInterface =
IdbInternalsHandler.getRemote();
getAllBucketsAcrossAllStorageKeys(): Promise<IdbPartitionMetadata[]> {
return promisifyMojoResult(
this.handler.getAllBucketsAcrossAllStorageKeys(), 'partitions');
}
downloadBucketData(bucketId: BucketId): Promise<bigint> {
return promisifyMojoResult(
this.handler.downloadBucketData(bucketId), 'connectionCount');
}
forceClose(bucketId: BucketId): Promise<bigint> {
return promisifyMojoResult(
this.handler.forceClose(bucketId), 'connectionCount');
}
}
const internalsRemote = new IdbInternalsRemote();
function initialize() {
internalsRemote.getAllBucketsAcrossAllStorageKeys()
.then(onStorageKeysReady)
.catch(errorMsg => console.error(errorMsg));
}
class BucketElement extends HTMLElement {
// this field is filled by the jstemplate annotations in the HTML code
idbBucketId: BucketId;
progressNode: HTMLElement;
connectionCountNode: HTMLElement;
constructor() {
super();
this.addControlListener('.download', internalsRemote.downloadBucketData);
this.addControlListener('.force-close', internalsRemote.forceClose);
this.progressNode = this.getNode('.download-status');
this.connectionCountNode = this.getNode('.connection-count');
}
private getNode(selector: string) {
const controlNode = this.querySelector<HTMLElement>(`${selector}`);
assert(controlNode);
return controlNode;
}
private addControlListener(
selector: string, idbMojoFunc: (id: BucketId) => Promise<bigint>) {
const eventHandler = () => {
// Show loading
this.progressNode.style.display = 'inline';
idbMojoFunc.bind(internalsRemote)(this.idbBucketId)
.then(this.onLoadComplete.bind(this))
.catch(errorMsg => console.error(errorMsg));
};
const control = this.getNode(`.control${selector}`);
control.addEventListener('click', eventHandler);
}
private onLoadComplete(connectionCount: bigint) {
this.progressNode.style.display = 'none';
this.connectionCountNode.innerText = connectionCount.toString();
}
}
function onStorageKeysReady(partitions: IdbPartitionMetadata[]) {
const template = jstGetTemplate('indexeddb-list-template');
getRequiredElement('indexeddb-list').appendChild(template);
jstProcess(
new JsEvalContext({
partitions,
stringifyMojo,
}),
template);
}
customElements.define('indexeddb-bucket', BucketElement);
document.addEventListener('DOMContentLoaded', initialize);