2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @extends {WebInspector.Panel}
35 WebInspector.AuditsPanel = function()
37 WebInspector.Panel.call(this, "audits");
38 this.registerRequiredCSS("auditsPanel.css");
40 this.createSplitViewWithSidebarTree();
41 this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("", {}, true);
42 this.sidebarTree.appendChild(this.auditsTreeElement);
43 this.auditsTreeElement.listItemElement.addStyleClass("hidden");
44 this.auditsTreeElement.expand();
46 this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement();
47 this.auditsTreeElement.appendChild(this.auditsItemTreeElement);
49 this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"), {}, true);
50 this.sidebarTree.appendChild(this.auditResultsTreeElement);
51 this.auditResultsTreeElement.expand();
53 this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear audit results."), "clear-status-bar-item");
54 this.clearResultsButton.addEventListener("click", this._clearButtonClicked, this);
56 this.viewsContainerElement = this.splitView.mainElement;
58 this._constructCategories();
60 this._launcherView = new WebInspector.AuditLauncherView(this.initiateAudit.bind(this));
61 for (var id in this.categoriesById)
62 this._launcherView.addCategory(this.categoriesById[id]);
64 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoadEventFired, this);
65 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this);
68 WebInspector.AuditsPanel.prototype = {
69 get toolbarItemLabel()
71 return WebInspector.UIString("Audits");
76 return [this.clearResultsButton.element];
79 get mainResourceLoadTime()
81 return this._mainResourceLoadTime;
84 _onLoadEventFired: function(event)
86 this._mainResourceLoadTime = event.data;
87 this._didMainResourceLoad();
90 get mainResourceDOMContentTime()
92 return this._mainResourceDOMContentTime;
95 _domContentLoadedEventFired: function(event)
97 this._mainResourceDOMContentTime = event.data;
102 return this._auditCategoriesById;
105 addCategory: function(category)
107 this.categoriesById[category.id] = category;
108 this._launcherView.addCategory(category);
111 getCategory: function(id)
113 return this.categoriesById[id];
116 _constructCategories: function()
118 this._auditCategoriesById = {};
119 for (var categoryCtorID in WebInspector.AuditCategories) {
120 var auditCategory = new WebInspector.AuditCategories[categoryCtorID]();
121 auditCategory._id = categoryCtorID;
122 this.categoriesById[categoryCtorID] = auditCategory;
126 _executeAudit: function(categories, resultCallback)
128 var resources = WebInspector.networkLog.resources;
130 var rulesRemaining = 0;
131 for (var i = 0; i < categories.length; ++i)
132 rulesRemaining += categories[i].ruleCount;
135 var mainResourceURL = WebInspector.inspectedPageURL;
137 function ruleResultReadyCallback(categoryResult, ruleResult)
139 if (ruleResult && ruleResult.children)
140 categoryResult.addRuleResult(ruleResult);
144 if (!rulesRemaining && resultCallback)
145 resultCallback(mainResourceURL, results);
148 if (!rulesRemaining) {
149 resultCallback(mainResourceURL, results);
153 for (var i = 0; i < categories.length; ++i) {
154 var category = categories[i];
155 var result = new WebInspector.AuditCategoryResult(category);
156 results.push(result);
157 category.run(resources, ruleResultReadyCallback.bind(null, result));
161 _auditFinishedCallback: function(launcherCallback, mainResourceURL, results)
163 var children = this.auditResultsTreeElement.children;
165 for (var i = 0; i < children.length; ++i) {
166 if (children[i].mainResourceURL === mainResourceURL)
170 var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(results, mainResourceURL, ordinal);
171 this.auditResultsTreeElement.appendChild(resultTreeElement);
172 resultTreeElement.revealAndSelect();
173 if (launcherCallback)
177 initiateAudit: function(categoryIds, runImmediately, launcherCallback)
179 if (!categoryIds || !categoryIds.length)
183 for (var i = 0; i < categoryIds.length; ++i)
184 categories.push(this.categoriesById[categoryIds[i]]);
186 function initiateAuditCallback(categories, launcherCallback)
188 this._executeAudit(categories, this._auditFinishedCallback.bind(this, launcherCallback));
192 initiateAuditCallback.call(this, categories, launcherCallback);
194 this._reloadResources(initiateAuditCallback.bind(this, categories, launcherCallback));
196 WebInspector.userMetrics.AuditsStarted.record();
199 _reloadResources: function(callback)
201 this._pageReloadCallback = callback;
202 PageAgent.reload(false);
205 _didMainResourceLoad: function()
207 if (this._pageReloadCallback) {
208 var callback = this._pageReloadCallback;
209 delete this._pageReloadCallback;
214 showResults: function(categoryResults)
216 if (!categoryResults._resultView)
217 categoryResults._resultView = new WebInspector.AuditResultView(categoryResults);
219 this.visibleView = categoryResults._resultView;
222 showLauncherView: function()
224 this.visibleView = this._launcherView;
229 return this._visibleView;
234 if (this._visibleView === x)
237 if (this._visibleView)
238 this._visibleView.detach();
240 this._visibleView = x;
243 x.show(this.viewsContainerElement);
248 WebInspector.Panel.prototype.wasShown.call(this);
249 if (!this._visibleView)
250 this.auditsItemTreeElement.select();
253 _clearButtonClicked: function()
255 this.auditsItemTreeElement.revealAndSelect();
256 this.auditResultsTreeElement.removeChildren();
260 WebInspector.AuditsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
265 WebInspector.AuditCategory = function(displayName)
267 this._displayName = displayName;
271 WebInspector.AuditCategory.prototype = {
274 // this._id value is injected at construction time.
280 return this._displayName;
285 this._ensureInitialized();
286 return this._rules.length;
289 addRule: function(rule, severity)
291 rule.severity = severity;
292 this._rules.push(rule);
295 run: function(resources, callback)
297 this._ensureInitialized();
298 for (var i = 0; i < this._rules.length; ++i)
299 this._rules[i].run(resources, callback);
302 _ensureInitialized: function()
304 if (!this._initialized) {
305 if ("initialize" in this)
307 this._initialized = true;
315 WebInspector.AuditRule = function(id, displayName)
318 this._displayName = displayName;
321 WebInspector.AuditRule.Severity = {
327 WebInspector.AuditRule.SeverityOrder = {
333 WebInspector.AuditRule.prototype = {
341 return this._displayName;
344 set severity(severity)
346 this._severity = severity;
349 run: function(resources, callback)
351 var result = new WebInspector.AuditRuleResult(this.displayName);
352 result.severity = this._severity;
353 this.doRun(resources, result, callback);
356 doRun: function(resources, result, callback)
358 throw new Error("doRun() not implemented");
365 WebInspector.AuditCategoryResult = function(category)
367 this.title = category.displayName;
368 this.ruleResults = [];
371 WebInspector.AuditCategoryResult.prototype = {
372 addRuleResult: function(ruleResult)
374 this.ruleResults.push(ruleResult);
380 * @param {boolean=} expanded
381 * @param {string=} className
383 WebInspector.AuditRuleResult = function(value, expanded, className)
386 this.className = className;
387 this.expanded = expanded;
388 this.violationCount = 0;
390 r: WebInspector.AuditRuleResult.linkifyDisplayName
392 var standardFormatters = Object.keys(String.standardFormatters);
393 for (var i = 0; i < standardFormatters.length; ++i)
394 this._formatters[standardFormatters[i]] = String.standardFormatters[standardFormatters[i]];
397 WebInspector.AuditRuleResult.linkifyDisplayName = function(url)
399 return WebInspector.linkifyURLAsNode(url, WebInspector.displayNameForURL(url));
402 WebInspector.AuditRuleResult.resourceDomain = function(domain)
404 return domain || WebInspector.UIString("[empty domain]");
407 WebInspector.AuditRuleResult.prototype = {
409 * @param {boolean=} expanded
410 * @param {string=} className
412 addChild: function(value, expanded, className)
416 var entry = new WebInspector.AuditRuleResult(value, expanded, className);
417 this.children.push(entry);
421 addURL: function(url)
423 return this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url));
426 addURLs: function(urls)
428 for (var i = 0; i < urls.length; ++i)
429 this.addURL(urls[i]);
432 addSnippet: function(snippet)
434 return this.addChild(snippet, false, "source-code");
438 * @param {string} format
439 * @param {...*} vararg
441 addFormatted: function(format, vararg)
443 var substitutions = Array.prototype.slice.call(arguments, 1);
444 var fragment = document.createDocumentFragment();
446 var formattedResult = String.format(format, substitutions, this._formatters, fragment, this._append).formattedResult;
447 if (formattedResult instanceof Node)
448 formattedResult.normalize();
449 return this.addChild(formattedResult);
452 _append: function(a, b)
454 if (!(b instanceof Node))
455 b = document.createTextNode(b);
463 * @extends {WebInspector.SidebarTreeElement}
465 WebInspector.AuditsSidebarTreeElement = function()
469 WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"), "", null, false);
472 WebInspector.AuditsSidebarTreeElement.prototype = {
475 WebInspector.SidebarTreeElement.prototype.onattach.call(this);
480 WebInspector.panels.audits.showLauncherView();
490 this.refreshTitles();
494 WebInspector.AuditsSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
498 * @extends {WebInspector.SidebarTreeElement}
500 WebInspector.AuditResultSidebarTreeElement = function(results, mainResourceURL, ordinal)
502 this.results = results;
503 this.mainResourceURL = mainResourceURL;
505 WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal), "", {}, false);
508 WebInspector.AuditResultSidebarTreeElement.prototype = {
511 WebInspector.panels.audits.showResults(this.results);
520 WebInspector.AuditResultSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
522 // Contributed audit rules should go into this namespace.
523 WebInspector.AuditRules = {};
525 // Contributed audit categories should go into this namespace.
526 WebInspector.AuditCategories = {};