Web Inspector: Audit: allow audits to be enabled/disabled
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / AuditTestGroup.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.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase
27 {
28     constructor(name, tests, options = {})
29     {
30         console.assert(Array.isArray(tests));
31
32         // Set disabled once `_tests` is set so that it propagates.
33         let disabled = options.disabled;
34         options.disabled = false;
35
36         super(name, options);
37
38         this._tests = tests;
39         this._preventDisabledPropagation = false;
40
41         if (disabled)
42             this.disabled = disabled;
43
44         for (let test of this._tests) {
45             test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
46             test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
47             test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
48         }
49     }
50
51     // Static
52
53     static async fromPayload(payload)
54     {
55         if (typeof payload !== "object" || payload === null)
56             return null;
57
58         let {type, name, tests, description, disabled} = payload;
59
60         if (type !== WI.AuditTestGroup.TypeIdentifier)
61             return null;
62
63         if (typeof name !== "string")
64             return null;
65
66         if (!Array.isArray(tests))
67             return null;
68
69         tests = await Promise.all(tests.map(async (test) => {
70             let testCase = await WI.AuditTestCase.fromPayload(test);
71             if (testCase)
72                 return testCase;
73
74             let testGroup = await WI.AuditTestGroup.fromPayload(test);
75             if (testGroup)
76                 return testGroup;
77
78             return null;
79         }));
80         tests = tests.filter((test) => !!test);
81         if (!tests.length)
82             return null;
83
84         let options = {};
85         if (typeof description === "string")
86             options.description = description;
87         if (typeof disabled === "boolean")
88             options.disabled = disabled;
89
90         return new WI.AuditTestGroup(name, tests, options);
91     }
92
93     // Public
94
95     get tests() { return this._tests; }
96
97     get disabled()
98     {
99         return super.disabled;
100     }
101
102     set disabled(disabled)
103     {
104         if (!this._preventDisabledPropagation) {
105             for (let test of this._tests)
106                 test.disabled = disabled;
107         }
108
109         super.disabled = disabled;
110     }
111
112     stop()
113     {
114         // Called from WI.AuditManager.
115
116         for (let test of this._tests)
117             test.stop();
118
119         super.stop();
120     }
121
122     clearResult(options = {})
123     {
124         let cleared = !!this._result;
125         for (let test of this._tests) {
126             if (test.clearResult(options))
127                 cleared = true;
128         }
129
130         return super.clearResult({
131             ...options,
132             suppressResultClearedEvent: !cleared,
133         });
134     }
135
136     toJSON(key)
137     {
138         let json = super.toJSON(key);
139         json.tests = this._tests.map((testCase) => testCase.toJSON(key));
140         return json;
141     }
142
143     // Protected
144
145     async run()
146     {
147         let count = this._tests.length;
148         for (let index = 0; index < count && this._runningState === WI.AuditManager.RunningState.Active; ++index) {
149             let test = this._tests[index];
150             if (test.disabled)
151                 continue;
152
153             await test.start();
154
155             if (test instanceof WI.AuditTestCase)
156                 this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {index, count});
157         }
158
159         this._updateResult();
160     }
161
162     // Private
163
164     _updateResult()
165     {
166         let results = this._tests.map((test) => test.result).filter((result) => !!result);
167         if (!results.length)
168             return;
169
170         this._result = new WI.AuditTestGroupResult(this.name, results, {
171             description: this.description,
172         });
173     }
174
175     _handleTestCompleted(event)
176     {
177         if (this._runningState === WI.AuditManager.RunningState.Active)
178             return;
179
180         this._updateResult();
181         this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed);
182     }
183
184     _handleTestDisabledChanged(event)
185     {
186         let enabledTestCount = this._tests.filter((test) => !test.disabled).length;
187         if (event.target.disabled && !enabledTestCount)
188             this.disabled = true;
189         else if (!event.target.disabled && enabledTestCount === 1) {
190             this._preventDisabledPropagation = true;
191             this.disabled = false;
192             this._preventDisabledPropagation = false;
193         } else {
194             // Don't change `disabled`, as we're currently in an "indeterminate" state.
195             this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
196         }
197     }
198
199     _handleTestProgress(event)
200     {
201         if (this._runningState !== WI.AuditManager.RunningState.Active)
202             return;
203
204         let walk = (tests) => {
205             let count = 0;
206             for (let test of tests) {
207                 if (test.disabled)
208                     continue;
209
210                 if (test instanceof WI.AuditTestCase)
211                     ++count;
212                 else if (test instanceof WI.AuditTestGroup)
213                     count += walk(test.tests);
214             }
215             return count;
216         };
217
218         this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {
219             index: event.data.index + walk(this._tests.slice(0, this._tests.indexOf(event.target))),
220             count: walk(this._tests),
221         });
222     }
223 };
224
225 WI.AuditTestGroup.TypeIdentifier = "test-group";