Commit 935ce418 authored by Eugene Ostroukhov's avatar Eugene Ostroukhov Committed by Commit Bot

[DevTools] Hooking up IndexedDB live updating

Frontend for IndexedDB live updating.
Based on https://chromium-review.googlesource.com/676273 by Kristi Park.

Bug: 729793
Change-Id: I8be1c8fafa0377c22eee261b0f00ab62f4bb6cc8
Reviewed-on: https://chromium-review.googlesource.com/798211
Commit-Queue: Eugene Ostroukhov <eostroukhov@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#521737}
parent 30379143
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback1
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback2
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback3
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback4
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback1
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback2
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback3
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback4
Tests that database names are correctly loaded and saved in IndexedDBModel.
Dumping database names:
......
......@@ -18,28 +18,61 @@ Dumping IndexedDB tree:
Index: testIndex
Object store: testObjectStore2
Index: testIndex
Dumping ObjectStore data:
Object store: testObjectStore1
Number of entries: 0
Index: testIndex
Number of entries: 0
Object store: testObjectStore2
Number of entries: 0
Index: testIndex
Number of entries: 0
Added testObjectStore1 entry.
Dumping ObjectStore data:
Object store: testObjectStore1
(no entries)
Number of entries: 0
Index: testIndex
Number of entries: 0
Object store: testObjectStore2
(no entries)
Number of entries: 0
Index: testIndex
Number of entries: 0
Refreshed database view.
Dumping ObjectStore data:
Object store: testObjectStore1
Number of entries: 1
Key = testKey, value = [object Object]
Index: testIndex
Number of entries: 1
Key = testKey, value = [object Object]
Object store: testObjectStore2
(no entries)
Number of entries: 0
Index: testIndex
Number of entries: 0
Added testObjectStore2 entry.
Dumping ObjectStore data:
Object store: testObjectStore1
Number of entries: 1
Key = testKey, value = [object Object]
Index: testIndex
Number of entries: 1
Key = testKey, value = [object Object]
Object store: testObjectStore2
(no entries)
Number of entries: 0
Index: testIndex
Number of entries: 0
Right-click refreshed database.
Dumping ObjectStore data:
Object store: testObjectStore1
Number of entries: 1
Key = testKey, value = [object Object]
Index: testIndex
Number of entries: 1
Key = testKey, value = [object Object]
Object store: testObjectStore2
Number of entries: 1
Key = testKey2, value = [object Object]
Index: testIndex
Number of entries: 1
Key = testKey2, value = [object Object]
......@@ -15,6 +15,7 @@
var keyPath = 'testKey';
var indexedDBModel = TestRunner.mainTarget.model(Resources.IndexedDBModel);
indexedDBModel._throttler._timeout = 100000; // Disable live updating.
var databaseId;
function waitRefreshDatabase() {
......@@ -51,24 +52,6 @@
UI.panels.resources._sidebar.indexedDBListTreeElement.refreshIndexedDB();
}
function dumpObjectStores() {
TestRunner.addResult('Dumping ObjectStore data:');
var idbDatabaseTreeElement = UI.panels.resources._sidebar.indexedDBListTreeElement._idbDatabaseTreeElements[0];
for (var i = 0; i < idbDatabaseTreeElement.childCount(); ++i) {
var objectStoreTreeElement = idbDatabaseTreeElement.childAt(i);
TestRunner.addResult(' Object store: ' + objectStoreTreeElement.title);
var entries = objectStoreTreeElement._view._entries;
if (!entries.length) {
TestRunner.addResult(' (no entries)');
continue;
}
for (var j = 0; j < entries.length; ++j) {
TestRunner.addResult(' Key = ' + entries[j].key._value + ', value = ' + entries[j].value);
}
}
}
// Initial tree
ApplicationTestRunner.dumpIndexedDBTree();
......@@ -97,34 +80,34 @@
await waitRefreshDatabase();
TestRunner.addResult('Created second objectstore.');
ApplicationTestRunner.dumpIndexedDBTree();
// Load objectstore data views
for (var i = 0; i < idbDatabaseTreeElement.childCount(); ++i) {
var objectStoreTreeElement = idbDatabaseTreeElement.childAt(i);
objectStoreTreeElement.onselect(false);
}
InspectorTest.dumpObjectStores();
// Add entries
await ApplicationTestRunner.addIDBValueAsync(databaseName, objectStoreName1, 'testKey', 'testValue');
TestRunner.addResult('Added ' + objectStoreName1 + ' entry.');
dumpObjectStores();
InspectorTest.dumpObjectStores();
// Refresh database view
await waitRefreshDatabase();
await waitUpdateDataView(); // Wait for second objectstore data to load on page.
await waitUpdateDataView(); // Wait for indexes and second object store to refresh.
await waitUpdateDataView();
await waitUpdateDataView();
TestRunner.addResult('Refreshed database view.');
dumpObjectStores();
InspectorTest.dumpObjectStores();
// Add entries
await ApplicationTestRunner.addIDBValueAsync(databaseName, objectStoreName2, 'testKey2', 'testValue2');
TestRunner.addResult('Added ' + objectStoreName2 + ' entry.');
dumpObjectStores();
InspectorTest.dumpObjectStores();
// Right-click refresh database view
await waitRefreshDatabaseRightClick();
await waitUpdateDataView(); // Wait for second objectstore data to load on page.
await waitUpdateDataView(); // Wait for indexes and second object store to refresh.
await waitUpdateDataView();
await waitUpdateDataView();
TestRunner.addResult('Right-click refreshed database.');
dumpObjectStores();
InspectorTest.dumpObjectStores();
await ApplicationTestRunner.deleteDatabaseAsync(databaseName);
TestRunner.completeTest();
})();
Tests that the IndexedDB database content live updates.
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
Object store: objectStore1
Index: index1
Object store marked needs refresh = false
Index marked needs refresh = false
Add entry to objectStore1:
Object store marked needs refresh = true
Index marked needs refresh = true
Dumping ObjectStore data:
Object store: objectStore1
Number of entries: 0
Index: index1
Number of entries: 0
Refresh views:
Object store marked needs refresh = false
Index marked needs refresh = false
Dumping ObjectStore data:
Object store: objectStore1
Number of entries: 1
Key = testKey, value = [object Object]
Index: index1
Number of entries: 1
Key = testKey, value = [object Object]
Delete entry from objectStore1:
Object store marked needs refresh = true
Index marked needs refresh = true
Dumping ObjectStore data:
Object store: objectStore1
Number of entries: 1
Key = testKey, value = [object Object]
Index: index1
Number of entries: 1
Key = testKey, value = [object Object]
Refresh views:
Object store marked needs refresh = false
Index marked needs refresh = false
Dumping ObjectStore data:
Object store: objectStore1
Number of entries: 0
Index: index1
Number of entries: 0
<html>
<head>
<script src="../../inspector/inspector-test.js"></script>
<script src="../../inspector/indexeddb/indexeddb-test.js"></script>
<script>
async function test() {
let indexedDBModel = TestRunner.mainTarget.model(Resources.IndexedDBModel);
indexedDBModel._throttler._timeout = 0;
var objectStore;
var objectStoreView;
var indexView;
function isMarkedNeedsRefresh() {
if (!objectStore) {
objectStore = UI.panels.resources._sidebar.indexedDBListTreeElement._idbDatabaseTreeElements[0].childAt(0);
objectStore.onselect(false);
objectStore.childAt(0).onselect(false);
objectStoreView = objectStore._view;
indexView = objectStore.childAt(0)._view;
}
TestRunner.addResult('Object store marked needs refresh = ' + objectStoreView._needsRefresh.visible());
TestRunner.addResult('Index marked needs refresh = ' + indexView._needsRefresh.visible());
}
let promise = InspectorTest.addSnifferPromise(Resources.IndexedDBTreeElement.prototype, '_addIndexedDB');
await ApplicationTestRunner.createDatabaseAsync('database1');
await promise;
promise = InspectorTest.addSnifferPromise(Resources.IDBObjectStoreTreeElement.prototype, 'update');
await ApplicationTestRunner.createObjectStoreAsync('database1', 'objectStore1', 'index1');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
isMarkedNeedsRefresh();
TestRunner.addResult('\nAdd entry to objectStore1:');
promise = InspectorTest.addSnifferPromise(Resources.IDBDataView.prototype, 'markNeedsRefresh');
await ApplicationTestRunner.addIDBValueAsync('database1', 'objectStore1', 'testKey', 'testValue');
await promise;
isMarkedNeedsRefresh();
ApplicationTestRunner.dumpObjectStores();
TestRunner.addResult('\nRefresh views:');
promise = InspectorTest.addSnifferPromise(Resources.IDBDataView.prototype, '_updatedDataForTests');
objectStoreView._updateData(true);
await promise;
promise = InspectorTest.addSnifferPromise(Resources.IDBDataView.prototype, '_updatedDataForTests');
indexView._updateData(true);
await promise;
isMarkedNeedsRefresh();
ApplicationTestRunner.dumpObjectStores();
TestRunner.addResult('\nDelete entry from objectStore1:');
promise = InspectorTest.addSnifferPromise(Resources.IDBDataView.prototype, 'markNeedsRefresh');
await ApplicationTestRunner.deleteIDBValueAsync('database1', 'objectStore1', 'testKey');
await promise;
isMarkedNeedsRefresh();
ApplicationTestRunner.dumpObjectStores();
TestRunner.addResult('\nRefresh views:');
promise = InspectorTest.addSnifferPromise(Resources.IDBDataView.prototype, '_updatedDataForTests');
objectStoreView._updateData(true);
await promise;
promise = InspectorTest.addSnifferPromise(Resources.IDBDataView.prototype, '_updatedDataForTests');
indexView._updateData(true);
await promise;
isMarkedNeedsRefresh();
ApplicationTestRunner.dumpObjectStores();
promise = InspectorTest.addSnifferPromise(Resources.IndexedDBTreeElement.prototype, 'setExpandable');
await ApplicationTestRunner.deleteDatabaseAsync('database1');
await promise;
InspectorTest.completeTest();
}
</script>
</head>
<body onload="runTest()">
<p>Tests that the IndexedDB database content live updates.</p>
</body>
</html>
Tests that the IndexedDB database list live updates.
Dumping IndexedDB tree:
(empty)
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
(no object stores)
database: database2 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
Object store: objectStore1
Index: index1
database: database2 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
Object store: objectStore1
Index: index1
Object store: objectStore2
Index: index2
database: database2 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
Object store: objectStore1
Index: index1
Index: index3
Object store: objectStore2
Index: index2
database: database2 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
Object store: objectStore1
Index: index1
Object store: objectStore2
Index: index2
database: database2 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
database: database1 - http://127.0.0.1:8000
Object store: objectStore1
Index: index1
database: database2 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
database: database2 - http://127.0.0.1:8000
(no object stores)
Dumping IndexedDB tree:
(empty)
<html>
<head>
<script src="../../inspector/inspector-test.js"></script>
<script src="../../inspector/indexeddb/indexeddb-test.js"></script>
<script>
async function test() {
let indexedDBModel = TestRunner.mainTarget.model(Resources.IndexedDBModel);
indexedDBModel._throttler._timeout = 0;
ApplicationTestRunner.dumpIndexedDBTree();
let promise = InspectorTest.addSnifferPromise(Resources.IndexedDBTreeElement.prototype, '_addIndexedDB');
await ApplicationTestRunner.createDatabaseAsync('database1');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IndexedDBTreeElement.prototype, '_addIndexedDB');
await ApplicationTestRunner.createDatabaseAsync('database2');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IDBObjectStoreTreeElement.prototype, 'update');
await ApplicationTestRunner.createObjectStoreAsync('database1', 'objectStore1', 'index1');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IDBObjectStoreTreeElement.prototype, 'update');
await ApplicationTestRunner.createObjectStoreAsync('database1', 'objectStore2', 'index2');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IDBObjectStoreTreeElement.prototype, 'update');
await ApplicationTestRunner.createObjectStoreIndexAsync('database1', 'objectStore1', 'index3');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IDBObjectStoreTreeElement.prototype, '_indexRemoved');
await ApplicationTestRunner.deleteObjectStoreIndexAsync('database1', 'objectStore1', 'index3');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IDBObjectStoreTreeElement.prototype, 'update');
await ApplicationTestRunner.deleteObjectStoreAsync('database1', 'objectStore2');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IndexedDBTreeElement.prototype, 'setExpandable');
await ApplicationTestRunner.deleteDatabaseAsync('database1');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
promise = InspectorTest.addSnifferPromise(Resources.IndexedDBTreeElement.prototype, 'setExpandable');
await ApplicationTestRunner.deleteDatabaseAsync('database2');
await promise;
ApplicationTestRunner.dumpIndexedDBTree();
InspectorTest.completeTest();
}
</script>
</head>
<body onload="runTest()">
<p>Tests that the IndexedDB database list live updates.</p>
</body>
</html>
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback1
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback2
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback3
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback4
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback5
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback6
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback1
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback2
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback3
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback4
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback5
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback6
Tests IndexedDB tree element on resources panel.
Expanded IndexedDB tree element.
......
......@@ -9,7 +9,7 @@
var mainFrameId = TestRunner.resourceTreeModel.mainFrame.id;
var indexedDBModel;
var withoutIndexedDBURL = 'http://localhost:8000/inspector/indexeddb/resources/without-indexed-db.html';
var withoutIndexedDBURL = 'http://localhost:8000/devtools/indexeddb/resources/without-indexed-db.html';
var originalURL = 'http://127.0.0.1:8000/devtools/indexeddb/resources-panel.js';
var databaseName = 'testDatabase';
var objectStoreName = 'testObjectStore';
......
Tests that IndexedDB live update only tracks valid security origins.
Invalid Origins:
http, valid = false
test://fake, valid = false
test://fake.origin.com, valid = false
chrome://test, valid = false
Valid Origins:
http://fake.origin.com, valid = true
https://fake.origin.com, valid = true
<html>
<head>
<script src="../../inspector/inspector-test.js"></script>
<script src="../../inspector/indexeddb/indexeddb-test.js"></script>
<script>
async function test() {
let indexedDBModel = TestRunner.mainTarget.model(Resources.IndexedDBModel);
let invalidOrigins = ['http', 'test://fake', 'test://fake.origin.com', 'chrome://test'];
let validOrigins = ['http://fake.origin.com', 'https://fake.origin.com'];
InspectorTest.addResult('Invalid Origins:');
invalidOrigins.map(origin => {
InspectorTest.addResult(origin + ", valid = " + indexedDBModel._isValidSecurityOrigin(origin));
});
InspectorTest.addResult('\nValid Origins:');
validOrigins.map(origin => {
InspectorTest.addResult(origin + ", valid = " + indexedDBModel._isValidSecurityOrigin(origin));
});
InspectorTest.completeTest();
}
</script>
</head>
<body onload="runTest()">
<p>Tests that IndexedDB live update only tracks valid security origins.</p>
</body>
</html>
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback1
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback2
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback3
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback4
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback5
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback6
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback7
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback8
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback9
CONSOLE MESSAGE: line 124: InspectorTest.IndexedDB_callback10
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback1
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback2
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback3
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback4
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback5
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback6
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback7
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback8
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback9
CONSOLE MESSAGE: line 2: InspectorTest.IndexedDB_callback10
Tests that deleted databases do not get recreated.
Preparing database
......
......@@ -42,6 +42,30 @@ ApplicationTestRunner.dumpIndexedDBTree = function() {
}
};
ApplicationTestRunner.dumpObjectStores = function() {
TestRunner.addResult('Dumping ObjectStore data:');
let idbDatabaseTreeElement = UI.panels.resources._sidebar.indexedDBListTreeElement._idbDatabaseTreeElements[0];
for (let i = 0; i < idbDatabaseTreeElement.childCount(); ++i) {
let objectStoreTreeElement = idbDatabaseTreeElement.childAt(i);
objectStoreTreeElement.onselect(false);
TestRunner.addResult(' Object store: ' + objectStoreTreeElement.title);
let entries = objectStoreTreeElement._view._entries;
TestRunner.addResult(' Number of entries: ' + entries.length);
for (let j = 0; j < entries.length; ++j)
TestRunner.addResult(' Key = ' + entries[j].key._value + ', value = ' + entries[j].value);
for (let k = 0; k < objectStoreTreeElement.childCount(); ++k) {
let indexTreeElement = objectStoreTreeElement.childAt(k);
TestRunner.addResult(' Index: ' + indexTreeElement.title);
indexTreeElement.onselect(false);
let entries = indexTreeElement._view._entries;
TestRunner.addResult(' Number of entries: ' + entries.length);
for (let j = 0; j < entries.length; ++j)
TestRunner.addResult(' Key = ' + entries[j].primaryKey._value + ', value = ' + entries[j].value);
}
}
};
var lastCallbackId = 0;
var callbacks = {};
var callbackIdPrefix = 'InspectorTest.IndexedDB_callback';
......@@ -122,10 +146,28 @@ ApplicationTestRunner.createDatabaseAsync = function(databaseName) {
return TestRunner.evaluateInPageAsync('createDatabaseAsync(\'' + databaseName + '\')');
};
ApplicationTestRunner.createObjectStoreAsync = function(databaseName, objectStoreName, indexName, keyPath) {
ApplicationTestRunner.deleteDatabaseAsync = function(databaseName) {
return TestRunner.evaluateInPageAsync('deleteDatabaseAsync(\'' + databaseName + '\')');
};
ApplicationTestRunner.createObjectStoreAsync = function(databaseName, objectStoreName, indexName) {
return TestRunner.evaluateInPageAsync(
'createObjectStoreAsync(\'' + databaseName + '\', \'' + objectStoreName + '\', \'' + indexName + '\')');
};
ApplicationTestRunner.deleteObjectStoreAsync = function(databaseName, objectStoreName) {
return TestRunner.evaluateInPageAsync(
'deleteObjectStoreAsync(\'' + databaseName + '\', \'' + objectStoreName + '\')');
};
ApplicationTestRunner.createObjectStoreIndexAsync = function(databaseName, objectStoreName, indexName) {
return TestRunner.evaluateInPageAsync(
'createObjectStoreIndexAsync(\'' + databaseName + '\', \'' + objectStoreName + '\', \'' + indexName + '\')');
};
ApplicationTestRunner.deleteObjectStoreIndexAsync = function(databaseName, objectStoreName, indexName) {
return TestRunner.evaluateInPageAsync(
'createObjectStoreAsync(\'' + databaseName + '\', \'' + objectStoreName + '\', \'' + indexName + '\', \'' +
keyPath + '\')');
'deleteObjectStoreIndexAsync(\'' + databaseName + '\', \'' + objectStoreName + '\', \'' + indexName + '\')');
};
ApplicationTestRunner.addIDBValueAsync = function(databaseName, objectStoreName, key, value) {
......@@ -133,6 +175,16 @@ ApplicationTestRunner.addIDBValueAsync = function(databaseName, objectStoreName,
'addIDBValueAsync(\'' + databaseName + '\', \'' + objectStoreName + '\', \'' + key + '\', \'' + value + '\')');
};
ApplicationTestRunner.addIDBValueAsync = function(databaseName, objectStoreName, key, value) {
return TestRunner.evaluateInPageAsync(
'addIDBValueAsync(\'' + databaseName + '\', \'' + objectStoreName + '\', \'' + key + '\', \'' + value + '\')');
};
ApplicationTestRunner.deleteIDBValueAsync = function(databaseName, objectStoreName, key) {
return TestRunner.evaluateInPageAsync(
'deleteIDBValueAsync(\'' + databaseName + '\', \'' + objectStoreName + '\', \'' + key + '\')');
};
TestRunner.deprecatedInitAsync(`
function dispatchCallback(callbackId) {
console.log(callbackId);
......@@ -286,42 +338,80 @@ TestRunner.deprecatedInitAsync(`
return promise;
}
function createObjectStoreAsync(databaseName, objectStoreName, indexName, keyPath) {
var callback;
var promise = new Promise(fulfill => callback = fulfill);
function upgradeRequestAsync(databaseName, onUpgradeNeeded, callback) {
var request = indexedDB.open(databaseName);
request.onerror = onIndexedDBError;
request.onsuccess = function(event) {
var db = request.result;
var version = db.version;
db.close();
var upgradeRequest = indexedDB.open(databaseName, version + 1);
upgradeRequest.onerror = onIndexedDBError;
upgradeRequest.onupgradeneeded = function(e) {
onUpgradeNeeded(e.target.result, e.target.transaction, callback);
}
upgradeRequest.onsuccess = function(e) {
var upgradeDb = e.target.result;
upgradeDb.close();
callback();
}
}
}
var store = upgradeDb.createObjectStore(objectStoreName, {
keyPath: 'test',
autoIncrement: false
});
function deleteDatabaseAsync(databaseName) {
var callback;
var promise = new Promise((fulfill) => callback = fulfill);
var request = indexedDB.deleteDatabase(databaseName);
request.onerror = onIndexedDBError;
request.onsuccess = callback;
return promise;
}
store.createIndex(indexName, 'test', {
unique: false,
multiEntry: false
});
function createObjectStoreAsync(databaseName, objectStoreName, indexName) {
var callback;
var promise = new Promise((fulfill) => callback = fulfill);
var onUpgradeNeeded = function(upgradeDb, transaction, callback) {
var store = upgradeDb.createObjectStore(objectStoreName, { keyPath: "test", autoIncrement: false });
store.createIndex(indexName, "test", { unique: false, multiEntry: false });
callback();
}
upgradeRequestAsync(databaseName, onUpgradeNeeded, callback)
return promise;
}
callback();
};
function deleteObjectStoreAsync(databaseName, objectStoreName) {
var callback;
var promise = new Promise((fulfill) => callback = fulfill);
var onUpgradeNeeded = function(upgradeDb, transaction, callback) {
upgradeDb.deleteObjectStore(objectStoreName);
callback();
}
upgradeRequestAsync(databaseName, onUpgradeNeeded, callback)
return promise;
}
upgradeRequest.onsuccess = function(e) {
var upgradeDb = e.target.result;
upgradeDb.close();
callback();
};
};
function createObjectStoreIndexAsync(databaseName, objectStoreName, indexName) {
var callback;
var promise = new Promise((fulfill) => callback = fulfill);
var onUpgradeNeeded = function(upgradeDb, transaction, callback) {
var store = transaction.objectStore(objectStoreName);
store.createIndex(indexName, "test", { unique: false, multiEntry: false });
callback();
}
upgradeRequestAsync(databaseName, onUpgradeNeeded, callback)
return promise;
}
function deleteObjectStoreIndexAsync(databaseName, objectStoreName, indexName) {
var callback;
var promise = new Promise((fulfill) => callback = fulfill);
var onUpgradeNeeded = function(upgradeDb, transaction, callback) {
var store = transaction.objectStore(objectStoreName);
store.deleteIndex(indexName);
callback();
}
upgradeRequestAsync(databaseName, onUpgradeNeeded, callback)
return promise;
}
......@@ -351,4 +441,24 @@ TestRunner.deprecatedInitAsync(`
return promise;
}
function deleteIDBValueAsync(databaseName, objectStoreName, key) {
var callback;
var promise = new Promise((fulfill) => callback = fulfill);
var request = indexedDB.open(databaseName);
request.onerror = onIndexedDBError;
request.onsuccess = function(event) {
var db = request.result;
var transaction = db.transaction(objectStoreName, "readwrite");
var store = transaction.objectStore(objectStoreName);
store.delete(key);
transaction.onerror = onIndexedDBError;
transaction.oncomplete = function() {
db.close();
callback();
};
}
return promise;
}
`);
......@@ -1026,6 +1026,9 @@ Resources.IndexedDBTreeElement = class extends Resources.StorageCategoryTreeElem
Resources.IndexedDBModel, Resources.IndexedDBModel.Events.DatabaseRemoved, this._indexedDBRemoved, this);
SDK.targetManager.addModelListener(
Resources.IndexedDBModel, Resources.IndexedDBModel.Events.DatabaseLoaded, this._indexedDBLoaded, this);
SDK.targetManager.addModelListener(
Resources.IndexedDBModel, Resources.IndexedDBModel.Events.IndexedDBContentUpdated,
this._indexedDBContentUpdated, this);
/** @type {!Array.<!Resources.IDBDatabaseTreeElement>} */
this._idbDatabaseTreeElements = [];
......@@ -1098,12 +1101,26 @@ Resources.IndexedDBTreeElement = class extends Resources.StorageCategoryTreeElem
_indexedDBLoaded(event) {
var database = /** @type {!Resources.IndexedDBModel.Database} */ (event.data.database);
var model = /** @type {!Resources.IndexedDBModel} */ (event.data.model);
var entriesUpdated = /** @type {boolean} */ (event.data.entriesUpdated);
var idbDatabaseTreeElement = this._idbDatabaseTreeElement(model, database.databaseId);
if (!idbDatabaseTreeElement)
return;
idbDatabaseTreeElement.update(database, entriesUpdated);
}
/**
* @param {!Common.Event} event
*/
_indexedDBContentUpdated(event) {
var databaseId = /** @type {!Resources.IndexedDBModel.DatabaseId} */ (event.data.databaseId);
var objectStoreName = /** @type {string} */ (event.data.objectStoreName);
var model = /** @type {!Resources.IndexedDBModel} */ (event.data.model);
idbDatabaseTreeElement.update(database);
var idbDatabaseTreeElement = this._idbDatabaseTreeElement(model, databaseId);
if (!idbDatabaseTreeElement)
return;
idbDatabaseTreeElement.indexedDBContentUpdated(objectStoreName);
}
/**
......@@ -1167,10 +1184,19 @@ Resources.IDBDatabaseTreeElement = class extends Resources.BaseStorageTreeElemen
this._model.refreshDatabase(this._databaseId);
}
/**
* @param {string} objectStoreName
*/
indexedDBContentUpdated(objectStoreName) {
if (this._idbObjectStoreTreeElements[objectStoreName])
this._idbObjectStoreTreeElements[objectStoreName].markNeedsRefresh();
}
/**
* @param {!Resources.IndexedDBModel.Database} database
* @param {boolean} entriesUpdated
*/
update(database) {
update(database, entriesUpdated) {
this._database = database;
var objectStoreNames = {};
for (var objectStoreName in this._database.objectStores) {
......@@ -1182,7 +1208,7 @@ Resources.IDBDatabaseTreeElement = class extends Resources.BaseStorageTreeElemen
this._idbObjectStoreTreeElements[objectStore.name] = idbObjectStoreTreeElement;
this.appendChild(idbObjectStoreTreeElement);
}
this._idbObjectStoreTreeElements[objectStore.name].update(objectStore);
this._idbObjectStoreTreeElements[objectStore.name].update(objectStore, entriesUpdated);
}
for (var objectStoreName in this._idbObjectStoreTreeElements) {
if (!objectStoreNames[objectStoreName])
......@@ -1260,6 +1286,13 @@ Resources.IDBObjectStoreTreeElement = class extends Resources.BaseStorageTreeEle
this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true);
}
markNeedsRefresh() {
if (this._view)
this._view.markNeedsRefresh();
for (var indexName in this._idbIndexTreeElements)
this._idbIndexTreeElements[indexName].markNeedsRefresh();
}
_handleContextMenuEvent(event) {
var contextMenu = new UI.ContextMenu(event);
contextMenu.defaultSection().appendItem(Common.UIString('Clear'), this._clearObjectStore.bind(this));
......@@ -1268,13 +1301,14 @@ Resources.IDBObjectStoreTreeElement = class extends Resources.BaseStorageTreeEle
async _clearObjectStore() {
await this._model.clearObjectStore(this._databaseId, this._objectStore.name);
this.update(this._objectStore);
this.update(this._objectStore, true);
}
/**
* @param {!Resources.IndexedDBModel.ObjectStore} objectStore
* @param {boolean} entriesUpdated
*/
update(objectStore) {
update(objectStore, entriesUpdated) {
this._objectStore = objectStore;
var indexNames = {};
......@@ -1287,7 +1321,7 @@ Resources.IDBObjectStoreTreeElement = class extends Resources.BaseStorageTreeEle
this._idbIndexTreeElements[index.name] = idbIndexTreeElement;
this.appendChild(idbIndexTreeElement);
}
this._idbIndexTreeElements[index.name].update(index);
this._idbIndexTreeElements[index.name].update(this._objectStore, index, entriesUpdated);
}
for (var indexName in this._idbIndexTreeElements) {
if (!indexNames[indexName])
......@@ -1303,7 +1337,7 @@ Resources.IDBObjectStoreTreeElement = class extends Resources.BaseStorageTreeEle
if (this.childCount())
this.expand();
if (this._view)
if (this._view && entriesUpdated)
this._view.update(this._objectStore, null);
this._updateTooltip();
......@@ -1372,14 +1406,22 @@ Resources.IDBIndexTreeElement = class extends Resources.BaseStorageTreeElement {
this._objectStore.name + '/' + this._index.name;
}
markNeedsRefresh() {
if (this._view)
this._view.markNeedsRefresh();
}
/**
* @param {!Resources.IndexedDBModel.ObjectStore} objectStore
* @param {!Resources.IndexedDBModel.Index} index
* @param {boolean} entriesUpdated
*/
update(index) {
update(objectStore, index, entriesUpdated) {
this._objectStore = objectStore;
this._index = index;
if (this._view)
this._view.update(this._index);
if (this._view && entriesUpdated)
this._view.update(this._objectStore, this._index);
this._updateTooltip();
}
......
......@@ -29,6 +29,7 @@
*/
/**
* @implements {Protocol.StorageDispatcher}
* @unrestricted
*/
Resources.IndexedDBModel = class extends SDK.SDKModel {
......@@ -37,13 +38,18 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
*/
constructor(target) {
super(target);
target.registerStorageDispatcher(this);
this._securityOriginManager = target.model(SDK.SecurityOriginManager);
this._agent = target.indexedDBAgent();
this._indexedDBAgent = target.indexedDBAgent();
this._storageAgent = target.storageAgent();
/** @type {!Map.<!Resources.IndexedDBModel.DatabaseId, !Resources.IndexedDBModel.Database>} */
this._databases = new Map();
/** @type {!Object.<string, !Array.<string>>} */
this._databaseNamesBySecurityOrigin = {};
this._originsUpdated = new Set();
this._throttler = new Common.Throttler(1000);
}
/**
......@@ -142,7 +148,7 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
if (this._enabled)
return;
this._agent.enable();
this._indexedDBAgent.enable();
this._securityOriginManager.addEventListener(
SDK.SecurityOriginManager.Events.SecurityOriginAdded, this._securityOriginAdded, this);
this._securityOriginManager.addEventListener(
......@@ -171,7 +177,7 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
async deleteDatabase(databaseId) {
if (!this._enabled)
return;
await this._agent.deleteDatabase(databaseId.securityOrigin, databaseId.name);
await this._indexedDBAgent.deleteDatabase(databaseId.securityOrigin, databaseId.name);
this._loadDatabaseNames(databaseId.securityOrigin);
}
......@@ -185,7 +191,7 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
*/
refreshDatabase(databaseId) {
this._loadDatabase(databaseId);
this._loadDatabase(databaseId, true);
}
/**
......@@ -194,7 +200,7 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
* @return {!Promise}
*/
clearObjectStore(databaseId, objectStoreName) {
return this._agent.clearObjectStore(databaseId.securityOrigin, databaseId.name, objectStoreName);
return this._indexedDBAgent.clearObjectStore(databaseId.securityOrigin, databaseId.name, objectStoreName);
}
/**
......@@ -220,6 +226,8 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
console.assert(!this._databaseNamesBySecurityOrigin[securityOrigin]);
this._databaseNamesBySecurityOrigin[securityOrigin] = [];
this._loadDatabaseNames(securityOrigin);
if (this._isValidSecurityOrigin(securityOrigin))
this._storageAgent.trackIndexedDBForOrigin(securityOrigin);
}
/**
......@@ -230,6 +238,17 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
for (var i = 0; i < this._databaseNamesBySecurityOrigin[securityOrigin].length; ++i)
this._databaseRemoved(securityOrigin, this._databaseNamesBySecurityOrigin[securityOrigin][i]);
delete this._databaseNamesBySecurityOrigin[securityOrigin];
if (this._isValidSecurityOrigin(securityOrigin))
this._storageAgent.untrackIndexedDBForOrigin(securityOrigin);
}
/**
* @param {string} securityOrigin
* @return {boolean}
*/
_isValidSecurityOrigin(securityOrigin) {
var parsedURL = securityOrigin.asParsedURL();
return !!parsedURL && parsedURL.scheme.startsWith('http');
}
/**
......@@ -286,21 +305,25 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
/**
* @param {string} securityOrigin
* @return {!Promise<!Array.<string>>} databaseNames
*/
async _loadDatabaseNames(securityOrigin) {
var databaseNames = await this._agent.requestDatabaseNames(securityOrigin);
var databaseNames = await this._indexedDBAgent.requestDatabaseNames(securityOrigin);
if (!databaseNames)
return;
return [];
if (!this._databaseNamesBySecurityOrigin[securityOrigin])
return;
return [];
this._updateOriginDatabaseNames(securityOrigin, databaseNames);
return databaseNames;
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
* @param {boolean} entriesUpdated
*/
async _loadDatabase(databaseId) {
var databaseWithObjectStores = await this._agent.requestDatabase(databaseId.securityOrigin, databaseId.name);
async _loadDatabase(databaseId, entriesUpdated) {
var databaseWithObjectStores =
await this._indexedDBAgent.requestDatabase(databaseId.securityOrigin, databaseId.name);
if (!databaseWithObjectStores)
return;
......@@ -324,7 +347,8 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
}
this.dispatchEventToListeners(
Resources.IndexedDBModel.Events.DatabaseLoaded, {model: this, database: databaseModel});
Resources.IndexedDBModel.Events.DatabaseLoaded,
{model: this, database: databaseModel, entriesUpdated: entriesUpdated});
}
/**
......@@ -366,7 +390,7 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
async _requestData(databaseId, databaseName, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback) {
var keyRange = Resources.IndexedDBModel.keyRangeFromIDBKeyRange(idbKeyRange) || undefined;
var response = await this._agent.invoke_requestData({
var response = await this._indexedDBAgent.invoke_requestData({
securityOrigin: databaseId.securityOrigin,
databaseName,
objectStoreName,
......@@ -394,6 +418,58 @@ Resources.IndexedDBModel = class extends SDK.SDKModel {
}
callback(entries, response.hasMore);
}
/**
* @param {string} securityOrigin
*/
async _refreshDatabaseList(securityOrigin) {
var databaseNames = await this._loadDatabaseNames(securityOrigin);
for (var databaseName of databaseNames)
this._loadDatabase(new Resources.IndexedDBModel.DatabaseId(securityOrigin, databaseName), false);
}
/**
* @param {string} securityOrigin
* @override
*/
indexedDBListUpdated(securityOrigin) {
this._originsUpdated.add(securityOrigin);
this._throttler.schedule(() => {
var promises = Array.from(this._originsUpdated, securityOrigin => {
this._refreshDatabaseList(securityOrigin);
});
this._originsUpdated.clear();
return Promise.all(promises);
});
}
/**
* @param {string} securityOrigin
* @param {string} databaseName
* @param {string} objectStoreName
* @override
*/
indexedDBContentUpdated(securityOrigin, databaseName, objectStoreName) {
var databaseId = new Resources.IndexedDBModel.DatabaseId(securityOrigin, databaseName);
this.dispatchEventToListeners(
Resources.IndexedDBModel.Events.IndexedDBContentUpdated,
{databaseId: databaseId, objectStoreName: objectStoreName, model: this});
}
/**
* @param {string} securityOrigin
* @override
*/
cacheStorageListUpdated(securityOrigin) {
}
/**
* @param {string} securityOrigin
* @override
*/
cacheStorageContentUpdated(securityOrigin) {
}
};
SDK.SDKModel.register(Resources.IndexedDBModel, SDK.Target.Capability.None, false);
......@@ -416,7 +492,8 @@ Resources.IndexedDBModel.Events = {
DatabaseAdded: Symbol('DatabaseAdded'),
DatabaseRemoved: Symbol('DatabaseRemoved'),
DatabaseLoaded: Symbol('DatabaseLoaded'),
DatabaseNamesRefreshed: Symbol('DatabaseNamesRefreshed')
DatabaseNamesRefreshed: Symbol('DatabaseNamesRefreshed'),
IndexedDBContentUpdated: Symbol('IndexedDBContentUpdated')
};
/**
......
......@@ -120,6 +120,10 @@ Resources.IDBDataView = class extends UI.SimpleView {
this._clearButton = new UI.ToolbarButton(Common.UIString('Clear object store'), 'largeicon-clear');
this._clearButton.addEventListener(UI.ToolbarButton.Events.Click, this._clearButtonClicked, this);
this._needsRefresh = new UI.ToolbarItem(UI.createLabel(Common.UIString('Data may be stale'), 'smallicon-warning'));
this._needsRefresh.setVisible(false);
this._needsRefresh.setTitle(Common.UIString('Some entries may have been modified'));
this._createEditorToolbar();
this._pageSize = 50;
......@@ -211,13 +215,15 @@ Resources.IDBDataView = class extends UI.SimpleView {
this._pageForwardButton.addEventListener(UI.ToolbarButton.Events.Click, this._pageForwardButtonClicked, this);
editorToolbar.appendToolbarItem(this._pageForwardButton);
this._keyInputElement = UI.createInput('key-input');
editorToolbar.element.appendChild(this._keyInputElement);
this._keyInputElement = UI.createInput('toolbar-input');
editorToolbar.appendToolbarItem(new UI.ToolbarItem(this._keyInputElement));
this._keyInputElement.placeholder = Common.UIString('Start from key');
this._keyInputElement.addEventListener('paste', this._keyInputChanged.bind(this), false);
this._keyInputElement.addEventListener('cut', this._keyInputChanged.bind(this), false);
this._keyInputElement.addEventListener('keypress', this._keyInputChanged.bind(this), false);
this._keyInputElement.addEventListener('keydown', this._keyInputChanged.bind(this), false);
editorToolbar.appendToolbarItem(this._needsRefresh);
}
/**
......@@ -313,6 +319,7 @@ Resources.IDBDataView = class extends UI.SimpleView {
this._pageBackButton.setEnabled(!!skipCount);
this._pageForwardButton.setEnabled(hasMore);
this._needsRefresh.setVisible(false);
this._updatedDataForTests();
}
......@@ -348,6 +355,10 @@ Resources.IDBDataView = class extends UI.SimpleView {
this._updateData(true);
}
markNeedsRefresh() {
this._needsRefresh.setVisible(true);
}
clear() {
this._dataGrid.rootNode().removeChildren();
this._entries = [];
......
......@@ -39,11 +39,6 @@
border-bottom: 1px solid #ccc;
}
.indexed-db-data-view .data-view-toolbar .key-input {
margin: auto 0;
width: 200px;
}
.indexed-db-data-view .data-grid {
flex: auto;
}
......@@ -83,3 +78,7 @@
white-space: pre-wrap;
unicode-bidi: -webkit-isolate;
}
.resources-toolbar {
padding-right: 10px;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment