Web Inspector: scrolled Snapshot list is reset to top and drawn blank after switching...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / DOMStorageContentView.js
1 /*
2  * Copyright (C) 2013-2015 Apple 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  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 WebInspector.DOMStorageContentView = class DOMStorageContentView extends WebInspector.ContentView
28 {
29     constructor(representedObject)
30     {
31         super(representedObject);
32
33         this.element.classList.add("dom-storage");
34
35         representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemsCleared, this.itemsCleared, this);
36         representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemAdded, this.itemAdded, this);
37         representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemRemoved, this.itemRemoved, this);
38         representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemUpdated, this.itemUpdated, this);
39
40         let columns = {};
41         columns.key = {title: WebInspector.UIString("Key"), sortable: true};
42         columns.value = {title: WebInspector.UIString("Value"), sortable: true};
43
44         this._dataGrid = new WebInspector.DataGrid(columns, this._editingCallback.bind(this), this._deleteCallback.bind(this));
45         this._dataGrid.sortOrder = WebInspector.DataGrid.SortOrder.Ascending;
46         this._dataGrid.sortColumnIdentifier = "key";
47         this._dataGrid.createSettings("dom-storage-content-view");
48         this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, this._sortDataGrid, this);
49
50         this.addSubview(this._dataGrid);
51
52         this._populate();
53     }
54
55     // Public
56
57     saveToCookie(cookie)
58     {
59         cookie.type = WebInspector.ContentViewCookieType.DOMStorage;
60         cookie.isLocalStorage = this.representedObject.isLocalStorage();
61         cookie.host = this.representedObject.host;
62     }
63
64     get scrollableElements()
65     {
66         return [this._dataGrid.scrollContainer];
67     }
68
69     itemsCleared(event)
70     {
71         this._dataGrid.removeChildren();
72         this._dataGrid.addPlaceholderNode();
73     }
74
75     itemRemoved(event)
76     {
77         for (let node of this._dataGrid.children) {
78             if (node.data.key === event.data.key)
79                 return this._dataGrid.removeChild(node);
80         }
81     }
82
83     itemAdded(event)
84     {
85         let {key, value} = event.data;
86         value = this._truncateValue(value);
87
88         // Enforce key uniqueness.
89         for (let node of this._dataGrid.children) {
90             if (node.data.key === key)
91                 return;
92         }
93
94         this._dataGrid.appendChild(new WebInspector.DataGridNode({key, value}, false));
95         this._sortDataGrid();
96     }
97
98     itemUpdated(event)
99     {
100         let {key, value} = event.data;
101         value = this._truncateValue(value);
102
103         let keyFound = false;
104         for (let childNode of this._dataGrid.children) {
105             if (childNode.data.key === key) {
106                 // Remove any rows that are now duplicates.
107                 if (keyFound) {
108                     this._dataGrid.removeChild(childNode);
109                     continue;
110                 }
111
112                 keyFound = true;
113                 childNode.data.value = value;
114                 childNode.refresh();
115             }
116         }
117         this._sortDataGrid();
118     }
119
120     // Private
121
122     _truncateValue(value)
123     {
124         return value.truncate(200);
125     }
126
127     _populate()
128     {
129         this.representedObject.getEntries(function(error, entries) {
130             if (error)
131                 return;
132
133             for (let [key, value] of entries) {
134                 if (!key || !value)
135                     continue;
136
137                 value = this._truncateValue(value);
138                 let node = new WebInspector.DataGridNode({key, value}, false);
139                 this._dataGrid.appendChild(node);
140             }
141
142             this._sortDataGrid();
143             this._dataGrid.addPlaceholderNode();
144             this._dataGrid.updateLayout();
145         }.bind(this));
146     }
147
148     _sortDataGrid()
149     {
150         let sortColumnIdentifier = this._dataGrid.sortColumnIdentifier || "key";
151
152         function comparator(a, b)
153         {
154             return a.data[sortColumnIdentifier].localeCompare(b.data[sortColumnIdentifier]);
155         }
156
157         this._dataGrid.sortNodesImmediately(comparator);
158     }
159
160     _deleteCallback(node)
161     {
162         if (!node || node.isPlaceholderNode)
163             return;
164
165         this._dataGrid.removeChild(node);
166         this.representedObject.removeItem(node.data["key"]);
167     }
168
169     _editingCallback(editingNode, columnIdentifier, oldText, newText, moveDirection)
170     {
171         var key = editingNode.data["key"].trim().removeWordBreakCharacters();
172         var value = editingNode.data["value"].trim().removeWordBreakCharacters();
173         var previousValue = oldText.trim().removeWordBreakCharacters();
174         var enteredValue = newText.trim().removeWordBreakCharacters();
175         var hasUncommittedEdits = editingNode.__hasUncommittedEdits;
176         var hasChange = previousValue !== enteredValue;
177         var isEditingKey = columnIdentifier === "key";
178         var isEditingValue = !isEditingKey;
179         var domStorage = this.representedObject;
180
181         // Nothing changed, just bail.
182         if (!hasChange && !hasUncommittedEdits)
183             return;
184
185         // Something changed, save the original key/value and enter uncommitted state.
186         if (hasChange && !editingNode.__hasUncommittedEdits) {
187             editingNode.__hasUncommittedEdits = true;
188             editingNode.__originalKey = isEditingKey ? previousValue : key;
189             editingNode.__originalValue = isEditingValue ? previousValue : value;
190         }
191
192         function cleanup()
193         {
194             editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
195             editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
196             editingNode.element.classList.remove(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
197             editingNode.__hasUncommittedEdits = undefined;
198             editingNode.__originalKey = undefined;
199             editingNode.__originalValue = undefined;
200         }
201
202         function restoreOriginalValues()
203         {
204             editingNode.data.key = editingNode.__originalKey;
205             editingNode.data.value = editingNode.__originalValue;
206             editingNode.refresh();
207             cleanup();
208         }
209
210         // If the key/value field was cleared, add "missing" style.
211         if (isEditingKey) {
212             if (key.length)
213                 editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
214             else
215                 editingNode.element.classList.add(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
216         } else if (isEditingValue) {
217             if (value.length)
218                 editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
219             else
220                 editingNode.element.classList.add(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
221         }
222
223         // Check for key duplicates. If this is a new row, or an existing row that changed key.
224         var keyChanged = key !== editingNode.__originalKey;
225         if (keyChanged) {
226             if (domStorage.entries.has(key))
227                 editingNode.element.classList.add(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
228             else
229                 editingNode.element.classList.remove(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
230         }
231
232         // See if we are done editing this row or not.
233         var columnIndex = this._dataGrid.orderedColumns.indexOf(columnIdentifier);
234         var mayMoveToNextRow = moveDirection === "forward" && columnIndex === this._dataGrid.orderedColumns.length - 1;
235         var mayMoveToPreviousRow = moveDirection === "backward" && columnIndex === 0;
236         var doneEditing = mayMoveToNextRow || mayMoveToPreviousRow || !moveDirection;
237
238         // Expecting more edits on this row.
239         if (!doneEditing)
240             return;
241
242         // Key and value were cleared, remove the row.
243         if (!key.length && !value.length && !editingNode.isPlaceholderNode) {
244             this._dataGrid.removeChild(editingNode);
245             domStorage.removeItem(editingNode.__originalKey);
246             return;                
247         }
248
249         // Done editing but leaving the row in an invalid state. Leave in uncommitted state.
250         var isDuplicate = editingNode.element.classList.contains(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
251         if (!key.length || !value.length || isDuplicate)
252             return;
253
254         // Commit.
255         if (keyChanged && !editingNode.isPlaceholderNode)
256             domStorage.removeItem(editingNode.__originalKey);
257         if (editingNode.isPlaceholderNode)
258             this._dataGrid.addPlaceholderNode();
259         cleanup();
260         domStorage.setItem(key, value);
261     }
262 };
263
264 WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName = "duplicate-key";
265 WebInspector.DOMStorageContentView.MissingKeyStyleClassName = "missing-key";
266 WebInspector.DOMStorageContentView.MissingValueStyleClassName = "missing-value";