Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / resources / IndexedDBModel.js
blob07f6ea26330e513f2536dff86d5a413fd615a49e
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
31 /**
32  * @constructor
33  * @extends {WebInspector.SDKModel}
34  */
35 WebInspector.IndexedDBModel = function(target)
37     WebInspector.SDKModel.call(this, WebInspector.IndexedDBModel, target);
38     this._agent = target.indexedDBAgent();
40     /** @type {!Map.<!WebInspector.IndexedDBModel.DatabaseId, !WebInspector.IndexedDBModel.Database>} */
41     this._databases = new Map();
42     /** @type {!Object.<string, !Array.<string>>} */
43     this._databaseNamesBySecurityOrigin = {};
46 WebInspector.IndexedDBModel.KeyTypes = {
47     NumberType:  "number",
48     StringType:  "string",
49     DateType:    "date",
50     ArrayType:   "array"
53 WebInspector.IndexedDBModel.KeyPathTypes = {
54     NullType:    "null",
55     StringType:  "string",
56     ArrayType:   "array"
59 /**
60  * @param {*} idbKey
61  * @return {?Object}
62  */
63 WebInspector.IndexedDBModel.keyFromIDBKey = function(idbKey)
65     if (typeof(idbKey) === "undefined" || idbKey === null)
66         return null;
68     var key = {};
69     switch (typeof(idbKey)) {
70     case "number":
71         key.number = idbKey;
72         key.type = WebInspector.IndexedDBModel.KeyTypes.NumberType;
73         break;
74     case "string":
75         key.string = idbKey;
76         key.type = WebInspector.IndexedDBModel.KeyTypes.StringType;
77         break;
78     case "object":
79         if (idbKey instanceof Date) {
80             key.date = idbKey.getTime();
81             key.type = WebInspector.IndexedDBModel.KeyTypes.DateType;
82         } else if (Array.isArray(idbKey)) {
83             key.array = [];
84             for (var i = 0; i < idbKey.length; ++i)
85                 key.array.push(WebInspector.IndexedDBModel.keyFromIDBKey(idbKey[i]));
86             key.type = WebInspector.IndexedDBModel.KeyTypes.ArrayType;
87         }
88         break;
89     default:
90         return null;
91     }
92     return key;
95 /**
96  * @param {?IDBKeyRange=} idbKeyRange
97  * @return {?{lower: ?Object, upper: ?Object, lowerOpen: *, upperOpen: *}}
98  */
99 WebInspector.IndexedDBModel.keyRangeFromIDBKeyRange = function(idbKeyRange)
101     if (typeof idbKeyRange === "undefined" || idbKeyRange === null)
102         return null;
104     var keyRange = {};
105     keyRange.lower = WebInspector.IndexedDBModel.keyFromIDBKey(idbKeyRange.lower);
106     keyRange.upper = WebInspector.IndexedDBModel.keyFromIDBKey(idbKeyRange.upper);
107     keyRange.lowerOpen = idbKeyRange.lowerOpen;
108     keyRange.upperOpen = idbKeyRange.upperOpen;
109     return keyRange;
113  * @param {!IndexedDBAgent.KeyPath} keyPath
114  * @return {?string|!Array.<string>|undefined}
115  */
116 WebInspector.IndexedDBModel.idbKeyPathFromKeyPath = function(keyPath)
118     var idbKeyPath;
119     switch (keyPath.type) {
120     case WebInspector.IndexedDBModel.KeyPathTypes.NullType:
121         idbKeyPath = null;
122         break;
123     case WebInspector.IndexedDBModel.KeyPathTypes.StringType:
124         idbKeyPath = keyPath.string;
125         break;
126     case WebInspector.IndexedDBModel.KeyPathTypes.ArrayType:
127         idbKeyPath = keyPath.array;
128         break;
129     }
130     return idbKeyPath;
134  * @param {?string|!Array.<string>|undefined} idbKeyPath
135  * @return {?string}
136  */
137 WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath = function(idbKeyPath)
139     if (typeof idbKeyPath === "string")
140         return "\"" + idbKeyPath + "\"";
141     if (idbKeyPath instanceof Array)
142         return "[\"" + idbKeyPath.join("\", \"") + "\"]";
143     return null;
146 WebInspector.IndexedDBModel.EventTypes = {
147     DatabaseAdded: "DatabaseAdded",
148     DatabaseRemoved: "DatabaseRemoved",
149     DatabaseLoaded: "DatabaseLoaded"
152 WebInspector.IndexedDBModel.prototype = {
153     enable: function()
154     {
155         if (this._enabled)
156             return;
158         this._agent.enable();
160         this.target().resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, this._securityOriginAdded, this);
161         this.target().resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, this._securityOriginRemoved, this);
163         var securityOrigins = this.target().resourceTreeModel.securityOrigins();
164         for (var i = 0; i < securityOrigins.length; ++i)
165             this._addOrigin(securityOrigins[i]);
167         this._enabled = true;
168     },
170     refreshDatabaseNames: function()
171     {
172         for (var securityOrigin in this._databaseNamesBySecurityOrigin)
173             this._loadDatabaseNames(securityOrigin);
174     },
176     /**
177      * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
178      */
179     refreshDatabase: function(databaseId)
180     {
181         this._loadDatabase(databaseId);
182     },
184     /**
185      * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
186      * @param {string} objectStoreName
187      * @param {function()} callback
188      */
189     clearObjectStore: function(databaseId, objectStoreName, callback)
190     {
191         this._agent.clearObjectStore(databaseId.securityOrigin, databaseId.name, objectStoreName, callback);
192     },
194     /**
195      * @param {!WebInspector.Event} event
196      */
197     _securityOriginAdded: function(event)
198     {
199         var securityOrigin = /** @type {string} */ (event.data);
200         this._addOrigin(securityOrigin);
201     },
203     /**
204      * @param {!WebInspector.Event} event
205      */
206     _securityOriginRemoved: function(event)
207     {
208         var securityOrigin = /** @type {string} */ (event.data);
209         this._removeOrigin(securityOrigin);
210     },
212     /**
213      * @param {string} securityOrigin
214      */
215     _addOrigin: function(securityOrigin)
216     {
217         console.assert(!this._databaseNamesBySecurityOrigin[securityOrigin]);
218         this._databaseNamesBySecurityOrigin[securityOrigin] = [];
219         this._loadDatabaseNames(securityOrigin);
220     },
222     /**
223      * @param {string} securityOrigin
224      */
225     _removeOrigin: function(securityOrigin)
226     {
227         console.assert(this._databaseNamesBySecurityOrigin[securityOrigin]);
228         for (var i = 0; i < this._databaseNamesBySecurityOrigin[securityOrigin].length; ++i)
229             this._databaseRemoved(securityOrigin, this._databaseNamesBySecurityOrigin[securityOrigin][i]);
230         delete this._databaseNamesBySecurityOrigin[securityOrigin];
231     },
233     /**
234      * @param {string} securityOrigin
235      * @param {!Array.<string>} databaseNames
236      */
237     _updateOriginDatabaseNames: function(securityOrigin, databaseNames)
238     {
239         var newDatabaseNames = databaseNames.keySet();
240         var oldDatabaseNames = this._databaseNamesBySecurityOrigin[securityOrigin].keySet();
242         this._databaseNamesBySecurityOrigin[securityOrigin] = databaseNames;
244         for (var databaseName in oldDatabaseNames) {
245             if (!newDatabaseNames[databaseName])
246                 this._databaseRemoved(securityOrigin, databaseName);
247         }
248         for (var databaseName in newDatabaseNames) {
249             if (!oldDatabaseNames[databaseName])
250                 this._databaseAdded(securityOrigin, databaseName);
251         }
252     },
254     /**
255      * @return {!Array.<!WebInspector.IndexedDBModel.DatabaseId>}
256      */
257     databases: function()
258     {
259         var result = [];
260         for (var securityOrigin in this._databaseNamesBySecurityOrigin) {
261             var databaseNames = this._databaseNamesBySecurityOrigin[securityOrigin];
262             for (var i = 0; i < databaseNames.length; ++i) {
263                 result.push(new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseNames[i]));
264             }
265         }
266         return result;
267     },
269     /**
270      * @param {string} securityOrigin
271      * @param {string} databaseName
272      */
273     _databaseAdded: function(securityOrigin, databaseName)
274     {
275         var databaseId = new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseName);
276         this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseAdded, databaseId);
277     },
279     /**
280      * @param {string} securityOrigin
281      * @param {string} databaseName
282      */
283     _databaseRemoved: function(securityOrigin, databaseName)
284     {
285         var databaseId = new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseName);
286         this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseRemoved, databaseId);
287     },
289     /**
290      * @param {string} securityOrigin
291      */
292     _loadDatabaseNames: function(securityOrigin)
293     {
294         /**
295          * @param {?Protocol.Error} error
296          * @param {!Array.<string>} databaseNames
297          * @this {WebInspector.IndexedDBModel}
298          */
299         function callback(error, databaseNames)
300         {
301             if (error) {
302                 console.error("IndexedDBAgent error: " + error);
303                 return;
304             }
306             if (!this._databaseNamesBySecurityOrigin[securityOrigin])
307                 return;
308             this._updateOriginDatabaseNames(securityOrigin, databaseNames);
309         }
311         this._agent.requestDatabaseNames(securityOrigin, callback.bind(this));
312     },
314     /**
315      * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
316      */
317     _loadDatabase: function(databaseId)
318     {
319         /**
320          * @param {?Protocol.Error} error
321          * @param {!IndexedDBAgent.DatabaseWithObjectStores} databaseWithObjectStores
322          * @this {WebInspector.IndexedDBModel}
323          */
324         function callback(error, databaseWithObjectStores)
325         {
326             if (error) {
327                 console.error("IndexedDBAgent error: " + error);
328                 return;
329             }
331             if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
332                 return;
333             var databaseModel = new WebInspector.IndexedDBModel.Database(databaseId, databaseWithObjectStores.version, databaseWithObjectStores.intVersion);
334             this._databases.set(databaseId, databaseModel);
335             for (var i = 0; i < databaseWithObjectStores.objectStores.length; ++i) {
336                 var objectStore = databaseWithObjectStores.objectStores[i];
337                 var objectStoreIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(objectStore.keyPath);
338                 var objectStoreModel = new WebInspector.IndexedDBModel.ObjectStore(objectStore.name, objectStoreIDBKeyPath, objectStore.autoIncrement);
339                 for (var j = 0; j < objectStore.indexes.length; ++j) {
340                      var index = objectStore.indexes[j];
341                      var indexIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(index.keyPath);
342                      var indexModel = new WebInspector.IndexedDBModel.Index(index.name, indexIDBKeyPath, index.unique, index.multiEntry);
343                      objectStoreModel.indexes[indexModel.name] = indexModel;
344                 }
345                 databaseModel.objectStores[objectStoreModel.name] = objectStoreModel;
346             }
348             this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseLoaded, databaseModel);
349         }
351         this._agent.requestDatabase(databaseId.securityOrigin, databaseId.name, callback.bind(this));
352     },
354     /**
355      * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
356      * @param {string} objectStoreName
357      * @param {?IDBKeyRange} idbKeyRange
358      * @param {number} skipCount
359      * @param {number} pageSize
360      * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback
361      */
362     loadObjectStoreData: function(databaseId, objectStoreName, idbKeyRange, skipCount, pageSize, callback)
363     {
364         this._requestData(databaseId, databaseId.name, objectStoreName, "", idbKeyRange, skipCount, pageSize, callback);
365     },
367     /**
368      * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
369      * @param {string} objectStoreName
370      * @param {string} indexName
371      * @param {?IDBKeyRange} idbKeyRange
372      * @param {number} skipCount
373      * @param {number} pageSize
374      * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback
375      */
376     loadIndexData: function(databaseId, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback)
377     {
378         this._requestData(databaseId, databaseId.name, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback);
379     },
381     /**
382      * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
383      * @param {string} databaseName
384      * @param {string} objectStoreName
385      * @param {string} indexName
386      * @param {?IDBKeyRange} idbKeyRange
387      * @param {number} skipCount
388      * @param {number} pageSize
389      * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback
390      */
391     _requestData: function(databaseId, databaseName, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback)
392     {
393         /**
394          * @param {?Protocol.Error} error
395          * @param {!Array.<!IndexedDBAgent.DataEntry>} dataEntries
396          * @param {boolean} hasMore
397          * @this {WebInspector.IndexedDBModel}
398          */
399         function innerCallback(error, dataEntries, hasMore)
400         {
401             if (error) {
402                 console.error("IndexedDBAgent error: " + error);
403                 return;
404             }
406             if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
407                 return;
408             var entries = [];
409             for (var i = 0; i < dataEntries.length; ++i) {
410                 var key = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].key));
411                 var primaryKey = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].primaryKey));
412                 var value = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].value));
413                 entries.push(new WebInspector.IndexedDBModel.Entry(key, primaryKey, value));
414             }
415             callback(entries, hasMore);
416         }
418         var keyRange = WebInspector.IndexedDBModel.keyRangeFromIDBKeyRange(idbKeyRange);
419         this._agent.requestData(databaseId.securityOrigin, databaseName, objectStoreName, indexName, skipCount, pageSize, keyRange ? keyRange : undefined, innerCallback.bind(this));
420     },
422     __proto__: WebInspector.SDKModel.prototype
426  * @constructor
427  * @param {!WebInspector.RemoteObject} key
428  * @param {!WebInspector.RemoteObject} primaryKey
429  * @param {!WebInspector.RemoteObject} value
430  */
431 WebInspector.IndexedDBModel.Entry = function(key, primaryKey, value)
433     this.key = key;
434     this.primaryKey = primaryKey;
435     this.value = value;
439  * @constructor
440  * @param {string} securityOrigin
441  * @param {string} name
442  */
443 WebInspector.IndexedDBModel.DatabaseId = function(securityOrigin, name)
445     this.securityOrigin = securityOrigin;
446     this.name = name;
449 WebInspector.IndexedDBModel.DatabaseId.prototype = {
450     /**
451      * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
452      * @return {boolean}
453      */
454     equals: function(databaseId)
455     {
456         return this.name === databaseId.name && this.securityOrigin === databaseId.securityOrigin;
457     },
460  * @constructor
461  * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
462  * @param {string} version
463  * @param {number} intVersion
464  */
465 WebInspector.IndexedDBModel.Database = function(databaseId, version, intVersion)
467     this.databaseId = databaseId;
468     this.version = version;
469     this.intVersion = intVersion;
470     this.objectStores = {};
474  * @constructor
475  * @param {string} name
476  * @param {*} keyPath
477  * @param {boolean} autoIncrement
478  */
479 WebInspector.IndexedDBModel.ObjectStore = function(name, keyPath, autoIncrement)
481     this.name = name;
482     this.keyPath = keyPath;
483     this.autoIncrement = autoIncrement;
484     this.indexes = {};
487 WebInspector.IndexedDBModel.ObjectStore.prototype = {
488     /**
489      * @type {string}
490      */
491     get keyPathString()
492     {
493         return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath);
494     }
498  * @constructor
499  * @param {string} name
500  * @param {*} keyPath
501  * @param {boolean} unique
502  * @param {boolean} multiEntry
503  */
504 WebInspector.IndexedDBModel.Index = function(name, keyPath, unique, multiEntry)
506     this.name = name;
507     this.keyPath = keyPath;
508     this.unique = unique;
509     this.multiEntry = multiEntry;
512 WebInspector.IndexedDBModel.Index.prototype = {
513     /**
514      * @type {string}
515      */
516     get keyPathString()
517     {
518         return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath);
519     }
523  * @param {!WebInspector.Target} target
524  * @return {?WebInspector.IndexedDBModel}
525  */
526 WebInspector.IndexedDBModel.fromTarget = function(target)
528     var model = /** @type {?WebInspector.IndexedDBModel} */ (target.model(WebInspector.IndexedDBModel));
529     if (!model)
530         model = new WebInspector.IndexedDBModel(target);
531     return model;