1 /* 2 * Copyright (C) 2011 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 * @param {!WebInspector.AuditController} auditController 34 * @extends {WebInspector.View} 35 */ 36 WebInspector.AuditLauncherView = function(auditController) 37 { 38 WebInspector.View.call(this); 39 40 this._auditController = auditController; 41 42 this._categoryIdPrefix = "audit-category-item-"; 43 this._auditRunning = false; 44 45 this.element.classList.add("audit-launcher-view"); 46 this.element.classList.add("panel-enabler-view"); 47 48 this._contentElement = document.createElement("div"); 49 this._contentElement.className = "audit-launcher-view-content"; 50 this.element.appendChild(this._contentElement); 51 this._boundCategoryClickListener = this._categoryClicked.bind(this); 52 53 this._resetResourceCount(); 54 55 this._sortedCategories = []; 56 57 this._headerElement = document.createElement("h1"); 58 this._headerElement.className = "no-audits"; 59 this._headerElement.textContent = WebInspector.UIString("No audits to run"); 60 this._contentElement.appendChild(this._headerElement); 61 62 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this); 63 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this); 64 65 var defaultSelectedAuditCategory = {}; 66 defaultSelectedAuditCategory[WebInspector.AuditLauncherView.AllCategoriesKey] = true; 67 this._selectedCategoriesSetting = WebInspector.settings.createSetting("selectedAuditCategories", defaultSelectedAuditCategory); 68 } 69 70 WebInspector.AuditLauncherView.AllCategoriesKey = "__AllCategories"; 71 72 WebInspector.AuditLauncherView.prototype = { 73 _resetResourceCount: function() 74 { 75 this._loadedResources = 0; 76 this._totalResources = 0; 77 }, 78 79 _onRequestStarted: function(event) 80 { 81 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data); 82 // Ignore long-living WebSockets for the sake of progress indicator, as we won't be waiting them anyway. 83 if (request.type === WebInspector.resourceTypes.WebSocket) 84 return; 85 ++this._totalResources; 86 this._updateResourceProgress(); 87 }, 88 89 _onRequestFinished: function(event) 90 { 91 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data); 92 // See resorceStarted for details. 93 if (request.type === WebInspector.resourceTypes.WebSocket) 94 return; 95 ++this._loadedResources; 96 this._updateResourceProgress(); 97 }, 98 99 /** 100 * @param {!WebInspector.AuditCategory} category 101 */ 102 addCategory: function(category) 103 { 104 if (!this._sortedCategories.length) 105 this._createLauncherUI(); 106 107 var selectedCategories = this._selectedCategoriesSetting.get(); 108 var categoryElement = this._createCategoryElement(category.displayName, category.id); 109 category._checkboxElement = categoryElement.firstChild; 110 if (this._selectAllCheckboxElement.checked || selectedCategories[category.displayName]) { 111 category._checkboxElement.checked = true; 112 ++this._currentCategoriesCount; 113 } 114 115 /** 116 * @param {!WebInspector.AuditCategory} a 117 * @param {!WebInspector.AuditCategory} b 118 * @return {number} 119 */ 120 function compareCategories(a, b) 121 { 122 var aTitle = a.displayName || ""; 123 var bTitle = b.displayName || ""; 124 return aTitle.localeCompare(bTitle); 125 } 126 var insertBefore = insertionIndexForObjectInListSortedByFunction(category, this._sortedCategories, compareCategories); 127 this._categoriesElement.insertBefore(categoryElement, this._categoriesElement.children[insertBefore]); 128 this._sortedCategories.splice(insertBefore, 0, category); 129 this._selectedCategoriesUpdated(); 130 }, 131 132 /** 133 * @param {boolean} auditRunning 134 */ 135 _setAuditRunning: function(auditRunning) 136 { 137 if (this._auditRunning === auditRunning) 138 return; 139 this._auditRunning = auditRunning; 140 this._updateButton(); 141 this._toggleUIComponents(this._auditRunning); 142 if (this._auditRunning) 143 this._startAudit(); 144 else 145 this._stopAudit(); 146 }, 147 148 _startAudit: function() 149 { 150 var catIds = []; 151 for (var category = 0; category < this._sortedCategories.length; ++category) { 152 if (this._sortedCategories[category]._checkboxElement.checked) 153 catIds.push(this._sortedCategories[category].id); 154 } 155 156 this._resetResourceCount(); 157 this._progressIndicator = new WebInspector.ProgressIndicator(); 158 this._buttonContainerElement.appendChild(this._progressIndicator.element); 159 this._displayResourceLoadingProgress = true; 160 161 /** 162 * @this {WebInspector.AuditLauncherView} 163 */ 164 function onAuditStarted() 165 { 166 this._displayResourceLoadingProgress = false; 167 } 168 this._auditController.initiateAudit(catIds, this._progressIndicator, this._auditPresentStateElement.checked, onAuditStarted.bind(this), this._setAuditRunning.bind(this, false)); 169 }, 170 171 _stopAudit: function() 172 { 173 this._displayResourceLoadingProgress = false; 174 this._progressIndicator.cancel(); 175 this._progressIndicator.done(); 176 delete this._progressIndicator; 177 }, 178 179 /** 180 * @param {boolean} disable 181 */ 182 _toggleUIComponents: function(disable) 183 { 184 this._selectAllCheckboxElement.disabled = disable; 185 this._categoriesElement.disabled = disable; 186 this._auditPresentStateElement.disabled = disable; 187 this._auditReloadedStateElement.disabled = disable; 188 }, 189 190 _launchButtonClicked: function(event) 191 { 192 this._setAuditRunning(!this._auditRunning); 193 }, 194 195 _clearButtonClicked: function() 196 { 197 this._auditController.clearResults(); 198 }, 199 200 /** 201 * @param {boolean} checkCategories 202 * @param {boolean=} userGesture 203 */ 204 _selectAllClicked: function(checkCategories, userGesture) 205 { 206 var childNodes = this._categoriesElement.childNodes; 207 for (var i = 0, length = childNodes.length; i < length; ++i) 208 childNodes[i].firstChild.checked = checkCategories; 209 this._currentCategoriesCount = checkCategories ? this._sortedCategories.length : 0; 210 this._selectedCategoriesUpdated(userGesture); 211 }, 212 213 _categoryClicked: function(event) 214 { 215 this._currentCategoriesCount += event.target.checked ? 1 : -1; 216 this._selectAllCheckboxElement.checked = this._currentCategoriesCount === this._sortedCategories.length; 217 this._selectedCategoriesUpdated(true); 218 }, 219 220 /** 221 * @param {string} title 222 * @param {string} id 223 */ 224 _createCategoryElement: function(title, id) 225 { 226 var labelElement = document.createElement("label"); 227 labelElement.id = this._categoryIdPrefix + id; 228 229 var element = document.createElement("input"); 230 element.type = "checkbox"; 231 if (id !== "") 232 element.addEventListener("click", this._boundCategoryClickListener, false); 233 labelElement.appendChild(element); 234 labelElement.appendChild(document.createTextNode(title)); 235 labelElement.__displayName = title; 236 237 return labelElement; 238 }, 239 240 _createLauncherUI: function() 241 { 242 this._headerElement = document.createElement("h1"); 243 this._headerElement.textContent = WebInspector.UIString("Select audits to run"); 244 245 for (var child = 0; child < this._contentElement.children.length; ++child) 246 this._contentElement.removeChild(this._contentElement.children[child]); 247 248 this._contentElement.appendChild(this._headerElement); 249 250 /** 251 * @param {?Event} event 252 * @this {WebInspector.AuditLauncherView} 253 */ 254 function handleSelectAllClick(event) 255 { 256 this._selectAllClicked(event.target.checked, true); 257 } 258 var categoryElement = this._createCategoryElement(WebInspector.UIString("Select All"), ""); 259 categoryElement.id = "audit-launcher-selectall"; 260 this._selectAllCheckboxElement = categoryElement.firstChild; 261 this._selectAllCheckboxElement.checked = this._selectedCategoriesSetting.get()[WebInspector.AuditLauncherView.AllCategoriesKey]; 262 this._selectAllCheckboxElement.addEventListener("click", handleSelectAllClick.bind(this), false); 263 this._contentElement.appendChild(categoryElement); 264 265 this._categoriesElement = this._contentElement.createChild("fieldset", "audit-categories-container"); 266 this._currentCategoriesCount = 0; 267 268 this._contentElement.createChild("div", "flexible-space"); 269 270 this._buttonContainerElement = this._contentElement.createChild("div", "button-container"); 271 272 var labelElement = this._buttonContainerElement.createChild("label"); 273 this._auditPresentStateElement = labelElement.createChild("input"); 274 this._auditPresentStateElement.name = "audit-mode"; 275 this._auditPresentStateElement.type = "radio"; 276 this._auditPresentStateElement.checked = true; 277 this._auditPresentStateLabelElement = document.createTextNode(WebInspector.UIString("Audit Present State")); 278 labelElement.appendChild(this._auditPresentStateLabelElement); 279 280 labelElement = this._buttonContainerElement.createChild("label"); 281 this._auditReloadedStateElement = labelElement.createChild("input"); 282 this._auditReloadedStateElement.name = "audit-mode"; 283 this._auditReloadedStateElement.type = "radio"; 284 labelElement.appendChild(document.createTextNode("Reload Page and Audit on Load")); 285 286 this._launchButton = this._buttonContainerElement.createChild("button"); 287 this._launchButton.textContent = WebInspector.UIString("Run"); 288 this._launchButton.addEventListener("click", this._launchButtonClicked.bind(this), false); 289 290 this._clearButton = this._buttonContainerElement.createChild("button"); 291 this._clearButton.textContent = WebInspector.UIString("Clear"); 292 this._clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false); 293 294 this._selectAllClicked(this._selectAllCheckboxElement.checked); 295 }, 296 297 _updateResourceProgress: function() 298 { 299 if (this._displayResourceLoadingProgress) 300 this._progressIndicator.setTitle(WebInspector.UIString("Loading (%d of %d)", this._loadedResources, this._totalResources)); 301 }, 302 303 /** 304 * @param {boolean=} userGesture 305 */ 306 _selectedCategoriesUpdated: function(userGesture) 307 { 308 // Save present categories only upon user gesture to clean up junk from past versions and removed extensions. 309 // Do not remove old categories if not handling a user gesture, as there's chance categories will be added 310 // later during start-up. 311 var selectedCategories = userGesture ? {} : this._selectedCategoriesSetting.get(); 312 var childNodes = this._categoriesElement.childNodes; 313 for (var i = 0, length = childNodes.length; i < length; ++i) 314 selectedCategories[childNodes[i].__displayName] = childNodes[i].firstChild.checked; 315 selectedCategories[WebInspector.AuditLauncherView.AllCategoriesKey] = this._selectAllCheckboxElement.checked; 316 this._selectedCategoriesSetting.set(selectedCategories); 317 this._updateButton(); 318 }, 319 320 _updateButton: function() 321 { 322 this._launchButton.textContent = this._auditRunning ? WebInspector.UIString("Stop") : WebInspector.UIString("Run"); 323 this._launchButton.disabled = !this._currentCategoriesCount; 324 }, 325 326 __proto__: WebInspector.View.prototype 327 } 328