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