1 /* 2 * Copyright (C) 2012 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 * @extends {WebInspector.Panel} 34 */ 35 WebInspector.AuditsPanel = function() 36 { 37 WebInspector.Panel.call(this, "audits"); 38 this.registerRequiredCSS("panelEnablerView.css"); 39 this.registerRequiredCSS("auditsPanel.css"); 40 41 this.createSidebarViewWithTree(); 42 this.splitView.mainElement.classList.add("vbox"); 43 44 this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("", {}, true); 45 this.sidebarTree.appendChild(this.auditsTreeElement); 46 this.auditsTreeElement.listItemElement.classList.add("hidden"); 47 48 this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement(this); 49 this.auditsTreeElement.appendChild(this.auditsItemTreeElement); 50 51 this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"), {}, true); 52 this.sidebarTree.appendChild(this.auditResultsTreeElement); 53 this.auditResultsTreeElement.expand(); 54 55 this.viewsContainerElement = this.splitView.mainElement; 56 57 this._constructCategories(); 58 59 this._auditController = new WebInspector.AuditController(this); 60 this._launcherView = new WebInspector.AuditLauncherView(this._auditController); 61 for (var id in this.categoriesById) 62 this._launcherView.addCategory(this.categoriesById[id]); 63 } 64 65 WebInspector.AuditsPanel.prototype = { 66 /** 67 * @return {boolean} 68 */ 69 canSearch: function() 70 { 71 return false; 72 }, 73 74 /** 75 * @return {!Object.<string, !WebInspector.AuditCategory>} 76 */ 77 get categoriesById() 78 { 79 return this._auditCategoriesById; 80 }, 81 82 /** 83 * @param {!WebInspector.AuditCategory} category 84 */ 85 addCategory: function(category) 86 { 87 this.categoriesById[category.id] = category; 88 this._launcherView.addCategory(category); 89 }, 90 91 /** 92 * @param {string} id 93 * @return {!WebInspector.AuditCategory} 94 */ 95 getCategory: function(id) 96 { 97 return this.categoriesById[id]; 98 }, 99 100 _constructCategories: function() 101 { 102 this._auditCategoriesById = {}; 103 for (var categoryCtorID in WebInspector.AuditCategories) { 104 var auditCategory = new WebInspector.AuditCategories[categoryCtorID](); 105 auditCategory._id = categoryCtorID; 106 this.categoriesById[categoryCtorID] = auditCategory; 107 } 108 }, 109 110 /** 111 * @param {string} mainResourceURL 112 * @param {!Array.<!WebInspector.AuditCategoryResult>} results 113 */ 114 auditFinishedCallback: function(mainResourceURL, results) 115 { 116 var children = this.auditResultsTreeElement.children; 117 var ordinal = 1; 118 for (var i = 0; i < children.length; ++i) { 119 if (children[i].mainResourceURL === mainResourceURL) 120 ordinal++; 121 } 122 123 var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(this, results, mainResourceURL, ordinal); 124 this.auditResultsTreeElement.appendChild(resultTreeElement); 125 resultTreeElement.revealAndSelect(); 126 }, 127 128 /** 129 * @param {!Array.<!WebInspector.AuditCategoryResult>} categoryResults 130 */ 131 showResults: function(categoryResults) 132 { 133 if (!categoryResults._resultView) 134 categoryResults._resultView = new WebInspector.AuditResultView(categoryResults); 135 136 this.visibleView = categoryResults._resultView; 137 }, 138 139 showLauncherView: function() 140 { 141 this.visibleView = this._launcherView; 142 }, 143 144 get visibleView() 145 { 146 return this._visibleView; 147 }, 148 149 set visibleView(x) 150 { 151 if (this._visibleView === x) 152 return; 153 154 if (this._visibleView) 155 this._visibleView.detach(); 156 157 this._visibleView = x; 158 159 if (x) 160 x.show(this.viewsContainerElement); 161 }, 162 163 wasShown: function() 164 { 165 WebInspector.Panel.prototype.wasShown.call(this); 166 if (!this._visibleView) 167 this.auditsItemTreeElement.select(); 168 }, 169 170 clearResults: function() 171 { 172 this.auditsItemTreeElement.revealAndSelect(); 173 this.auditResultsTreeElement.removeChildren(); 174 }, 175 176 __proto__: WebInspector.Panel.prototype 177 } 178 179 /** 180 * @constructor 181 * @param {string} displayName 182 */ 183 WebInspector.AuditCategory = function(displayName) 184 { 185 this._displayName = displayName; 186 this._rules = []; 187 } 188 189 WebInspector.AuditCategory.prototype = { 190 /** 191 * @return {string} 192 */ 193 get id() 194 { 195 // this._id value is injected at construction time. 196 return this._id; 197 }, 198 199 /** 200 * @return {string} 201 */ 202 get displayName() 203 { 204 return this._displayName; 205 }, 206 207 /** 208 * @param {!WebInspector.AuditRule} rule 209 * @param {!WebInspector.AuditRule.Severity} severity 210 */ 211 addRule: function(rule, severity) 212 { 213 rule.severity = severity; 214 this._rules.push(rule); 215 }, 216 217 /** 218 * @param {!Array.<!WebInspector.NetworkRequest>} requests 219 * @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback 220 * @param {function()} categoryDoneCallback 221 * @param {!WebInspector.Progress} progress 222 */ 223 run: function(requests, ruleResultCallback, categoryDoneCallback, progress) 224 { 225 this._ensureInitialized(); 226 var remainingRulesCount = this._rules.length; 227 progress.setTotalWork(remainingRulesCount); 228 function callbackWrapper(result) 229 { 230 ruleResultCallback(result); 231 progress.worked(); 232 if (!--remainingRulesCount) 233 categoryDoneCallback(); 234 } 235 for (var i = 0; i < this._rules.length; ++i) 236 this._rules[i].run(requests, callbackWrapper, progress); 237 }, 238 239 _ensureInitialized: function() 240 { 241 if (!this._initialized) { 242 if ("initialize" in this) 243 this.initialize(); 244 this._initialized = true; 245 } 246 } 247 } 248 249 /** 250 * @constructor 251 * @param {string} id 252 * @param {string} displayName 253 */ 254 WebInspector.AuditRule = function(id, displayName) 255 { 256 this._id = id; 257 this._displayName = displayName; 258 } 259 260 /** 261 * @enum {string} 262 */ 263 WebInspector.AuditRule.Severity = { 264 Info: "info", 265 Warning: "warning", 266 Severe: "severe" 267 } 268 269 /** 270 * @enum {number} 271 */ 272 WebInspector.AuditRule.SeverityOrder = { 273 "info": 3, 274 "warning": 2, 275 "severe": 1 276 } 277 278 WebInspector.AuditRule.prototype = { 279 get id() 280 { 281 return this._id; 282 }, 283 284 get displayName() 285 { 286 return this._displayName; 287 }, 288 289 /** 290 * @param {!WebInspector.AuditRule.Severity} severity 291 */ 292 set severity(severity) 293 { 294 this._severity = severity; 295 }, 296 297 /** 298 * @param {!Array.<!WebInspector.NetworkRequest>} requests 299 * @param {function(!WebInspector.AuditRuleResult)} callback 300 * @param {!WebInspector.Progress} progress 301 */ 302 run: function(requests, callback, progress) 303 { 304 if (progress.isCanceled()) 305 return; 306 307 var result = new WebInspector.AuditRuleResult(this.displayName); 308 result.severity = this._severity; 309 this.doRun(requests, result, callback, progress); 310 }, 311 312 /** 313 * @param {!Array.<!WebInspector.NetworkRequest>} requests 314 * @param {!WebInspector.AuditRuleResult} result 315 * @param {function(!WebInspector.AuditRuleResult)} callback 316 * @param {!WebInspector.Progress} progress 317 */ 318 doRun: function(requests, result, callback, progress) 319 { 320 throw new Error("doRun() not implemented"); 321 } 322 } 323 324 /** 325 * @constructor 326 * @param {!WebInspector.AuditCategory} category 327 */ 328 WebInspector.AuditCategoryResult = function(category) 329 { 330 this.title = category.displayName; 331 this.ruleResults = []; 332 } 333 334 WebInspector.AuditCategoryResult.prototype = { 335 /** 336 * @param {!WebInspector.AuditRuleResult} ruleResult 337 */ 338 addRuleResult: function(ruleResult) 339 { 340 this.ruleResults.push(ruleResult); 341 } 342 } 343 344 /** 345 * @constructor 346 * @param {(string|boolean|number|!Object)} value 347 * @param {boolean=} expanded 348 * @param {string=} className 349 */ 350 WebInspector.AuditRuleResult = function(value, expanded, className) 351 { 352 this.value = value; 353 this.className = className; 354 this.expanded = expanded; 355 this.violationCount = 0; 356 this._formatters = { 357 r: WebInspector.AuditRuleResult.linkifyDisplayName 358 }; 359 var standardFormatters = Object.keys(String.standardFormatters); 360 for (var i = 0; i < standardFormatters.length; ++i) 361 this._formatters[standardFormatters[i]] = String.standardFormatters[standardFormatters[i]]; 362 } 363 364 /** 365 * @param {string} url 366 * @return {!Element} 367 */ 368 WebInspector.AuditRuleResult.linkifyDisplayName = function(url) 369 { 370 return WebInspector.linkifyURLAsNode(url, WebInspector.displayNameForURL(url)); 371 } 372 373 WebInspector.AuditRuleResult.resourceDomain = function(domain) 374 { 375 return domain || WebInspector.UIString("[empty domain]"); 376 } 377 378 WebInspector.AuditRuleResult.prototype = { 379 /** 380 * @param {(string|boolean|number|!Object)} value 381 * @param {boolean=} expanded 382 * @param {string=} className 383 * @return {!WebInspector.AuditRuleResult} 384 */ 385 addChild: function(value, expanded, className) 386 { 387 if (!this.children) 388 this.children = []; 389 var entry = new WebInspector.AuditRuleResult(value, expanded, className); 390 this.children.push(entry); 391 return entry; 392 }, 393 394 /** 395 * @param {string} url 396 */ 397 addURL: function(url) 398 { 399 this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url)); 400 }, 401 402 /** 403 * @param {!Array.<string>} urls 404 */ 405 addURLs: function(urls) 406 { 407 for (var i = 0; i < urls.length; ++i) 408 this.addURL(urls[i]); 409 }, 410 411 /** 412 * @param {string} snippet 413 */ 414 addSnippet: function(snippet) 415 { 416 this.addChild(snippet, false, "source-code"); 417 }, 418 419 /** 420 * @param {string} format 421 * @param {...*} vararg 422 * @return {!WebInspector.AuditRuleResult} 423 */ 424 addFormatted: function(format, vararg) 425 { 426 var substitutions = Array.prototype.slice.call(arguments, 1); 427 var fragment = document.createDocumentFragment(); 428 429 function append(a, b) 430 { 431 if (!(b instanceof Node)) 432 b = document.createTextNode(b); 433 a.appendChild(b); 434 return a; 435 } 436 437 var formattedResult = String.format(format, substitutions, this._formatters, fragment, append).formattedResult; 438 if (formattedResult instanceof Node) 439 formattedResult.normalize(); 440 return this.addChild(formattedResult); 441 } 442 } 443 444 /** 445 * @constructor 446 * @extends {WebInspector.SidebarTreeElement} 447 * @param {!WebInspector.AuditsPanel} panel 448 */ 449 WebInspector.AuditsSidebarTreeElement = function(panel) 450 { 451 this._panel = panel; 452 this.small = false; 453 WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"), "", null, false); 454 } 455 456 WebInspector.AuditsSidebarTreeElement.prototype = { 457 onattach: function() 458 { 459 WebInspector.SidebarTreeElement.prototype.onattach.call(this); 460 }, 461 462 onselect: function() 463 { 464 this._panel.showLauncherView(); 465 }, 466 467 get selectable() 468 { 469 return true; 470 }, 471 472 refresh: function() 473 { 474 this.refreshTitles(); 475 }, 476 477 __proto__: WebInspector.SidebarTreeElement.prototype 478 } 479 480 /** 481 * @constructor 482 * @extends {WebInspector.SidebarTreeElement} 483 * @param {!WebInspector.AuditsPanel} panel 484 * @param {!Array.<!WebInspector.AuditCategoryResult>} results 485 * @param {string} mainResourceURL 486 * @param {number} ordinal 487 */ 488 WebInspector.AuditResultSidebarTreeElement = function(panel, results, mainResourceURL, ordinal) 489 { 490 this._panel = panel; 491 this.results = results; 492 this.mainResourceURL = mainResourceURL; 493 WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal), "", {}, false); 494 } 495 496 WebInspector.AuditResultSidebarTreeElement.prototype = { 497 onselect: function() 498 { 499 this._panel.showResults(this.results); 500 }, 501 502 get selectable() 503 { 504 return true; 505 }, 506 507 __proto__: WebInspector.SidebarTreeElement.prototype 508 } 509 510 // Contributed audit rules should go into this namespace. 511 WebInspector.AuditRules = {}; 512 513 /** 514 * Contributed audit categories should go into this namespace. 515 * @type {!Object.<string, function(new:WebInspector.AuditCategory)>} 516 */ 517 WebInspector.AuditCategories = {}; 518 519 importScript("AuditCategories.js"); 520 importScript("AuditController.js"); 521 importScript("AuditFormatters.js"); 522 importScript("AuditLauncherView.js"); 523 importScript("AuditResultView.js"); 524 importScript("AuditRules.js"); 525