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 */
35WebInspector.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 */
47WebInspector.DOMStorage.storageId = function(securityOrigin, isLocalStorage)
48{
49    return { securityOrigin: securityOrigin, isLocalStorage: isLocalStorage };
50}
51
52WebInspector.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 */
112WebInspector.DOMStorageAction = function(domStorage)
113{
114    this._domStorage = domStorage;
115}
116
117WebInspector.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 */
140WebInspector.DOMStorageRemoveItemAction = function(domStorage, key)
141{
142    WebInspector.DOMStorageAction.call(this, domStorage);
143    this._key = key;
144}
145
146WebInspector.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 */
195WebInspector.DOMStorageSetItemAction = function(domStorage, key, value)
196{
197    WebInspector.DOMStorageAction.call(this, domStorage);
198    this._key = key;
199    this._value = value;
200}
201
202WebInspector.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 */
256WebInspector.DOMStorageHistory = function(domStorage)
257{
258    this._domStorage = domStorage;
259
260    /** @type {!Array.<!WebInspector.DOMStorageAction>} */
261    this._actions = [];
262    this._undoableActionIndex = -1;
263}
264
265WebInspector.DOMStorageHistory.MAX_UNDO_STACK_DEPTH = 256;
266
267WebInspector.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 */
316WebInspector.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
326WebInspector.DOMStorageModel.Events = {
327    DOMStorageAdded: "DOMStorageAdded",
328    DOMStorageRemoved: "DOMStorageRemoved",
329    DOMStorageItemsCleared: "DOMStorageItemsCleared",
330    DOMStorageItemRemoved: "DOMStorageItemRemoved",
331    DOMStorageItemAdded: "DOMStorageItemAdded",
332    DOMStorageItemUpdated: "DOMStorageItemUpdated"
333}
334
335WebInspector.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 */
473WebInspector.DOMStorageDispatcher = function(model)
474{
475    this._model = model;
476}
477
478WebInspector.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 */
522WebInspector.domStorageModel = null;
523