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