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