Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / audits / AuditsPanel.js
blob1a5060a392934579a18572878412583600167d5d
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  */
31 /**
32  * @constructor
33  * @extends {WebInspector.PanelWithSidebar}
34  */
35 WebInspector.AuditsPanel = function()
37     WebInspector.PanelWithSidebar.call(this, "audits");
38     this.registerRequiredCSS("ui/panelEnablerView.css");
39     this.registerRequiredCSS("audits/auditsPanel.css");
41     var sidebarTree = new TreeOutline();
42     sidebarTree.element.classList.add("sidebar-tree");
43     this.panelSidebarElement().appendChild(sidebarTree.element);
44     this.setDefaultFocusedElement(sidebarTree.element);
46     this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("");
47     sidebarTree.appendChild(this.auditsTreeElement);
48     this.auditsTreeElement.listItemElement.classList.add("hidden");
50     this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement(this);
51     this.auditsTreeElement.appendChild(this.auditsItemTreeElement);
53     this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"));
54     sidebarTree.appendChild(this.auditResultsTreeElement);
55     this.auditResultsTreeElement.expand();
57     this._constructCategories();
59     this._auditController = new WebInspector.AuditController(this);
60     this._launcherView = new WebInspector.AuditLauncherView(this._auditController);
61     for (var id in this.categoriesById)
62         this._launcherView.addCategory(this.categoriesById[id]);
64     var extensionCategories = WebInspector.extensionServer.auditCategories();
65     for (var i = 0; i < extensionCategories.length; ++i) {
66         var category = extensionCategories[i];
67         this.addCategory(new WebInspector.AuditExtensionCategory(category.extensionOrigin, category.id, category.displayName, category.ruleCount));
68     }
69     WebInspector.extensionServer.addEventListener(WebInspector.ExtensionServer.Events.AuditCategoryAdded, this._extensionAuditCategoryAdded, this);
72 WebInspector.AuditsPanel.prototype = {
74     /**
75      * @return {!Object.<string, !WebInspector.AuditCategory>}
76      */
77     get categoriesById()
78     {
79         return this._auditCategoriesById;
80     },
82     /**
83      * @param {!WebInspector.AuditCategory} category
84      */
85     addCategory: function(category)
86     {
87         this.categoriesById[category.id] = category;
88         this._launcherView.addCategory(category);
89     },
91     /**
92      * @param {string} id
93      * @return {!WebInspector.AuditCategory}
94      */
95     getCategory: function(id)
96     {
97         return this.categoriesById[id];
98     },
100     _constructCategories: function()
101     {
102         this._auditCategoriesById = {};
103         for (var categoryCtorID in WebInspector.AuditCategories) {
104             var auditCategory = new WebInspector.AuditCategories[categoryCtorID]();
105             auditCategory._id = categoryCtorID;
106             this.categoriesById[categoryCtorID] = auditCategory;
107         }
108     },
110     /**
111      * @param {string} mainResourceURL
112      * @param {!Array.<!WebInspector.AuditCategoryResult>} results
113      */
114     auditFinishedCallback: function(mainResourceURL, results)
115     {
116         var ordinal = 1;
117         for (var child of this.auditResultsTreeElement.children()) {
118             if (child.mainResourceURL === mainResourceURL)
119                 ordinal++;
120         }
122         var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(this, results, mainResourceURL, ordinal);
123         this.auditResultsTreeElement.appendChild(resultTreeElement);
124         resultTreeElement.revealAndSelect();
125     },
127     /**
128      * @param {!Array.<!WebInspector.AuditCategoryResult>} categoryResults
129      */
130     showResults: function(categoryResults)
131     {
132         if (!categoryResults._resultView)
133             categoryResults._resultView = new WebInspector.AuditResultView(categoryResults);
135         this.visibleView = categoryResults._resultView;
136     },
138     showLauncherView: function()
139     {
140         this.visibleView = this._launcherView;
141     },
143     get visibleView()
144     {
145         return this._visibleView;
146     },
148     set visibleView(x)
149     {
150         if (this._visibleView === x)
151             return;
153         if (this._visibleView)
154             this._visibleView.detach();
156         this._visibleView = x;
158         if (x)
159             this.splitWidget().setMainWidget(x);
160     },
162     wasShown: function()
163     {
164         WebInspector.Panel.prototype.wasShown.call(this);
165         if (!this._visibleView)
166             this.auditsItemTreeElement.select();
167     },
169     clearResults: function()
170     {
171         this.auditsItemTreeElement.revealAndSelect();
172         this.auditResultsTreeElement.removeChildren();
173     },
175     /**
176      * @param {!WebInspector.Event} event
177      */
178     _extensionAuditCategoryAdded: function(event)
179     {
180         var category = /** @type {!WebInspector.ExtensionAuditCategory} */ (event.data);
181         this.addCategory(new WebInspector.AuditExtensionCategory(category.extensionOrigin, category.id, category.displayName, category.ruleCount));
182     },
184     __proto__: WebInspector.PanelWithSidebar.prototype
188  * @constructor
189  * @implements {WebInspector.AuditCategory}
190  * @param {string} displayName
191  */
192 WebInspector.AuditCategoryImpl = function(displayName)
194     this._displayName = displayName;
195     this._rules = [];
198 WebInspector.AuditCategoryImpl.prototype = {
199     /**
200      * @override
201      * @return {string}
202      */
203     get id()
204     {
205         // this._id value is injected at construction time.
206         return this._id;
207     },
209     /**
210      * @override
211      * @return {string}
212      */
213     get displayName()
214     {
215         return this._displayName;
216     },
218     /**
219      * @param {!WebInspector.AuditRule} rule
220      * @param {!WebInspector.AuditRule.Severity} severity
221      */
222     addRule: function(rule, severity)
223     {
224         rule.severity = severity;
225         this._rules.push(rule);
226     },
228     /**
229      * @override
230      * @param {!WebInspector.Target} target
231      * @param {!Array.<!WebInspector.NetworkRequest>} requests
232      * @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
233      * @param {!WebInspector.Progress} progress
234      */
235     run: function(target, requests, ruleResultCallback, progress)
236     {
237         this._ensureInitialized();
238         var remainingRulesCount = this._rules.length;
239         progress.setTotalWork(remainingRulesCount);
240         function callbackWrapper(result)
241         {
242             ruleResultCallback(result);
243             progress.worked();
244             if (!--remainingRulesCount)
245                 progress.done();
246         }
247         for (var i = 0; i < this._rules.length; ++i) {
248             if (!progress.isCanceled())
249                 this._rules[i].run(target, requests, callbackWrapper, progress);
250             else
251                 callbackWrapper(null);
252         }
253     },
255     _ensureInitialized: function()
256     {
257         if (!this._initialized) {
258             if ("initialize" in this)
259                 this.initialize();
260             this._initialized = true;
261         }
262     }
266  * @constructor
267  * @param {string} id
268  * @param {string} displayName
269  */
270 WebInspector.AuditRule = function(id, displayName)
272     this._id = id;
273     this._displayName = displayName;
277  * @enum {string}
278  */
279 WebInspector.AuditRule.Severity = {
280     Info: "info",
281     Warning: "warning",
282     Severe: "severe"
285 WebInspector.AuditRule.SeverityOrder = {
286     "info": 3,
287     "warning": 2,
288     "severe": 1
291 WebInspector.AuditRule.prototype = {
292     get id()
293     {
294         return this._id;
295     },
297     get displayName()
298     {
299         return this._displayName;
300     },
302     /**
303      * @param {!WebInspector.AuditRule.Severity} severity
304      */
305     set severity(severity)
306     {
307         this._severity = severity;
308     },
310     /**
311      * @param {!WebInspector.Target} target
312      * @param {!Array.<!WebInspector.NetworkRequest>} requests
313      * @param {function(?WebInspector.AuditRuleResult)} callback
314      * @param {!WebInspector.Progress} progress
315      */
316     run: function(target, requests, callback, progress)
317     {
318         if (progress.isCanceled())
319             return;
321         var result = new WebInspector.AuditRuleResult(this.displayName);
322         result.severity = this._severity;
323         this.doRun(target, requests, result, callback, progress);
324     },
326     /**
327      * @param {!WebInspector.Target} target
328      * @param {!Array.<!WebInspector.NetworkRequest>} requests
329      * @param {!WebInspector.AuditRuleResult} result
330      * @param {function(?WebInspector.AuditRuleResult)} callback
331      * @param {!WebInspector.Progress} progress
332      */
333     doRun: function(target, requests, result, callback, progress)
334     {
335         throw new Error("doRun() not implemented");
336     }
340  * @constructor
341  * @param {!WebInspector.AuditCategory} category
342  */
343 WebInspector.AuditCategoryResult = function(category)
345     this.title = category.displayName;
346     this.ruleResults = [];
349 WebInspector.AuditCategoryResult.prototype = {
350     /**
351      * @param {!WebInspector.AuditRuleResult} ruleResult
352      */
353     addRuleResult: function(ruleResult)
354     {
355         this.ruleResults.push(ruleResult);
356     }
360  * @constructor
361  * @param {(string|boolean|number|!Object)} value
362  * @param {boolean=} expanded
363  * @param {string=} className
364  */
365 WebInspector.AuditRuleResult = function(value, expanded, className)
367     this.value = value;
368     this.className = className;
369     this.expanded = expanded;
370     this.violationCount = 0;
371     this._formatters = {
372         r: WebInspector.AuditRuleResult.linkifyDisplayName
373     };
374     var standardFormatters = Object.keys(String.standardFormatters);
375     for (var i = 0; i < standardFormatters.length; ++i)
376         this._formatters[standardFormatters[i]] = String.standardFormatters[standardFormatters[i]];
380  * @param {string} url
381  * @return {!Element}
382  */
383 WebInspector.AuditRuleResult.linkifyDisplayName = function(url)
385     return WebInspector.linkifyURLAsNode(url, WebInspector.displayNameForURL(url));
389  * @param {string} domain
390  * @return {string}
391  */
392 WebInspector.AuditRuleResult.resourceDomain = function(domain)
394     return domain || WebInspector.UIString("[empty domain]");
397 WebInspector.AuditRuleResult.prototype = {
398     /**
399      * @param {(string|boolean|number|!Object)} value
400      * @param {boolean=} expanded
401      * @param {string=} className
402      * @return {!WebInspector.AuditRuleResult}
403      */
404     addChild: function(value, expanded, className)
405     {
406         if (!this.children)
407             this.children = [];
408         var entry = new WebInspector.AuditRuleResult(value, expanded, className);
409         this.children.push(entry);
410         return entry;
411     },
413     /**
414      * @param {string} url
415      */
416     addURL: function(url)
417     {
418         this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url));
419     },
421     /**
422      * @param {!Array.<string>} urls
423      */
424     addURLs: function(urls)
425     {
426         for (var i = 0; i < urls.length; ++i)
427             this.addURL(urls[i]);
428     },
430     /**
431      * @param {string} snippet
432      */
433     addSnippet: function(snippet)
434     {
435         this.addChild(snippet, false, "source-code");
436     },
438     /**
439      * @param {string} format
440      * @param {...*} vararg
441      * @return {!WebInspector.AuditRuleResult}
442      */
443     addFormatted: function(format, vararg)
444     {
445         var substitutions = Array.prototype.slice.call(arguments, 1);
446         var fragment = createDocumentFragment();
448         function append(a, b)
449         {
450             if (!(b instanceof Node))
451                 b = createTextNode(b);
452             a.appendChild(b);
453             return a;
454         }
456         var formattedResult = String.format(format, substitutions, this._formatters, fragment, append).formattedResult;
457         if (formattedResult instanceof Node)
458             formattedResult.normalize();
459         return this.addChild(formattedResult);
460     }
464  * @constructor
465  * @extends {WebInspector.SidebarTreeElement}
466  * @param {!WebInspector.AuditsPanel} panel
467  */
468 WebInspector.AuditsSidebarTreeElement = function(panel)
470     this._panel = panel;
471     this.small = false;
472     WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"));
475 WebInspector.AuditsSidebarTreeElement.prototype = {
476     onattach: function()
477     {
478         WebInspector.SidebarTreeElement.prototype.onattach.call(this);
479     },
481     /**
482      * @override
483      * @return {boolean}
484      */
485     onselect: function()
486     {
487         this._panel.showLauncherView();
488         return true;
489     },
491     get selectable()
492     {
493         return true;
494     },
496     refresh: function()
497     {
498         this.refreshTitles();
499     },
501     __proto__: WebInspector.SidebarTreeElement.prototype
505  * @constructor
506  * @extends {WebInspector.SidebarTreeElement}
507  * @param {!WebInspector.AuditsPanel} panel
508  * @param {!Array.<!WebInspector.AuditCategoryResult>} results
509  * @param {string} mainResourceURL
510  * @param {number} ordinal
511  */
512 WebInspector.AuditResultSidebarTreeElement = function(panel, results, mainResourceURL, ordinal)
514     this._panel = panel;
515     this.results = results;
516     this.mainResourceURL = mainResourceURL;
517     WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal));
520 WebInspector.AuditResultSidebarTreeElement.prototype = {
521     /**
522      * @override
523      * @return {boolean}
524      */
525     onselect: function()
526     {
527         this._panel.showResults(this.results);
528         return true;
529     },
531     get selectable()
532     {
533         return true;
534     },
536     __proto__: WebInspector.SidebarTreeElement.prototype
539 WebInspector.AuditsPanel.show = function()
541     WebInspector.inspectorView.setCurrentPanel(WebInspector.AuditsPanel.instance());
545  * @return {!WebInspector.AuditsPanel}
546  */
547 WebInspector.AuditsPanel.instance = function()
549     if (!WebInspector.AuditsPanel._instanceObject)
550         WebInspector.AuditsPanel._instanceObject = new WebInspector.AuditsPanel();
551     return WebInspector.AuditsPanel._instanceObject;
555  * @constructor
556  * @implements {WebInspector.PanelFactory}
557  */
558 WebInspector.AuditsPanelFactory = function()
562 WebInspector.AuditsPanelFactory.prototype = {
563     /**
564      * @override
565      * @return {!WebInspector.Panel}
566      */
567     createPanel: function()
568     {
569         return WebInspector.AuditsPanel.instance();
570     }
573 // Contributed audit rules should go into this namespace.
574 WebInspector.AuditRules = {};
577  * Contributed audit categories should go into this namespace.
578  * @type {!Object.<string, function(new:WebInspector.AuditCategory)>}
579  */
580 WebInspector.AuditCategories = {};