1/* 2 * Copyright (C) 2009 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 31WebInspector.AuditsPanel = function() 32{ 33 WebInspector.Panel.call(this); 34 35 this._constructCategories(); 36 37 this.createSidebar(); 38 this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("", {}, true); 39 this.sidebarTree.appendChild(this.auditsTreeElement); 40 this.auditsTreeElement.expand(); 41 42 this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement(); 43 this.auditsTreeElement.appendChild(this.auditsItemTreeElement); 44 45 this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"), {}, true); 46 this.sidebarTree.appendChild(this.auditResultsTreeElement); 47 this.auditResultsTreeElement.expand(); 48 49 this.element.addStyleClass("audits"); 50 51 this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear audit results."), "clear-audit-results-status-bar-item"); 52 this.clearResultsButton.addEventListener("click", this._clearButtonClicked.bind(this), false); 53 54 this.viewsContainerElement = document.createElement("div"); 55 this.viewsContainerElement.id = "audit-views"; 56 this.element.appendChild(this.viewsContainerElement); 57} 58 59WebInspector.AuditsPanel.prototype = { 60 toolbarItemClass: "audits", 61 62 get toolbarItemLabel() 63 { 64 return WebInspector.UIString("Audits"); 65 }, 66 67 get statusBarItems() 68 { 69 return [this.clearResultsButton.element]; 70 }, 71 72 get mainResourceLoadTime() 73 { 74 return this._mainResourceLoadTime; 75 }, 76 77 set mainResourceLoadTime(x) 78 { 79 this._mainResourceLoadTime = x; 80 this._didMainResourceLoad(); 81 }, 82 83 get mainResourceDOMContentTime() 84 { 85 return this._mainResourceDOMContentTime; 86 }, 87 88 set mainResourceDOMContentTime(x) 89 { 90 this._mainResourceDOMContentTime = x; 91 }, 92 93 get categoriesById() 94 { 95 return this._auditCategoriesById; 96 }, 97 98 get visibleView() 99 { 100 return this._visibleView; 101 }, 102 103 _constructCategories: function() 104 { 105 this._auditCategoriesById = {}; 106 for (var categoryCtorID in WebInspector.AuditCategories) { 107 var auditCategory = new WebInspector.AuditCategories[categoryCtorID](); 108 auditCategory._id = categoryCtorID; 109 this.categoriesById[categoryCtorID] = auditCategory; 110 } 111 }, 112 113 _executeAudit: function(categories, resultCallback) 114 { 115 var resources = []; 116 for (var id in WebInspector.resources) 117 resources.push(WebInspector.resources[id]); 118 119 var rulesRemaining = 0; 120 for (var i = 0; i < categories.length; ++i) 121 rulesRemaining += categories[i].ruleCount; 122 123 var results = []; 124 var mainResourceURL = WebInspector.mainResource.url; 125 126 function ruleResultReadyCallback(categoryResult, ruleResult) 127 { 128 if (ruleResult.children) 129 categoryResult.entries.push(ruleResult); 130 131 --rulesRemaining; 132 133 if (!rulesRemaining && resultCallback) 134 resultCallback(mainResourceURL, results); 135 } 136 137 if (!rulesRemaining) { 138 resultCallback(mainResourceURL, results); 139 return; 140 } 141 142 for (var i = 0; i < categories.length; ++i) { 143 var category = categories[i]; 144 var result = new WebInspector.AuditCategoryResult(category); 145 results.push(result); 146 category.runRules(resources, ruleResultReadyCallback.bind(null, result)); 147 } 148 }, 149 150 _auditFinishedCallback: function(launcherCallback, mainResourceURL, results) 151 { 152 var children = this.auditResultsTreeElement.children; 153 var ordinal = 1; 154 for (var i = 0; i < children.length; ++i) { 155 if (children[i].mainResourceURL === mainResourceURL) 156 ordinal++; 157 } 158 159 var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(results, mainResourceURL, ordinal); 160 this.auditResultsTreeElement.appendChild(resultTreeElement); 161 resultTreeElement.reveal(); 162 resultTreeElement.select(); 163 if (launcherCallback) 164 launcherCallback(); 165 }, 166 167 initiateAudit: function(categoryIds, runImmediately, launcherCallback) 168 { 169 if (!categoryIds || !categoryIds.length) 170 return; 171 172 var categories = []; 173 for (var i = 0; i < categoryIds.length; ++i) 174 categories.push(this.categoriesById[categoryIds[i]]); 175 176 function initiateAuditCallback(categories, launcherCallback) 177 { 178 this._executeAudit(categories, this._auditFinishedCallback.bind(this, launcherCallback)); 179 } 180 181 if (runImmediately) 182 initiateAuditCallback.call(this, categories, launcherCallback); 183 else 184 this._reloadResources(initiateAuditCallback.bind(this, categories, launcherCallback)); 185 }, 186 187 _reloadResources: function(callback) 188 { 189 this._resourceTrackingCallback = callback; 190 191 if (!InspectorBackend.resourceTrackingEnabled()) { 192 InspectorBackend.enableResourceTracking(false); 193 this._updateLauncherViewControls(true); 194 } else 195 InjectedScriptAccess.getDefault().evaluate("window.location.reload()", switchCallback); 196 }, 197 198 _didMainResourceLoad: function() 199 { 200 if (this._resourceTrackingCallback) { 201 var callback = this._resourceTrackingCallback; 202 this._resourceTrackingCallback = null; 203 callback(); 204 } 205 }, 206 207 showResults: function(categoryResults) 208 { 209 if (!categoryResults._resultView) 210 categoryResults._resultView = new WebInspector.AuditResultView(categoryResults); 211 212 this.showView(categoryResults._resultView); 213 }, 214 215 showLauncherView: function() 216 { 217 if (!this._launcherView) 218 this._launcherView = new WebInspector.AuditLauncherView(this.categoriesById, this.initiateAudit.bind(this)); 219 220 this.showView(this._launcherView); 221 }, 222 223 showView: function(view) 224 { 225 if (view) { 226 if (this._visibleView === view) 227 return; 228 this._closeVisibleView(); 229 this._visibleView = view; 230 } 231 var visibleView = this.visibleView; 232 if (visibleView) 233 visibleView.show(this.viewsContainerElement); 234 }, 235 236 show: function() 237 { 238 WebInspector.Panel.prototype.show.call(this); 239 240 this.showView(); 241 this._updateLauncherViewControls(InspectorBackend.resourceTrackingEnabled()); 242 }, 243 244 attach: function() 245 { 246 WebInspector.Panel.prototype.attach.call(this); 247 248 this.auditsItemTreeElement.select(); 249 }, 250 251 updateMainViewWidth: function(width) 252 { 253 this.viewsContainerElement.style.left = width + "px"; 254 }, 255 256 _updateLauncherViewControls: function(isTracking) 257 { 258 if (this._launcherView) 259 this._launcherView.updateResourceTrackingState(isTracking); 260 }, 261 262 _clearButtonClicked: function() 263 { 264 this.auditsItemTreeElement.reveal(); 265 this.auditsItemTreeElement.select(); 266 this.auditResultsTreeElement.removeChildren(); 267 }, 268 269 _closeVisibleView: function() 270 { 271 if (this.visibleView) 272 this.visibleView.hide(); 273 } 274} 275 276WebInspector.AuditsPanel.prototype.__proto__ = WebInspector.Panel.prototype; 277 278 279 280WebInspector.AuditCategory = function(displayName) 281{ 282 this._displayName = displayName; 283 this._rules = []; 284} 285 286WebInspector.AuditCategory.prototype = { 287 get id() 288 { 289 // this._id value is injected at construction time. 290 return this._id; 291 }, 292 293 get displayName() 294 { 295 return this._displayName; 296 }, 297 298 get ruleCount() 299 { 300 this._ensureInitialized(); 301 return this._rules.length; 302 }, 303 304 addRule: function(rule) 305 { 306 this._rules.push(rule); 307 }, 308 309 runRules: function(resources, callback) 310 { 311 this._ensureInitialized(); 312 for (var i = 0; i < this._rules.length; ++i) 313 this._rules[i].run(resources, callback); 314 }, 315 316 _ensureInitialized: function() 317 { 318 if (!this._initialized) { 319 if ("initialize" in this) 320 this.initialize(); 321 this._initialized = true; 322 } 323 } 324} 325 326 327WebInspector.AuditRule = function(id, displayName, parametersObject) 328{ 329 this._id = id; 330 this._displayName = displayName; 331 this._parametersObject = parametersObject; 332} 333 334WebInspector.AuditRule.prototype = { 335 get id() 336 { 337 return this._id; 338 }, 339 340 get displayName() 341 { 342 return this._displayName; 343 }, 344 345 run: function(resources, callback) 346 { 347 this.doRun(resources, new WebInspector.AuditRuleResult(this.displayName), callback); 348 }, 349 350 doRun: function(resources, result, callback) 351 { 352 throw new Error("doRun() not implemented"); 353 }, 354 355 getValue: function(key) 356 { 357 if (key in this._parametersObject) 358 return this._parametersObject[key]; 359 else 360 throw new Error(key + " not found in rule parameters"); 361 } 362} 363 364 365WebInspector.AuditCategoryResult = function(category) 366{ 367 this.title = category.displayName; 368 this.entries = []; 369} 370 371WebInspector.AuditCategoryResult.prototype = { 372 addEntry: function(value) 373 { 374 var entry = new WebInspector.AuditRuleResult(value); 375 this.entries.push(entry); 376 return entry; 377 } 378} 379 380/** 381 * @param {string} value The result message HTML contents. 382 */ 383WebInspector.AuditRuleResult = function(value) 384{ 385 this.value = value; 386 this.type = WebInspector.AuditRuleResult.Type.NA; 387} 388 389WebInspector.AuditRuleResult.Type = { 390 // Does not denote a discovered flaw but rather represents an informational message. 391 NA: 0, 392 393 // Denotes a minor impact on the checked metric. 394 Hint: 1, 395 396 // Denotes a major impact on the checked metric. 397 Violation: 2 398} 399 400WebInspector.AuditRuleResult.prototype = { 401 appendChild: function(value) 402 { 403 if (!this.children) 404 this.children = []; 405 var entry = new WebInspector.AuditRuleResult(value); 406 this.children.push(entry); 407 return entry; 408 }, 409 410 set type(x) 411 { 412 this._type = x; 413 }, 414 415 get type() 416 { 417 return this._type; 418 } 419} 420 421 422WebInspector.AuditsSidebarTreeElement = function() 423{ 424 this.small = false; 425 426 WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"), "", null, false); 427} 428 429WebInspector.AuditsSidebarTreeElement.prototype = { 430 onattach: function() 431 { 432 WebInspector.SidebarTreeElement.prototype.onattach.call(this); 433 }, 434 435 onselect: function() 436 { 437 WebInspector.panels.audits.showLauncherView(); 438 }, 439 440 get selectable() 441 { 442 return true; 443 }, 444 445 refresh: function() 446 { 447 this.refreshTitles(); 448 } 449} 450 451WebInspector.AuditsSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 452 453 454WebInspector.AuditResultSidebarTreeElement = function(results, mainResourceURL, ordinal) 455{ 456 this.results = results; 457 this.mainResourceURL = mainResourceURL; 458 459 WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal), "", {}, false); 460} 461 462WebInspector.AuditResultSidebarTreeElement.prototype = { 463 onselect: function() 464 { 465 WebInspector.panels.audits.showResults(this.results); 466 }, 467 468 get selectable() 469 { 470 return true; 471 } 472} 473 474WebInspector.AuditResultSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 475 476// Contributed audit rules should go into this namespace. 477WebInspector.AuditRules = {}; 478 479// Contributed audit categories should go into this namespace. 480WebInspector.AuditCategories = {}; 481