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