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