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.addStyleClass("audit-launcher-view"); 46 this.element.addStyleClass("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 function onAuditStarted() 162 { 163 this._displayResourceLoadingProgress = false; 164 } 165 this._auditController.initiateAudit(catIds, this._progressIndicator, this._auditPresentStateElement.checked, onAuditStarted.bind(this), this._setAuditRunning.bind(this, false)); 166 }, 167 168 _stopAudit: function() 169 { 170 this._displayResourceLoadingProgress = false; 171 this._progressIndicator.cancel(); 172 this._progressIndicator.done(); 173 delete this._progressIndicator; 174 }, 175 176 /** 177 * @param {boolean} disable 178 */ 179 _toggleUIComponents: function(disable) 180 { 181 this._selectAllCheckboxElement.disabled = disable; 182 this._categoriesElement.disabled = disable; 183 this._auditPresentStateElement.disabled = disable; 184 this._auditReloadedStateElement.disabled = disable; 185 }, 186 187 _launchButtonClicked: function(event) 188 { 189 this._setAuditRunning(!this._auditRunning); 190 }, 191 192 /** 193 * @param {boolean} checkCategories 194 * @param {boolean=} userGesture 195 */ 196 _selectAllClicked: function(checkCategories, userGesture) 197 { 198 var childNodes = this._categoriesElement.childNodes; 199 for (var i = 0, length = childNodes.length; i < length; ++i) 200 childNodes[i].firstChild.checked = checkCategories; 201 this._currentCategoriesCount = checkCategories ? this._sortedCategories.length : 0; 202 this._selectedCategoriesUpdated(userGesture); 203 }, 204 205 _categoryClicked: function(event) 206 { 207 this._currentCategoriesCount += event.target.checked ? 1 : -1; 208 this._selectAllCheckboxElement.checked = this._currentCategoriesCount === this._sortedCategories.length; 209 this._selectedCategoriesUpdated(true); 210 }, 211 212 /** 213 * @param {string} title 214 * @param {string} id 215 */ 216 _createCategoryElement: function(title, id) 217 { 218 var labelElement = document.createElement("label"); 219 labelElement.id = this._categoryIdPrefix + id; 220 221 var element = document.createElement("input"); 222 element.type = "checkbox"; 223 if (id !== "") 224 element.addEventListener("click", this._boundCategoryClickListener, false); 225 labelElement.appendChild(element); 226 labelElement.appendChild(document.createTextNode(title)); 227 labelElement.__displayName = title; 228 229 return labelElement; 230 }, 231 232 _createLauncherUI: function() 233 { 234 this._headerElement = document.createElement("h1"); 235 this._headerElement.textContent = WebInspector.UIString("Select audits to run"); 236 237 for (var child = 0; child < this._contentElement.children.length; ++child) 238 this._contentElement.removeChild(this._contentElement.children[child]); 239 240 this._contentElement.appendChild(this._headerElement); 241 242 function handleSelectAllClick(event) 243 { 244 this._selectAllClicked(event.target.checked, true); 245 } 246 var categoryElement = this._createCategoryElement(WebInspector.UIString("Select All"), ""); 247 categoryElement.id = "audit-launcher-selectall"; 248 this._selectAllCheckboxElement = categoryElement.firstChild; 249 this._selectAllCheckboxElement.checked = this._selectedCategoriesSetting.get()[WebInspector.AuditLauncherView.AllCategoriesKey]; 250 this._selectAllCheckboxElement.addEventListener("click", handleSelectAllClick.bind(this), false); 251 this._contentElement.appendChild(categoryElement); 252 253 this._categoriesElement = this._contentElement.createChild("fieldset", "audit-categories-container"); 254 this._currentCategoriesCount = 0; 255 256 this._contentElement.createChild("div", "flexible-space"); 257 258 this._buttonContainerElement = this._contentElement.createChild("div", "button-container"); 259 260 var labelElement = this._buttonContainerElement.createChild("label"); 261 this._auditPresentStateElement = labelElement.createChild("input"); 262 this._auditPresentStateElement.name = "audit-mode"; 263 this._auditPresentStateElement.type = "radio"; 264 this._auditPresentStateElement.checked = true; 265 this._auditPresentStateLabelElement = document.createTextNode(WebInspector.UIString("Audit Present State")); 266 labelElement.appendChild(this._auditPresentStateLabelElement); 267 268 labelElement = this._buttonContainerElement.createChild("label"); 269 this._auditReloadedStateElement = labelElement.createChild("input"); 270 this._auditReloadedStateElement.name = "audit-mode"; 271 this._auditReloadedStateElement.type = "radio"; 272 labelElement.appendChild(document.createTextNode("Reload Page and Audit on Load")); 273 274 this._launchButton = this._buttonContainerElement.createChild("button"); 275 this._launchButton.textContent = WebInspector.UIString("Run"); 276 this._launchButton.addEventListener("click", this._launchButtonClicked.bind(this), false); 277 278 this._selectAllClicked(this._selectAllCheckboxElement.checked); 279 }, 280 281 _updateResourceProgress: function() 282 { 283 if (this._displayResourceLoadingProgress) 284 this._progressIndicator.setTitle(WebInspector.UIString("Loading (%d of %d)", this._loadedResources, this._totalResources)); 285 }, 286 287 /** 288 * @param {boolean=} userGesture 289 */ 290 _selectedCategoriesUpdated: function(userGesture) 291 { 292 // Save present categories only upon user gesture to clean up junk from past versions and removed extensions. 293 // Do not remove old categories if not handling a user gesture, as there's chance categories will be added 294 // later during start-up. 295 var selectedCategories = userGesture ? {} : this._selectedCategoriesSetting.get(); 296 var childNodes = this._categoriesElement.childNodes; 297 for (var i = 0, length = childNodes.length; i < length; ++i) 298 selectedCategories[childNodes[i].__displayName] = childNodes[i].firstChild.checked; 299 selectedCategories[WebInspector.AuditLauncherView.AllCategoriesKey] = this._selectAllCheckboxElement.checked; 300 this._selectedCategoriesSetting.set(selectedCategories); 301 this._updateButton(); 302 }, 303 304 _updateButton: function() 305 { 306 this._launchButton.textContent = this._auditRunning ? WebInspector.UIString("Stop") : WebInspector.UIString("Run"); 307 this._launchButton.disabled = !this._currentCategoriesCount; 308 }, 309 310 __proto__: WebInspector.View.prototype 311 } 312