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