blob: 1a55c198b62637386bdaa7676862af91a1174368 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Resources.ServiceWorkerCacheView = class extends UI.SimpleView {
/**
* @param {!SDK.ServiceWorkerCacheModel} model
* @param {!SDK.ServiceWorkerCacheModel.Cache} cache
*/
constructor(model, cache) {
super(Common.UIString('Cache'));
this.registerRequiredCSS('resources/serviceWorkerCacheViews.css');
this._model = model;
this._entriesForTest = null;
this.element.classList.add('service-worker-cache-data-view');
this.element.classList.add('storage-view');
const editorToolbar = new UI.Toolbar('data-view-toolbar', this.element);
this._splitWidget = new UI.SplitWidget(false, false);
this._splitWidget.show(this.element);
this._previewPanel = new UI.VBox();
const resizer = this._previewPanel.element.createChild('div', 'cache-preview-panel-resizer');
this._splitWidget.setMainWidget(this._previewPanel);
this._splitWidget.installResizer(resizer);
/** @type {?UI.Widget} */
this._preview = null;
this._cache = cache;
/** @type {?DataGrid.DataGrid} */
this._dataGrid = null;
this._refreshThrottler = new Common.Throttler(300);
this._refreshButton = new UI.ToolbarButton(Common.UIString('Refresh'), 'largeicon-refresh');
this._refreshButton.addEventListener(UI.ToolbarButton.Events.Click, this._refreshButtonClicked, this);
editorToolbar.appendToolbarItem(this._refreshButton);
this._deleteSelectedButton = new UI.ToolbarButton(Common.UIString('Delete Selected'), 'largeicon-delete');
this._deleteSelectedButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._deleteButtonClicked(null));
editorToolbar.appendToolbarItem(this._deleteSelectedButton);
const entryPathFilterBox = new UI.ToolbarInput(ls`Filter by Path`, '', 1);
editorToolbar.appendToolbarItem(entryPathFilterBox);
const entryPathFilterThrottler = new Common.Throttler(300);
this._entryPathFilter = '';
entryPathFilterBox.addEventListener(UI.ToolbarInput.Event.TextChanged, () => {
entryPathFilterThrottler.schedule(() => {
this._entryPathFilter = entryPathFilterBox.value();
return this._updateData(true);
});
});
this._returnCount = /** @type {?number} */ (null);
this._summaryBarElement = /** @type {?Element} */ (null);
this._loadingPromise = /** @type {?Promise} */ (null);
this.update(cache);
}
_resetDataGrid() {
if (this._dataGrid) {
this._dataGrid.asWidget().detach();
}
this._dataGrid = this._createDataGrid();
const dataGridWidget = this._dataGrid.asWidget();
this._splitWidget.setSidebarWidget(dataGridWidget);
dataGridWidget.setMinimumSize(0, 250);
}
/**
* @override
*/
wasShown() {
this._model.addEventListener(
SDK.ServiceWorkerCacheModel.Events.CacheStorageContentUpdated, this._cacheContentUpdated, this);
this._updateData(true);
}
/**
* @override
*/
willHide() {
this._model.removeEventListener(
SDK.ServiceWorkerCacheModel.Events.CacheStorageContentUpdated, this._cacheContentUpdated, this);
}
/**
* @param {?UI.Widget} preview
*/
_showPreview(preview) {
if (preview && this._preview === preview) {
return;
}
if (this._preview) {
this._preview.detach();
}
if (!preview) {
preview = new UI.EmptyWidget(Common.UIString('Select a cache entry above to preview'));
}
this._preview = preview;
this._preview.show(this._previewPanel.element);
}
/**
* @return {!DataGrid.DataGrid}
*/
_createDataGrid() {
const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
{id: 'number', title: '#', sortable: false, width: '3px'},
{id: 'name', title: Common.UIString('Name'), weight: 4, sortable: true},
{id: 'responseType', title: ls`Response-Type`, weight: 1, align: DataGrid.DataGrid.Align.Right, sortable: true},
{id: 'contentType', title: Common.UIString('Content-Type'), weight: 1, sortable: true}, {
id: 'contentLength',
title: Common.UIString('Content-Length'),
weight: 1,
align: DataGrid.DataGrid.Align.Right,
sortable: true
},
{
id: 'responseTime',
title: Common.UIString('Time Cached'),
width: '12em',
weight: 1,
align: DataGrid.DataGrid.Align.Right,
sortable: true
}
]);
const dataGrid = new DataGrid.DataGrid(
columns, undefined, this._deleteButtonClicked.bind(this), this._updateData.bind(this, true));
dataGrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this._sortingChanged, this);
dataGrid.addEventListener(
DataGrid.DataGrid.Events.SelectedNode, event => this._previewCachedResponse(event.data.data), this);
dataGrid.setStriped(true);
return dataGrid;
}
_sortingChanged() {
if (!this._dataGrid) {
return;
}
const accending = this._dataGrid.isSortOrderAscending();
const columnId = this._dataGrid.sortColumnId();
let comparator;
if (columnId === 'name') {
comparator = (a, b) => a._name.localeCompare(b._name);
} else if (columnId === 'contentType') {
comparator = (a, b) => a.data.mimeType.localeCompare(b.data.mimeType);
} else if (columnId === 'contentLength') {
comparator = (a, b) => a.data.resourceSize - b.data.resourceSize;
} else if (columnId === 'responseTime') {
comparator = (a, b) => a.data.endTime - b.data.endTime;
} else if (columnId === 'responseType') {
comparator = (a, b) => a._responseType.localeCompare(b._responseType);
}
const children = this._dataGrid.rootNode().children.slice();
this._dataGrid.rootNode().removeChildren();
children.sort((a, b) => {
const result = comparator(a, b);
return accending ? result : -result;
});
children.forEach(child => this._dataGrid.rootNode().appendChild(child));
}
/**
* @param {?DataGrid.DataGridNode} node
*/
async _deleteButtonClicked(node) {
if (!node) {
node = this._dataGrid && this._dataGrid.selectedNode;
if (!node) {
return;
}
}
await this._model.deleteCacheEntry(this._cache, /** @type {string} */ (node.data.url()));
node.remove();
}
/**
* @param {!SDK.ServiceWorkerCacheModel.Cache} cache
*/
update(cache) {
this._cache = cache;
this._resetDataGrid();
this._updateData(true);
}
_updateSummaryBar() {
if (!this._summaryBarElement) {
this._summaryBarElement = this.element.createChild('div', 'cache-storage-summary-bar');
}
this._summaryBarElement.removeChildren();
const span = this._summaryBarElement.createChild('span');
if (this._entryPathFilter) {
span.textContent = ls`Matching entries: ${this._returnCount}`;
} else {
span.textContent = ls`Total entries: ${this._returnCount}`;
}
}
/**
* @param {number} skipCount
* @param {!Array<!Protocol.CacheStorage.DataEntry>} entries
* @param {number} returnCount
* @this {Resources.ServiceWorkerCacheView}
*/
_updateDataCallback(skipCount, entries, returnCount) {
const selected = this._dataGrid.selectedNode && this._dataGrid.selectedNode.data.url();
this._refreshButton.setEnabled(true);
this._entriesForTest = entries;
this._returnCount = returnCount;
this._updateSummaryBar();
/** @type {!Map<string, !DataGrid.DataGridNode>} */
const oldEntries = new Map();
const rootNode = this._dataGrid.rootNode();
for (const node of rootNode.children) {
oldEntries.set(node.data.url, node);
}
rootNode.removeChildren();
let selectedNode = null;
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
let node = oldEntries.get(entry.requestURL);
if (!node || node.data.responseTime !== entry.responseTime) {
node = new Resources.ServiceWorkerCacheView.DataGridNode(i, this._createRequest(entry), entry.responseType);
node.selectable = true;
} else {
node.data.number = i;
}
rootNode.appendChild(node);
if (entry.requestURL === selected) {
selectedNode = node;
}
}
if (!selectedNode) {
this._showPreview(null);
} else {
selectedNode.revealAndSelect();
}
this._updatedForTest();
}
/**
* @param {boolean} force
*/
async _updateData(force) {
if (!force && this._loadingPromise) {
return this._loadingPromise;
}
this._refreshButton.setEnabled(false);
if (this._loadingPromise) {
return this._loadingPromise;
}
this._loadingPromise = new Promise(resolve => {
this._model.loadAllCacheData(this._cache, this._entryPathFilter, (entries, returnCount) => {
resolve([entries, returnCount]);
});
});
const [entries, returnCount] = await this._loadingPromise;
this._updateDataCallback(0, entries, returnCount);
this._loadingPromise = null;
}
/**
* @param {!Common.Event} event
*/
_refreshButtonClicked(event) {
this._updateData(true);
}
/**
* @param {!Common.Event} event
*/
_cacheContentUpdated(event) {
const nameAndOrigin = event.data;
if (this._cache.securityOrigin !== nameAndOrigin.origin || this._cache.cacheName !== nameAndOrigin.cacheName) {
return;
}
this._refreshThrottler.schedule(() => Promise.resolve(this._updateData(true)), true);
}
/**
* @param {!SDK.NetworkRequest} request
*/
async _previewCachedResponse(request) {
let preview = request[Resources.ServiceWorkerCacheView._previewSymbol];
if (!preview) {
preview = new Resources.ServiceWorkerCacheView.RequestView(request);
request[Resources.ServiceWorkerCacheView._previewSymbol] = preview;
}
// It is possible that table selection changes before the preview opens.
if (request === this._dataGrid.selectedNode.data) {
this._showPreview(preview);
}
}
/**
* @param {!Protocol.CacheStorage.DataEntry} entry
* @return {!SDK.NetworkRequest}
*/
_createRequest(entry) {
const request = new SDK.NetworkRequest('cache-storage-' + entry.requestURL, entry.requestURL, '', '', '', null);
request.requestMethod = entry.requestMethod;
request.setRequestHeaders(entry.requestHeaders);
request.statusCode = entry.responseStatus;
request.statusText = entry.responseStatusText;
request.protocol = new Common.ParsedURL(entry.requestURL).scheme;
request.responseHeaders = entry.responseHeaders;
request.setRequestHeadersText('');
request.endTime = entry.responseTime;
let header = entry.responseHeaders.find(header => header.name.toLowerCase() === 'content-type');
const contentType = header ? header.value : 'text/plain';
request.mimeType = contentType;
header = entry.responseHeaders.find(header => header.name.toLowerCase() === 'content-length');
request.resourceSize = (header && header.value) | 0;
let resourceType = Common.ResourceType.fromMimeType(contentType);
if (!resourceType) {
resourceType = Common.ResourceType.fromURL(entry.requestURL) || Common.resourceTypes.Other;
}
request.setResourceType(resourceType);
request.setContentDataProvider(this._requestContent.bind(this, request));
return request;
}
/**
* @param {!SDK.NetworkRequest} request
* @return {!Promise<!SDK.NetworkRequest.ContentData>}
*/
async _requestContent(request) {
const isText = request.resourceType().isTextType();
const contentData = {error: null, content: null, encoded: !isText};
const response = await this._cache.requestCachedResponse(request.url(), request.requestHeaders());
if (response) {
contentData.content = isText ? window.atob(response.body) : response.body;
}
return contentData;
}
_updatedForTest() {
}
};
Resources.ServiceWorkerCacheView._previewSymbol = Symbol('preview');
Resources.ServiceWorkerCacheView.DataGridNode = class extends DataGrid.DataGridNode {
/**
* @param {number} number
* @param {!SDK.NetworkRequest} request
* @param {!Protocol.CacheStorage.CachedResponseType} responseType
*/
constructor(number, request, responseType) {
super(request);
this._number = number;
const parsed = new Common.ParsedURL(request.url());
if (parsed.isValid) {
this._name = request.url().trimURL(parsed.domain());
} else {
this._name = request.url();
}
this._request = request;
this._responseType = responseType;
}
/**
* @override
* @param {string} columnId
* @return {!Element}
*/
createCell(columnId) {
const cell = this.createTD(columnId);
let value;
if (columnId === 'number') {
value = String(this._number);
} else if (columnId === 'name') {
value = this._name;
} else if (columnId === 'responseType') {
if (this._responseType === 'opaqueResponse') {
value = 'opaque';
} else if (this._responseType === 'opaqueRedirect') {
value = 'opaqueredirect';
} else {
value = this._responseType;
}
} else if (columnId === 'contentType') {
value = this._request.mimeType;
} else if (columnId === 'contentLength') {
value = (this._request.resourceSize | 0).toLocaleString('en-US');
} else if (columnId === 'responseTime') {
value = new Date(this._request.endTime * 1000).toLocaleString();
}
DataGrid.DataGrid.setElementText(cell, value || '', true);
cell.title = this._request.url();
return cell;
}
};
Resources.ServiceWorkerCacheView.RequestView = class extends UI.VBox {
/**
* @param {!SDK.NetworkRequest} request
*/
constructor(request) {
super();
this._tabbedPane = new UI.TabbedPane();
this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabSelected, this._tabSelected, this);
this._resourceViewTabSetting = Common.settings.createSetting('cacheStorageViewTab', 'preview');
this._tabbedPane.appendTab('headers', Common.UIString('Headers'), new Network.RequestHeadersView(request));
this._tabbedPane.appendTab('preview', Common.UIString('Preview'), new Network.RequestPreviewView(request));
this._tabbedPane.show(this.element);
}
/**
* @override
*/
wasShown() {
super.wasShown();
this._selectTab();
}
/**
* @param {string=} tabId
*/
_selectTab(tabId) {
if (!tabId) {
tabId = this._resourceViewTabSetting.get();
}
if (!this._tabbedPane.selectTab(tabId)) {
this._tabbedPane.selectTab('headers');
}
}
_tabSelected(event) {
if (!event.data.isUserGesture) {
return;
}
this._resourceViewTabSetting.set(event.data.tabId);
}
};