561730e3d2c13c3e04ba6fe9445e4a1a13c8ef75
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / AuditManager.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.AuditManager = class AuditManager extends WI.Object
27 {
28     constructor()
29     {
30         super();
31
32         this._tests = [];
33         this._results = [];
34
35         this._runningState = WI.AuditManager.RunningState.Inactive;
36         this._runningTests = [];
37
38         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
39     }
40
41     static synthesizeError(message)
42     {
43         let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, WI.UIString("Audit test error: %s").format(message));
44         consoleMessage.shouldRevealConsole = true;
45
46         WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
47     }
48
49     // Public
50
51     get tests() { return this._tests; }
52     get results() { return this._results; }
53     get runningState() { return this._runningState; }
54
55     async start(tests)
56     {
57         console.assert(this._runningState === WI.AuditManager.RunningState.Inactive);
58         if (this._runningState !== WI.AuditManager.RunningState.Inactive)
59             return;
60
61         if (tests && tests.length)
62             tests = tests.filter((test) => typeof test === "object" && test instanceof WI.AuditTestBase);
63         else
64             tests = this._tests;
65
66         if (!tests.length)
67             return;
68
69         this._runningState = WI.AuditManager.RunningState.Active;
70         this._runningTests = tests;
71         for (let test of this._runningTests)
72             test.clearResult();
73
74         this.dispatchEventToListeners(WI.AuditManager.Event.TestScheduled);
75
76         await Promise.chain(this._runningTests.map((test) => () => this._runningState === WI.AuditManager.RunningState.Active ? test.start() : null));
77
78         let result = this._runningTests.map((test) => test.result).filter((result) => !!result);
79
80         this._runningState = WI.AuditManager.RunningState.Inactive;
81         this._runningTests = [];
82
83         this._addResult(result);
84     }
85
86     stop()
87     {
88         console.assert(this._runningState === WI.AuditManager.RunningState.Active);
89         if (this._runningState !== WI.AuditManager.RunningState.Active)
90             return;
91
92         for (let test of this._runningTests)
93             test.stop();
94
95         this._runningState = WI.AuditManager.RunningState.Stopping;
96     }
97
98     async processJSON({json, error})
99     {
100         if (error) {
101             WI.AuditManager.synthesizeError(error);
102             return;
103         }
104
105         let object = await WI.AuditTestGroup.fromPayload(json) || await WI.AuditTestCase.fromPayload(json);
106         if (!object) {
107             object = await WI.AuditTestGroupResult.fromPayload(json) || await WI.AuditTestCaseResult.fromPayload(json);
108             if (!object) {
109                 WI.AuditManager.synthesizeError(WI.UIString("invalid JSON."));
110                 return;
111             }
112         }
113
114         if (object instanceof WI.AuditTestBase) {
115             this._addTest(object);
116             WI.objectStores.audits.addObject(object);
117         } else if (object instanceof WI.AuditTestResultBase)
118             this._addResult(object);
119
120         WI.showRepresentedObject(object);
121     }
122
123     export(object)
124     {
125         console.assert(object instanceof WI.AuditTestCase || object instanceof WI.AuditTestGroup || object instanceof WI.AuditTestCaseResult || object instanceof WI.AuditTestGroupResult, object);
126
127         let filename = object.name;
128         if (object instanceof WI.AuditTestResultBase)
129             filename = WI.UIString("%s Result").format(filename);
130
131         let url = "web-inspector:///" + encodeURI(filename) + ".json";
132
133         WI.FileUtilities.save({
134             url,
135             content: JSON.stringify(object),
136             forceSaveAs: true,
137         });
138     }
139
140     loadStoredTests()
141     {
142         WI.objectStores.audits.getAll().then(async (tests) => {
143             for (let payload of tests) {
144                 let test = await WI.AuditTestGroup.fromPayload(payload) || await WI.AuditTestCase.fromPayload(payload);
145                 if (!test)
146                     continue;
147
148                 const key = null;
149                 WI.objectStores.audits.associateObject(test, key, payload);
150
151                 this._addTest(test);
152             }
153         });
154     }
155
156     removeTest(test)
157     {
158         this._tests.remove(test);
159
160         this.dispatchEventToListeners(WI.AuditManager.Event.TestRemoved, {test});
161
162         WI.objectStores.audits.deleteObject(test);
163     }
164
165     // Private
166
167     _addTest(test)
168     {
169         this._tests.push(test);
170
171         this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
172     }
173
174     _addResult(result)
175     {
176         if (!result || (Array.isArray(result) && !result.length))
177             return;
178
179         this._results.push(result);
180
181         this.dispatchEventToListeners(WI.AuditManager.Event.TestCompleted, {
182             result,
183             index: this._results.length - 1,
184         });
185     }
186
187     _handleFrameMainResourceDidChange(event)
188     {
189         if (!event.target.isMainFrame())
190             return;
191
192         for (let test of this._tests)
193             test.clearResult();
194     }
195 };
196
197 WI.AuditManager.RunningState = {
198     Inactive: "inactive",
199     Active: "active",
200     Stopping: "stopping",
201 };
202
203 WI.AuditManager.Event = {
204     TestAdded: "audit-manager-test-added",
205     TestCompleted: "audit-manager-test-completed",
206     TestRemoved: "audit-manager-test-removed",
207     TestScheduled: "audit-manager-test-scheduled",
208 };