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