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