Web Inspector: Audit: allow audits to be enabled/disabled
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / AuditTreeElement.js
1 /*
2  * Copyright (C) 2018 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement
27 {
28     constructor(representedObject)
29     {
30         let isTestCase = representedObject instanceof WI.AuditTestCase;
31         let isTestGroup = representedObject instanceof WI.AuditTestGroup;
32         let isTestCaseResult = representedObject instanceof WI.AuditTestCaseResult;
33         let isTestGroupResult = representedObject instanceof WI.AuditTestGroupResult;
34         console.assert(isTestCase || isTestGroup || isTestCaseResult || isTestGroupResult);
35
36         let classNames = ["audit"];
37         if (isTestCase)
38             classNames.push("test-case");
39         else if (isTestGroup)
40             classNames.push("test-group");
41         else if (isTestCaseResult)
42             classNames.push("test-case-result");
43         else if (isTestGroupResult)
44             classNames.push("test-group-result");
45
46         let options = {
47             hasChildren: isTestGroup || isTestGroupResult,
48         };
49
50         const subtitle = null;
51         super(classNames, representedObject.name, subtitle, representedObject, options);
52
53         if (isTestGroup)
54             this._expandedSetting = new WI.Setting(`audit-tree-element-${this.representedObject.name}-expanded`, false);
55     }
56
57     // Protected
58
59     onattach()
60     {
61         super.onattach();
62
63         if (this.representedObject instanceof WI.AuditTestBase) {
64             this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
65             this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestResultCleared, this);
66
67             if (this.representedObject instanceof WI.AuditTestCase)
68                 this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestCaseScheduled, this);
69             else if (this.representedObject instanceof WI.AuditTestGroup)
70                 this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
71
72             WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleManagerEditingChanged, this);
73             WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditManagerTestScheduled, this);
74             WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditManagerTestCompleted, this);
75         }
76
77         if (this._expandedSetting && this._expandedSetting.value)
78             this.expand();
79
80         this._updateLevel();
81     }
82
83     ondetach()
84     {
85         WI.auditManager.removeEventListener(null, null, this);
86         this.representedObject.removeEventListener(null, null, this);
87
88         super.ondetach();
89     }
90
91     onpopulate()
92     {
93         super.onpopulate();
94
95         if (this.children.length && !this.shouldRefreshChildren)
96             return;
97
98         this.shouldRefreshChildren = false;
99
100         this.removeChildren();
101
102         if (this.representedObject instanceof WI.AuditTestGroup) {
103             for (let test of this.representedObject.tests)
104                 this.appendChild(new WI.AuditTreeElement(test));
105         } else if (this.representedObject instanceof WI.AuditTestGroupResult) {
106             for (let result of this.representedObject.results)
107                 this.appendChild(new WI.AuditTreeElement(result));
108         }
109     }
110
111     onexpand()
112     {
113         console.assert(this.expanded);
114
115         if (this._expandedSetting)
116             this._expandedSetting.value = this.expanded;
117     }
118
119     oncollapse()
120     {
121         console.assert(!this.expanded);
122
123         if (this._expandedSetting)
124             this._expandedSetting.value = this.expanded;
125     }
126
127     ondelete()
128     {
129         if (!(this.representedObject instanceof WI.AuditTestBase))
130             return false;
131
132         if (!(this.parent instanceof WI.TreeOutline))
133             return false;
134
135         WI.auditManager.removeTest(this.representedObject);
136
137         return true;
138     }
139
140     populateContextMenu(contextMenu, event)
141     {
142         if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive) {
143             contextMenu.appendItem(WI.UIString("Start"), (event) => {
144                 this._start();
145             });
146         }
147
148         contextMenu.appendSeparator();
149
150         if (this.representedObject instanceof WI.AuditTestCase || this.representedObject instanceof WI.AuditTestGroup) {
151             contextMenu.appendItem(WI.UIString("Export Test"), (event) => {
152                 WI.auditManager.export(this.representedObject);
153             });
154         }
155
156         if (this.representedObject.result) {
157             contextMenu.appendItem(WI.UIString("Export Result"), (event) => {
158                 WI.auditManager.export(this.representedObject.result);
159             });
160         }
161
162         contextMenu.appendSeparator();
163
164         super.populateContextMenu(contextMenu, event);
165     }
166
167     canSelectOnMouseDown(event)
168     {
169         return !WI.auditManager.editing;
170     }
171
172     // Private
173
174     _start()
175     {
176         if (WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive)
177             return;
178
179         WI.auditManager.start([this.representedObject]);
180     }
181
182     _updateLevel()
183     {
184         let className = "";
185
186         let result = this.representedObject.result;
187         if (result) {
188             if (result.didError)
189                 className = WI.AuditTestCaseResult.Level.Error;
190             else if (result.didFail)
191                 className = WI.AuditTestCaseResult.Level.Fail;
192             else if (result.didWarn)
193                 className = WI.AuditTestCaseResult.Level.Warn;
194             else if (result.didPass)
195                 className = WI.AuditTestCaseResult.Level.Pass;
196             else if (result.unsupported)
197                 className = WI.AuditTestCaseResult.Level.Unsupported;
198         }
199
200         this.status = document.createElement("img");
201
202         if (this.representedObject instanceof WI.AuditTestCase || this.representedObject instanceof WI.AuditTestGroup) {
203             this.status.title = WI.UIString("Start");
204             this.status.addEventListener("click", this._handleStatusClick.bind(this));
205
206             if (!className)
207                 className = "show-on-hover";
208         }
209
210         this.status.classList.add(className);
211     }
212
213     _showRunningSpinner()
214     {
215         if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) {
216             this._updateLevel();
217             return;
218         }
219
220         if (!this.status || !this.status.__spinner) {
221             let spinner = new WI.IndeterminateProgressSpinner;
222             this.status = spinner.element;
223             this.status.__spinner = true;
224         }
225     }
226
227     _showRunningProgress(progress)
228     {
229         if (!this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) {
230             this._updateLevel();
231             return;
232         }
233
234         if (!this.status || !this.status.__progress) {
235             this.status = document.createElement("progress");
236             this.status.__progress = true;
237         }
238
239         this.status.value = progress || 0;
240     }
241
242     _updateTestGroupDisabled()
243     {
244         this.status.checked = !this.representedObject.disabled;
245
246         if (this.representedObject instanceof WI.AuditTestGroup)
247             this.status.indeterminate = this.representedObject.tests.some((test) => test.disabled !== this.representedObject.tests[0].disabled);
248     }
249
250     _handleTestCaseCompleted(event)
251     {
252         this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCaseCompleted, this);
253
254         this._updateLevel();
255     }
256
257     _handleTestDisabledChanged(event)
258     {
259         if (this.status instanceof HTMLInputElement && this.status.type === "checkbox")
260             this._updateTestGroupDisabled();
261     }
262
263     _handleTestResultCleared(event)
264     {
265         this._updateLevel();
266     }
267
268     _handleTestCaseScheduled(event)
269     {
270         this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCaseCompleted, this);
271
272         this._showRunningSpinner();
273     }
274
275     _handleTestGroupCompleted(event)
276     {
277         this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestGroupCompleted, this);
278         this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
279
280         this._updateLevel();
281     }
282
283     _handleTestGroupProgress(event)
284     {
285         let {index, count} = event.data;
286         this._showRunningProgress((index + 1) / count);
287     }
288
289     _handleTestGroupScheduled(event)
290     {
291         this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestGroupCompleted, this);
292         this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
293
294         this._showRunningProgress();
295     }
296
297     _handleManagerEditingChanged(event)
298     {
299         if (WI.auditManager.editing) {
300             this.status = document.createElement("input");
301             this.status.type = "checkbox";
302             this._updateTestGroupDisabled();
303             this.status.addEventListener("change", () => {
304                 this.representedObject.disabled = !this.representedObject.disabled;
305             });
306
307             this.addClassName("editing-audits");
308         } else {
309             this.removeClassName("editing-audits");
310
311             this._updateLevel();
312         }
313     }
314
315     _handleAuditManagerTestScheduled(event)
316     {
317         this.addClassName("manager-active");
318     }
319
320     _handleAuditManagerTestCompleted(event)
321     {
322         this.removeClassName("manager-active");
323     }
324
325     _handleStatusClick(event)
326     {
327         this._start();
328     }
329 };