Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2011 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 WebInspector.AuditsPanel = function()
     32 {
     33     WebInspector.Panel.call(this, "audits");
     34 
     35     this.createSidebar();
     36     this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("", {}, true);
     37     this.sidebarTree.appendChild(this.auditsTreeElement);
     38     this.auditsTreeElement.listItemElement.addStyleClass("hidden");
     39     this.auditsTreeElement.expand();
     40 
     41     this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement();
     42     this.auditsTreeElement.appendChild(this.auditsItemTreeElement);
     43 
     44     this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"), {}, true);
     45     this.sidebarTree.appendChild(this.auditResultsTreeElement);
     46     this.auditResultsTreeElement.expand();
     47 
     48     this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear audit results."), "clear-status-bar-item");
     49     this.clearResultsButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
     50 
     51     this.viewsContainerElement = document.createElement("div");
     52     this.viewsContainerElement.id = "audit-views";
     53     this.element.appendChild(this.viewsContainerElement);
     54 
     55     this._constructCategories();
     56 
     57     this._launcherView = new WebInspector.AuditLauncherView(this.initiateAudit.bind(this));
     58     for (id in this.categoriesById)
     59         this._launcherView.addCategory(this.categoriesById[id]);
     60 }
     61 
     62 WebInspector.AuditsPanel.prototype = {
     63     get toolbarItemLabel()
     64     {
     65         return WebInspector.UIString("Audits");
     66     },
     67 
     68     get statusBarItems()
     69     {
     70         return [this.clearResultsButton.element];
     71     },
     72 
     73     get mainResourceLoadTime()
     74     {
     75         return this._mainResourceLoadTime;
     76     },
     77 
     78     set mainResourceLoadTime(x)
     79     {
     80         this._mainResourceLoadTime = x;
     81         this._didMainResourceLoad();
     82     },
     83 
     84     get mainResourceDOMContentTime()
     85     {
     86         return this._mainResourceDOMContentTime;
     87     },
     88 
     89     set mainResourceDOMContentTime(x)
     90     {
     91         this._mainResourceDOMContentTime = x;
     92     },
     93 
     94     get categoriesById()
     95     {
     96         return this._auditCategoriesById;
     97     },
     98 
     99     addCategory: function(category)
    100     {
    101         this.categoriesById[category.id] = category;
    102         this._launcherView.addCategory(category);
    103     },
    104 
    105     getCategory: function(id)
    106     {
    107         return this.categoriesById[id];
    108     },
    109 
    110     _constructCategories: function()
    111     {
    112         this._auditCategoriesById = {};
    113         for (var categoryCtorID in WebInspector.AuditCategories) {
    114             var auditCategory = new WebInspector.AuditCategories[categoryCtorID]();
    115             auditCategory._id = categoryCtorID;
    116             this.categoriesById[categoryCtorID] = auditCategory;
    117         }
    118     },
    119 
    120     _executeAudit: function(categories, resultCallback)
    121     {
    122         var resources = WebInspector.networkResources;
    123 
    124         var rulesRemaining = 0;
    125         for (var i = 0; i < categories.length; ++i)
    126             rulesRemaining += categories[i].ruleCount;
    127 
    128         var results = [];
    129         var mainResourceURL = WebInspector.mainResource.url;
    130 
    131         function ruleResultReadyCallback(categoryResult, ruleResult)
    132         {
    133             if (ruleResult && ruleResult.children)
    134                 categoryResult.addRuleResult(ruleResult);
    135 
    136             --rulesRemaining;
    137 
    138             if (!rulesRemaining && resultCallback)
    139                 resultCallback(mainResourceURL, results);
    140         }
    141 
    142         if (!rulesRemaining) {
    143             resultCallback(mainResourceURL, results);
    144             return;
    145         }
    146 
    147         for (var i = 0; i < categories.length; ++i) {
    148             var category = categories[i];
    149             var result = new WebInspector.AuditCategoryResult(category);
    150             results.push(result);
    151             category.run(resources, ruleResultReadyCallback.bind(null, result));
    152         }
    153     },
    154 
    155     _auditFinishedCallback: function(launcherCallback, mainResourceURL, results)
    156     {
    157         var children = this.auditResultsTreeElement.children;
    158         var ordinal = 1;
    159         for (var i = 0; i < children.length; ++i) {
    160             if (children[i].mainResourceURL === mainResourceURL)
    161                 ordinal++;
    162         }
    163 
    164         var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(results, mainResourceURL, ordinal);
    165         this.auditResultsTreeElement.appendChild(resultTreeElement);
    166         resultTreeElement.reveal();
    167         resultTreeElement.select();
    168         if (launcherCallback)
    169             launcherCallback();
    170     },
    171 
    172     initiateAudit: function(categoryIds, runImmediately, launcherCallback)
    173     {
    174         if (!categoryIds || !categoryIds.length)
    175             return;
    176 
    177         var categories = [];
    178         for (var i = 0; i < categoryIds.length; ++i)
    179             categories.push(this.categoriesById[categoryIds[i]]);
    180 
    181         function initiateAuditCallback(categories, launcherCallback)
    182         {
    183             this._executeAudit(categories, this._auditFinishedCallback.bind(this, launcherCallback));
    184         }
    185 
    186         if (runImmediately)
    187             initiateAuditCallback.call(this, categories, launcherCallback);
    188         else
    189             this._reloadResources(initiateAuditCallback.bind(this, categories, launcherCallback));
    190     },
    191 
    192     _reloadResources: function(callback)
    193     {
    194         this._pageReloadCallback = callback;
    195         PageAgent.reloadPage(false);
    196     },
    197 
    198     _didMainResourceLoad: function()
    199     {
    200         if (this._pageReloadCallback) {
    201             var callback = this._pageReloadCallback;
    202             delete this._pageReloadCallback;
    203             callback();
    204         }
    205     },
    206 
    207     showResults: function(categoryResults)
    208     {
    209         if (!categoryResults._resultView)
    210             categoryResults._resultView = new WebInspector.AuditResultView(categoryResults);
    211 
    212         this.visibleView = categoryResults._resultView;
    213     },
    214 
    215     showLauncherView: function()
    216     {
    217         this.visibleView = this._launcherView;
    218     },
    219 
    220     get visibleView()
    221     {
    222         return this._visibleView;
    223     },
    224 
    225     set visibleView(x)
    226     {
    227         if (this._visibleView === x)
    228             return;
    229 
    230         if (this._visibleView)
    231             this._visibleView.hide();
    232 
    233         this._visibleView = x;
    234 
    235         if (x)
    236             x.show(this.viewsContainerElement);
    237     },
    238 
    239     attach: function()
    240     {
    241         WebInspector.Panel.prototype.attach.call(this);
    242 
    243         this.auditsItemTreeElement.select();
    244     },
    245 
    246     updateMainViewWidth: function(width)
    247     {
    248         this.viewsContainerElement.style.left = width + "px";
    249     },
    250 
    251     _clearButtonClicked: function()
    252     {
    253         this.auditsItemTreeElement.reveal();
    254         this.auditsItemTreeElement.select();
    255         this.auditResultsTreeElement.removeChildren();
    256     }
    257 }
    258 
    259 WebInspector.AuditsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
    260 
    261 
    262 
    263 WebInspector.AuditCategory = function(displayName)
    264 {
    265     this._displayName = displayName;
    266     this._rules = [];
    267 }
    268 
    269 WebInspector.AuditCategory.prototype = {
    270     get id()
    271     {
    272         // this._id value is injected at construction time.
    273         return this._id;
    274     },
    275 
    276     get displayName()
    277     {
    278         return this._displayName;
    279     },
    280 
    281     get ruleCount()
    282     {
    283         this._ensureInitialized();
    284         return this._rules.length;
    285     },
    286 
    287     addRule: function(rule, severity)
    288     {
    289         rule.severity = severity;
    290         this._rules.push(rule);
    291     },
    292 
    293     run: function(resources, callback)
    294     {
    295         this._ensureInitialized();
    296         for (var i = 0; i < this._rules.length; ++i)
    297             this._rules[i].run(resources, callback);
    298     },
    299 
    300     _ensureInitialized: function()
    301     {
    302         if (!this._initialized) {
    303             if ("initialize" in this)
    304                 this.initialize();
    305             this._initialized = true;
    306         }
    307     }
    308 }
    309 
    310 
    311 WebInspector.AuditRule = function(id, displayName)
    312 {
    313     this._id = id;
    314     this._displayName = displayName;
    315 }
    316 
    317 WebInspector.AuditRule.Severity = {
    318     Info: "info",
    319     Warning: "warning",
    320     Severe: "severe"
    321 }
    322 
    323 WebInspector.AuditRule.SeverityOrder = {
    324     "info": 3,
    325     "warning": 2,
    326     "severe": 1
    327 }
    328 
    329 WebInspector.AuditRule.prototype = {
    330     get id()
    331     {
    332         return this._id;
    333     },
    334 
    335     get displayName()
    336     {
    337         return this._displayName;
    338     },
    339 
    340     set severity(severity)
    341     {
    342         this._severity = severity;
    343     },
    344 
    345     run: function(resources, callback)
    346     {
    347         var result = new WebInspector.AuditRuleResult(this.displayName);
    348         result.severity = this._severity;
    349         this.doRun(resources, result, callback);
    350     },
    351 
    352     doRun: function(resources, result, callback)
    353     {
    354         throw new Error("doRun() not implemented");
    355     }
    356 }
    357 
    358 WebInspector.AuditCategoryResult = function(category)
    359 {
    360     this.title = category.displayName;
    361     this.ruleResults = [];
    362 }
    363 
    364 WebInspector.AuditCategoryResult.prototype = {
    365     addRuleResult: function(ruleResult)
    366     {
    367         this.ruleResults.push(ruleResult);
    368     }
    369 }
    370 
    371 WebInspector.AuditRuleResult = function(value, expanded, className)
    372 {
    373     this.value = value;
    374     this.className = className;
    375     this.expanded = expanded;
    376     this.violationCount = 0;
    377 }
    378 
    379 WebInspector.AuditRuleResult.linkifyDisplayName = function(url)
    380 {
    381     return WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url));
    382 }
    383 
    384 WebInspector.AuditRuleResult.resourceDomain = function(domain)
    385 {
    386     return domain || WebInspector.UIString("[empty domain]");
    387 }
    388 
    389 WebInspector.AuditRuleResult.prototype = {
    390     addChild: function(value, expanded, className)
    391     {
    392         if (!this.children)
    393             this.children = [];
    394         var entry = new WebInspector.AuditRuleResult(value, expanded, className);
    395         this.children.push(entry);
    396         return entry;
    397     },
    398 
    399     addURL: function(url)
    400     {
    401         return this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url));
    402     },
    403 
    404     addURLs: function(urls)
    405     {
    406         for (var i = 0; i < urls.length; ++i)
    407             this.addURL(urls[i]);
    408     },
    409 
    410     addSnippet: function(snippet)
    411     {
    412         return this.addChild(snippet, false, "source-code");
    413     }
    414 }
    415 
    416 WebInspector.AuditsSidebarTreeElement = function()
    417 {
    418     this.small = false;
    419 
    420     WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"), "", null, false);
    421 }
    422 
    423 WebInspector.AuditsSidebarTreeElement.prototype = {
    424     onattach: function()
    425     {
    426         WebInspector.SidebarTreeElement.prototype.onattach.call(this);
    427     },
    428 
    429     onselect: function()
    430     {
    431         WebInspector.panels.audits.showLauncherView();
    432     },
    433 
    434     get selectable()
    435     {
    436         return true;
    437     },
    438 
    439     refresh: function()
    440     {
    441         this.refreshTitles();
    442     }
    443 }
    444 
    445 WebInspector.AuditsSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
    446 
    447 
    448 WebInspector.AuditResultSidebarTreeElement = function(results, mainResourceURL, ordinal)
    449 {
    450     this.results = results;
    451     this.mainResourceURL = mainResourceURL;
    452 
    453     WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal), "", {}, false);
    454 }
    455 
    456 WebInspector.AuditResultSidebarTreeElement.prototype = {
    457     onselect: function()
    458     {
    459         WebInspector.panels.audits.showResults(this.results);
    460     },
    461 
    462     get selectable()
    463     {
    464         return true;
    465     }
    466 }
    467 
    468 WebInspector.AuditResultSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
    469 
    470 // Contributed audit rules should go into this namespace.
    471 WebInspector.AuditRules = {};
    472 
    473 // Contributed audit categories should go into this namespace.
    474 WebInspector.AuditCategories = {};
    475