• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 * @param {!WebInspector.AuditController} auditController
34 * @extends {WebInspector.VBox}
35 */
36WebInspector.AuditLauncherView = function(auditController)
37{
38    WebInspector.VBox.call(this);
39    this.setMinimumSize(100, 25);
40
41    this._auditController = auditController;
42
43    this._categoryIdPrefix = "audit-category-item-";
44    this._auditRunning = false;
45
46    this.element.classList.add("audit-launcher-view");
47    this.element.classList.add("panel-enabler-view");
48
49    this._contentElement = document.createElement("div");
50    this._contentElement.className = "audit-launcher-view-content";
51    this.element.appendChild(this._contentElement);
52    this._boundCategoryClickListener = this._categoryClicked.bind(this);
53
54    this._resetResourceCount();
55
56    this._sortedCategories = [];
57
58    this._headerElement = document.createElement("h1");
59    this._headerElement.className = "no-audits";
60    this._headerElement.textContent = WebInspector.UIString("No audits to run");
61    this._contentElement.appendChild(this._headerElement);
62
63    var target = this._auditController.target();
64    target.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this);
65    target.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this);
66    target.profilingLock.addEventListener(WebInspector.Lock.Events.StateChanged, this._updateButton, this);
67
68    var defaultSelectedAuditCategory = {};
69    defaultSelectedAuditCategory[WebInspector.AuditLauncherView.AllCategoriesKey] = true;
70    this._selectedCategoriesSetting = WebInspector.settings.createSetting("selectedAuditCategories", defaultSelectedAuditCategory);
71}
72
73WebInspector.AuditLauncherView.AllCategoriesKey = "__AllCategories";
74
75WebInspector.AuditLauncherView.prototype = {
76    _resetResourceCount: function()
77    {
78        this._loadedResources = 0;
79        this._totalResources = 0;
80    },
81
82    _onRequestStarted: function(event)
83    {
84        var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
85        // Ignore long-living WebSockets for the sake of progress indicator, as we won't be waiting them anyway.
86        if (request.type === WebInspector.resourceTypes.WebSocket)
87            return;
88        ++this._totalResources;
89        this._updateResourceProgress();
90    },
91
92    _onRequestFinished: function(event)
93    {
94        var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
95        // See resorceStarted for details.
96        if (request.type === WebInspector.resourceTypes.WebSocket)
97            return;
98        ++this._loadedResources;
99        this._updateResourceProgress();
100    },
101
102    /**
103     * @param {!WebInspector.AuditCategory} category
104     */
105    addCategory: function(category)
106    {
107        if (!this._sortedCategories.length)
108            this._createLauncherUI();
109
110        var selectedCategories = this._selectedCategoriesSetting.get();
111        var categoryElement = this._createCategoryElement(category.displayName, category.id);
112        category._checkboxElement = categoryElement.firstChild;
113        if (this._selectAllCheckboxElement.checked || selectedCategories[category.displayName]) {
114            category._checkboxElement.checked = true;
115            ++this._currentCategoriesCount;
116        }
117
118        /**
119         * @param {!WebInspector.AuditCategory} a
120         * @param {!WebInspector.AuditCategory} b
121         * @return {number}
122         */
123        function compareCategories(a, b)
124        {
125            var aTitle = a.displayName || "";
126            var bTitle = b.displayName || "";
127            return aTitle.localeCompare(bTitle);
128        }
129        var insertBefore = insertionIndexForObjectInListSortedByFunction(category, this._sortedCategories, compareCategories);
130        this._categoriesElement.insertBefore(categoryElement, this._categoriesElement.children[insertBefore]);
131        this._sortedCategories.splice(insertBefore, 0, category);
132        this._selectedCategoriesUpdated();
133    },
134
135    /**
136     * @param {boolean} auditRunning
137     */
138    _setAuditRunning: function(auditRunning)
139    {
140        if (this._auditRunning === auditRunning)
141            return;
142        this._auditRunning = auditRunning;
143        this._updateButton();
144        this._toggleUIComponents(this._auditRunning);
145        var target = this._auditController.target();
146        if (this._auditRunning) {
147            target.profilingLock.acquire();
148            this._startAudit();
149        } else {
150            this._stopAudit();
151            target.profilingLock.release();
152        }
153    },
154
155    _startAudit: function()
156    {
157        var catIds = [];
158        for (var category = 0; category < this._sortedCategories.length; ++category) {
159            if (this._sortedCategories[category]._checkboxElement.checked)
160                catIds.push(this._sortedCategories[category].id);
161        }
162
163        this._resetResourceCount();
164        this._progressIndicator = new WebInspector.ProgressIndicator();
165        this._buttonContainerElement.appendChild(this._progressIndicator.element);
166        this._displayResourceLoadingProgress = true;
167
168        /**
169         * @this {WebInspector.AuditLauncherView}
170         */
171        function onAuditStarted()
172        {
173            this._displayResourceLoadingProgress = false;
174        }
175        this._auditController.initiateAudit(catIds, this._progressIndicator, this._auditPresentStateElement.checked, onAuditStarted.bind(this), this._setAuditRunning.bind(this, false));
176    },
177
178    _stopAudit: function()
179    {
180        this._displayResourceLoadingProgress = false;
181        this._progressIndicator.cancel();
182        this._progressIndicator.done();
183        delete this._progressIndicator;
184    },
185
186    /**
187     * @param {boolean} disable
188     */
189    _toggleUIComponents: function(disable)
190    {
191        this._selectAllCheckboxElement.disabled = disable;
192        this._categoriesElement.disabled = disable;
193        this._auditPresentStateElement.disabled = disable;
194        this._auditReloadedStateElement.disabled = disable;
195    },
196
197    _launchButtonClicked: function(event)
198    {
199        this._setAuditRunning(!this._auditRunning);
200    },
201
202    _clearButtonClicked: function()
203    {
204        this._auditController.clearResults();
205    },
206
207    /**
208     * @param {boolean} checkCategories
209     * @param {boolean=} userGesture
210     */
211    _selectAllClicked: function(checkCategories, userGesture)
212    {
213        var childNodes = this._categoriesElement.childNodes;
214        for (var i = 0, length = childNodes.length; i < length; ++i)
215            childNodes[i].firstChild.checked = checkCategories;
216        this._currentCategoriesCount = checkCategories ? this._sortedCategories.length : 0;
217        this._selectedCategoriesUpdated(userGesture);
218    },
219
220    _categoryClicked: function(event)
221    {
222        this._currentCategoriesCount += event.target.checked ? 1 : -1;
223        this._selectAllCheckboxElement.checked = this._currentCategoriesCount === this._sortedCategories.length;
224        this._selectedCategoriesUpdated(true);
225    },
226
227    /**
228     * @param {string} title
229     * @param {string} id
230     */
231    _createCategoryElement: function(title, id)
232    {
233        var labelElement = document.createElement("label");
234        labelElement.id = this._categoryIdPrefix + id;
235
236        var element = document.createElement("input");
237        element.type = "checkbox";
238        if (id !== "")
239            element.addEventListener("click", this._boundCategoryClickListener, false);
240        labelElement.appendChild(element);
241        labelElement.appendChild(document.createTextNode(title));
242        labelElement.__displayName = title;
243
244        return labelElement;
245    },
246
247    _createLauncherUI: function()
248    {
249        this._headerElement = document.createElement("h1");
250        this._headerElement.textContent = WebInspector.UIString("Select audits to run");
251
252        for (var child = 0; child < this._contentElement.children.length; ++child)
253            this._contentElement.removeChild(this._contentElement.children[child]);
254
255        this._contentElement.appendChild(this._headerElement);
256
257        /**
258         * @param {?Event} event
259         * @this {WebInspector.AuditLauncherView}
260         */
261        function handleSelectAllClick(event)
262        {
263            this._selectAllClicked(event.target.checked, true);
264        }
265        var categoryElement = this._createCategoryElement(WebInspector.UIString("Select All"), "");
266        categoryElement.id = "audit-launcher-selectall";
267        this._selectAllCheckboxElement = categoryElement.firstChild;
268        this._selectAllCheckboxElement.checked = this._selectedCategoriesSetting.get()[WebInspector.AuditLauncherView.AllCategoriesKey];
269        this._selectAllCheckboxElement.addEventListener("click", handleSelectAllClick.bind(this), false);
270        this._contentElement.appendChild(categoryElement);
271
272        this._categoriesElement = this._contentElement.createChild("fieldset", "audit-categories-container");
273        this._currentCategoriesCount = 0;
274
275        this._contentElement.createChild("div", "flexible-space");
276
277        this._buttonContainerElement = this._contentElement.createChild("div", "button-container");
278
279        var labelElement = this._buttonContainerElement.createChild("label");
280        this._auditPresentStateElement = labelElement.createChild("input");
281        this._auditPresentStateElement.name = "audit-mode";
282        this._auditPresentStateElement.type = "radio";
283        this._auditPresentStateElement.checked = true;
284        this._auditPresentStateLabelElement = document.createTextNode(WebInspector.UIString("Audit Present State"));
285        labelElement.appendChild(this._auditPresentStateLabelElement);
286
287        labelElement = this._buttonContainerElement.createChild("label");
288        this._auditReloadedStateElement = labelElement.createChild("input");
289        this._auditReloadedStateElement.name = "audit-mode";
290        this._auditReloadedStateElement.type = "radio";
291        labelElement.appendChild(document.createTextNode("Reload Page and Audit on Load"));
292
293        this._launchButton = this._buttonContainerElement.createChild("button");
294        this._launchButton.textContent = WebInspector.UIString("Run");
295        this._launchButton.addEventListener("click", this._launchButtonClicked.bind(this), false);
296
297        this._clearButton = this._buttonContainerElement.createChild("button");
298        this._clearButton.textContent = WebInspector.UIString("Clear");
299        this._clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
300
301        this._selectAllClicked(this._selectAllCheckboxElement.checked);
302    },
303
304    _updateResourceProgress: function()
305    {
306        if (this._displayResourceLoadingProgress)
307            this._progressIndicator.setTitle(WebInspector.UIString("Loading (%d of %d)", this._loadedResources, this._totalResources));
308    },
309
310    /**
311     * @param {boolean=} userGesture
312     */
313    _selectedCategoriesUpdated: function(userGesture)
314    {
315        // Save present categories only upon user gesture to clean up junk from past versions and removed extensions.
316        // Do not remove old categories if not handling a user gesture, as there's chance categories will be added
317        // later during start-up.
318        var selectedCategories = userGesture ? {} : this._selectedCategoriesSetting.get();
319        var childNodes = this._categoriesElement.childNodes;
320        for (var i = 0, length = childNodes.length; i < length; ++i)
321            selectedCategories[childNodes[i].__displayName] = childNodes[i].firstChild.checked;
322        selectedCategories[WebInspector.AuditLauncherView.AllCategoriesKey] = this._selectAllCheckboxElement.checked;
323        this._selectedCategoriesSetting.set(selectedCategories);
324        this._updateButton();
325    },
326
327    _updateButton: function()
328    {
329        var target = this._auditController.target();
330        var enable = this._auditRunning || (this._currentCategoriesCount && !target.profilingLock.isAcquired());
331        this._launchButton.textContent = this._auditRunning ? WebInspector.UIString("Stop") : WebInspector.UIString("Run");
332        this._launchButton.disabled = !enable;
333        this._launchButton.title = enable ? "" : WebInspector.UIString("Another profiler is already active");
334    },
335
336    __proto__: WebInspector.VBox.prototype
337}
338