Home | History | Annotate | Download | only in front_end
      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  */
     36 WebInspector.IDBDatabaseView = function(database)
     37 {
     38     WebInspector.View.call(this);
     39     this.registerRequiredCSS("indexedDBViews.css");
     40 
     41     this.element.addStyleClass("fill");
     42     this.element.addStyleClass("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 
     67 WebInspector.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  */
    110 WebInspector.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.addStyleClass("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.addStyleClass("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 
    140 WebInspector.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.addStyleClass("status-bar");
    209         editorToolbar.addStyleClass("data-view-toolbar");
    210 
    211         this._pageBackButton = editorToolbar.createChild("button", "back-button");
    212         this._pageBackButton.addStyleClass("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.addStyleClass("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          */
    322         function callback(entries, hasMore)
    323         {
    324             this._refreshButton.setEnabled(true);
    325             this.clear();
    326             this._entries = entries;
    327             for (var i = 0; i < entries.length; ++i) {
    328                 var data = {};
    329                 data["number"] = i + skipCount;
    330                 data["key"] = entries[i].key;
    331                 data["primaryKey"] = entries[i].primaryKey;
    332                 data["value"] = entries[i].value;
    333 
    334                 var primaryKey = JSON.stringify(this._isIndex ? entries[i].primaryKey : entries[i].key);
    335                 var node = new WebInspector.IDBDataGridNode(data);
    336                 this._dataGrid.rootNode().appendChild(node);
    337             }
    338 
    339             this._pageBackButton.disabled = skipCount === 0;
    340             this._pageForwardButton.disabled = !hasMore;
    341         }
    342 
    343         var idbKeyRange = key ? window.webkitIDBKeyRange.lowerBound(key) : null;
    344         if (this._isIndex)
    345             this._model.loadIndexData(this._databaseId, this._objectStore.name, this._index.name, idbKeyRange, skipCount, pageSize, callback.bind(this));
    346         else
    347             this._model.loadObjectStoreData(this._databaseId, this._objectStore.name, idbKeyRange, skipCount, pageSize, callback.bind(this));
    348     },
    349 
    350     _refreshButtonClicked: function(event)
    351     {
    352         this._updateData(true);
    353     },
    354 
    355     _clearButtonClicked: function(event)
    356     {
    357         function cleared() {
    358             this._clearButton.setEnabled(true);
    359             this._updateData(true);
    360         }
    361         this._clearButton.setEnabled(false);
    362         this._model.clearObjectStore(this._databaseId, this._objectStore.name, cleared.bind(this));
    363     },
    364 
    365     get statusBarItems()
    366     {
    367         return [this._refreshButton.element, this._clearButton.element];
    368     },
    369 
    370     clear: function()
    371     {
    372         this._dataGrid.rootNode().removeChildren();
    373         for (var i = 0; i < this._entries.length; ++i) {
    374             this._entries[i].key.release();
    375             this._entries[i].primaryKey.release();
    376             this._entries[i].value.release();
    377         }
    378         this._entries = [];
    379     },
    380 
    381     __proto__: WebInspector.View.prototype
    382 }
    383 
    384 /**
    385  * @constructor
    386  * @extends {WebInspector.DataGridNode}
    387  * @param {*} data
    388  */
    389 WebInspector.IDBDataGridNode = function(data)
    390 {
    391     WebInspector.DataGridNode.call(this, data, false);
    392     this.selectable = false;
    393 }
    394 
    395 WebInspector.IDBDataGridNode.prototype = {
    396     /**
    397      * @return {Element}
    398      */
    399     createCell: function(columnIdentifier)
    400     {
    401         var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
    402         var value = this.data[columnIdentifier];
    403 
    404         switch (columnIdentifier) {
    405         case "value":
    406         case "key":
    407         case "primaryKey":
    408             cell.removeChildren();
    409             this._formatValue(cell, value);
    410             break;
    411         default:
    412         }
    413 
    414         return cell;
    415     },
    416 
    417     _formatValue: function(cell, value)
    418     {
    419         var type = value.subtype || value.type;
    420         var contents = cell.createChild("div", "source-code console-formatted-" + type);
    421 
    422         switch (type) {
    423         case "object":
    424         case "array":
    425             var section = new WebInspector.ObjectPropertiesSection(value, value.description)
    426             section.editable = false;
    427             section.skipProto = true;
    428             contents.appendChild(section.element);
    429             break;
    430         case "string":
    431             contents.addStyleClass("primitive-value");
    432             contents.appendChild(document.createTextNode("\"" + value.description + "\""));
    433             break;
    434         default:
    435             contents.addStyleClass("primitive-value");
    436             contents.appendChild(document.createTextNode(value.description));
    437         }
    438     },
    439 
    440     __proto__: WebInspector.DataGridNode.prototype
    441 }
    442 
    443