• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.TargetAwareObject}
34 */
35WebInspector.IndexedDBModel = function(target)
36{
37    WebInspector.TargetAwareObject.call(this, target);
38    this._agent = target.indexedDBAgent();
39    this._agent.enable();
40
41    target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, this._securityOriginAdded, this);
42    target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, this._securityOriginRemoved, this);
43
44    /** @type {!Map.<!WebInspector.IndexedDBModel.DatabaseId, !WebInspector.IndexedDBModel.Database>} */
45    this._databases = new Map();
46    /** @type {!Object.<string, !Array.<string>>} */
47    this._databaseNamesBySecurityOrigin = {};
48    this._reset();
49}
50
51WebInspector.IndexedDBModel.KeyTypes = {
52    NumberType:  "number",
53    StringType:  "string",
54    DateType:    "date",
55    ArrayType:   "array"
56};
57
58WebInspector.IndexedDBModel.KeyPathTypes = {
59    NullType:    "null",
60    StringType:  "string",
61    ArrayType:   "array"
62};
63
64/**
65 * @param {*} idbKey
66 * @return {?Object}
67 */
68WebInspector.IndexedDBModel.keyFromIDBKey = function(idbKey)
69{
70    if (typeof(idbKey) === "undefined" || idbKey === null)
71        return null;
72
73    var key = {};
74    switch (typeof(idbKey)) {
75    case "number":
76        key.number = idbKey;
77        key.type = WebInspector.IndexedDBModel.KeyTypes.NumberType;
78        break;
79    case "string":
80        key.string = idbKey;
81        key.type = WebInspector.IndexedDBModel.KeyTypes.StringType;
82        break;
83    case "object":
84        if (idbKey instanceof Date) {
85            key.date = idbKey.getTime();
86            key.type = WebInspector.IndexedDBModel.KeyTypes.DateType;
87        } else if (idbKey instanceof Array) {
88            key.array = [];
89            for (var i = 0; i < idbKey.length; ++i)
90                key.array.push(WebInspector.IndexedDBModel.keyFromIDBKey(idbKey[i]));
91            key.type = WebInspector.IndexedDBModel.KeyTypes.ArrayType;
92        }
93        break;
94    default:
95        return null;
96    }
97    return key;
98}
99
100/**
101 * @param {?IDBKeyRange=} idbKeyRange
102 * @return {?{lower: ?Object, upper: ?Object, lowerOpen: *, upperOpen: *}}
103 */
104WebInspector.IndexedDBModel.keyRangeFromIDBKeyRange = function(idbKeyRange)
105{
106    if (typeof idbKeyRange === "undefined" || idbKeyRange === null)
107        return null;
108
109    var keyRange = {};
110    keyRange.lower = WebInspector.IndexedDBModel.keyFromIDBKey(idbKeyRange.lower);
111    keyRange.upper = WebInspector.IndexedDBModel.keyFromIDBKey(idbKeyRange.upper);
112    keyRange.lowerOpen = idbKeyRange.lowerOpen;
113    keyRange.upperOpen = idbKeyRange.upperOpen;
114    return keyRange;
115}
116
117/**
118 * @param {!IndexedDBAgent.KeyPath} keyPath
119 * @return {?string|!Array.<string>|undefined}
120 */
121WebInspector.IndexedDBModel.idbKeyPathFromKeyPath = function(keyPath)
122{
123    var idbKeyPath;
124    switch (keyPath.type) {
125    case WebInspector.IndexedDBModel.KeyPathTypes.NullType:
126        idbKeyPath = null;
127        break;
128    case WebInspector.IndexedDBModel.KeyPathTypes.StringType:
129        idbKeyPath = keyPath.string;
130        break;
131    case WebInspector.IndexedDBModel.KeyPathTypes.ArrayType:
132        idbKeyPath = keyPath.array;
133        break;
134    }
135    return idbKeyPath;
136}
137
138/**
139 * @param {?string|!Array.<string>|undefined} idbKeyPath
140 * @return {?string}
141 */
142WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath = function(idbKeyPath)
143{
144    if (typeof idbKeyPath === "string")
145        return "\"" + idbKeyPath + "\"";
146    if (idbKeyPath instanceof Array)
147        return "[\"" + idbKeyPath.join("\", \"") + "\"]";
148    return null;
149}
150
151WebInspector.IndexedDBModel.EventTypes = {
152    DatabaseAdded: "DatabaseAdded",
153    DatabaseRemoved: "DatabaseRemoved",
154    DatabaseLoaded: "DatabaseLoaded"
155}
156
157WebInspector.IndexedDBModel.prototype = {
158    _reset: function()
159    {
160        for (var securityOrigin in this._databaseNamesBySecurityOrigin)
161            this._removeOrigin(securityOrigin);
162        var securityOrigins = this.target().resourceTreeModel.securityOrigins();
163        for (var i = 0; i < securityOrigins.length; ++i)
164            this._addOrigin(securityOrigins[i]);
165    },
166
167    refreshDatabaseNames: function()
168    {
169        for (var securityOrigin in this._databaseNamesBySecurityOrigin)
170            this._loadDatabaseNames(securityOrigin);
171    },
172
173    /**
174     * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
175     */
176    refreshDatabase: function(databaseId)
177    {
178        this._loadDatabase(databaseId);
179    },
180
181    /**
182     * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
183     * @param {string} objectStoreName
184     * @param {function()} callback
185     */
186    clearObjectStore: function(databaseId, objectStoreName, callback)
187    {
188        this._agent.clearObjectStore(databaseId.securityOrigin, databaseId.name, objectStoreName, callback);
189    },
190
191    /**
192     * @param {!WebInspector.Event} event
193     */
194    _securityOriginAdded: function(event)
195    {
196        var securityOrigin = /** @type {string} */ (event.data);
197        this._addOrigin(securityOrigin);
198    },
199
200    /**
201     * @param {!WebInspector.Event} event
202     */
203    _securityOriginRemoved: function(event)
204    {
205        var securityOrigin = /** @type {string} */ (event.data);
206        this._removeOrigin(securityOrigin);
207    },
208
209    /**
210     * @param {string} securityOrigin
211     */
212    _addOrigin: function(securityOrigin)
213    {
214        console.assert(!this._databaseNamesBySecurityOrigin[securityOrigin]);
215        this._databaseNamesBySecurityOrigin[securityOrigin] = [];
216        this._loadDatabaseNames(securityOrigin);
217    },
218
219    /**
220     * @param {string} securityOrigin
221     */
222    _removeOrigin: function(securityOrigin)
223    {
224        console.assert(this._databaseNamesBySecurityOrigin[securityOrigin]);
225        for (var i = 0; i < this._databaseNamesBySecurityOrigin[securityOrigin].length; ++i)
226            this._databaseRemoved(securityOrigin, this._databaseNamesBySecurityOrigin[securityOrigin][i]);
227        delete this._databaseNamesBySecurityOrigin[securityOrigin];
228    },
229
230    /**
231     * @param {string} securityOrigin
232     * @param {!Array.<string>} databaseNames
233     */
234    _updateOriginDatabaseNames: function(securityOrigin, databaseNames)
235    {
236        var newDatabaseNames = {};
237        for (var i = 0; i < databaseNames.length; ++i)
238            newDatabaseNames[databaseNames[i]] = true;
239        var oldDatabaseNames = {};
240        for (var i = 0; i < this._databaseNamesBySecurityOrigin[securityOrigin].length; ++i)
241            oldDatabaseNames[this._databaseNamesBySecurityOrigin[securityOrigin][i]] = true;
242
243        this._databaseNamesBySecurityOrigin[securityOrigin] = databaseNames;
244
245        for (var databaseName in oldDatabaseNames) {
246            if (!newDatabaseNames[databaseName])
247                this._databaseRemoved(securityOrigin, databaseName);
248        }
249        for (var databaseName in newDatabaseNames) {
250            if (!oldDatabaseNames[databaseName])
251                this._databaseAdded(securityOrigin, databaseName);
252        }
253    },
254
255    /**
256     * @param {string} securityOrigin
257     * @param {string} databaseName
258     */
259    _databaseAdded: function(securityOrigin, databaseName)
260    {
261        var databaseId = new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseName);
262        this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseAdded, databaseId);
263    },
264
265    /**
266     * @param {string} securityOrigin
267     * @param {string} databaseName
268     */
269    _databaseRemoved: function(securityOrigin, databaseName)
270    {
271        var databaseId = new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseName);
272        this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseRemoved, databaseId);
273    },
274
275    /**
276     * @param {string} securityOrigin
277     */
278    _loadDatabaseNames: function(securityOrigin)
279    {
280        /**
281         * @param {?Protocol.Error} error
282         * @param {!Array.<string>} databaseNames
283         * @this {WebInspector.IndexedDBModel}
284         */
285        function callback(error, databaseNames)
286        {
287            if (error) {
288                console.error("IndexedDBAgent error: " + error);
289                return;
290            }
291
292            if (!this._databaseNamesBySecurityOrigin[securityOrigin])
293                return;
294            this._updateOriginDatabaseNames(securityOrigin, databaseNames);
295        }
296
297        this._agent.requestDatabaseNames(securityOrigin, callback.bind(this));
298    },
299
300    /**
301     * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
302     */
303    _loadDatabase: function(databaseId)
304    {
305        /**
306         * @param {?Protocol.Error} error
307         * @param {!IndexedDBAgent.DatabaseWithObjectStores} databaseWithObjectStores
308         * @this {WebInspector.IndexedDBModel}
309         */
310        function callback(error, databaseWithObjectStores)
311        {
312            if (error) {
313                console.error("IndexedDBAgent error: " + error);
314                return;
315            }
316
317            if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
318                return;
319            var databaseModel = new WebInspector.IndexedDBModel.Database(databaseId, databaseWithObjectStores.version, databaseWithObjectStores.intVersion);
320            this._databases.put(databaseId, databaseModel);
321            for (var i = 0; i < databaseWithObjectStores.objectStores.length; ++i) {
322                var objectStore = databaseWithObjectStores.objectStores[i];
323                var objectStoreIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(objectStore.keyPath);
324                var objectStoreModel = new WebInspector.IndexedDBModel.ObjectStore(objectStore.name, objectStoreIDBKeyPath, objectStore.autoIncrement);
325                for (var j = 0; j < objectStore.indexes.length; ++j) {
326                     var index = objectStore.indexes[j];
327                     var indexIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(index.keyPath);
328                     var indexModel = new WebInspector.IndexedDBModel.Index(index.name, indexIDBKeyPath, index.unique, index.multiEntry);
329                     objectStoreModel.indexes[indexModel.name] = indexModel;
330                }
331                databaseModel.objectStores[objectStoreModel.name] = objectStoreModel;
332            }
333
334            this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseLoaded, databaseModel);
335        }
336
337        this._agent.requestDatabase(databaseId.securityOrigin, databaseId.name, callback.bind(this));
338    },
339
340    /**
341     * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
342     * @param {string} objectStoreName
343     * @param {?IDBKeyRange} idbKeyRange
344     * @param {number} skipCount
345     * @param {number} pageSize
346     * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback
347     */
348    loadObjectStoreData: function(databaseId, objectStoreName, idbKeyRange, skipCount, pageSize, callback)
349    {
350        this._requestData(databaseId, databaseId.name, objectStoreName, "", idbKeyRange, skipCount, pageSize, callback);
351    },
352
353    /**
354     * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
355     * @param {string} objectStoreName
356     * @param {string} indexName
357     * @param {?IDBKeyRange} idbKeyRange
358     * @param {number} skipCount
359     * @param {number} pageSize
360     * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback
361     */
362    loadIndexData: function(databaseId, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback)
363    {
364        this._requestData(databaseId, databaseId.name, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback);
365    },
366
367    /**
368     * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
369     * @param {string} databaseName
370     * @param {string} objectStoreName
371     * @param {string} indexName
372     * @param {?IDBKeyRange} idbKeyRange
373     * @param {number} skipCount
374     * @param {number} pageSize
375     * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback
376     */
377    _requestData: function(databaseId, databaseName, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback)
378    {
379        /**
380         * @param {?Protocol.Error} error
381         * @param {!Array.<!IndexedDBAgent.DataEntry>} dataEntries
382         * @param {boolean} hasMore
383         * @this {WebInspector.IndexedDBModel}
384         */
385        function innerCallback(error, dataEntries, hasMore)
386        {
387            if (error) {
388                console.error("IndexedDBAgent error: " + error);
389                return;
390            }
391
392            if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
393                return;
394            var entries = [];
395            for (var i = 0; i < dataEntries.length; ++i) {
396                var key = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].key));
397                var primaryKey = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].primaryKey));
398                var value = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].value));
399                entries.push(new WebInspector.IndexedDBModel.Entry(key, primaryKey, value));
400            }
401            callback(entries, hasMore);
402        }
403
404        var keyRange = WebInspector.IndexedDBModel.keyRangeFromIDBKeyRange(idbKeyRange);
405        this._agent.requestData(databaseId.securityOrigin, databaseName, objectStoreName, indexName, skipCount, pageSize, keyRange ? keyRange : undefined, innerCallback.bind(this));
406    },
407
408    __proto__: WebInspector.TargetAwareObject.prototype
409}
410
411/**
412 * @constructor
413 * @param {!WebInspector.RemoteObject} key
414 * @param {!WebInspector.RemoteObject} primaryKey
415 * @param {!WebInspector.RemoteObject} value
416 */
417WebInspector.IndexedDBModel.Entry = function(key, primaryKey, value)
418{
419    this.key = key;
420    this.primaryKey = primaryKey;
421    this.value = value;
422}
423
424/**
425 * @constructor
426 * @param {string} securityOrigin
427 * @param {string} name
428 */
429WebInspector.IndexedDBModel.DatabaseId = function(securityOrigin, name)
430{
431    this.securityOrigin = securityOrigin;
432    this.name = name;
433}
434
435WebInspector.IndexedDBModel.DatabaseId.prototype = {
436    /**
437     * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
438     * @return {boolean}
439     */
440    equals: function(databaseId)
441    {
442        return this.name === databaseId.name && this.securityOrigin === databaseId.securityOrigin;
443    },
444}
445/**
446 * @constructor
447 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
448 * @param {string} version
449 * @param {number} intVersion
450 */
451WebInspector.IndexedDBModel.Database = function(databaseId, version, intVersion)
452{
453    this.databaseId = databaseId;
454    this.version = version;
455    this.intVersion = intVersion;
456    this.objectStores = {};
457}
458
459/**
460 * @constructor
461 * @param {string} name
462 * @param {*} keyPath
463 * @param {boolean} autoIncrement
464 */
465WebInspector.IndexedDBModel.ObjectStore = function(name, keyPath, autoIncrement)
466{
467    this.name = name;
468    this.keyPath = keyPath;
469    this.autoIncrement = autoIncrement;
470    this.indexes = {};
471}
472
473WebInspector.IndexedDBModel.ObjectStore.prototype = {
474    /**
475     * @type {string}
476     */
477    get keyPathString()
478    {
479        return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath);
480    }
481}
482
483/**
484 * @constructor
485 * @param {string} name
486 * @param {*} keyPath
487 * @param {boolean} unique
488 * @param {boolean} multiEntry
489 */
490WebInspector.IndexedDBModel.Index = function(name, keyPath, unique, multiEntry)
491{
492    this.name = name;
493    this.keyPath = keyPath;
494    this.unique = unique;
495    this.multiEntry = multiEntry;
496}
497
498WebInspector.IndexedDBModel.Index.prototype = {
499    /**
500     * @type {string}
501     */
502    get keyPathString()
503    {
504        return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath);
505    }
506}
507