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.SDKModel} 34 */ 35WebInspector.IndexedDBModel = function(target) 36{ 37 WebInspector.SDKModel.call(this, WebInspector.IndexedDBModel, 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 = databaseNames.keySet(); 237 var oldDatabaseNames = this._databaseNamesBySecurityOrigin[securityOrigin].keySet(); 238 239 this._databaseNamesBySecurityOrigin[securityOrigin] = databaseNames; 240 241 for (var databaseName in oldDatabaseNames) { 242 if (!newDatabaseNames[databaseName]) 243 this._databaseRemoved(securityOrigin, databaseName); 244 } 245 for (var databaseName in newDatabaseNames) { 246 if (!oldDatabaseNames[databaseName]) 247 this._databaseAdded(securityOrigin, databaseName); 248 } 249 }, 250 251 /** 252 * @return {!Array.<!WebInspector.IndexedDBModel.DatabaseId>} 253 */ 254 databases: function() 255 { 256 var result = []; 257 for (var securityOrigin in this._databaseNamesBySecurityOrigin) { 258 var databaseNames = this._databaseNamesBySecurityOrigin[securityOrigin]; 259 for (var i = 0; i < databaseNames.length; ++i) { 260 result.push(new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseNames[i])); 261 } 262 } 263 return result; 264 }, 265 266 /** 267 * @param {string} securityOrigin 268 * @param {string} databaseName 269 */ 270 _databaseAdded: function(securityOrigin, databaseName) 271 { 272 var databaseId = new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseName); 273 this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseAdded, databaseId); 274 }, 275 276 /** 277 * @param {string} securityOrigin 278 * @param {string} databaseName 279 */ 280 _databaseRemoved: function(securityOrigin, databaseName) 281 { 282 var databaseId = new WebInspector.IndexedDBModel.DatabaseId(securityOrigin, databaseName); 283 this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseRemoved, databaseId); 284 }, 285 286 /** 287 * @param {string} securityOrigin 288 */ 289 _loadDatabaseNames: function(securityOrigin) 290 { 291 /** 292 * @param {?Protocol.Error} error 293 * @param {!Array.<string>} databaseNames 294 * @this {WebInspector.IndexedDBModel} 295 */ 296 function callback(error, databaseNames) 297 { 298 if (error) { 299 console.error("IndexedDBAgent error: " + error); 300 return; 301 } 302 303 if (!this._databaseNamesBySecurityOrigin[securityOrigin]) 304 return; 305 this._updateOriginDatabaseNames(securityOrigin, databaseNames); 306 } 307 308 this._agent.requestDatabaseNames(securityOrigin, callback.bind(this)); 309 }, 310 311 /** 312 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId 313 */ 314 _loadDatabase: function(databaseId) 315 { 316 /** 317 * @param {?Protocol.Error} error 318 * @param {!IndexedDBAgent.DatabaseWithObjectStores} databaseWithObjectStores 319 * @this {WebInspector.IndexedDBModel} 320 */ 321 function callback(error, databaseWithObjectStores) 322 { 323 if (error) { 324 console.error("IndexedDBAgent error: " + error); 325 return; 326 } 327 328 if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin]) 329 return; 330 var databaseModel = new WebInspector.IndexedDBModel.Database(databaseId, databaseWithObjectStores.version, databaseWithObjectStores.intVersion); 331 this._databases.set(databaseId, databaseModel); 332 for (var i = 0; i < databaseWithObjectStores.objectStores.length; ++i) { 333 var objectStore = databaseWithObjectStores.objectStores[i]; 334 var objectStoreIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(objectStore.keyPath); 335 var objectStoreModel = new WebInspector.IndexedDBModel.ObjectStore(objectStore.name, objectStoreIDBKeyPath, objectStore.autoIncrement); 336 for (var j = 0; j < objectStore.indexes.length; ++j) { 337 var index = objectStore.indexes[j]; 338 var indexIDBKeyPath = WebInspector.IndexedDBModel.idbKeyPathFromKeyPath(index.keyPath); 339 var indexModel = new WebInspector.IndexedDBModel.Index(index.name, indexIDBKeyPath, index.unique, index.multiEntry); 340 objectStoreModel.indexes[indexModel.name] = indexModel; 341 } 342 databaseModel.objectStores[objectStoreModel.name] = objectStoreModel; 343 } 344 345 this.dispatchEventToListeners(WebInspector.IndexedDBModel.EventTypes.DatabaseLoaded, databaseModel); 346 } 347 348 this._agent.requestDatabase(databaseId.securityOrigin, databaseId.name, callback.bind(this)); 349 }, 350 351 /** 352 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId 353 * @param {string} objectStoreName 354 * @param {?IDBKeyRange} idbKeyRange 355 * @param {number} skipCount 356 * @param {number} pageSize 357 * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback 358 */ 359 loadObjectStoreData: function(databaseId, objectStoreName, idbKeyRange, skipCount, pageSize, callback) 360 { 361 this._requestData(databaseId, databaseId.name, objectStoreName, "", idbKeyRange, skipCount, pageSize, callback); 362 }, 363 364 /** 365 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId 366 * @param {string} objectStoreName 367 * @param {string} indexName 368 * @param {?IDBKeyRange} idbKeyRange 369 * @param {number} skipCount 370 * @param {number} pageSize 371 * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback 372 */ 373 loadIndexData: function(databaseId, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback) 374 { 375 this._requestData(databaseId, databaseId.name, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback); 376 }, 377 378 /** 379 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId 380 * @param {string} databaseName 381 * @param {string} objectStoreName 382 * @param {string} indexName 383 * @param {?IDBKeyRange} idbKeyRange 384 * @param {number} skipCount 385 * @param {number} pageSize 386 * @param {function(!Array.<!WebInspector.IndexedDBModel.Entry>, boolean)} callback 387 */ 388 _requestData: function(databaseId, databaseName, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback) 389 { 390 /** 391 * @param {?Protocol.Error} error 392 * @param {!Array.<!IndexedDBAgent.DataEntry>} dataEntries 393 * @param {boolean} hasMore 394 * @this {WebInspector.IndexedDBModel} 395 */ 396 function innerCallback(error, dataEntries, hasMore) 397 { 398 if (error) { 399 console.error("IndexedDBAgent error: " + error); 400 return; 401 } 402 403 if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin]) 404 return; 405 var entries = []; 406 for (var i = 0; i < dataEntries.length; ++i) { 407 var key = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].key)); 408 var primaryKey = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].primaryKey)); 409 var value = WebInspector.RemoteObject.fromLocalObject(JSON.parse(dataEntries[i].value)); 410 entries.push(new WebInspector.IndexedDBModel.Entry(key, primaryKey, value)); 411 } 412 callback(entries, hasMore); 413 } 414 415 var keyRange = WebInspector.IndexedDBModel.keyRangeFromIDBKeyRange(idbKeyRange); 416 this._agent.requestData(databaseId.securityOrigin, databaseName, objectStoreName, indexName, skipCount, pageSize, keyRange ? keyRange : undefined, innerCallback.bind(this)); 417 }, 418 419 __proto__: WebInspector.SDKModel.prototype 420} 421 422/** 423 * @constructor 424 * @param {!WebInspector.RemoteObject} key 425 * @param {!WebInspector.RemoteObject} primaryKey 426 * @param {!WebInspector.RemoteObject} value 427 */ 428WebInspector.IndexedDBModel.Entry = function(key, primaryKey, value) 429{ 430 this.key = key; 431 this.primaryKey = primaryKey; 432 this.value = value; 433} 434 435/** 436 * @constructor 437 * @param {string} securityOrigin 438 * @param {string} name 439 */ 440WebInspector.IndexedDBModel.DatabaseId = function(securityOrigin, name) 441{ 442 this.securityOrigin = securityOrigin; 443 this.name = name; 444} 445 446WebInspector.IndexedDBModel.DatabaseId.prototype = { 447 /** 448 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId 449 * @return {boolean} 450 */ 451 equals: function(databaseId) 452 { 453 return this.name === databaseId.name && this.securityOrigin === databaseId.securityOrigin; 454 }, 455} 456/** 457 * @constructor 458 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId 459 * @param {string} version 460 * @param {number} intVersion 461 */ 462WebInspector.IndexedDBModel.Database = function(databaseId, version, intVersion) 463{ 464 this.databaseId = databaseId; 465 this.version = version; 466 this.intVersion = intVersion; 467 this.objectStores = {}; 468} 469 470/** 471 * @constructor 472 * @param {string} name 473 * @param {*} keyPath 474 * @param {boolean} autoIncrement 475 */ 476WebInspector.IndexedDBModel.ObjectStore = function(name, keyPath, autoIncrement) 477{ 478 this.name = name; 479 this.keyPath = keyPath; 480 this.autoIncrement = autoIncrement; 481 this.indexes = {}; 482} 483 484WebInspector.IndexedDBModel.ObjectStore.prototype = { 485 /** 486 * @type {string} 487 */ 488 get keyPathString() 489 { 490 return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath); 491 } 492} 493 494/** 495 * @constructor 496 * @param {string} name 497 * @param {*} keyPath 498 * @param {boolean} unique 499 * @param {boolean} multiEntry 500 */ 501WebInspector.IndexedDBModel.Index = function(name, keyPath, unique, multiEntry) 502{ 503 this.name = name; 504 this.keyPath = keyPath; 505 this.unique = unique; 506 this.multiEntry = multiEntry; 507} 508 509WebInspector.IndexedDBModel.Index.prototype = { 510 /** 511 * @type {string} 512 */ 513 get keyPathString() 514 { 515 return WebInspector.IndexedDBModel.keyPathStringFromIDBKeyPath(this.keyPath); 516 } 517} 518