Web Inspector: Audit: save imported audits across WebInspector sessions
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2018 01:18:07 +0000 (01:18 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2018 01:18:07 +0000 (01:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=190858
<rdar://problem/45527625>

Reviewed by Brian Burg.

Source/WebInspectorUI:

* UserInterface/Base/ObjectStore.js: Added.
(WI.ObjectStore):
(WI.ObjectStore.supported):
(WI.ObjectStore._open):
(WI.ObjectStore.get _databaseName):
(WI.ObjectStore.prototype.associateObject):
(WI.ObjectStore.prototype.async getAll):
(WI.ObjectStore.prototype.async add):
(WI.ObjectStore.prototype.async addObject):
(WI.ObjectStore.prototype.async delete):
(WI.ObjectStore.prototype.async deleteObject):
(WI.ObjectStore.prototype._resolveKeyPath):
(WI.ObjectStore.prototype.async _operation.listener):
(WI.ObjectStore.prototype.async _operation):
Wrapper for a global `IndexedDB` instance for all of WebInspector (per level). Instances of
`WI.ObjectStore` are able to control a given `IDBObjectStore` using a promise-based API.

*NOTE*: due to the constraint that `IDBObjectStore`s are only able to be created when the
owner `IndexedDB` is "upgrade"d, all `WI.ObjectStore` must be declared before the database
is opened for the first time. Additionally, any time a new `WI.ObjectStore` is added, the
`version` needs to be incremented to ensure that the "upgrade" event fires.

To use any of the `*Object` functions, one must implement a `toJSON` on the object provided.
This is so that `WI.ObjectStore` is able to add the resulting identifier value to the owner
object while storing its `toJSON` value in the IndexedDB (e.g. for objects that have cycles).

* UserInterface/Controllers/AuditManager.js:
(WI.AuditManager.prototype.import):
(WI.AuditManager.prototype.loadStoredTests): Added.
(WI.AuditManager.prototype.removeTest): Added.
(WI.AuditManager.prototype._addTest):

* UserInterface/Views/AuditTabContentView.js:
(WI.AuditTabContentView.prototype.initialLayout): Added.
Attempt to load stored audits when the Audit tab is first shown (lazy-load).

* UserInterface/Views/AuditNavigationSidebarPanel.js:
(WI.AuditNavigationSidebarPanel.prototype.initialLayout):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestRemoved): Added.

* UserInterface/Views/AuditTreeElement.js:
(WI.AuditTreeElement.prototype.ondelete):
Only allow top-level audits to be deleted, as that is what matches the `WI.ObjectStore`.

* UserInterface/Main.html:
* UserInterface/Test.html:

LayoutTests:

* inspector/unit-tests/objectStore/add-expected.txt: Added.
* inspector/unit-tests/objectStore/add.html: Added.
* inspector/unit-tests/objectStore/addObject-expected.txt: Added.
* inspector/unit-tests/objectStore/addObject.html: Added.
* inspector/unit-tests/objectStore/basic-expected.txt: Added.
* inspector/unit-tests/objectStore/basic.html: Added.
* inspector/unit-tests/objectStore/delete-expected.txt: Added.
* inspector/unit-tests/objectStore/delete.html: Added.
* inspector/unit-tests/objectStore/deleteObject-expected.txt: Added.
* inspector/unit-tests/objectStore/deleteObject.html: Added.
* inspector/unit-tests/objectStore/resources/objectStore-utilities.js: Added.
(TestPage.registerInitializer.InspectorTest.ObjectStore.TestObject):
(TestPage.registerInitializer.InspectorTest.ObjectStore.TestObject.prototype.toJSON):
(TestPage.registerInitializer.InspectorTest.ObjectStore.createSuite):
(TestPage.registerInitializer.InspectorTest.ObjectStore.createObjectStore):
(TestPage.registerInitializer.InspectorTest.ObjectStore.add):
(TestPage.registerInitializer.InspectorTest.ObjectStore.addObject):
(TestPage.registerInitializer.InspectorTest.ObjectStore.delete):
(TestPage.registerInitializer.InspectorTest.ObjectStore.deleteObject):
(TestPage.registerInitializer.InspectorTest.ObjectStore.logValues):
(TestPage.registerInitializer.InspectorTest.ObjectStore.wrapTest):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@237665 268f45cc-cd09-0410-ab3c-d52691b4dbfc

20 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/unit-tests/objectStore/add-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/add.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/addObject-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/addObject.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/basic-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/basic.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/delete-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/delete.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/deleteObject-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/deleteObject.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/objectStore/resources/objectStore-utilities.js [new file with mode: 0644]
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Base/ObjectStore.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Test.html
Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js
Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js

index 77d8822..d5c9134 100644 (file)
@@ -1,3 +1,33 @@
+2018-10-31  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Audit: save imported audits across WebInspector sessions
+        https://bugs.webkit.org/show_bug.cgi?id=190858
+        <rdar://problem/45527625>
+
+        Reviewed by Brian Burg.
+
+        * inspector/unit-tests/objectStore/add-expected.txt: Added.
+        * inspector/unit-tests/objectStore/add.html: Added.
+        * inspector/unit-tests/objectStore/addObject-expected.txt: Added.
+        * inspector/unit-tests/objectStore/addObject.html: Added.
+        * inspector/unit-tests/objectStore/basic-expected.txt: Added.
+        * inspector/unit-tests/objectStore/basic.html: Added.
+        * inspector/unit-tests/objectStore/delete-expected.txt: Added.
+        * inspector/unit-tests/objectStore/delete.html: Added.
+        * inspector/unit-tests/objectStore/deleteObject-expected.txt: Added.
+        * inspector/unit-tests/objectStore/deleteObject.html: Added.
+        * inspector/unit-tests/objectStore/resources/objectStore-utilities.js: Added.
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.TestObject):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.TestObject.prototype.toJSON):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.createSuite):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.createObjectStore):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.add):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.addObject):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.delete):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.deleteObject):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.logValues):
+        (TestPage.registerInitializer.InspectorTest.ObjectStore.wrapTest):
+
 2018-10-31  Alicia Boya GarcĂ­a  <aboya@igalia.com>
 
         [MSE] Use tolerance when growing the coded frame group
diff --git a/LayoutTests/inspector/unit-tests/objectStore/add-expected.txt b/LayoutTests/inspector/unit-tests/objectStore/add-expected.txt
new file mode 100644 (file)
index 0000000..1426e0c
--- /dev/null
@@ -0,0 +1,78 @@
+Tests WI.ObjectStore.prototype.add.
+
+
+== Running test suite: WI.ObjectStore.prototype.add
+-- Running test case: WI.ObjectStore.prototype.add.NoParameters
+PASS: Should produce an exception.
+TypeError: Not enough arguments
+[]
+
+-- Running test case: WI.ObjectStore.prototype.add.Boolean
+add: [false]
+add: [false,true]
+[false,true]
+
+-- Running test case: WI.ObjectStore.prototype.add.Number
+add: [11]
+add: [11,22]
+[11,22]
+
+-- Running test case: WI.ObjectStore.prototype.add.String
+add: ["foo"]
+add: ["foo","bar"]
+["foo","bar"]
+
+-- Running test case: WI.ObjectStore.prototype.add.Array
+add: [[11]]
+add: [[11],[22]]
+[[11],[22]]
+
+-- Running test case: WI.ObjectStore.prototype.add.Null
+add: [null]
+[null]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.WithoutKeyPathOrAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: The object store uses out-of-line keys and has no key generator and the key parameter was not provided.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.KeyPathMissingOnObjectWithoutAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: Evaluating the object store's key path did not yield a value.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithoutAutoIncrement
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1}]
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+[{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.KeyPathMissingOnObjectWithAutoIncrement
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1}]
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+[{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithAutoIncrement
+add: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1}]
+add: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+[{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.AutoIncrementWithoutKeyPath
+add: [{"a":1}]
+add: [{"a":1},{"b":2}]
+[{"a":1},{"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithoutAutoIncrement.Sub
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1}]
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+[{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.KeyPathMissingOnObjectWithAutoIncrement.Sub
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}}]
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+[{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+
+-- Running test case: WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithAutoIncrement.Sub
+add: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1}]
+add: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+[{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+
diff --git a/LayoutTests/inspector/unit-tests/objectStore/add.html b/LayoutTests/inspector/unit-tests/objectStore/add.html
new file mode 100644 (file)
index 0000000..7515f77
--- /dev/null
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/objectStore-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.ObjectStore.createSuite("WI.ObjectStore.prototype.add");
+
+    function testAdd(name, {options, tests}) {
+        InspectorTest.ObjectStore.wrapTest(name, async function() {
+            InspectorTest.ObjectStore.createObjectStore(options);
+
+            for (let {value, expected} of tests)
+                await InspectorTest.ObjectStore.add(value, expected);
+        });
+    }
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.add.NoParameters", async function() {
+        let objectStore = InspectorTest.ObjectStore.createObjectStore();
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.add();
+            await objectStore.add(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Boolean", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: false, expected: 1},
+            {value: true, expected: 2},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Number", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: 11, expected: 1},
+            {value: 22, expected: 2},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.String", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: "foo", expected: 1},
+            {value: "bar", expected: 2},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Array", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: [11], expected: 1},
+            {value: [22], expected: 2},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Null", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: null, expected: 1},
+        ],
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.add.Object.WithoutKeyPathOrAutoIncrement", async function() {
+        let objectStore = InspectorTest.ObjectStore.createObjectStore();
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.add(InspectorTest.ObjectStore.basicObject1);
+            await objectStore.add(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.add.Object.KeyPathMissingOnObjectWithoutAutoIncrement", async function() {
+        const options = {
+            keyPath: "KeyPathMissingOnObjectWithoutAutoIncrement",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.add(InspectorTest.ObjectStore.basicObject1);
+            await objectStore.add(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithoutAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Object.KeyPathMissingOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Object.AutoIncrementWithoutKeyPath", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithoutAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement.Sub"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Object.KeyPathMissingOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testAdd("WI.ObjectStore.prototype.add.Object.KeyPathSetOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests WI.ObjectStore.prototype.add.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/unit-tests/objectStore/addObject-expected.txt b/LayoutTests/inspector/unit-tests/objectStore/addObject-expected.txt
new file mode 100644 (file)
index 0000000..f7fce76
--- /dev/null
@@ -0,0 +1,54 @@
+Tests WI.ObjectStore.prototype.addObject.
+
+
+== Running test suite: WI.ObjectStore.prototype.addObject
+-- Running test case: WI.ObjectStore.prototype.addObject.NoParameters
+PASS: Should produce an exception.
+TypeError: undefined is not an object (evaluating 'object.toJSON')
+[]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.WithoutKeyPathOrAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: The object store uses out-of-line keys and has no key generator and the key parameter was not provided.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.KeyPathMissingOnObjectWithoutAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: Evaluating the object store's key path did not yield a value.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithoutAutoIncrement
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1}]
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+[{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.KeyPathMissingOnObjectWithAutoIncrement
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1}]
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+[{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithAutoIncrement
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1}]
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+[{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.AutoIncrementWithoutKeyPath
+addObject: [{"a":1}]
+addObject: [{"a":1},{"b":2}]
+[{"a":1},{"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithoutAutoIncrement.Sub
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1}]
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+[{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.KeyPathMissingOnObjectWithAutoIncrement.Sub
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}}]
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+[{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+
+-- Running test case: WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithAutoIncrement.Sub
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1}]
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+[{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+
diff --git a/LayoutTests/inspector/unit-tests/objectStore/addObject.html b/LayoutTests/inspector/unit-tests/objectStore/addObject.html
new file mode 100644 (file)
index 0000000..03523fb
--- /dev/null
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/objectStore-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.ObjectStore.createSuite("WI.ObjectStore.prototype.addObject");
+
+    function testAddObject(name, {options, tests}) {
+        InspectorTest.ObjectStore.wrapTest(name, async function() {
+            InspectorTest.ObjectStore.createObjectStore(options);
+
+            for (let {value, expected} of tests)
+                await InspectorTest.ObjectStore.addObject(new InspectorTest.ObjectStore.TestObject(value), expected);
+        });
+    }
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.addObject.NoParameters", async function() {
+        let objectStore = InspectorTest.ObjectStore.createObjectStore();
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.addObject();
+            await objectStore.addObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2));
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.addObject.WithoutKeyPathOrAutoIncrement", async function() {
+        let objectStore = InspectorTest.ObjectStore.createObjectStore();
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.addObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject1));
+            await objectStore.addObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2));
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.addObject.KeyPathMissingOnObjectWithoutAutoIncrement", async function() {
+        const options = {
+            keyPath: "KeyPathMissingOnObjectWithoutAutoIncrement",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.addObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject1));
+            await objectStore.addObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2));
+        });
+    });
+
+    testAddObject("WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithoutAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testAddObject("WI.ObjectStore.prototype.addObject.KeyPathMissingOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testAddObject("WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testAddObject("WI.ObjectStore.prototype.addObject.AutoIncrementWithoutKeyPath", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testAddObject("WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithoutAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement.Sub"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testAddObject("WI.ObjectStore.prototype.addObject.KeyPathMissingOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testAddObject("WI.ObjectStore.prototype.addObject.KeyPathSetOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests WI.ObjectStore.prototype.addObject.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/unit-tests/objectStore/basic-expected.txt b/LayoutTests/inspector/unit-tests/objectStore/basic-expected.txt
new file mode 100644 (file)
index 0000000..06a4c6f
--- /dev/null
@@ -0,0 +1,19 @@
+Tests basic functionality of WI.ObjectStore.
+
+
+== Running test suite: WI.ObjectStore
+-- Running test case: WI.ObjectStore.InitiallyNull
+PASS: The database should initially be null/closed.
+
+-- Running test case: WI.ObjectStore.prototype._resolveKeyPath.Exists
+{"object":{"a":1},"key":["a"],"value":1}
+[]
+
+-- Running test case: WI.ObjectStore.prototype._resolveKeyPath.MissingPart
+{"object":{"sub.a":0,"a":1,"b":2},"key":"sub.a","value":0}
+[]
+
+-- Running test case: WI.ObjectStore.prototype._resolveKeyPath.MissingWhole
+{"object":{"a":1,"b":2},"key":"sub.a"}
+[]
+
diff --git a/LayoutTests/inspector/unit-tests/objectStore/basic.html b/LayoutTests/inspector/unit-tests/objectStore/basic.html
new file mode 100644 (file)
index 0000000..547fdda
--- /dev/null
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/objectStore-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.ObjectStore.createSuite("WI.ObjectStore");
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.InitiallyNull", async function() {
+        InspectorTest.expectNull(WI.ObjectStore._database, "The database should initially be null/closed.");
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype._resolveKeyPath.Exists", async function() {
+        const options = {
+            keyPath: "sub.a",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        const object = {sub: {a: 1}, b: 2};
+        InspectorTest.log(objectStore._resolveKeyPath(object));
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype._resolveKeyPath.MissingPart", async function() {
+        const options = {
+            keyPath: "sub.a",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        const object = {"sub.a": 0, a: 1, b: 2};
+        InspectorTest.log(objectStore._resolveKeyPath(object));
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype._resolveKeyPath.MissingWhole", async function() {
+        const options = {
+            keyPath: "sub.a",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        const object = {a: 1, b: 2};
+        InspectorTest.log(objectStore._resolveKeyPath(object));
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests basic functionality of WI.ObjectStore.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/unit-tests/objectStore/delete-expected.txt b/LayoutTests/inspector/unit-tests/objectStore/delete-expected.txt
new file mode 100644 (file)
index 0000000..4483e5c
--- /dev/null
@@ -0,0 +1,108 @@
+Tests WI.ObjectStore.prototype.delete.
+
+
+== Running test suite: WI.ObjectStore.prototype.delete
+-- Running test case: WI.ObjectStore.prototype.delete.NoParameters
+add: [{"b":2}]
+PASS: Should produce an exception.
+TypeError: Not enough arguments
+[{"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.delete.MissingObject
+add: [{"b":2}]
+PASS: Should produce an exception.
+DataError: Failed to execute 'delete' on 'IDBObjectStore': The parameter is not a valid key.
+[{"b":2}]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Boolean
+add: [false]
+add: [false,true]
+delete: [true]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Number
+add: [11]
+add: [11,22]
+delete: [22]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.String
+add: ["foo"]
+add: ["foo","bar"]
+delete: ["bar"]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Array
+add: [[11]]
+add: [[11],[22]]
+delete: [[22]]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Null
+add: [null]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.WithoutKeyPathOrAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: The object store uses out-of-line keys and has no key generator and the key parameter was not provided.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.KeyPathMissingOnObjectWithoutAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: Evaluating the object store's key path did not yield a value.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithoutAutoIncrement
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1}]
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+delete: [{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.KeyPathMissingOnObjectWithAutoIncrement
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1}]
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+delete: [{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithAutoIncrement
+add: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1}]
+add: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+delete: [{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.AutoIncrementWithoutKeyPath
+add: [{"a":1}]
+add: [{"a":1},{"b":2}]
+delete: [{"b":2}]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithoutAutoIncrement.Sub
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1}]
+add: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+delete: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.KeyPathMissingOnObjectWithAutoIncrement.Sub
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}}]
+add: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+delete: [{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+delete: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithAutoIncrement.Sub
+add: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1}]
+add: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+delete: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+delete: []
+[]
+
diff --git a/LayoutTests/inspector/unit-tests/objectStore/delete.html b/LayoutTests/inspector/unit-tests/objectStore/delete.html
new file mode 100644 (file)
index 0000000..185119e
--- /dev/null
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/objectStore-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.ObjectStore.createSuite("WI.ObjectStore.prototype.delete");
+
+    function testDelete(name, {options, tests}) {
+        InspectorTest.ObjectStore.wrapTest(name, async function() {
+            InspectorTest.ObjectStore.createObjectStore(options);
+
+            let keys = [];
+            for (let {value, expected} of tests)
+                keys.push(await InspectorTest.ObjectStore.add(value, expected));
+
+            for (let key of keys)
+                await InspectorTest.ObjectStore.delete(key);
+        });
+    }
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.delete.NoParameters", async function() {
+        const options = {
+            autoIncrement: true,
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.ObjectStore.add(InspectorTest.ObjectStore.basicObject2, 1);
+
+        await InspectorTest.expectException(async () => {
+            await objectStore.delete();
+            await objectStore.delete(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.delete.MissingObject", async function() {
+        const options = {
+            autoIncrement: true,
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.ObjectStore.add(InspectorTest.ObjectStore.basicObject2, 1);
+
+        await InspectorTest.expectException(async () => {
+            await objectStore.delete(InspectorTest.ObjectStore.basicObject1);
+            await objectStore.delete(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Boolean", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: false, expected: 1},
+            {value: true, expected: 2},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Number", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: 11, expected: 1},
+            {value: 22, expected: 2},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.String", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: "foo", expected: 1},
+            {value: "bar", expected: 2},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Array", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: [11], expected: 1},
+            {value: [22], expected: 2},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Null", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: null, expected: 1},
+        ],
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.delete.Object.WithoutKeyPathOrAutoIncrement", async function() {
+        let objectStore = InspectorTest.ObjectStore.createObjectStore();
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.add(InspectorTest.ObjectStore.basicObject1);
+            await objectStore.add(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.delete.Object.KeyPathMissingOnObjectWithoutAutoIncrement", async function() {
+        const options = {
+            keyPath: "KeyPathMissingOnObjectWithoutAutoIncrement",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.add(InspectorTest.ObjectStore.basicObject1);
+            await objectStore.add(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithoutAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Object.KeyPathMissingOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Object.AutoIncrementWithoutKeyPath", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithoutAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement.Sub"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Object.KeyPathMissingOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testDelete("WI.ObjectStore.prototype.delete.Object.KeyPathSetOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests WI.ObjectStore.prototype.delete.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/unit-tests/objectStore/deleteObject-expected.txt b/LayoutTests/inspector/unit-tests/objectStore/deleteObject-expected.txt
new file mode 100644 (file)
index 0000000..d9d9ad9
--- /dev/null
@@ -0,0 +1,80 @@
+Tests WI.ObjectStore.prototype.deleteObject.
+
+
+== Running test suite: WI.ObjectStore.prototype.deleteObject
+-- Running test case: WI.ObjectStore.prototype.deleteObject.NoParameters
+add: [{"_object":{"b":2}}]
+PASS: Should produce an exception.
+TypeError: undefined is not an object (evaluating 'object[key]')
+[{"_object":{"b":2}}]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.MissingObject
+add: [{"_object":{"b":2}}]
+PASS: Should produce an exception.
+DataError: Failed to execute 'delete' on 'IDBObjectStore': The parameter is not a valid key range.
+[{"_object":{"b":2}}]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithoutAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to execute 'delete' on 'IDBObjectStore': The parameter is not a valid key range.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.WithoutKeyPathOrAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: The object store uses out-of-line keys and has no key generator and the key parameter was not provided.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithoutAutoIncrement
+PASS: Should produce an exception.
+DataError: Failed to store record in an IDBObjectStore: Evaluating the object store's key path did not yield a value.
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithoutAutoIncrement
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1}]
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+deleteObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":99,"b":2}]
+deleteObject: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithAutoIncrement
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1}]
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":1},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+deleteObject: [{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":2}]
+deleteObject: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithAutoIncrement
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1}]
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":42,"a":1},{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+deleteObject: [{"KeyPathSetOnObjectWithAutoIncrement":99,"b":2}]
+deleteObject: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.AutoIncrementWithoutKeyPath
+addObject: [{"a":1}]
+addObject: [{"a":1},{"b":2}]
+deleteObject: [{"b":2}]
+deleteObject: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithoutAutoIncrement.Sub
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1}]
+addObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+deleteObject: [{"KeyPathSetOnObjectWithoutAutoIncrement":{"Sub":99},"b":2}]
+deleteObject: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithAutoIncrement.Sub
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}}]
+addObject: [{"a":1,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":1}},{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+deleteObject: [{"b":2,"KeyPathMissingOnObjectWithAutoIncrement":{"Sub":2}}]
+deleteObject: []
+[]
+
+-- Running test case: WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithAutoIncrement.Sub
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1}]
+addObject: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":42},"a":1},{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+deleteObject: [{"KeyPathSetOnObjectWithAutoIncrement":{"Sub":99},"b":2}]
+deleteObject: []
+[]
+
diff --git a/LayoutTests/inspector/unit-tests/objectStore/deleteObject.html b/LayoutTests/inspector/unit-tests/objectStore/deleteObject.html
new file mode 100644 (file)
index 0000000..7221bbb
--- /dev/null
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/objectStore-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.ObjectStore.createSuite("WI.ObjectStore.prototype.deleteObject");
+
+    function testDeleteObject(name, {options, tests}) {
+        InspectorTest.ObjectStore.wrapTest(name, async function() {
+            InspectorTest.ObjectStore.createObjectStore(options);
+
+            let objects = []
+            for (let {value, expected} of tests) {
+                let object = new InspectorTest.ObjectStore.TestObject(value);
+                await InspectorTest.ObjectStore.addObject(object, expected);
+                objects.push(object);
+            }
+
+            for (let object of objects)
+                await InspectorTest.ObjectStore.deleteObject(object);
+        });
+    }
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.deleteObject.NoParameters", async function() {
+        const options = {
+            autoIncrement: true,
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.ObjectStore.add(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2), 1);
+
+        await InspectorTest.expectException(async () => {
+            await objectStore.deleteObject();
+            await objectStore.deleteObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2));
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.deleteObject.MissingObject", async function() {
+        const options = {
+            autoIncrement: true,
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.ObjectStore.add(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2), 1);
+
+        await InspectorTest.expectException(async () => {
+            await objectStore.deleteObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject1));
+            await objectStore.deleteObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2));
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithoutAutoIncrement", async function() {
+        const options = {
+            keyPath: "KeyPathMissingOnObjectWithoutAutoIncrement",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.deleteObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject1));
+            await objectStore.deleteObject(new InspectorTest.ObjectStore.TestObject(InspectorTest.ObjectStore.basicObject2));
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.deleteObject.WithoutKeyPathOrAutoIncrement", async function() {
+        let objectStore = InspectorTest.ObjectStore.createObjectStore();
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.add(InspectorTest.ObjectStore.basicObject1);
+            await objectStore.add(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    InspectorTest.ObjectStore.wrapTest("WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithoutAutoIncrement", async function() {
+        const options = {
+            keyPath: "KeyPathMissingOnObjectWithoutAutoIncrement",
+        };
+        let objectStore = InspectorTest.ObjectStore.createObjectStore(options);
+
+        await InspectorTest.expectException(async function() {
+            await objectStore.add(InspectorTest.ObjectStore.basicObject1);
+            await objectStore.add(InspectorTest.ObjectStore.basicObject2);
+        });
+    });
+
+    testDeleteObject("WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithoutAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testDeleteObject("WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testDeleteObject("WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithAutoIncrement", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 42, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: 99, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testDeleteObject("WI.ObjectStore.prototype.deleteObject.AutoIncrementWithoutKeyPath", {
+        options: {autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testDeleteObject("WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithoutAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithoutAutoIncrement.Sub"},
+        tests: [
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithoutAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    testDeleteObject("WI.ObjectStore.prototype.deleteObject.KeyPathMissingOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathMissingOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: InspectorTest.ObjectStore.basicObject1, expected: 1},
+            {value: InspectorTest.ObjectStore.basicObject2, expected: 2},
+        ],
+    });
+
+    testDeleteObject("WI.ObjectStore.prototype.deleteObject.KeyPathSetOnObjectWithAutoIncrement.Sub", {
+        options: {keyPath: "KeyPathSetOnObjectWithAutoIncrement.Sub", autoIncrement: true},
+        tests: [
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 42}, ...InspectorTest.ObjectStore.basicObject1}, expected: 42},
+            {value: {KeyPathSetOnObjectWithAutoIncrement: {Sub: 99}, ...InspectorTest.ObjectStore.basicObject2}, expected: 99},
+        ],
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests WI.ObjectStore.prototype.deleteObject.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/unit-tests/objectStore/resources/objectStore-utilities.js b/LayoutTests/inspector/unit-tests/objectStore/resources/objectStore-utilities.js
new file mode 100644 (file)
index 0000000..1947800
--- /dev/null
@@ -0,0 +1,93 @@
+TestPage.registerInitializer(() => {
+    let suite = null;
+
+    InspectorTest.ObjectStore = {};
+
+    InspectorTest.ObjectStore.TestObject = class TestObject {
+        constructor(object) {
+            this._object = object;
+        }
+        toJSON() {
+            return this._object;
+        }
+    };
+
+    InspectorTest.ObjectStore.basicObject1 = {a: 1};
+    InspectorTest.ObjectStore.basicObject2 = {b: 2};
+
+    InspectorTest.ObjectStore.createSuite = function(name) {
+        suite = InspectorTest.createAsyncSuite(name);
+        return suite;
+    };
+
+    InspectorTest.ObjectStore.createObjectStore = function(options = {}) {
+        WI.ObjectStore.__testObjectStore = new WI.ObjectStore("__testing", options);
+        return WI.ObjectStore.__testObjectStore;
+    };
+
+    InspectorTest.ObjectStore.add = async function(value, expected) {
+        let result = await WI.ObjectStore.__testObjectStore.add(value);
+        InspectorTest.assert(result === expected, `the key of the added item should be ${expected}, but is actually ${result}`);
+
+        await InspectorTest.ObjectStore.logValues("add: ");
+        return result;
+    };
+
+    InspectorTest.ObjectStore.addObject = async function(object, expected) {
+        let result = await WI.ObjectStore.__testObjectStore.addObject(object);
+        InspectorTest.assert(result === expected, `the key of the added item should be ${expected}, but is actually ${result}`);
+
+        let resolved = WI.ObjectStore.__testObjectStore._resolveKeyPath(object);
+        InspectorTest.assert(resolved.value === expected, `the resolved keyPath on the object should equal ${expected}, but is actually ${resolved.value}`);
+
+        await InspectorTest.ObjectStore.logValues("addObject: ");
+        return result;
+    };
+
+    InspectorTest.ObjectStore.delete = async function(value) {
+        let result = await WI.ObjectStore.__testObjectStore.delete(value);
+        InspectorTest.assert(result === undefined, `delete shouldn't return anything`);
+
+        await InspectorTest.ObjectStore.logValues("delete: ");
+    };
+
+    InspectorTest.ObjectStore.deleteObject = async function(object) {
+        let resolved = WI.ObjectStore.__testObjectStore._resolveKeyPath(object);
+        InspectorTest.assert(resolved.key in resolved.object, `the resolved keyPath on the object should exist`);
+
+        let result = await WI.ObjectStore.__testObjectStore.deleteObject(object);
+        InspectorTest.assert(result === undefined, `deleteObject shouldn't return anything`);
+
+        await InspectorTest.ObjectStore.logValues("deleteObject: ");
+    };
+
+    InspectorTest.ObjectStore.logValues = async function(prefix) {
+        if (!WI.ObjectStore.__testObjectStore)
+            return;
+
+        prefix = prefix || "";
+        let results = await WI.ObjectStore.__testObjectStore.getAll();
+        InspectorTest.log(prefix + JSON.stringify(results));
+    };
+
+    InspectorTest.ObjectStore.wrapTest = function(name, func) {
+        suite.addTestCase({
+            name,
+            async test() {
+                InspectorTest.assert(!WI.ObjectStore.__testObjectStore, "__testObjectStore should be deleted after each test");
+
+                await func();
+                await InspectorTest.ObjectStore.logValues();
+
+                delete WI.ObjectStore.__testObjectStore;
+
+                if (WI.ObjectStore._database) {
+                    WI.ObjectStore._database.close();
+                    WI.ObjectStore._database = null;
+                }
+
+                indexedDB.deleteDatabase(WI.ObjectStore._databaseName);
+            },
+        });
+    };
+});
index 7e53cbe..e95e00f 100644 (file)
@@ -1,3 +1,58 @@
+2018-10-31  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Audit: save imported audits across WebInspector sessions
+        https://bugs.webkit.org/show_bug.cgi?id=190858
+        <rdar://problem/45527625>
+
+        Reviewed by Brian Burg.
+
+        * UserInterface/Base/ObjectStore.js: Added.
+        (WI.ObjectStore):
+        (WI.ObjectStore.supported):
+        (WI.ObjectStore._open):
+        (WI.ObjectStore.get _databaseName):
+        (WI.ObjectStore.prototype.associateObject):
+        (WI.ObjectStore.prototype.async getAll):
+        (WI.ObjectStore.prototype.async add):
+        (WI.ObjectStore.prototype.async addObject):
+        (WI.ObjectStore.prototype.async delete):
+        (WI.ObjectStore.prototype.async deleteObject):
+        (WI.ObjectStore.prototype._resolveKeyPath):
+        (WI.ObjectStore.prototype.async _operation.listener):
+        (WI.ObjectStore.prototype.async _operation):
+        Wrapper for a global `IndexedDB` instance for all of WebInspector (per level). Instances of
+        `WI.ObjectStore` are able to control a given `IDBObjectStore` using a promise-based API.
+
+        *NOTE*: due to the constraint that `IDBObjectStore`s are only able to be created when the
+        owner `IndexedDB` is "upgrade"d, all `WI.ObjectStore` must be declared before the database
+        is opened for the first time. Additionally, any time a new `WI.ObjectStore` is added, the
+        `version` needs to be incremented to ensure that the "upgrade" event fires.
+
+        To use any of the `*Object` functions, one must implement a `toJSON` on the object provided.
+        This is so that `WI.ObjectStore` is able to add the resulting identifier value to the owner
+        object while storing its `toJSON` value in the IndexedDB (e.g. for objects that have cycles).
+
+        * UserInterface/Controllers/AuditManager.js:
+        (WI.AuditManager.prototype.import):
+        (WI.AuditManager.prototype.loadStoredTests): Added.
+        (WI.AuditManager.prototype.removeTest): Added.
+        (WI.AuditManager.prototype._addTest):
+
+        * UserInterface/Views/AuditTabContentView.js:
+        (WI.AuditTabContentView.prototype.initialLayout): Added.
+        Attempt to load stored audits when the Audit tab is first shown (lazy-load).
+
+        * UserInterface/Views/AuditNavigationSidebarPanel.js:
+        (WI.AuditNavigationSidebarPanel.prototype.initialLayout):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestRemoved): Added.
+
+        * UserInterface/Views/AuditTreeElement.js:
+        (WI.AuditTreeElement.prototype.ondelete):
+        Only allow top-level audits to be deleted, as that is what matches the `WI.ObjectStore`.
+
+        * UserInterface/Main.html:
+        * UserInterface/Test.html:
+
 2018-10-31  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Move a few remaining global WI settings to WI.settings
diff --git a/Source/WebInspectorUI/UserInterface/Base/ObjectStore.js b/Source/WebInspectorUI/UserInterface/Base/ObjectStore.js
new file mode 100644 (file)
index 0000000..b7bf893
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.ObjectStore = class ObjectStore
+{
+    constructor(name, options = {})
+    {
+        this._name = name;
+        this._options = options;
+    }
+
+    // Static
+
+    static supported()
+    {
+        return (!window.InspectorTest || WI.ObjectStore.__testObjectStore) && window.indexedDB;
+    }
+
+    static get _databaseName()
+    {
+        let inspectionLevel = InspectorFrontendHost ? InspectorFrontendHost.inspectionLevel() : 1;
+        let levelString = (inspectionLevel > 1) ? "-" + inspectionLevel : "";
+        return "com.apple.WebInspector" + levelString;
+    }
+
+    static _open(callback)
+    {
+        if (WI.ObjectStore._database) {
+            callback(WI.ObjectStore._database);
+            return;
+        }
+
+        const version = 1; // Increment this for every edit to `WI.objectStores`.
+
+        let databaseRequest = indexedDB.open(WI.ObjectStore._databaseName, version);
+        databaseRequest.addEventListener("upgradeneeded", (event) => {
+            let database = databaseRequest.result;
+
+            let objectStores = Object.values(WI.objectStores);
+            if (WI.ObjectStore.__testObjectStore)
+                objectStores.push(WI.ObjectStore.__testObjectStore);
+
+            let existingNames = new Set;
+            for (let objectStore of objectStores) {
+                if (!database.objectStoreNames.contains(objectStore._name))
+                    database.createObjectStore(objectStore._name, objectStore._options);
+
+                existingNames.add(objectStore._name);
+            }
+
+            for (let objectStoreName of database.objectStoreNames) {
+                if (!existingNames.has(objectStoreName))
+                    database.deleteObjectStore(objectStoreName);
+            }
+        });
+        databaseRequest.addEventListener("success", (successEvent) => {
+            WI.ObjectStore._database = databaseRequest.result;
+            WI.ObjectStore._database.addEventListener("close", (closeEvent) => {
+                WI.ObjectStore._database = null;
+            });
+
+            callback(WI.ObjectStore._database);
+        });
+    }
+
+    // Public
+
+    associateObject(object, key, value)
+    {
+        if (typeof value === "object")
+            value = this._resolveKeyPath(value, key).value;
+
+        let resolved = this._resolveKeyPath(object, key);
+        resolved.object[resolved.key] = value;
+    }
+
+    async getAll(...args)
+    {
+        if (!WI.ObjectStore.supported())
+            return undefined;
+
+        return this._operation("readonly", (objectStore) => objectStore.getAll(...args));
+    }
+
+    async add(...args)
+    {
+        if (!WI.ObjectStore.supported())
+            return undefined;
+
+        return this._operation("readwrite", (objectStore) => objectStore.add(...args));
+    }
+
+    async addObject(object, ...args)
+    {
+        if (!WI.ObjectStore.supported())
+            return undefined;
+
+        console.assert(typeof object.toJSON === "function", "ObjectStore cannot store an object without JSON serialization", object.constructor.name);
+        let result = await this.add(object.toJSON(), ...args);
+        this.associateObject(object, args[0], result);
+        return result;
+    }
+
+    async delete(...args)
+    {
+        if (!WI.ObjectStore.supported())
+            return undefined;
+
+        return this._operation("readwrite", (objectStore) => objectStore.delete(...args));
+    }
+
+    async deleteObject(object, ...args)
+    {
+        if (!WI.ObjectStore.supported())
+            return undefined;
+
+        return this.delete(this._resolveKeyPath(object).value, ...args);
+    }
+
+    // Private
+
+    _resolveKeyPath(object, keyPath)
+    {
+        keyPath = keyPath || this._options.keyPath || "";
+
+        let parts = keyPath.split(".");
+        let key = parts.splice(-1, 1);
+        while (parts.length) {
+            if (!object.hasOwnProperty(parts[0]))
+                break;
+            object = object[parts.shift()];
+        }
+
+        if (parts.length)
+            key = parts.join(".") + "." + key;
+
+        return {
+            object,
+            key,
+            value: object[key],
+        };
+    }
+
+    async _operation(mode, func)
+    {
+        // IndexedDB transactions will auto-close if there are no active operations at the end of a
+        // microtask, so we need to do everything using event listeners instead of promises.
+        return new Promise((resolve, reject) => {
+            WI.ObjectStore._open((database) => {
+                let transaction = database.transaction([this._name], mode);
+                let objectStore = transaction.objectStore(this._name);
+                let request = null;
+
+                try {
+                    request = func(objectStore);
+                } catch (e) {
+                    reject(e);
+                    return;
+                }
+
+                function listener(event) {
+                    transaction.removeEventListener("complete", listener);
+                    transaction.removeEventListener("error", listener);
+                    request.removeEventListener("success", listener);
+                    request.removeEventListener("error", listener);
+
+                    if (request.error) {
+                        reject(request.error);
+                        return;
+                    }
+
+                    resolve(request.result);
+                }
+                transaction.addEventListener("complete", listener, {once: true});
+                transaction.addEventListener("error", listener, {once: true});
+                request.addEventListener("success", listener, {once: true});
+                request.addEventListener("error", listener, {once: true});
+            });
+        });
+    }
+};
+
+WI.ObjectStore._database = null;
+
+// Be sure to update the `version` above when making changes.
+WI.objectStores = {
+    audits: new WI.ObjectStore("audit-manager-tests", {keyPath: "__id", autoIncrement: true}),
+};
index 7dd19cb..df73b57 100644 (file)
@@ -116,9 +116,10 @@ WI.AuditManager = class AuditManager extends WI.Object
                 }
             }
 
-            if (object instanceof WI.AuditTestBase)
+            if (object instanceof WI.AuditTestBase) {
                 this._addTest(object);
-            else if (object instanceof WI.AuditTestResultBase)
+                WI.objectStores.audits.addObject(object);
+            } else if (object instanceof WI.AuditTestResultBase)
                 this._addResult(object);
         });
     }
@@ -140,6 +141,31 @@ WI.AuditManager = class AuditManager extends WI.Object
         });
     }
 
+    loadStoredTests()
+    {
+        WI.objectStores.audits.getAll().then(async (tests) => {
+            for (let payload of tests) {
+                let test = await WI.AuditTestGroup.fromPayload(payload) || await WI.AuditTestCase.fromPayload(payload);
+                if (!test)
+                    continue;
+
+                const key = null;
+                WI.objectStores.audits.associateObject(test, key, payload);
+
+                this._addTest(test);
+            }
+        });
+    }
+
+    removeTest(test)
+    {
+        this._tests.remove(test);
+
+        this.dispatchEventToListeners(WI.AuditManager.Event.TestRemoved, {test});
+
+        WI.objectStores.audits.deleteObject(test);
+    }
+
     // Private
 
     _addTest(test)
@@ -172,5 +198,6 @@ WI.AuditManager.RunningState = {
 WI.AuditManager.Event = {
     TestAdded: "audit-manager-test-added",
     TestCompleted: "audit-manager-test-completed",
+    TestRemoved: "audit-manager-test-removed",
     TestScheduled: "audit-manager-test-scheduled",
 };
index ff76ca5..913e5d8 100644 (file)
     <script src="Base/ImageUtilities.js"></script>
     <script src="Base/LoadLocalizedStrings.js"></script>
     <script src="Base/MIMETypeUtilities.js"></script>
+    <script src="Base/ObjectStore.js"></script>
     <script src="Base/URLUtilities.js"></script>
     <script src="Base/Utilities.js"></script>
     <script src="Base/Setting.js"></script>
index 75dc0b2..145d823 100644 (file)
@@ -56,6 +56,7 @@
     <script src="Base/EventListenerSet.js"></script>
     <script src="Base/ImageUtilities.js"></script>
     <script src="Base/MIMETypeUtilities.js"></script>
+    <script src="Base/ObjectStore.js"></script>
     <script src="Base/URLUtilities.js"></script>
     <script src="Base/Utilities.js"></script>
     <script src="Base/Setting.js"></script>
index 1df8a99..826396e 100644 (file)
@@ -90,6 +90,7 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na
 
         WI.auditManager.addEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this);
         WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this);
+        WI.auditManager.addEventListener(WI.AuditManager.Event.TestRemoved, this._handleAuditTestRemoved, this);
         WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditTestScheduled, this);
 
         this.contentTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
@@ -147,6 +148,13 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na
         this._addResult(result, index);
     }
 
+    _handleAuditTestRemoved(event)
+    {
+        let {test} = event.data;
+        let treeElement = this.treeElementForRepresentedObject(test);
+        this.contentTreeOutline.removeChild(treeElement);
+    }
+
     _handleAuditTestScheduled(event)
     {
         this._updateStartStopButtonNavigationItemState();
index 29f481a..8f54b17 100644 (file)
@@ -83,6 +83,15 @@ WI.AuditTabContentView = class AuditTabContentView extends WI.ContentBrowserTabC
         super.hidden();
     }
 
+    // Protected
+
+    initialLayout()
+    {
+        super.initialLayout();
+
+        WI.auditManager.loadStoredTests();
+    }
+
     // Private
 
     _handleSpace(event)
index f685f6e..9379790 100644 (file)
@@ -98,6 +98,19 @@ WI.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement
         }
     }
 
+    ondelete()
+    {
+        if (!(this.representedObject instanceof WI.AuditTestBase))
+            return false;
+
+        if (!(this.parent instanceof WI.TreeOutline))
+            return false;
+
+        WI.auditManager.removeTest(this.representedObject);
+
+        return true;
+    }
+
     populateContextMenu(contextMenu, event)
     {
         if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive) {