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