1 /* 2 * Copyright (C) 2008 Nokia Inc. All rights reserved. 3 * Copyright (C) 2013 Samsung Electronics. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 /** 31 * @constructor 32 * @param {string} securityOrigin 33 * @param {boolean} isLocalStorage 34 */ 35 WebInspector.DOMStorage = function(securityOrigin, isLocalStorage) 36 { 37 this._securityOrigin = securityOrigin; 38 this._isLocalStorage = isLocalStorage; 39 this._storageHistory = new WebInspector.DOMStorageHistory(this); 40 } 41 42 /** 43 * @param {string} securityOrigin 44 * @param {boolean} isLocalStorage 45 * @return {DOMStorageAgent.StorageId} 46 */ 47 WebInspector.DOMStorage.storageId = function(securityOrigin, isLocalStorage) 48 { 49 return { securityOrigin: securityOrigin, isLocalStorage: isLocalStorage }; 50 } 51 52 WebInspector.DOMStorage.prototype = { 53 54 /** @return {DOMStorageAgent.StorageId} */ 55 get id() 56 { 57 return WebInspector.DOMStorage.storageId(this._securityOrigin, this._isLocalStorage); 58 }, 59 60 /** @return {string} */ 61 get securityOrigin() 62 { 63 return this._securityOrigin; 64 }, 65 66 /** @return {boolean} */ 67 get isLocalStorage() 68 { 69 return this._isLocalStorage; 70 }, 71 72 /** 73 * @param {function(?Protocol.Error, Array.<DOMStorageAgent.Item>):void=} callback 74 */ 75 getItems: function(callback) 76 { 77 DOMStorageAgent.getDOMStorageItems(this.id, callback); 78 }, 79 80 /** 81 * @param {string} key 82 * @param {string} value 83 */ 84 setItem: function(key, value) 85 { 86 this._storageHistory.perform(new WebInspector.DOMStorageSetItemAction(this, key, value)); 87 }, 88 89 /** 90 * @param {string} key 91 */ 92 removeItem: function(key) 93 { 94 this._storageHistory.perform(new WebInspector.DOMStorageRemoveItemAction(this, key)); 95 }, 96 97 undo: function() 98 { 99 this._storageHistory.undo(); 100 }, 101 102 redo: function() 103 { 104 this._storageHistory.redo(); 105 } 106 } 107 108 /** 109 * @constructor 110 * @param {WebInspector.DOMStorage} domStorage 111 */ 112 WebInspector.DOMStorageAction = function(domStorage) 113 { 114 this._domStorage = domStorage; 115 } 116 117 WebInspector.DOMStorageAction.prototype = { 118 /** 119 * @param {function()} callback 120 */ 121 perform: function(callback) 122 { 123 }, 124 125 undo: function() 126 { 127 }, 128 129 redo: function() 130 { 131 } 132 } 133 134 /** 135 * @constructor 136 * @extends {WebInspector.DOMStorageAction} 137 * @param {WebInspector.DOMStorage} domStorage 138 * @param {string} key 139 */ 140 WebInspector.DOMStorageRemoveItemAction = function(domStorage, key) 141 { 142 WebInspector.DOMStorageAction.call(this, domStorage); 143 this._key = key; 144 } 145 146 WebInspector.DOMStorageRemoveItemAction.prototype = { 147 /** 148 * @override 149 */ 150 perform: function(callback) 151 { 152 DOMStorageAgent.getValue(this._domStorage.id, this._key, valueReceived.bind(this)); 153 154 /** 155 * @param {?Protocol.Error} error 156 * @param {string=} value 157 */ 158 function valueReceived(error, value) 159 { 160 if (error) 161 return; 162 163 this._value = value; 164 this.redo(); 165 callback(); 166 } 167 }, 168 169 /** 170 * @override 171 */ 172 undo: function() 173 { 174 DOMStorageAgent.setDOMStorageItem(this._domStorage.id, this._key, this._value); 175 }, 176 177 /** 178 * @override 179 */ 180 redo: function() 181 { 182 DOMStorageAgent.removeDOMStorageItem(this._domStorage.id, this._key); 183 }, 184 185 __proto__: WebInspector.DOMStorageAction.prototype 186 } 187 188 /** 189 * @constructor 190 * @extends {WebInspector.DOMStorageAction} 191 * @param {WebInspector.DOMStorage} domStorage 192 * @param {string} key 193 * @param {string} value 194 */ 195 WebInspector.DOMStorageSetItemAction = function(domStorage, key, value) 196 { 197 WebInspector.DOMStorageAction.call(this, domStorage); 198 this._key = key; 199 this._value = value; 200 } 201 202 WebInspector.DOMStorageSetItemAction.prototype = { 203 /** 204 * @override 205 */ 206 perform: function(callback) 207 { 208 DOMStorageAgent.getValue(this._domStorage.id, this._key, valueReceived.bind(this)); 209 210 /** 211 * @param {?Protocol.Error} error 212 * @param {string=} value 213 */ 214 function valueReceived(error, value) 215 { 216 if (error) 217 return; 218 219 if (typeof value === "undefined") 220 delete this._exists; 221 else { 222 this._exists = true; 223 this._oldValue = value; 224 } 225 this.redo(); 226 callback(); 227 } 228 }, 229 230 /** 231 * @override 232 */ 233 undo: function() 234 { 235 if (!this._exists) 236 DOMStorageAgent.removeDOMStorageItem(this._domStorage.id, this._key); 237 else 238 DOMStorageAgent.setDOMStorageItem(this._domStorage.id, this._key, this._oldValue); 239 }, 240 241 /** 242 * @override 243 */ 244 redo: function() 245 { 246 DOMStorageAgent.setDOMStorageItem(this._domStorage.id, this._key, this._value); 247 }, 248 249 __proto__: WebInspector.DOMStorageAction.prototype 250 } 251 252 /** 253 * @constructor 254 * @param {WebInspector.DOMStorage} domStorage 255 */ 256 WebInspector.DOMStorageHistory = function(domStorage) 257 { 258 this._domStorage = domStorage; 259 260 /** @type {!Array.<!WebInspector.DOMStorageAction>} */ 261 this._actions = []; 262 this._undoableActionIndex = -1; 263 } 264 265 WebInspector.DOMStorageHistory.MAX_UNDO_STACK_DEPTH = 256; 266 267 WebInspector.DOMStorageHistory.prototype = { 268 /** 269 * @param {WebInspector.DOMStorageAction} action 270 */ 271 perform: function(action) 272 { 273 if (!action) 274 return; 275 276 action.perform(actionCompleted.bind(this)); 277 function actionCompleted() 278 { 279 if (this._undoableActionIndex + 1 === WebInspector.DOMStorageHistory.MAX_UNDO_STACK_DEPTH) { 280 this._actions.shift(); 281 --this._undoableActionIndex; 282 } else if (this._undoableActionIndex + 1 < this._actions.length) 283 this._actions.splice(this._undoableActionIndex + 1); 284 285 this._actions.push(action); 286 ++this._undoableActionIndex; 287 } 288 }, 289 290 undo: function() 291 { 292 if (this._undoableActionIndex < 0) 293 return; 294 295 var action = this._actions[this._undoableActionIndex]; 296 console.assert(action); 297 action.undo(); 298 --this._undoableActionIndex; 299 }, 300 301 redo: function() 302 { 303 if (this._undoableActionIndex >= this._actions.length - 1) 304 return; 305 306 var action = this._actions[++this._undoableActionIndex]; 307 console.assert(action); 308 action.redo(); 309 } 310 } 311 312 /** 313 * @constructor 314 * @extends {WebInspector.Object} 315 */ 316 WebInspector.DOMStorageModel = function() 317 { 318 /** @type {!Object.<string, !WebInspector.DOMStorage>} */ 319 this._storages = {}; 320 InspectorBackend.registerDOMStorageDispatcher(new WebInspector.DOMStorageDispatcher(this)); 321 DOMStorageAgent.enable(); 322 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, this._securityOriginAdded, this); 323 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, this._securityOriginRemoved, this); 324 } 325 326 WebInspector.DOMStorageModel.Events = { 327 DOMStorageAdded: "DOMStorageAdded", 328 DOMStorageRemoved: "DOMStorageRemoved", 329 DOMStorageItemsCleared: "DOMStorageItemsCleared", 330 DOMStorageItemRemoved: "DOMStorageItemRemoved", 331 DOMStorageItemAdded: "DOMStorageItemAdded", 332 DOMStorageItemUpdated: "DOMStorageItemUpdated" 333 } 334 335 WebInspector.DOMStorageModel.prototype = { 336 337 /** 338 * @param {WebInspector.Event} event 339 */ 340 _securityOriginAdded: function(event) 341 { 342 var securityOrigin = /** @type {string} */ (event.data); 343 var localStorageKey = this._storageKey(securityOrigin, true); 344 console.assert(!this._storages[localStorageKey]); 345 var localStorage = new WebInspector.DOMStorage(securityOrigin, true); 346 this._storages[localStorageKey] = localStorage; 347 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageAdded, localStorage); 348 349 var sessionStorageKey = this._storageKey(securityOrigin, false); 350 console.assert(!this._storages[sessionStorageKey]); 351 var sessionStorage = new WebInspector.DOMStorage(securityOrigin, false); 352 this._storages[sessionStorageKey] = sessionStorage; 353 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageAdded, sessionStorage); 354 }, 355 356 /** 357 * @param {WebInspector.Event} event 358 */ 359 _securityOriginRemoved: function(event) 360 { 361 var securityOrigin = /** @type {string} */ (event.data); 362 var localStorageKey = this._storageKey(securityOrigin, true); 363 var localStorage = this._storages[localStorageKey]; 364 console.assert(localStorage); 365 delete this._storages[localStorageKey]; 366 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageRemoved, localStorage); 367 368 var sessionStorageKey = this._storageKey(securityOrigin, false); 369 var sessionStorage = this._storages[sessionStorageKey]; 370 console.assert(sessionStorage); 371 delete this._storages[sessionStorageKey]; 372 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageRemoved, sessionStorage); 373 }, 374 375 /** 376 * @param {string} securityOrigin 377 * @param {boolean} isLocalStorage 378 * @return {string} 379 */ 380 _storageKey: function(securityOrigin, isLocalStorage) 381 { 382 return JSON.stringify(WebInspector.DOMStorage.storageId(securityOrigin, isLocalStorage)); 383 }, 384 385 /** 386 * @param {DOMStorageAgent.StorageId} storageId 387 */ 388 _domStorageItemsCleared: function(storageId) 389 { 390 var domStorage = this.storageForId(storageId); 391 var storageData = { 392 storage: domStorage 393 }; 394 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageItemsCleared, storageData); 395 }, 396 397 /** 398 * @param {DOMStorageAgent.StorageId} storageId 399 * @param {string} key 400 */ 401 _domStorageItemRemoved: function(storageId, key) 402 { 403 var domStorage = this.storageForId(storageId); 404 var storageData = { 405 storage: domStorage, 406 key: key 407 }; 408 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageItemRemoved, storageData); 409 }, 410 411 /** 412 * @param {DOMStorageAgent.StorageId} storageId 413 * @param {string} key 414 * @param {string} newValue 415 */ 416 _domStorageItemAdded: function(storageId, key, newValue) 417 { 418 var domStorage = this.storageForId(storageId); 419 var storageData = { 420 storage: domStorage, 421 key: key, 422 newValue: newValue 423 }; 424 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageItemAdded, storageData); 425 }, 426 427 /** 428 * @param {DOMStorageAgent.StorageId} storageId 429 * @param {string} key 430 * @param {string} oldValue 431 * @param {string} newValue 432 */ 433 _domStorageItemUpdated: function(storageId, key, oldValue, newValue) 434 { 435 var domStorage = this.storageForId(storageId); 436 var storageData = { 437 storage: domStorage, 438 key: key, 439 oldValue: oldValue, 440 newValue: newValue 441 }; 442 this.dispatchEventToListeners(WebInspector.DOMStorageModel.Events.DOMStorageItemUpdated, storageData); 443 }, 444 445 /** 446 * @param {DOMStorageAgent.StorageId} storageId 447 * @return {WebInspector.DOMStorage} 448 */ 449 storageForId: function(storageId) 450 { 451 return this._storages[JSON.stringify(storageId)]; 452 }, 453 454 /** 455 * @return {Array.<WebInspector.DOMStorage>} 456 */ 457 storages: function() 458 { 459 var result = []; 460 for (var id in this._storages) 461 result.push(this._storages[id]); 462 return result; 463 }, 464 465 __proto__: WebInspector.Object.prototype 466 } 467 468 /** 469 * @constructor 470 * @implements {DOMStorageAgent.Dispatcher} 471 * @param {WebInspector.DOMStorageModel} model 472 */ 473 WebInspector.DOMStorageDispatcher = function(model) 474 { 475 this._model = model; 476 } 477 478 WebInspector.DOMStorageDispatcher.prototype = { 479 480 /** 481 * @param {DOMStorageAgent.StorageId} storageId 482 */ 483 domStorageItemsCleared: function(storageId) 484 { 485 this._model._domStorageItemsCleared(storageId); 486 }, 487 488 /** 489 * @param {DOMStorageAgent.StorageId} storageId 490 * @param {string} key 491 */ 492 domStorageItemRemoved: function(storageId, key) 493 { 494 this._model._domStorageItemRemoved(storageId, key); 495 }, 496 497 /** 498 * @param {DOMStorageAgent.StorageId} storageId 499 * @param {string} key 500 * @param {string} newValue 501 */ 502 domStorageItemAdded: function(storageId, key, newValue) 503 { 504 this._model._domStorageItemAdded(storageId, key, newValue); 505 }, 506 507 /** 508 * @param {DOMStorageAgent.StorageId} storageId 509 * @param {string} key 510 * @param {string} oldValue 511 * @param {string} newValue 512 */ 513 domStorageItemUpdated: function(storageId, key, oldValue, newValue) 514 { 515 this._model._domStorageItemUpdated(storageId, key, oldValue, newValue); 516 }, 517 } 518 519 /** 520 * @type {WebInspector.DOMStorageModel} 521 */ 522 WebInspector.domStorageModel = null; 523