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