blob: 37ddf405ed140345e6a6ed9d69d775526050ea05 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @unrestricted
33 */
34Resources.IDBDatabaseView = class extends UI.VBox {
35 /**
36 * @param {!Resources.IndexedDBModel} model
37 * @param {?Resources.IndexedDBModel.Database} database
38 */
39 constructor(model, database) {
40 super();
41
42 this._model = model;
43 const databaseName = database ? database.databaseId.name : Common.UIString('Loading\u2026');
44
45 this._reportView = new UI.ReportView(databaseName);
46 this._reportView.show(this.contentElement);
47
48 const bodySection = this._reportView.appendSection('');
49 this._securityOriginElement = bodySection.appendField(Common.UIString('Security origin'));
50 this._versionElement = bodySection.appendField(Common.UIString('Version'));
51
52 const footer = this._reportView.appendSection('').appendRow();
53 this._clearButton = UI.createTextButton(
54 Common.UIString('Delete database'), () => this._deleteDatabase(), Common.UIString('Delete database'));
55 footer.appendChild(this._clearButton);
56
57 this._refreshButton = UI.createTextButton(
58 Common.UIString('Refresh database'), () => this._refreshDatabaseButtonClicked(),
59 Common.UIString('Refresh database'));
60 footer.appendChild(this._refreshButton);
61
62 if (database)
63 this.update(database);
64 }
65
66 _refreshDatabase() {
67 this._securityOriginElement.textContent = this._database.databaseId.securityOrigin;
68 this._versionElement.textContent = this._database.version;
69 }
70
71 _refreshDatabaseButtonClicked() {
72 this._model.refreshDatabase(this._database.databaseId);
73 }
74
75 /**
76 * @param {!Resources.IndexedDBModel.Database} database
77 */
78 update(database) {
79 this._database = database;
80 this._reportView.setTitle(this._database.databaseId.name);
81 this._refreshDatabase();
82 this._updatedForTests();
83 }
84
85 _updatedForTests() {
86 // Sniffed in tests.
87 }
88
89 async _deleteDatabase() {
90 const ok = await UI.ConfirmDialog.show(
91 Common.UIString('Please confirm delete of "%s" database.', this._database.databaseId.name), this.element);
92 if (ok)
93 this._model.deleteDatabase(this._database.databaseId);
94 }
95};
96
97/**
98 * @unrestricted
99 */
100Resources.IDBDataView = class extends UI.SimpleView {
101 /**
102 * @param {!Resources.IndexedDBModel} model
103 * @param {!Resources.IndexedDBModel.DatabaseId} databaseId
104 * @param {!Resources.IndexedDBModel.ObjectStore} objectStore
105 * @param {?Resources.IndexedDBModel.Index} index
106 * @param {function()} refreshObjectStoreCallback
107 */
108 constructor(model, databaseId, objectStore, index, refreshObjectStoreCallback) {
109 super(Common.UIString('IDB'));
110 this.registerRequiredCSS('resources/indexedDBViews.css');
111
112 this._model = model;
113 this._databaseId = databaseId;
114 this._isIndex = !!index;
115 this._refreshObjectStoreCallback = refreshObjectStoreCallback;
116
117 this.element.classList.add('indexed-db-data-view', 'storage-view');
118
119 this._refreshButton = new UI.ToolbarButton(Common.UIString('Refresh'), 'largeicon-refresh');
120 this._refreshButton.addEventListener(UI.ToolbarButton.Events.Click, this._refreshButtonClicked, this);
121
122 this._deleteSelectedButton = new UI.ToolbarButton(Common.UIString('Delete selected'), 'largeicon-delete');
123 this._deleteSelectedButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._deleteButtonClicked(null));
124
125 this._clearButton = new UI.ToolbarButton(Common.UIString('Clear object store'), 'largeicon-clear');
126 this._clearButton.addEventListener(UI.ToolbarButton.Events.Click, this._clearButtonClicked, this);
127
128 this._needsRefresh = new UI.ToolbarItem(UI.createLabel(Common.UIString('Data may be stale'), 'smallicon-warning'));
129 this._needsRefresh.setVisible(false);
130 this._needsRefresh.setTitle(Common.UIString('Some entries may have been modified'));
131
132 this._createEditorToolbar();
133
134 this._pageSize = 50;
135 this._skipCount = 0;
Harley Li2397c022019-02-15 22:54:12136 /** @type {?number} */
137 this._keyGeneratorValue = null;
Blink Reformat4c46d092018-04-07 15:32:37138
139 this.update(objectStore, index);
140 this._entries = [];
141 }
142
143 /**
144 * @return {!DataGrid.DataGrid}
145 */
146 _createDataGrid() {
147 const keyPath = this._isIndex ? this._index.keyPath : this._objectStore.keyPath;
148
149 const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([]);
150 columns.push({id: 'number', title: Common.UIString('#'), sortable: false, width: '50px'});
151 columns.push(
152 {id: 'key', titleDOMFragment: this._keyColumnHeaderFragment(Common.UIString('Key'), keyPath), sortable: false});
153 if (this._isIndex) {
154 columns.push({
155 id: 'primaryKey',
156 titleDOMFragment: this._keyColumnHeaderFragment(Common.UIString('Primary key'), this._objectStore.keyPath),
157 sortable: false
158 });
159 }
160 columns.push({id: 'value', title: Common.UIString('Value'), sortable: false});
161
162 const dataGrid = new DataGrid.DataGrid(
163 columns, undefined, this._deleteButtonClicked.bind(this), this._updateData.bind(this, true));
164 dataGrid.setStriped(true);
165 dataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, event => this._updateToolbarEnablement(), this);
166 return dataGrid;
167 }
168
169 /**
170 * @param {string} prefix
171 * @param {*} keyPath
172 * @return {!DocumentFragment}
173 */
174 _keyColumnHeaderFragment(prefix, keyPath) {
175 const keyColumnHeaderFragment = createDocumentFragment();
176 keyColumnHeaderFragment.createTextChild(prefix);
177 if (keyPath === null)
178 return keyColumnHeaderFragment;
179
180 keyColumnHeaderFragment.createTextChild(' (' + Common.UIString('Key path: '));
181 if (Array.isArray(keyPath)) {
182 keyColumnHeaderFragment.createTextChild('[');
183 for (let i = 0; i < keyPath.length; ++i) {
184 if (i !== 0)
185 keyColumnHeaderFragment.createTextChild(', ');
186 keyColumnHeaderFragment.appendChild(this._keyPathStringFragment(keyPath[i]));
187 }
188 keyColumnHeaderFragment.createTextChild(']');
189 } else {
190 const keyPathString = /** @type {string} */ (keyPath);
191 keyColumnHeaderFragment.appendChild(this._keyPathStringFragment(keyPathString));
192 }
193 keyColumnHeaderFragment.createTextChild(')');
194 return keyColumnHeaderFragment;
195 }
196
197 /**
198 * @param {string} keyPathString
199 * @return {!DocumentFragment}
200 */
201 _keyPathStringFragment(keyPathString) {
202 const keyPathStringFragment = createDocumentFragment();
203 keyPathStringFragment.createTextChild('"');
204 const keyPathSpan = keyPathStringFragment.createChild('span', 'source-code indexed-db-key-path');
205 keyPathSpan.textContent = keyPathString;
206 keyPathStringFragment.createTextChild('"');
207 return keyPathStringFragment;
208 }
209
210 _createEditorToolbar() {
211 const editorToolbar = new UI.Toolbar('data-view-toolbar', this.element);
212
213 editorToolbar.appendToolbarItem(this._refreshButton);
Blink Reformat4c46d092018-04-07 15:32:37214
215 editorToolbar.appendToolbarItem(new UI.ToolbarSeparator());
216
217 this._pageBackButton = new UI.ToolbarButton(Common.UIString('Show previous page'), 'largeicon-play-back');
218 this._pageBackButton.addEventListener(UI.ToolbarButton.Events.Click, this._pageBackButtonClicked, this);
219 editorToolbar.appendToolbarItem(this._pageBackButton);
220
221 this._pageForwardButton = new UI.ToolbarButton(Common.UIString('Show next page'), 'largeicon-play');
222 this._pageForwardButton.setEnabled(false);
223 this._pageForwardButton.addEventListener(UI.ToolbarButton.Events.Click, this._pageForwardButtonClicked, this);
224 editorToolbar.appendToolbarItem(this._pageForwardButton);
225
Erik Luoe6a74642018-10-26 22:03:54226 this._keyInput = new UI.ToolbarInput(ls`Start from key`, 0.5);
227 this._keyInput.addEventListener(UI.ToolbarInput.Event.TextChanged, this._updateData.bind(this, false));
228 editorToolbar.appendToolbarItem(this._keyInput);
Harley Lif0f328e2018-12-15 01:49:47229 editorToolbar.appendToolbarItem(new UI.ToolbarSeparator());
230 editorToolbar.appendToolbarItem(this._clearButton);
231 editorToolbar.appendToolbarItem(this._deleteSelectedButton);
Blink Reformat4c46d092018-04-07 15:32:37232
233 editorToolbar.appendToolbarItem(this._needsRefresh);
234 }
235
236 /**
237 * @param {!Common.Event} event
238 */
239 _pageBackButtonClicked(event) {
240 this._skipCount = Math.max(0, this._skipCount - this._pageSize);
241 this._updateData(false);
242 }
243
244 /**
245 * @param {!Common.Event} event
246 */
247 _pageForwardButtonClicked(event) {
248 this._skipCount = this._skipCount + this._pageSize;
249 this._updateData(false);
250 }
251
Blink Reformat4c46d092018-04-07 15:32:37252 refreshData() {
253 this._updateData(true);
254 }
255
256 /**
257 * @param {!Resources.IndexedDBModel.ObjectStore} objectStore
258 * @param {?Resources.IndexedDBModel.Index} index
259 */
260 update(objectStore, index) {
261 this._objectStore = objectStore;
262 this._index = index;
263
264 if (this._dataGrid)
265 this._dataGrid.asWidget().detach();
266 this._dataGrid = this._createDataGrid();
267 this._dataGrid.asWidget().show(this.element);
268
269 this._skipCount = 0;
270 this._updateData(true);
271 }
272
273 /**
274 * @param {string} keyString
275 */
276 _parseKey(keyString) {
277 let result;
278 try {
279 result = JSON.parse(keyString);
280 } catch (e) {
281 result = keyString;
282 }
283 return result;
284 }
285
286 /**
287 * @param {boolean} force
288 */
289 _updateData(force) {
Erik Luoe6a74642018-10-26 22:03:54290 const key = this._parseKey(this._keyInput.value());
Blink Reformat4c46d092018-04-07 15:32:37291 const pageSize = this._pageSize;
292 let skipCount = this._skipCount;
293 let selected = this._dataGrid.selectedNode ? this._dataGrid.selectedNode.data['number'] : 0;
294 selected = Math.max(selected, this._skipCount); // Page forward should select top entry
295 this._refreshButton.setEnabled(false);
296 this._clearButton.setEnabled(!this._isIndex);
297
298 if (!force && this._lastKey === key && this._lastPageSize === pageSize && this._lastSkipCount === skipCount)
299 return;
300
301 if (this._lastKey !== key || this._lastPageSize !== pageSize) {
302 skipCount = 0;
303 this._skipCount = 0;
304 }
305 this._lastKey = key;
306 this._lastPageSize = pageSize;
307 this._lastSkipCount = skipCount;
308
309 /**
310 * @param {!Array.<!Resources.IndexedDBModel.Entry>} entries
311 * @param {boolean} hasMore
312 * @this {Resources.IDBDataView}
313 */
314 function callback(entries, hasMore) {
315 this._refreshButton.setEnabled(true);
316 this.clear();
317 this._entries = entries;
318 let selectedNode = null;
319 for (let i = 0; i < entries.length; ++i) {
320 const data = {};
321 data['number'] = i + skipCount;
322 data['key'] = entries[i].key;
323 data['primaryKey'] = entries[i].primaryKey;
324 data['value'] = entries[i].value;
325
326 const node = new Resources.IDBDataGridNode(data);
327 this._dataGrid.rootNode().appendChild(node);
328 if (data['number'] <= selected)
329 selectedNode = node;
330 }
331
332 if (selectedNode)
333 selectedNode.select();
334 this._pageBackButton.setEnabled(!!skipCount);
335 this._pageForwardButton.setEnabled(hasMore);
336 this._needsRefresh.setVisible(false);
337 this._updateToolbarEnablement();
338 this._updatedDataForTests();
339 }
340
Harley Li2397c022019-02-15 22:54:12341 /**
342 * @param {?number} number
343 * @this {Resources.IDBDataView}
344 */
345 function callbackKeyGeneratorValue(number) {
346 this._keyGeneratorValue = number;
347 this._updateSummaryBar();
348 }
349
Blink Reformat4c46d092018-04-07 15:32:37350 const idbKeyRange = key ? window.IDBKeyRange.lowerBound(key) : null;
351 if (this._isIndex) {
352 this._model.loadIndexData(
353 this._databaseId, this._objectStore.name, this._index.name, idbKeyRange, skipCount, pageSize,
354 callback.bind(this));
355 } else {
356 this._model.loadObjectStoreData(
357 this._databaseId, this._objectStore.name, idbKeyRange, skipCount, pageSize, callback.bind(this));
358 }
Harley Li2397c022019-02-15 22:54:12359 this._model.getKeyGeneratorValue(this._databaseId, this._objectStore).then(callbackKeyGeneratorValue.bind(this));
360 this._updateSummaryBar();
361 }
362
363 _updateSummaryBar() {
364 if (this._keyGeneratorValue === null)
365 return;
366 if (!this._summaryBarElement)
367 this._summaryBarElement = this.element.createChild('div', 'object-store-summary-bar');
368 this._summaryBarElement.removeChildren();
369 const span = this._summaryBarElement.createChild('span');
370 span.textContent = ls`key generator value: ` + String(this._keyGeneratorValue);
Blink Reformat4c46d092018-04-07 15:32:37371 }
372
373 _updatedDataForTests() {
374 // Sniffed in tests.
375 }
376
377 /**
378 * @param {?Common.Event} event
379 */
380 _refreshButtonClicked(event) {
381 this._updateData(true);
382 }
383
384 /**
385 * @param {!Common.Event} event
386 */
387 async _clearButtonClicked(event) {
388 this._clearButton.setEnabled(false);
389 await this._model.clearObjectStore(this._databaseId, this._objectStore.name);
390 this._clearButton.setEnabled(true);
391 this._updateData(true);
392 }
393
394 markNeedsRefresh() {
395 this._needsRefresh.setVisible(true);
396 }
397
398 /**
399 * @param {?DataGrid.DataGridNode} node
400 */
401 async _deleteButtonClicked(node) {
402 if (!node) {
403 node = this._dataGrid.selectedNode;
404 if (!node)
405 return;
406 }
407 const key = /** @type {!SDK.RemoteObject} */ (this._isIndex ? node.data.primaryKey : node.data.key);
408 const keyValue = /** @type {!Array<?>|!Date|number|string} */ (key.value);
409 await this._model.deleteEntries(this._databaseId, this._objectStore.name, window.IDBKeyRange.only(keyValue));
410 this._refreshObjectStoreCallback();
411 }
412
413 clear() {
414 this._dataGrid.rootNode().removeChildren();
415 this._entries = [];
416 }
417
418 _updateToolbarEnablement() {
419 const empty = !this._dataGrid || this._dataGrid.rootNode().children.length === 0;
420 this._clearButton.setEnabled(!empty);
421 this._deleteSelectedButton.setEnabled(!empty && this._dataGrid.selectedNode !== null);
422 }
423};
424
425/**
426 * @unrestricted
427 */
428Resources.IDBDataGridNode = class extends DataGrid.DataGridNode {
429 /**
430 * @param {!Object.<string, *>} data
431 */
432 constructor(data) {
433 super(data, false);
434 this.selectable = true;
435 }
436
437 /**
438 * @override
439 * @return {!Element}
440 */
441 createCell(columnIdentifier) {
442 const cell = super.createCell(columnIdentifier);
443 const value = /** @type {!SDK.RemoteObject} */ (this.data[columnIdentifier]);
444
445 switch (columnIdentifier) {
446 case 'value':
447 case 'key':
448 case 'primaryKey':
449 cell.removeChildren();
450 const objectElement = ObjectUI.ObjectPropertiesSection.defaultObjectPresentation(value, undefined, true);
451 cell.appendChild(objectElement);
452 break;
453 default:
454 }
455
456 return cell;
457 }
458};