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