• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.View}
34 * @param {!WebInspector.IndexedDBModel.Database} database
35 */
36WebInspector.IDBDatabaseView = function(database)
37{
38    WebInspector.View.call(this);
39    this.registerRequiredCSS("indexedDBViews.css");
40
41    this.element.classList.add("fill");
42    this.element.classList.add("indexed-db-database-view");
43
44    this._headersListElement = this.element.createChild("ol", "outline-disclosure");
45    this._headersTreeOutline = new TreeOutline(this._headersListElement);
46    this._headersTreeOutline.expandTreeElementsWhenArrowing = true;
47
48    this._securityOriginTreeElement = new TreeElement("", null, false);
49    this._securityOriginTreeElement.selectable = false;
50    this._headersTreeOutline.appendChild(this._securityOriginTreeElement);
51
52    this._nameTreeElement = new TreeElement("", null, false);
53    this._nameTreeElement.selectable = false;
54    this._headersTreeOutline.appendChild(this._nameTreeElement);
55
56    this._intVersionTreeElement = new TreeElement("", null, false);
57    this._intVersionTreeElement.selectable = false;
58    this._headersTreeOutline.appendChild(this._intVersionTreeElement);
59
60    this._stringVersionTreeElement = new TreeElement("", null, false);
61    this._stringVersionTreeElement.selectable = false;
62    this._headersTreeOutline.appendChild(this._stringVersionTreeElement);
63
64    this.update(database);
65}
66
67WebInspector.IDBDatabaseView.prototype = {
68    /**
69     * @param {string} name
70     * @param {string} value
71     */
72    _formatHeader: function(name, value)
73    {
74        var fragment = document.createDocumentFragment();
75        fragment.createChild("div", "attribute-name").textContent = name + ":";
76        fragment.createChild("div", "attribute-value source-code").textContent = value;
77
78        return fragment;
79    },
80
81    _refreshDatabase: function()
82    {
83        this._securityOriginTreeElement.title = this._formatHeader(WebInspector.UIString("Security origin"), this._database.databaseId.securityOrigin);
84        this._nameTreeElement.title = this._formatHeader(WebInspector.UIString("Name"), this._database.databaseId.name);
85        this._stringVersionTreeElement.title = this._formatHeader(WebInspector.UIString("String Version"), this._database.version);
86        this._intVersionTreeElement.title = this._formatHeader(WebInspector.UIString("Integer Version"), this._database.intVersion);
87    },
88
89    /**
90     * @param {!WebInspector.IndexedDBModel.Database} database
91     */
92    update: function(database)
93    {
94        this._database = database;
95        this._refreshDatabase();
96    },
97
98    __proto__: WebInspector.View.prototype
99}
100
101
102/**
103 * @constructor
104 * @extends {WebInspector.View}
105 * @param {!WebInspector.IndexedDBModel} model
106 * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId
107 * @param {!WebInspector.IndexedDBModel.ObjectStore} objectStore
108 * @param {?WebInspector.IndexedDBModel.Index} index
109 */
110WebInspector.IDBDataView = function(model, databaseId, objectStore, index)
111{
112    WebInspector.View.call(this);
113    this.registerRequiredCSS("indexedDBViews.css");
114
115    this._model = model;
116    this._databaseId = databaseId;
117    this._isIndex = !!index;
118
119    this.element.classList.add("indexed-db-data-view");
120
121    var editorToolbar = this._createEditorToolbar();
122    this.element.appendChild(editorToolbar);
123
124    this._dataGridContainer = this.element.createChild("div", "fill");
125    this._dataGridContainer.classList.add("data-grid-container");
126
127    this._refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
128    this._refreshButton.addEventListener("click", this._refreshButtonClicked, this);
129
130    this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear object store"), "clear-storage-status-bar-item");
131    this._clearButton.addEventListener("click", this._clearButtonClicked, this);
132
133    this._pageSize = 50;
134    this._skipCount = 0;
135
136    this.update(objectStore, index);
137    this._entries = [];
138}
139
140WebInspector.IDBDataView.prototype = {
141    /**
142     * @return {!WebInspector.DataGrid}
143     */
144    _createDataGrid: function()
145    {
146        var keyPath = this._isIndex ? this._index.keyPath : this._objectStore.keyPath;
147
148        var columns = [];
149        columns.push({id: "number", title: WebInspector.UIString("#"), width: "50px"});
150        columns.push({id: "key", titleDOMFragment: this._keyColumnHeaderFragment(WebInspector.UIString("Key"), keyPath)});
151        if (this._isIndex)
152            columns.push({id: "primaryKey", titleDOMFragment: this._keyColumnHeaderFragment(WebInspector.UIString("Primary key"), this._objectStore.keyPath)});
153        columns.push({id: "value", title: WebInspector.UIString("Value")});
154
155        var dataGrid = new WebInspector.DataGrid(columns);
156        return dataGrid;
157    },
158
159    /**
160     * @param {string} prefix
161     * @param {*} keyPath
162     * @return {!DocumentFragment}
163     */
164    _keyColumnHeaderFragment: function(prefix, keyPath)
165    {
166        var keyColumnHeaderFragment = document.createDocumentFragment();
167        keyColumnHeaderFragment.appendChild(document.createTextNode(prefix));
168        if (keyPath === null)
169            return keyColumnHeaderFragment;
170
171        keyColumnHeaderFragment.appendChild(document.createTextNode(" (" + WebInspector.UIString("Key path: ")));
172        if (keyPath instanceof Array) {
173            keyColumnHeaderFragment.appendChild(document.createTextNode("["));
174            for (var i = 0; i < keyPath.length; ++i) {
175                if (i != 0)
176                    keyColumnHeaderFragment.appendChild(document.createTextNode(", "));
177                keyColumnHeaderFragment.appendChild(this._keyPathStringFragment(keyPath[i]));
178            }
179            keyColumnHeaderFragment.appendChild(document.createTextNode("]"));
180        } else {
181            var keyPathString = /** @type {string} */ (keyPath);
182            keyColumnHeaderFragment.appendChild(this._keyPathStringFragment(keyPathString));
183        }
184        keyColumnHeaderFragment.appendChild(document.createTextNode(")"));
185        return keyColumnHeaderFragment;
186    },
187
188    /**
189     * @param {string} keyPathString
190     * @return {!DocumentFragment}
191     */
192    _keyPathStringFragment: function(keyPathString)
193    {
194        var keyPathStringFragment = document.createDocumentFragment();
195        keyPathStringFragment.appendChild(document.createTextNode("\""));
196        var keyPathSpan = keyPathStringFragment.createChild("span", "source-code console-formatted-string");
197        keyPathSpan.textContent = keyPathString;
198        keyPathStringFragment.appendChild(document.createTextNode("\""));
199        return keyPathStringFragment;
200    },
201
202    /**
203     * @return {!Element}
204     */
205    _createEditorToolbar: function()
206    {
207        var editorToolbar = document.createElement("div");
208        editorToolbar.classList.add("status-bar");
209        editorToolbar.classList.add("data-view-toolbar");
210
211        this._pageBackButton = editorToolbar.createChild("button", "back-button");
212        this._pageBackButton.classList.add("status-bar-item");
213        this._pageBackButton.title = WebInspector.UIString("Show previous page.");
214        this._pageBackButton.disabled = true;
215        this._pageBackButton.appendChild(document.createElement("img"));
216        this._pageBackButton.addEventListener("click", this._pageBackButtonClicked.bind(this), false);
217        editorToolbar.appendChild(this._pageBackButton);
218
219        this._pageForwardButton = editorToolbar.createChild("button", "forward-button");
220        this._pageForwardButton.classList.add("status-bar-item");
221        this._pageForwardButton.title = WebInspector.UIString("Show next page.");
222        this._pageForwardButton.disabled = true;
223        this._pageForwardButton.appendChild(document.createElement("img"));
224        this._pageForwardButton.addEventListener("click", this._pageForwardButtonClicked.bind(this), false);
225        editorToolbar.appendChild(this._pageForwardButton);
226
227        this._keyInputElement = editorToolbar.createChild("input", "key-input");
228        this._keyInputElement.placeholder = WebInspector.UIString("Start from key");
229        this._keyInputElement.addEventListener("paste", this._keyInputChanged.bind(this));
230        this._keyInputElement.addEventListener("cut", this._keyInputChanged.bind(this));
231        this._keyInputElement.addEventListener("keypress", this._keyInputChanged.bind(this));
232        this._keyInputElement.addEventListener("keydown", this._keyInputChanged.bind(this));
233
234        return editorToolbar;
235    },
236
237    _pageBackButtonClicked: function()
238    {
239        this._skipCount = Math.max(0, this._skipCount - this._pageSize);
240        this._updateData(false);
241    },
242
243    _pageForwardButtonClicked: function()
244    {
245        this._skipCount = this._skipCount + this._pageSize;
246        this._updateData(false);
247    },
248
249    _keyInputChanged: function()
250    {
251        window.setTimeout(this._updateData.bind(this, false), 0);
252    },
253
254    /**
255     * @param {!WebInspector.IndexedDBModel.ObjectStore} objectStore
256     * @param {?WebInspector.IndexedDBModel.Index} index
257     */
258    update: function(objectStore, index)
259    {
260        this._objectStore = objectStore;
261        this._index = index;
262
263        if (this._dataGrid)
264            this._dataGrid.detach();
265        this._dataGrid = this._createDataGrid();
266        this._dataGrid.show(this._dataGridContainer);
267
268        this._skipCount = 0;
269        this._updateData(true);
270    },
271
272    /**
273     * @param {string} keyString
274     */
275    _parseKey: function(keyString)
276    {
277        var result;
278        try {
279            result = JSON.parse(keyString);
280        } catch (e) {
281            result = keyString;
282        }
283        return result;
284    },
285
286    /**
287     * @return {string}
288     */
289    _stringifyKey: function(key)
290    {
291        if (typeof(key) === "string")
292            return key;
293        return JSON.stringify(key);
294    },
295
296    /**
297     * @param {boolean} force
298     */
299    _updateData: function(force)
300    {
301        var key = this._parseKey(this._keyInputElement.value);
302        var pageSize = this._pageSize;
303        var skipCount = this._skipCount;
304        this._refreshButton.setEnabled(false);
305        this._clearButton.setEnabled(!this._isIndex);
306
307        if (!force && this._lastKey === key && this._lastPageSize === pageSize && this._lastSkipCount === skipCount)
308            return;
309
310        if (this._lastKey !== key || this._lastPageSize !== pageSize) {
311            skipCount = 0;
312            this._skipCount = 0;
313        }
314        this._lastKey = key;
315        this._lastPageSize = pageSize;
316        this._lastSkipCount = skipCount;
317
318        /**
319         * @param {!Array.<!WebInspector.IndexedDBModel.Entry>} entries
320         * @param {boolean} hasMore
321         * @this {WebInspector.IDBDataView}
322         */
323        function callback(entries, hasMore)
324        {
325            this._refreshButton.setEnabled(true);
326            this.clear();
327            this._entries = entries;
328            for (var i = 0; i < entries.length; ++i) {
329                var data = {};
330                data["number"] = i + skipCount;
331                data["key"] = entries[i].key;
332                data["primaryKey"] = entries[i].primaryKey;
333                data["value"] = entries[i].value;
334
335                var primaryKey = JSON.stringify(this._isIndex ? entries[i].primaryKey : entries[i].key);
336                var node = new WebInspector.IDBDataGridNode(data);
337                this._dataGrid.rootNode().appendChild(node);
338            }
339
340            this._pageBackButton.disabled = skipCount === 0;
341            this._pageForwardButton.disabled = !hasMore;
342        }
343
344        var idbKeyRange = key ? window.webkitIDBKeyRange.lowerBound(key) : null;
345        if (this._isIndex)
346            this._model.loadIndexData(this._databaseId, this._objectStore.name, this._index.name, idbKeyRange, skipCount, pageSize, callback.bind(this));
347        else
348            this._model.loadObjectStoreData(this._databaseId, this._objectStore.name, idbKeyRange, skipCount, pageSize, callback.bind(this));
349    },
350
351    _refreshButtonClicked: function(event)
352    {
353        this._updateData(true);
354    },
355
356    _clearButtonClicked: function(event)
357    {
358        /**
359         * @this {WebInspector.IDBDataView}
360         */
361        function cleared() {
362            this._clearButton.setEnabled(true);
363            this._updateData(true);
364        }
365        this._clearButton.setEnabled(false);
366        this._model.clearObjectStore(this._databaseId, this._objectStore.name, cleared.bind(this));
367    },
368
369    get statusBarItems()
370    {
371        return [this._refreshButton.element, this._clearButton.element];
372    },
373
374    clear: function()
375    {
376        this._dataGrid.rootNode().removeChildren();
377        for (var i = 0; i < this._entries.length; ++i) {
378            this._entries[i].key.release();
379            this._entries[i].primaryKey.release();
380            this._entries[i].value.release();
381        }
382        this._entries = [];
383    },
384
385    __proto__: WebInspector.View.prototype
386}
387
388/**
389 * @constructor
390 * @extends {WebInspector.DataGridNode}
391 * @param {!Object.<string, *>} data
392 */
393WebInspector.IDBDataGridNode = function(data)
394{
395    WebInspector.DataGridNode.call(this, data, false);
396    this.selectable = false;
397}
398
399WebInspector.IDBDataGridNode.prototype = {
400    /**
401     * @return {!Element}
402     */
403    createCell: function(columnIdentifier)
404    {
405        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
406        var value = this.data[columnIdentifier];
407
408        switch (columnIdentifier) {
409        case "value":
410        case "key":
411        case "primaryKey":
412            cell.removeChildren();
413            this._formatValue(cell, value);
414            break;
415        default:
416        }
417
418        return cell;
419    },
420
421    _formatValue: function(cell, value)
422    {
423        var type = value.subtype || value.type;
424        var contents = cell.createChild("div", "source-code console-formatted-" + type);
425
426        switch (type) {
427        case "object":
428        case "array":
429            var section = new WebInspector.ObjectPropertiesSection(value, value.description)
430            section.editable = false;
431            section.skipProto = true;
432            contents.appendChild(section.element);
433            break;
434        case "string":
435            contents.classList.add("primitive-value");
436            contents.appendChild(document.createTextNode("\"" + value.description + "\""));
437            break;
438        default:
439            contents.classList.add("primitive-value");
440            contents.appendChild(document.createTextNode(value.description));
441        }
442    },
443
444    __proto__: WebInspector.DataGridNode.prototype
445}
446
447