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