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