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