1 /* 2 * Copyright (C) 2010 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 /** 27 * @constructor 28 * @extends {WebInspector.View} 29 */ 30 WebInspector.ApplicationCacheItemsView = function(model, frameId) 31 { 32 WebInspector.View.call(this); 33 34 this._model = model; 35 36 this.element.classList.add("storage-view"); 37 this.element.classList.add("table"); 38 39 // FIXME: Needs better tooltip. (Localized) 40 this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item"); 41 this.deleteButton.visible = false; 42 this.deleteButton.addEventListener("click", this._deleteButtonClicked, this); 43 44 this.connectivityIcon = document.createElement("div"); 45 this.connectivityMessage = document.createElement("span"); 46 this.connectivityMessage.className = "storage-application-cache-connectivity"; 47 this.connectivityMessage.textContent = ""; 48 49 this.divider = document.createElement("span"); 50 this.divider.className = "status-bar-item status-bar-divider"; 51 52 this.statusIcon = document.createElement("div"); 53 this.statusMessage = document.createElement("span"); 54 this.statusMessage.className = "storage-application-cache-status"; 55 this.statusMessage.textContent = ""; 56 57 this._frameId = frameId; 58 59 this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("No Application Cache information available.")); 60 this._emptyView.show(this.element); 61 62 this._markDirty(); 63 64 var status = this._model.frameManifestStatus(frameId); 65 this.updateStatus(status); 66 67 this.updateNetworkState(this._model.onLine); 68 69 // FIXME: Status bar items don't work well enough yet, so they are being hidden. 70 // http://webkit.org/b/41637 Web Inspector: Give Semantics to "Refresh" and "Delete" Buttons in ApplicationCache DataGrid 71 this.deleteButton.element.style.display = "none"; 72 } 73 74 WebInspector.ApplicationCacheItemsView.prototype = { 75 get statusBarItems() 76 { 77 return [ 78 this.deleteButton.element, 79 this.connectivityIcon, this.connectivityMessage, this.divider, 80 this.statusIcon, this.statusMessage 81 ]; 82 }, 83 84 wasShown: function() 85 { 86 this._maybeUpdate(); 87 }, 88 89 willHide: function() 90 { 91 this.deleteButton.visible = false; 92 }, 93 94 _maybeUpdate: function() 95 { 96 if (!this.isShowing() || !this._viewDirty) 97 return; 98 99 this._update(); 100 this._viewDirty = false; 101 }, 102 103 _markDirty: function() 104 { 105 this._viewDirty = true; 106 }, 107 108 /** 109 * @param {number} status 110 */ 111 updateStatus: function(status) 112 { 113 var oldStatus = this._status; 114 this._status = status; 115 116 var statusInformation = {}; 117 // We should never have UNCACHED status, since we remove frames with UNCACHED application cache status from the tree. 118 statusInformation[applicationCache.UNCACHED] = { className: "red-ball", text: "UNCACHED" }; 119 statusInformation[applicationCache.IDLE] = { className: "green-ball", text: "IDLE" }; 120 statusInformation[applicationCache.CHECKING] = { className: "orange-ball", text: "CHECKING" }; 121 statusInformation[applicationCache.DOWNLOADING] = { className: "orange-ball", text: "DOWNLOADING" }; 122 statusInformation[applicationCache.UPDATEREADY] = { className: "green-ball", text: "UPDATEREADY" }; 123 statusInformation[applicationCache.OBSOLETE] = { className: "red-ball", text: "OBSOLETE" }; 124 125 var info = statusInformation[status] || statusInformation[applicationCache.UNCACHED]; 126 127 this.statusIcon.className = "storage-application-cache-status-icon " + info.className; 128 this.statusMessage.textContent = info.text; 129 130 if (this.isShowing() && this._status === applicationCache.IDLE && (oldStatus === applicationCache.UPDATEREADY || !this._resources)) 131 this._markDirty(); 132 this._maybeUpdate(); 133 }, 134 135 /** 136 * @param {boolean} isNowOnline 137 */ 138 updateNetworkState: function(isNowOnline) 139 { 140 if (isNowOnline) { 141 this.connectivityIcon.className = "storage-application-cache-connectivity-icon green-ball"; 142 this.connectivityMessage.textContent = WebInspector.UIString("Online"); 143 } else { 144 this.connectivityIcon.className = "storage-application-cache-connectivity-icon red-ball"; 145 this.connectivityMessage.textContent = WebInspector.UIString("Offline"); 146 } 147 }, 148 149 _update: function() 150 { 151 this._model.requestApplicationCache(this._frameId, this._updateCallback.bind(this)); 152 }, 153 154 /** 155 * @param {?ApplicationCacheAgent.ApplicationCache} applicationCache 156 */ 157 _updateCallback: function(applicationCache) 158 { 159 if (!applicationCache || !applicationCache.manifestURL) { 160 delete this._manifest; 161 delete this._creationTime; 162 delete this._updateTime; 163 delete this._size; 164 delete this._resources; 165 166 this._emptyView.show(this.element); 167 this.deleteButton.visible = false; 168 if (this._dataGrid) 169 this._dataGrid.element.classList.add("hidden"); 170 return; 171 } 172 // FIXME: are these variables needed anywhere else? 173 this._manifest = applicationCache.manifestURL; 174 this._creationTime = applicationCache.creationTime; 175 this._updateTime = applicationCache.updateTime; 176 this._size = applicationCache.size; 177 this._resources = applicationCache.resources; 178 179 if (!this._dataGrid) 180 this._createDataGrid(); 181 182 this._populateDataGrid(); 183 this._dataGrid.autoSizeColumns(20, 80); 184 this._dataGrid.element.classList.remove("hidden"); 185 this._emptyView.detach(); 186 this.deleteButton.visible = true; 187 188 // FIXME: For Chrome, put creationTime and updateTime somewhere. 189 // NOTE: localizedString has not yet been added. 190 // WebInspector.UIString("(%s) Created: %s Updated: %s", this._size, this._creationTime, this._updateTime); 191 }, 192 193 _createDataGrid: function() 194 { 195 var columns = [ 196 {title: WebInspector.UIString("Resource"), sort: WebInspector.DataGrid.Order.Ascending, sortable: true}, 197 {title: WebInspector.UIString("Type"), sortable: true}, 198 {title: WebInspector.UIString("Size"), align: WebInspector.DataGrid.Align.Right, sortable: true} 199 ]; 200 this._dataGrid = new WebInspector.DataGrid(columns); 201 this._dataGrid.show(this.element); 202 this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._populateDataGrid, this); 203 }, 204 205 _populateDataGrid: function() 206 { 207 var selectedResource = this._dataGrid.selectedNode ? this._dataGrid.selectedNode.resource : null; 208 var sortDirection = this._dataGrid.isSortOrderAscending() ? 1 : -1; 209 210 function numberCompare(field, resource1, resource2) 211 { 212 return sortDirection * (resource1[field] - resource2[field]); 213 } 214 function localeCompare(field, resource1, resource2) 215 { 216 return sortDirection * (resource1[field] + "").localeCompare(resource2[field] + "") 217 } 218 219 var comparator; 220 switch (parseInt(this._dataGrid.sortColumnIdentifier(), 10)) { 221 case 0: comparator = localeCompare.bind(this, "name"); break; 222 case 1: comparator = localeCompare.bind(this, "type"); break; 223 case 2: comparator = numberCompare.bind(this, "size"); break; 224 default: localeCompare.bind(this, "resource"); // FIXME: comparator = ? 225 } 226 227 this._resources.sort(comparator); 228 this._dataGrid.rootNode().removeChildren(); 229 230 var nodeToSelect; 231 for (var i = 0; i < this._resources.length; ++i) { 232 var data = {}; 233 var resource = this._resources[i]; 234 data[0] = resource.url; 235 data[1] = resource.type; 236 data[2] = Number.bytesToString(resource.size); 237 var node = new WebInspector.DataGridNode(data); 238 node.resource = resource; 239 node.selectable = true; 240 this._dataGrid.rootNode().appendChild(node); 241 if (resource === selectedResource) { 242 nodeToSelect = node; 243 nodeToSelect.selected = true; 244 } 245 } 246 247 if (!nodeToSelect && this._dataGrid.rootNode().children.length) 248 this._dataGrid.rootNode().children[0].selected = true; 249 }, 250 251 _deleteButtonClicked: function(event) 252 { 253 if (!this._dataGrid || !this._dataGrid.selectedNode) 254 return; 255 256 // FIXME: Delete Button semantics are not yet defined. (Delete a single, or all?) 257 this._deleteCallback(this._dataGrid.selectedNode); 258 }, 259 260 _deleteCallback: function(node) 261 { 262 // FIXME: Should we delete a single (selected) resource or all resources? 263 // InspectorBackend.deleteCachedResource(...) 264 // this._update(); 265 }, 266 267 __proto__: WebInspector.View.prototype 268 } 269 270