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.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 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.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 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.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 statusBarItems: function() 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 */ 389WebInspector.IDBDataGridNode = function(data) 390{ 391 WebInspector.DataGridNode.call(this, data, false); 392 this.selectable = false; 393} 394 395WebInspector.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