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