Home | History | Annotate | Download | only in front_end
      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  */
     35 WebInspector.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 
     65 WebInspector.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  */
    183 WebInspector.AuditCategory = function(displayName)
    184 {
    185     this._displayName = displayName;
    186     this._rules = [];
    187 }
    188 
    189 WebInspector.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  */
    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 /**
    270  * @enum {number}
    271  */
    272 WebInspector.AuditRule.SeverityOrder = {
    273     "info": 3,
    274     "warning": 2,
    275     "severe": 1
    276 }
    277 
    278 WebInspector.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  */
    328 WebInspector.AuditCategoryResult = function(category)
    329 {
    330     this.title = category.displayName;
    331     this.ruleResults = [];
    332 }
    333 
    334 WebInspector.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  */
    350 WebInspector.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  */
    368 WebInspector.AuditRuleResult.linkifyDisplayName = function(url)
    369 {
    370     return WebInspector.linkifyURLAsNode(url, WebInspector.displayNameForURL(url));
    371 }
    372 
    373 WebInspector.AuditRuleResult.resourceDomain = function(domain)
    374 {
    375     return domain || WebInspector.UIString("[empty domain]");
    376 }
    377 
    378 WebInspector.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  */
    449 WebInspector.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 
    456 WebInspector.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  */
    488 WebInspector.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 
    496 WebInspector.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.
    511 WebInspector.AuditRules = {};
    512 
    513 /**
    514  * Contributed audit categories should go into this namespace.
    515  * @type {!Object.<string, function(new:WebInspector.AuditCategory)>}
    516  */
    517 WebInspector.AuditCategories = {};
    518 
    519 importScript("AuditCategories.js");
    520 importScript("AuditController.js");
    521 importScript("AuditFormatters.js");
    522 importScript("AuditLauncherView.js");
    523 importScript("AuditResultView.js");
    524 importScript("AuditRules.js");
    525