blob: 1a5060a392934579a18572878412583600167d5d [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @extends {WebInspector.PanelWithSidebar}
*/
WebInspector.AuditsPanel = function()
{
WebInspector.PanelWithSidebar.call(this, "audits");
this.registerRequiredCSS("ui/panelEnablerView.css");
this.registerRequiredCSS("audits/auditsPanel.css");
var sidebarTree = new TreeOutline();
sidebarTree.element.classList.add("sidebar-tree");
this.panelSidebarElement().appendChild(sidebarTree.element);
this.setDefaultFocusedElement(sidebarTree.element);
this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("");
sidebarTree.appendChild(this.auditsTreeElement);
this.auditsTreeElement.listItemElement.classList.add("hidden");
this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement(this);
this.auditsTreeElement.appendChild(this.auditsItemTreeElement);
this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"));
sidebarTree.appendChild(this.auditResultsTreeElement);
this.auditResultsTreeElement.expand();
this._constructCategories();
this._auditController = new WebInspector.AuditController(this);
this._launcherView = new WebInspector.AuditLauncherView(this._auditController);
for (var id in this.categoriesById)
this._launcherView.addCategory(this.categoriesById[id]);
var extensionCategories = WebInspector.extensionServer.auditCategories();
for (var i = 0; i < extensionCategories.length; ++i) {
var category = extensionCategories[i];
this.addCategory(new WebInspector.AuditExtensionCategory(category.extensionOrigin, category.id, category.displayName, category.ruleCount));
}
WebInspector.extensionServer.addEventListener(WebInspector.ExtensionServer.Events.AuditCategoryAdded, this._extensionAuditCategoryAdded, this);
}
WebInspector.AuditsPanel.prototype = {
/**
* @return {!Object.<string, !WebInspector.AuditCategory>}
*/
get categoriesById()
{
return this._auditCategoriesById;
},
/**
* @param {!WebInspector.AuditCategory} category
*/
addCategory: function(category)
{
this.categoriesById[category.id] = category;
this._launcherView.addCategory(category);
},
/**
* @param {string} id
* @return {!WebInspector.AuditCategory}
*/
getCategory: function(id)
{
return this.categoriesById[id];
},
_constructCategories: function()
{
this._auditCategoriesById = {};
for (var categoryCtorID in WebInspector.AuditCategories) {
var auditCategory = new WebInspector.AuditCategories[categoryCtorID]();
auditCategory._id = categoryCtorID;
this.categoriesById[categoryCtorID] = auditCategory;
}
},
/**
* @param {string} mainResourceURL
* @param {!Array.<!WebInspector.AuditCategoryResult>} results
*/
auditFinishedCallback: function(mainResourceURL, results)
{
var ordinal = 1;
for (var child of this.auditResultsTreeElement.children()) {
if (child.mainResourceURL === mainResourceURL)
ordinal++;
}
var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(this, results, mainResourceURL, ordinal);
this.auditResultsTreeElement.appendChild(resultTreeElement);
resultTreeElement.revealAndSelect();
},
/**
* @param {!Array.<!WebInspector.AuditCategoryResult>} categoryResults
*/
showResults: function(categoryResults)
{
if (!categoryResults._resultView)
categoryResults._resultView = new WebInspector.AuditResultView(categoryResults);
this.visibleView = categoryResults._resultView;
},
showLauncherView: function()
{
this.visibleView = this._launcherView;
},
get visibleView()
{
return this._visibleView;
},
set visibleView(x)
{
if (this._visibleView === x)
return;
if (this._visibleView)
this._visibleView.detach();
this._visibleView = x;
if (x)
this.splitWidget().setMainWidget(x);
},
wasShown: function()
{
WebInspector.Panel.prototype.wasShown.call(this);
if (!this._visibleView)
this.auditsItemTreeElement.select();
},
clearResults: function()
{
this.auditsItemTreeElement.revealAndSelect();
this.auditResultsTreeElement.removeChildren();
},
/**
* @param {!WebInspector.Event} event
*/
_extensionAuditCategoryAdded: function(event)
{
var category = /** @type {!WebInspector.ExtensionAuditCategory} */ (event.data);
this.addCategory(new WebInspector.AuditExtensionCategory(category.extensionOrigin, category.id, category.displayName, category.ruleCount));
},
__proto__: WebInspector.PanelWithSidebar.prototype
}
/**
* @constructor
* @implements {WebInspector.AuditCategory}
* @param {string} displayName
*/
WebInspector.AuditCategoryImpl = function(displayName)
{
this._displayName = displayName;
this._rules = [];
}
WebInspector.AuditCategoryImpl.prototype = {
/**
* @override
* @return {string}
*/
get id()
{
// this._id value is injected at construction time.
return this._id;
},
/**
* @override
* @return {string}
*/
get displayName()
{
return this._displayName;
},
/**
* @param {!WebInspector.AuditRule} rule
* @param {!WebInspector.AuditRule.Severity} severity
*/
addRule: function(rule, severity)
{
rule.severity = severity;
this._rules.push(rule);
},
/**
* @override
* @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
* @param {!WebInspector.Progress} progress
*/
run: function(target, requests, ruleResultCallback, progress)
{
this._ensureInitialized();
var remainingRulesCount = this._rules.length;
progress.setTotalWork(remainingRulesCount);
function callbackWrapper(result)
{
ruleResultCallback(result);
progress.worked();
if (!--remainingRulesCount)
progress.done();
}
for (var i = 0; i < this._rules.length; ++i) {
if (!progress.isCanceled())
this._rules[i].run(target, requests, callbackWrapper, progress);
else
callbackWrapper(null);
}
},
_ensureInitialized: function()
{
if (!this._initialized) {
if ("initialize" in this)
this.initialize();
this._initialized = true;
}
}
}
/**
* @constructor
* @param {string} id
* @param {string} displayName
*/
WebInspector.AuditRule = function(id, displayName)
{
this._id = id;
this._displayName = displayName;
}
/**
* @enum {string}
*/
WebInspector.AuditRule.Severity = {
Info: "info",
Warning: "warning",
Severe: "severe"
}
WebInspector.AuditRule.SeverityOrder = {
"info": 3,
"warning": 2,
"severe": 1
}
WebInspector.AuditRule.prototype = {
get id()
{
return this._id;
},
get displayName()
{
return this._displayName;
},
/**
* @param {!WebInspector.AuditRule.Severity} severity
*/
set severity(severity)
{
this._severity = severity;
},
/**
* @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
run: function(target, requests, callback, progress)
{
if (progress.isCanceled())
return;
var result = new WebInspector.AuditRuleResult(this.displayName);
result.severity = this._severity;
this.doRun(target, requests, result, callback, progress);
},
/**
* @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
doRun: function(target, requests, result, callback, progress)
{
throw new Error("doRun() not implemented");
}
}
/**
* @constructor
* @param {!WebInspector.AuditCategory} category
*/
WebInspector.AuditCategoryResult = function(category)
{
this.title = category.displayName;
this.ruleResults = [];
}
WebInspector.AuditCategoryResult.prototype = {
/**
* @param {!WebInspector.AuditRuleResult} ruleResult
*/
addRuleResult: function(ruleResult)
{
this.ruleResults.push(ruleResult);
}
}
/**
* @constructor
* @param {(string|boolean|number|!Object)} value
* @param {boolean=} expanded
* @param {string=} className
*/
WebInspector.AuditRuleResult = function(value, expanded, className)
{
this.value = value;
this.className = className;
this.expanded = expanded;
this.violationCount = 0;
this._formatters = {
r: WebInspector.AuditRuleResult.linkifyDisplayName
};
var standardFormatters = Object.keys(String.standardFormatters);
for (var i = 0; i < standardFormatters.length; ++i)
this._formatters[standardFormatters[i]] = String.standardFormatters[standardFormatters[i]];
}
/**
* @param {string} url
* @return {!Element}
*/
WebInspector.AuditRuleResult.linkifyDisplayName = function(url)
{
return WebInspector.linkifyURLAsNode(url, WebInspector.displayNameForURL(url));
}
/**
* @param {string} domain
* @return {string}
*/
WebInspector.AuditRuleResult.resourceDomain = function(domain)
{
return domain || WebInspector.UIString("[empty domain]");
}
WebInspector.AuditRuleResult.prototype = {
/**
* @param {(string|boolean|number|!Object)} value
* @param {boolean=} expanded
* @param {string=} className
* @return {!WebInspector.AuditRuleResult}
*/
addChild: function(value, expanded, className)
{
if (!this.children)
this.children = [];
var entry = new WebInspector.AuditRuleResult(value, expanded, className);
this.children.push(entry);
return entry;
},
/**
* @param {string} url
*/
addURL: function(url)
{
this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url));
},
/**
* @param {!Array.<string>} urls
*/
addURLs: function(urls)
{
for (var i = 0; i < urls.length; ++i)
this.addURL(urls[i]);
},
/**
* @param {string} snippet
*/
addSnippet: function(snippet)
{
this.addChild(snippet, false, "source-code");
},
/**
* @param {string} format
* @param {...*} vararg
* @return {!WebInspector.AuditRuleResult}
*/
addFormatted: function(format, vararg)
{
var substitutions = Array.prototype.slice.call(arguments, 1);
var fragment = createDocumentFragment();
function append(a, b)
{
if (!(b instanceof Node))
b = createTextNode(b);
a.appendChild(b);
return a;
}
var formattedResult = String.format(format, substitutions, this._formatters, fragment, append).formattedResult;
if (formattedResult instanceof Node)
formattedResult.normalize();
return this.addChild(formattedResult);
}
}
/**
* @constructor
* @extends {WebInspector.SidebarTreeElement}
* @param {!WebInspector.AuditsPanel} panel
*/
WebInspector.AuditsSidebarTreeElement = function(panel)
{
this._panel = panel;
this.small = false;
WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"));
}
WebInspector.AuditsSidebarTreeElement.prototype = {
onattach: function()
{
WebInspector.SidebarTreeElement.prototype.onattach.call(this);
},
/**
* @override
* @return {boolean}
*/
onselect: function()
{
this._panel.showLauncherView();
return true;
},
get selectable()
{
return true;
},
refresh: function()
{
this.refreshTitles();
},
__proto__: WebInspector.SidebarTreeElement.prototype
}
/**
* @constructor
* @extends {WebInspector.SidebarTreeElement}
* @param {!WebInspector.AuditsPanel} panel
* @param {!Array.<!WebInspector.AuditCategoryResult>} results
* @param {string} mainResourceURL
* @param {number} ordinal
*/
WebInspector.AuditResultSidebarTreeElement = function(panel, results, mainResourceURL, ordinal)
{
this._panel = panel;
this.results = results;
this.mainResourceURL = mainResourceURL;
WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal));
}
WebInspector.AuditResultSidebarTreeElement.prototype = {
/**
* @override
* @return {boolean}
*/
onselect: function()
{
this._panel.showResults(this.results);
return true;
},
get selectable()
{
return true;
},
__proto__: WebInspector.SidebarTreeElement.prototype
}
WebInspector.AuditsPanel.show = function()
{
WebInspector.inspectorView.setCurrentPanel(WebInspector.AuditsPanel.instance());
}
/**
* @return {!WebInspector.AuditsPanel}
*/
WebInspector.AuditsPanel.instance = function()
{
if (!WebInspector.AuditsPanel._instanceObject)
WebInspector.AuditsPanel._instanceObject = new WebInspector.AuditsPanel();
return WebInspector.AuditsPanel._instanceObject;
}
/**
* @constructor
* @implements {WebInspector.PanelFactory}
*/
WebInspector.AuditsPanelFactory = function()
{
}
WebInspector.AuditsPanelFactory.prototype = {
/**
* @override
* @return {!WebInspector.Panel}
*/
createPanel: function()
{
return WebInspector.AuditsPanel.instance();
}
}
// Contributed audit rules should go into this namespace.
WebInspector.AuditRules = {};
/**
* Contributed audit categories should go into this namespace.
* @type {!Object.<string, function(new:WebInspector.AuditCategory)>}
*/
WebInspector.AuditCategories = {};