884846e0435353ec81178efb8471e38c6d48b123
[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     get scrollableElements()
121     {
122         if (!this._dataGrid)
123             return [];
124         return [this._dataGrid.scrollContainer];
125     }
126
127     // Private
128
129     _truncateValue(value)
130     {
131         return value.truncate(200);
132     }
133
134     _populate()
135     {
136         this.representedObject.getEntries(function(error, entries) {
137             if (error)
138                 return;
139
140             for (let [key, value] of entries) {
141                 if (!key || !value)
142                     continue;
143
144                 value = this._truncateValue(value);
145                 let node = new WebInspector.DataGridNode({key, value}, false);
146                 this._dataGrid.appendChild(node);
147             }
148
149             this._sortDataGrid();
150             this._dataGrid.addPlaceholderNode();
151             this._dataGrid.updateLayout();
152         }.bind(this));
153     }
154
155     _sortDataGrid()
156     {
157         let sortColumnIdentifier = this._dataGrid.sortColumnIdentifier || "key";
158
159         function comparator(a, b)
160         {
161             return a.data[sortColumnIdentifier].localeCompare(b.data[sortColumnIdentifier]);
162         }
163
164         this._dataGrid.sortNodesImmediately(comparator);
165     }
166
167     _deleteCallback(node)
168     {
169         if (!node || node.isPlaceholderNode)
170             return;
171
172         this._dataGrid.removeChild(node);
173         this.representedObject.removeItem(node.data["key"]);
174     }
175
176     _editingCallback(editingNode, columnIdentifier, oldText, newText, moveDirection)
177     {
178         var key = editingNode.data["key"].trim().removeWordBreakCharacters();
179         var value = editingNode.data["value"].trim().removeWordBreakCharacters();
180         var previousValue = oldText.trim().removeWordBreakCharacters();
181         var enteredValue = newText.trim().removeWordBreakCharacters();
182         var hasUncommittedEdits = editingNode.__hasUncommittedEdits;
183         var hasChange = previousValue !== enteredValue;
184         var isEditingKey = columnIdentifier === "key";
185         var isEditingValue = !isEditingKey;
186         var domStorage = this.representedObject;
187
188         // Nothing changed, just bail.
189         if (!hasChange && !hasUncommittedEdits)
190             return;
191
192         // Something changed, save the original key/value and enter uncommitted state.
193         if (hasChange && !editingNode.__hasUncommittedEdits) {
194             editingNode.__hasUncommittedEdits = true;
195             editingNode.__originalKey = isEditingKey ? previousValue : key;
196             editingNode.__originalValue = isEditingValue ? previousValue : value;
197         }
198
199         function cleanup()
200         {
201             editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
202             editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
203             editingNode.element.classList.remove(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
204             editingNode.__hasUncommittedEdits = undefined;
205             editingNode.__originalKey = undefined;
206             editingNode.__originalValue = undefined;
207         }
208
209         function restoreOriginalValues()
210         {
211             editingNode.data.key = editingNode.__originalKey;
212             editingNode.data.value = editingNode.__originalValue;
213             editingNode.refresh();
214             cleanup();
215         }
216
217         // If the key/value field was cleared, add "missing" style.
218         if (isEditingKey) {
219             if (key.length)
220                 editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
221             else
222                 editingNode.element.classList.add(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
223         } else if (isEditingValue) {
224             if (value.length)
225                 editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
226             else
227                 editingNode.element.classList.add(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
228         }
229
230         // Check for key duplicates. If this is a new row, or an existing row that changed key.
231         var keyChanged = key !== editingNode.__originalKey;
232         if (keyChanged) {
233             if (domStorage.entries.has(key))
234                 editingNode.element.classList.add(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
235             else
236                 editingNode.element.classList.remove(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
237         }
238
239         // See if we are done editing this row or not.
240         var columnIndex = this._dataGrid.orderedColumns.indexOf(columnIdentifier);
241         var mayMoveToNextRow = moveDirection === "forward" && columnIndex === this._dataGrid.orderedColumns.length - 1;
242         var mayMoveToPreviousRow = moveDirection === "backward" && columnIndex === 0;
243         var doneEditing = mayMoveToNextRow || mayMoveToPreviousRow || !moveDirection;
244
245         // Expecting more edits on this row.
246         if (!doneEditing)
247             return;
248
249         // Key and value were cleared, remove the row.
250         if (!key.length && !value.length && !editingNode.isPlaceholderNode) {
251             this._dataGrid.removeChild(editingNode);
252             domStorage.removeItem(editingNode.__originalKey);
253             return;                
254         }
255
256         // Done editing but leaving the row in an invalid state. Leave in uncommitted state.
257         var isDuplicate = editingNode.element.classList.contains(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
258         if (!key.length || !value.length || isDuplicate)
259             return;
260
261         // Commit.
262         if (keyChanged && !editingNode.isPlaceholderNode)
263             domStorage.removeItem(editingNode.__originalKey);
264         if (editingNode.isPlaceholderNode)
265             this._dataGrid.addPlaceholderNode();
266         cleanup();
267         domStorage.setItem(key, value);
268     }
269 };
270
271 WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName = "duplicate-key";
272 WebInspector.DOMStorageContentView.MissingKeyStyleClassName = "missing-key";
273 WebInspector.DOMStorageContentView.MissingValueStyleClassName = "missing-value";