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