Web Inspector: Audit: allow audits to be enabled/disabled
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Base / ObjectStore.js
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.ObjectStore = class ObjectStore
27 {
28     constructor(name, options = {})
29     {
30         this._name = name;
31         this._options = options;
32     }
33
34     // Static
35
36     static supported()
37     {
38         return (!window.InspectorTest || WI.ObjectStore.__testObjectStore) && window.indexedDB;
39     }
40
41     static get _databaseName()
42     {
43         let inspectionLevel = InspectorFrontendHost ? InspectorFrontendHost.inspectionLevel() : 1;
44         let levelString = (inspectionLevel > 1) ? "-" + inspectionLevel : "";
45         return "com.apple.WebInspector" + levelString;
46     }
47
48     static _open(callback)
49     {
50         if (WI.ObjectStore._database) {
51             callback(WI.ObjectStore._database);
52             return;
53         }
54
55         if (Array.isArray(WI.ObjectStore._databaseCallbacks)) {
56             WI.ObjectStore._databaseCallbacks.push(callback);
57             return;
58         }
59
60         WI.ObjectStore._databaseCallbacks = [callback];
61
62         const version = 1; // Increment this for every edit to `WI.objectStores`.
63
64         let databaseRequest = indexedDB.open(WI.ObjectStore._databaseName, version);
65         databaseRequest.addEventListener("upgradeneeded", (event) => {
66             let database = databaseRequest.result;
67
68             let objectStores = Object.values(WI.objectStores);
69             if (WI.ObjectStore.__testObjectStore)
70                 objectStores.push(WI.ObjectStore.__testObjectStore);
71
72             let existingNames = new Set;
73             for (let objectStore of objectStores) {
74                 if (!database.objectStoreNames.contains(objectStore._name))
75                     database.createObjectStore(objectStore._name, objectStore._options);
76
77                 existingNames.add(objectStore._name);
78             }
79
80             for (let objectStoreName of database.objectStoreNames) {
81                 if (!existingNames.has(objectStoreName))
82                     database.deleteObjectStore(objectStoreName);
83             }
84         });
85         databaseRequest.addEventListener("success", (successEvent) => {
86             WI.ObjectStore._database = databaseRequest.result;
87             WI.ObjectStore._database.addEventListener("close", (closeEvent) => {
88                 WI.ObjectStore._database = null;
89             });
90
91             for (let databaseCallback of WI.ObjectStore._databaseCallbacks)
92                 databaseCallback(WI.ObjectStore._database);
93
94             WI.ObjectStore._databaseCallbacks = null;
95         });
96     }
97
98     // Public
99
100     associateObject(object, key, value)
101     {
102         if (typeof value === "object")
103             value = this._resolveKeyPath(value, key).value;
104
105         let resolved = this._resolveKeyPath(object, key);
106         resolved.object[resolved.key] = value;
107     }
108
109     async getAll(...args)
110     {
111         if (!WI.ObjectStore.supported())
112             return undefined;
113
114         return this._operation("readonly", (objectStore) => objectStore.getAll(...args));
115     }
116
117     async add(...args)
118     {
119         if (!WI.ObjectStore.supported())
120             return undefined;
121
122         return this._operation("readwrite", (objectStore) => objectStore.add(...args));
123     }
124
125     async addObject(object, ...args)
126     {
127         if (!WI.ObjectStore.supported())
128             return undefined;
129
130         console.assert(typeof object.toJSON === "function", "ObjectStore cannot store an object without JSON serialization", object.constructor.name);
131         let result = await this.add(object.toJSON(WI.ObjectStore.toJSONSymbol), ...args);
132         this.associateObject(object, args[0], result);
133         return result;
134     }
135
136     async delete(...args)
137     {
138         if (!WI.ObjectStore.supported())
139             return undefined;
140
141         return this._operation("readwrite", (objectStore) => objectStore.delete(...args));
142     }
143
144     async deleteObject(object, ...args)
145     {
146         if (!WI.ObjectStore.supported())
147             return undefined;
148
149         return this.delete(this._resolveKeyPath(object).value, ...args);
150     }
151
152     async clear(...args)
153     {
154         if (!WI.ObjectStore.supported())
155             return undefined;
156
157         return this._operation("readwrite", (objectStore) => objectStore.clear(...args));
158     }
159
160     // Private
161
162     _resolveKeyPath(object, keyPath)
163     {
164         keyPath = keyPath || this._options.keyPath || "";
165
166         let parts = keyPath.split(".");
167         let key = parts.splice(-1, 1);
168         while (parts.length) {
169             if (!object.hasOwnProperty(parts[0]))
170                 break;
171             object = object[parts.shift()];
172         }
173
174         if (parts.length)
175             key = parts.join(".") + "." + key;
176
177         return {
178             object,
179             key,
180             value: object[key],
181         };
182     }
183
184     async _operation(mode, func)
185     {
186         // IndexedDB transactions will auto-close if there are no active operations at the end of a
187         // microtask, so we need to do everything using event listeners instead of promises.
188         return new Promise((resolve, reject) => {
189             WI.ObjectStore._open((database) => {
190                 let transaction = database.transaction([this._name], mode);
191                 let objectStore = transaction.objectStore(this._name);
192                 let request = null;
193
194                 try {
195                     request = func(objectStore);
196                 } catch (e) {
197                     reject(e);
198                     return;
199                 }
200
201                 function listener(event) {
202                     transaction.removeEventListener("complete", listener);
203                     transaction.removeEventListener("error", listener);
204                     request.removeEventListener("success", listener);
205                     request.removeEventListener("error", listener);
206
207                     if (request.error) {
208                         reject(request.error);
209                         return;
210                     }
211
212                     resolve(request.result);
213                 }
214                 transaction.addEventListener("complete", listener, {once: true});
215                 transaction.addEventListener("error", listener, {once: true});
216                 request.addEventListener("success", listener, {once: true});
217                 request.addEventListener("error", listener, {once: true});
218             });
219         });
220     }
221 };
222
223 WI.ObjectStore._database = null;
224 WI.ObjectStore._databaseCallbacks = null;
225
226 WI.ObjectStore.toJSONSymbol = Symbol("ObjectStore-toJSON");
227
228 // Be sure to update the `version` above when making changes.
229 WI.objectStores = {
230     audits: new WI.ObjectStore("audit-manager-tests", {keyPath: "__id", autoIncrement: true}),
231 };