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