blob: 390d1a226d4b74b3d87e84488fb1533c08bc2c04 [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.
*/
/**
* @unrestricted
*/
Audits.AuditsPanel = class extends UI.PanelWithSidebar {
constructor() {
super('audits');
this.registerRequiredCSS('ui/panelEnablerView.css');
this.registerRequiredCSS('audits/auditsPanel.css');
this._sidebarTree = new UI.TreeOutlineInShadow();
this._sidebarTree.registerRequiredCSS('audits/auditsSidebarTree.css');
this.panelSidebarElement().appendChild(this._sidebarTree.element);
this._auditsItemTreeElement = new Audits.AuditsSidebarTreeElement(this);
this._sidebarTree.appendChild(this._auditsItemTreeElement);
this._auditResultsTreeElement = new UI.TreeElement(Common.UIString('RESULTS'), true);
this._auditResultsTreeElement.selectable = false;
this._auditResultsTreeElement.listItemElement.classList.add('audits-sidebar-results');
this._auditResultsTreeElement.expand();
this._auditResultsTreeElement.setCollapsible(false);
this._sidebarTree.appendChild(this._auditResultsTreeElement);
this._constructCategories();
this._auditController = new Audits.AuditController(this);
this._launcherView = new Audits.AuditLauncherView(this._auditController);
for (var id in this.categoriesById)
this._launcherView.addCategory(this.categoriesById[id]);
var extensionCategories = Extensions.extensionServer.auditCategories();
for (var i = 0; i < extensionCategories.length; ++i) {
var category = extensionCategories[i];
this.addCategory(new Audits.AuditExtensionCategory(
category.extensionOrigin, category.id, category.displayName, category.ruleCount));
}
Extensions.extensionServer.addEventListener(
Extensions.ExtensionServer.Events.AuditCategoryAdded, this._extensionAuditCategoryAdded, this);
}
/**
* @return {!Audits.AuditsPanel}
*/
static instance() {
return /** @type {!Audits.AuditsPanel} */ (self.runtime.sharedInstance(Audits.AuditsPanel));
}
/**
* @return {!Object.<string, !Audits.AuditCategory>}
*/
get categoriesById() {
return this._auditCategoriesById;
}
/**
* @param {!Audits.AuditCategory} category
*/
addCategory(category) {
this.categoriesById[category.id] = category;
this._launcherView.addCategory(category);
}
/**
* @param {string} id
* @return {!Audits.AuditCategory}
*/
getCategory(id) {
return this.categoriesById[id];
}
_constructCategories() {
this._auditCategoriesById = {};
for (var categoryCtorID in Audits.AuditCategories) {
var auditCategory = new Audits.AuditCategories[categoryCtorID]();
auditCategory._id = categoryCtorID;
this.categoriesById[categoryCtorID] = auditCategory;
}
}
/**
* @param {string} mainResourceURL
* @param {!Array.<!Audits.AuditCategoryResult>} results
*/
auditFinishedCallback(mainResourceURL, results) {
var ordinal = 1;
for (var child of this._auditResultsTreeElement.children()) {
if (child.mainResourceURL === mainResourceURL)
ordinal++;
}
var resultTreeElement = new Audits.AuditResultSidebarTreeElement(this, results, mainResourceURL, ordinal);
this._auditResultsTreeElement.appendChild(resultTreeElement);
resultTreeElement.revealAndSelect();
}
/**
* @param {!Array.<!Audits.AuditCategoryResult>} categoryResults
*/
showResults(categoryResults) {
if (!categoryResults._resultLocation) {
categoryResults.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
var resultView = UI.viewManager.createStackLocation();
resultView.widget().element.classList.add('audit-result-view');
for (var i = 0; i < categoryResults.length; ++i)
resultView.showView(new Audits.AuditCategoryResultPane(categoryResults[i]));
categoryResults._resultLocation = resultView;
}
this.visibleView = categoryResults._resultLocation.widget();
}
showLauncherView() {
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);
}
/**
* @override
*/
wasShown() {
super.wasShown();
if (!this._visibleView)
this._auditsItemTreeElement.select();
}
/**
* @override
*/
focus() {
this._sidebarTree.focus();
}
clearResults() {
this._auditsItemTreeElement.revealAndSelect();
this._auditResultsTreeElement.removeChildren();
}
/**
* @param {!Common.Event} event
*/
_extensionAuditCategoryAdded(event) {
var category = /** @type {!Extensions.ExtensionAuditCategory} */ (event.data);
this.addCategory(new Audits.AuditExtensionCategory(
category.extensionOrigin, category.id, category.displayName, category.ruleCount));
}
};
/**
* @implements {Audits.AuditCategory}
* @unrestricted
*/
Audits.AuditCategoryImpl = class {
/**
* @param {string} displayName
*/
constructor(displayName) {
this._displayName = displayName;
this._rules = [];
}
/**
* @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 {!Audits.AuditRule} rule
* @param {!Audits.AuditRule.Severity} severity
*/
addRule(rule, severity) {
rule.severity = severity;
this._rules.push(rule);
}
/**
* @override
* @param {!SDK.Target} target
* @param {!Array.<!SDK.NetworkRequest>} requests
* @param {function(!Audits.AuditRuleResult)} ruleResultCallback
* @param {!Common.Progress} progress
*/
run(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() {
if (!this._initialized) {
if ('initialize' in this)
this.initialize();
this._initialized = true;
}
}
};
/**
* @unrestricted
*/
Audits.AuditRule = class {
/**
* @param {string} id
* @param {string} displayName
*/
constructor(id, displayName) {
this._id = id;
this._displayName = displayName;
}
get id() {
return this._id;
}
get displayName() {
return this._displayName;
}
/**
* @param {!Audits.AuditRule.Severity} severity
*/
set severity(severity) {
this._severity = severity;
}
/**
* @param {!SDK.Target} target
* @param {!Array.<!SDK.NetworkRequest>} requests
* @param {function(?Audits.AuditRuleResult)} callback
* @param {!Common.Progress} progress
*/
run(target, requests, callback, progress) {
if (progress.isCanceled())
return;
var result = new Audits.AuditRuleResult(this.displayName);
result.severity = this._severity;
this.doRun(target, requests, result, callback, progress);
}
/**
* @param {!SDK.Target} target
* @param {!Array.<!SDK.NetworkRequest>} requests
* @param {!Audits.AuditRuleResult} result
* @param {function(?Audits.AuditRuleResult)} callback
* @param {!Common.Progress} progress
*/
doRun(target, requests, result, callback, progress) {
throw new Error('doRun() not implemented');
}
};
/**
* @enum {string}
*/
Audits.AuditRule.Severity = {
Info: 'info',
Warning: 'warning',
Severe: 'severe'
};
Audits.AuditRule.SeverityOrder = {
'info': 3,
'warning': 2,
'severe': 1
};
/**
* @unrestricted
*/
Audits.AuditCategoryResult = class {
/**
* @param {!Audits.AuditCategory} category
*/
constructor(category) {
this.title = category.displayName;
this.ruleResults = [];
}
/**
* @param {!Audits.AuditRuleResult} ruleResult
*/
addRuleResult(ruleResult) {
this.ruleResults.push(ruleResult);
}
};
/**
* @unrestricted
*/
Audits.AuditRuleResult = class {
/**
* @param {(string|boolean|number|!Object)} value
* @param {boolean=} expanded
* @param {string=} className
*/
constructor(value, expanded, className) {
this.value = value;
this.className = className;
this.expanded = expanded;
this.violationCount = 0;
this._formatters = {r: Audits.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}
*/
static linkifyDisplayName(url) {
return Components.Linkifier.linkifyURL(url, Bindings.displayNameForURL(url));
}
/**
* @param {string} domain
* @return {string}
*/
static resourceDomain(domain) {
return domain || Common.UIString('[empty domain]');
}
/**
* @param {(string|boolean|number|!Object)} value
* @param {boolean=} expanded
* @param {string=} className
* @return {!Audits.AuditRuleResult}
*/
addChild(value, expanded, className) {
if (!this.children)
this.children = [];
var entry = new Audits.AuditRuleResult(value, expanded, className);
this.children.push(entry);
return entry;
}
/**
* @param {string} url
*/
addURL(url) {
this.addChild(Audits.AuditRuleResult.linkifyDisplayName(url));
}
/**
* @param {!Array.<string>} urls
*/
addURLs(urls) {
for (var i = 0; i < urls.length; ++i)
this.addURL(urls[i]);
}
/**
* @param {string} snippet
*/
addSnippet(snippet) {
this.addChild(snippet, false, 'source-code');
}
/**
* @param {string} format
* @param {...*} vararg
* @return {!Audits.AuditRuleResult}
*/
addFormatted(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);
}
};
/**
* @unrestricted
*/
Audits.AuditsSidebarTreeElement = class extends UI.TreeElement {
/**
* @param {!Audits.AuditsPanel} panel
*/
constructor(panel) {
super(Common.UIString('Audits'), false);
this.selectable = true;
this._panel = panel;
this.listItemElement.classList.add('audits-sidebar-header');
this.listItemElement.insertBefore(createElementWithClass('div', 'icon'), this.listItemElement.firstChild);
}
/**
* @override
* @return {boolean}
*/
onselect() {
this._panel.showLauncherView();
return true;
}
};
/**
* @unrestricted
*/
Audits.AuditResultSidebarTreeElement = class extends UI.TreeElement {
/**
* @param {!Audits.AuditsPanel} panel
* @param {!Array.<!Audits.AuditCategoryResult>} results
* @param {string} mainResourceURL
* @param {number} ordinal
*/
constructor(panel, results, mainResourceURL, ordinal) {
super(String.sprintf('%s (%d)', mainResourceURL, ordinal), false);
this.selectable = true;
this._panel = panel;
this.results = results;
this.mainResourceURL = mainResourceURL;
this.listItemElement.classList.add('audit-result-sidebar-tree-item');
this.listItemElement.insertBefore(createElementWithClass('div', 'icon'), this.listItemElement.firstChild);
}
/**
* @override
* @return {boolean}
*/
onselect() {
this._panel.showResults(this.results);
return true;
}
};
// Contributed audit rules should go into this namespace.
Audits.AuditRules = {};
/**
* Contributed audit categories should go into this namespace.
* @type {!Object.<string, function(new:Audits.AuditCategory)>}
*/
Audits.AuditCategories = {};