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