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