Home | History | Annotate | Download | only in front_end
      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  */
     35 WebInspector.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 
     49 WebInspector.IndexedDBModel.KeyTypes = {
     50     NumberType:  "number",
     51     StringType:  "string",
     52     DateType:    "date",
     53     ArrayType:   "array"
     54 };
     55 
     56 WebInspector.IndexedDBModel.KeyPathTypes = {
     57     NullType:    "null",
     58     StringType:  "string",
     59     ArrayType:   "array"
     60 };
     61 
     62 WebInspector.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 
     94 WebInspector.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  */
    111 WebInspector.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 
    128 WebInspector.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 
    137 WebInspector.IndexedDBModel.EventTypes = {
    138     DatabaseAdded: "DatabaseAdded",
    139     DatabaseRemoved: "DatabaseRemoved",
    140     DatabaseLoaded: "DatabaseLoaded"
    141 }
    142 
    143 WebInspector.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          */
    270         function callback(error, databaseNames)
    271         {
    272             if (error) {
    273                 console.error("IndexedDBAgent error: " + error);
    274                 return;
    275             }
    276 
    277             if (!this._databaseNamesBySecurityOrigin[securityOrigin])
    278                 return;
    279             this._updateOriginDatabaseNames(securityOrigin, databaseNames);
    280         }
    281 
    282         IndexedDBAgent.requestDatabaseNames(securityOrigin, callback.bind(this));
    283     },
    284 
    285     /**
    286      * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
    287      */
    288     _loadDatabase: function(databaseId)
    289     {
    290         /**
    291          * @param {?Protocol.Error} error
    292          * @param {IndexedDBAgent.DatabaseWithObjectStores} databaseWithObjectStores
    293          */
    294         function callback(error, databaseWithObjectStores)
    295         {
    296             if (error) {
    297                 console.error("IndexedDBAgent error: " + error);
    298                 return;
    299             }
    300 
    301             if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
    302                 return;
    303             var databaseModel = new WebInspector.IndexedDBModel.Database(databaseId, databaseWithObjectStores.version, databaseWithObjectStores.intVersion);
    304             this._databases.put(databaseId, databaseModel);
    305             for (var i = 0; i < databaseWithObjectStores.objectStores.length; ++i) {
    306                 var objectStore = databaseWithObjectStores.objectStores[i];
    307                 var objectStoreIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(objectStore.keyPath);
    308                 var objectStoreModel = new WebInspector.IndexedDBModel.ObjectStore(objectStore.name, objectStoreIDBKeyPath, objectStore.autoIncrement);
    309                 for (var j = 0; j < objectStore.indexes.length; ++j) {
    310                      var index = objectStore.indexes[j];
    311                      var indexIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(index.keyPath);
    312                      var indexModel = new WebInspector.IndexedDBModel.Index(index.name, indexIDBKeyPath, index.unique, index.multiEntry);
    313                      objectStoreModel.indexes[indexModel.name] = indexModel;
    314                 }
    315                 databaseModel.objectStores[objectStoreModel.name] = objectStoreModel;
    316             }
    317 
    318             this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseLoaded, databaseModel);
    319         }
    320 
    321         IndexedDBAgent.requestDatabase(databaseId.securityOrigin, databaseId.name, callback.bind(this));
    322     },
    323 
    324     /**
    325      * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
    326      * @param {string} objectStoreName
    327      * @param {webkitIDBKeyRange} idbKeyRange
    328      * @param {number} skipCount
    329      * @param {number} pageSize
    330      * @param {function(Array.<WebInspector.IndexedDBModel.Entry>, boolean)} callback
    331      */
    332     loadObjectStoreData: function(databaseId, objectStoreName, idbKeyRange, skipCount, pageSize, callback)
    333     {
    334         this._requestData(databaseId, databaseId.name, objectStoreName, "", idbKeyRange, skipCount, pageSize, callback);
    335     },
    336 
    337     /**
    338      * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
    339      * @param {string} objectStoreName
    340      * @param {string} indexName
    341      * @param {webkitIDBKeyRange} idbKeyRange
    342      * @param {number} skipCount
    343      * @param {number} pageSize
    344      * @param {function(Array.<WebInspector.IndexedDBModel.Entry>, boolean)} callback
    345      */
    346     loadIndexData: function(databaseId, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback)
    347     {
    348         this._requestData(databaseId, databaseId.name, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback);
    349     },
    350 
    351     /**
    352      * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
    353      * @param {string} databaseName
    354      * @param {string} objectStoreName
    355      * @param {string} indexName
    356      * @param {webkitIDBKeyRange} idbKeyRange
    357      * @param {number} skipCount
    358      * @param {number} pageSize
    359      * @param {function(Array.<WebInspector.IndexedDBModel.Entry>, boolean)} callback
    360      */
    361     _requestData: function(databaseId, databaseName, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback)
    362     {
    363         /**
    364          * @param {?Protocol.Error} error
    365          * @param {Array.<IndexedDBAgent.DataEntry>} dataEntries
    366          * @param {boolean} hasMore
    367          */
    368         function innerCallback(error, dataEntries, hasMore)
    369         {
    370             if (error) {
    371                 console.error("IndexedDBAgent error: " + error);
    372                 return;
    373             }
    374 
    375             if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
    376                 return;
    377             var entries = [];
    378             for (var i = 0; i < dataEntries.length; ++i) {
    379                 var key = WebInspector.RemoteObject.fromPayload(dataEntries[i].key);
    380                 var primaryKey = WebInspector.RemoteObject.fromPayload(dataEntries[i].primaryKey);
    381                 var value = WebInspector.RemoteObject.fromPayload(dataEntries[i].value);
    382                 entries.push(new WebInspector.IndexedDBModel.Entry(key, primaryKey, value));
    383             }
    384             callback(entries, hasMore);
    385         }
    386 
    387         var keyRange = WebInspector.IndexedDBModel.keyRangeFromIDBKeyRange(idbKeyRange);
    388         IndexedDBAgent.requestData(databaseId.securityOrigin, databaseName, objectStoreName, indexName, skipCount, pageSize, keyRange ? keyRange : undefined, innerCallback.bind(this));
    389     },
    390 
    391     __proto__: WebInspector.Object.prototype
    392 }
    393 
    394 /**
    395  * @constructor
    396  * @param {WebInspector.RemoteObject} key
    397  * @param {WebInspector.RemoteObject} primaryKey
    398  * @param {WebInspector.RemoteObject} value
    399  */
    400 WebInspector.IndexedDBModel.Entry = function(key, primaryKey, value)
    401 {
    402     this.key = key;
    403     this.primaryKey = primaryKey;
    404     this.value = value;
    405 }
    406 
    407 /**
    408  * @constructor
    409  * @param {string} securityOrigin
    410  * @param {string} name
    411  */
    412 WebInspector.IndexedDBModel.DatabaseId = function(securityOrigin, name)
    413 {
    414     this.securityOrigin = securityOrigin;
    415     this.name = name;
    416 }
    417 
    418 WebInspector.IndexedDBModel.DatabaseId.prototype = {
    419     /**
    420      * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
    421      */
    422     equals: function(databaseId)
    423     {
    424         return this.name === databaseId.name && this.securityOrigin === databaseId.securityOrigin;
    425     },
    426 }
    427 /**
    428  * @constructor
    429  * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
    430  * @param {string} version
    431  */
    432 WebInspector.IndexedDBModel.Database = function(databaseId, version, intVersion)
    433 {
    434     this.databaseId = databaseId;
    435     this.version = version;
    436     this.intVersion = intVersion;
    437     this.objectStores = {};
    438 }
    439 
    440 /**
    441  * @constructor
    442  * @param {string} name
    443  * @param {*} keyPath
    444  */
    445 WebInspector.IndexedDBModel.ObjectStore = function(name, keyPath, autoIncrement)
    446 {
    447     this.name = name;
    448     this.keyPath = keyPath;
    449     this.autoIncrement = autoIncrement;
    450     this.indexes = {};
    451 }
    452 
    453 WebInspector.IndexedDBModel.ObjectStore.prototype = {
    454     /**
    455      * @type {string}
    456      */
    457     get keyPathString()
    458     {
    459         return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath);
    460     }
    461 }
    462 
    463 /**
    464  * @constructor
    465  * @param {string} name
    466  * @param {*} keyPath
    467  */
    468 WebInspector.IndexedDBModel.Index = function(name, keyPath, unique, multiEntry)
    469 {
    470     this.name = name;
    471     this.keyPath = keyPath;
    472     this.unique = unique;
    473     this.multiEntry = multiEntry;
    474 }
    475 
    476 WebInspector.IndexedDBModel.Index.prototype = {
    477     /**
    478      * @type {string}
    479      */
    480     get keyPathString()
    481     {
    482         return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath);
    483     }
    484 }
    485