Web Inspector: "Worker not found" uncaught protocol errors
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Protocol / Connection.js
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  * Copyright (C) 2013-2016 Apple Inc. All rights reserved.
4  * Copyright (C) 2014 University of Washington.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following disclaimer
14  * in the documentation and/or other materials provided with the
15  * distribution.
16  *     * Neither the name of Google Inc. nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 InspectorBackend.globalSequenceId = 1;
34
35 InspectorBackend.Connection = class InspectorBackendConnection
36 {
37     constructor()
38     {
39         this._pendingResponses = new Map;
40         this._agents = {};
41         this._deferredCallbacks = [];
42         this._target = null;
43     }
44
45     // Public
46
47     get target()
48     {
49         return this._target;
50     }
51
52     set target(target)
53     {
54         console.assert(!this._target);
55
56         this._target = target;
57
58         for (let domain in this._agents) {
59             let dispatcher = this._agents[domain].dispatcher;
60             if (dispatcher)
61                 dispatcher.target = target;
62         }
63     }
64
65     dispatch(message)
66     {
67         let messageObject = typeof message === "string" ? JSON.parse(message) : message;
68
69         if ("id" in messageObject)
70             this._dispatchResponse(messageObject);
71         else
72             this._dispatchEvent(messageObject);
73     }
74
75     runAfterPendingDispatches(callback)
76     {
77         console.assert(typeof callback === "function");
78
79         if (!this._pendingResponses.size)
80             callback.call(this);
81         else
82             this._deferredCallbacks.push(callback);
83     }
84
85     // Protected
86
87     sendMessageToBackend(message)
88     {
89         throw new Error("Should be implemented by a InspectorBackend.Connection subclass");
90     }
91
92     // Private
93
94     _dispatchResponse(messageObject)
95     {
96         console.assert(this._pendingResponses.size >= 0);
97
98         if (messageObject["error"]) {
99             // FIXME: Eliminate Target.exists
100             if (messageObject["error"].code !== -32000 && messageObject["error"].message !== "'Target' domain was not found")
101                 console.error("Request with id = " + messageObject["id"] + " failed. " + JSON.stringify(messageObject["error"]));
102         }
103
104         let sequenceId = messageObject["id"];
105         console.assert(this._pendingResponses.has(sequenceId), sequenceId, this._target ? this._target.identifier : "(unknown)", this._pendingResponses);
106
107         let responseData = this._pendingResponses.take(sequenceId) || {};
108         let {request, command, callback, promise} = responseData;
109
110         let processingStartTimestamp = performance.now();
111         for (let tracer of InspectorBackend.activeTracers)
112             tracer.logWillHandleResponse(this, messageObject);
113
114         InspectorBackend.currentDispatchState.request = request;
115         InspectorBackend.currentDispatchState.response = messageObject;
116
117         if (typeof callback === "function")
118             this._dispatchResponseToCallback(command, request, messageObject, callback);
119         else if (typeof promise === "object")
120             this._dispatchResponseToPromise(command, messageObject, promise);
121         else
122             console.error("Received a command response without a corresponding callback or promise.", messageObject, command);
123
124         InspectorBackend.currentDispatchState.request = null;
125         InspectorBackend.currentDispatchState.response = null;
126
127         let processingTime = (performance.now() - processingStartTimestamp).toFixed(3);
128         let roundTripTime = (processingStartTimestamp - responseData.sendRequestTimestamp).toFixed(3);
129
130         for (let tracer of InspectorBackend.activeTracers)
131             tracer.logDidHandleResponse(this, messageObject, {rtt: roundTripTime, dispatch: processingTime});
132
133         if (this._deferredCallbacks.length && !this._pendingResponses.size)
134             this._flushPendingScripts();
135     }
136
137     _dispatchResponseToCallback(command, requestObject, responseObject, callback)
138     {
139         let callbackArguments = [];
140         callbackArguments.push(responseObject["error"] ? responseObject["error"].message : null);
141
142         if (responseObject["result"]) {
143             for (let parameterName of command.replySignature)
144                 callbackArguments.push(responseObject["result"][parameterName]);
145         }
146
147         try {
148             callback.apply(null, callbackArguments);
149         } catch (e) {
150             WI.reportInternalError(e, {"cause": `An uncaught exception was thrown while dispatching response callback for command ${command.qualifiedName}.`});
151         }
152     }
153
154     _dispatchResponseToPromise(command, messageObject, promise)
155     {
156         let {resolve, reject} = promise;
157         if (messageObject["error"])
158             reject(new Error(messageObject["error"].message));
159         else
160             resolve(messageObject["result"]);
161     }
162
163     _dispatchEvent(messageObject)
164     {
165         let qualifiedName = messageObject["method"];
166         let [domainName, eventName] = qualifiedName.split(".");
167         if (!(domainName in this._agents)) {
168             console.error("Protocol Error: Attempted to dispatch method '" + eventName + "' for non-existing domain '" + domainName + "'", messageObject);
169             return;
170         }
171
172         let agent = this._agents[domainName];
173         if (!agent.active) {
174             console.error("Protocol Error: Attempted to dispatch method for domain '" + domainName + "' which exists but is not active.", messageObject);
175             return;
176         }
177
178         let event = agent.getEvent(eventName);
179         if (!event) {
180             console.error("Protocol Error: Attempted to dispatch an unspecified method '" + qualifiedName + "'", messageObject);
181             return;
182         }
183
184         let eventArguments = [];
185         if (messageObject["params"])
186             eventArguments = event.parameterNames.map((name) => messageObject["params"][name]);
187
188         let processingStartTimestamp = performance.now();
189         for (let tracer of InspectorBackend.activeTracers)
190             tracer.logWillHandleEvent(this, messageObject);
191
192         InspectorBackend.currentDispatchState.event = messageObject;
193
194         try {
195             agent.dispatchEvent(eventName, eventArguments);
196         } catch (e) {
197             for (let tracer of InspectorBackend.activeTracers)
198                 tracer.logFrontendException(this, messageObject, e);
199
200             WI.reportInternalError(e, {"cause": `An uncaught exception was thrown while handling event: ${qualifiedName}`});
201         }
202
203         InspectorBackend.currentDispatchState.event = null;
204
205         let processingDuration = (performance.now() - processingStartTimestamp).toFixed(3);
206         for (let tracer of InspectorBackend.activeTracers)
207             tracer.logDidHandleEvent(this, messageObject, {dispatch: processingDuration});
208     }
209
210     _sendCommandToBackendWithCallback(command, parameters, callback)
211     {
212         let sequenceId = InspectorBackend.globalSequenceId++;
213
214         let messageObject = {
215             "id": sequenceId,
216             "method": command.qualifiedName,
217         };
218
219         if (!isEmptyObject(parameters))
220             messageObject["params"] = parameters;
221
222         let responseData = {command, request: messageObject, callback};
223
224         if (InspectorBackend.activeTracer)
225             responseData.sendRequestTimestamp = performance.now();
226
227         this._pendingResponses.set(sequenceId, responseData);
228         this._sendMessageToBackend(messageObject);
229     }
230
231     _sendCommandToBackendExpectingPromise(command, parameters)
232     {
233         let sequenceId = InspectorBackend.globalSequenceId++;
234
235         let messageObject = {
236             "id": sequenceId,
237             "method": command.qualifiedName,
238         };
239
240         if (!isEmptyObject(parameters))
241             messageObject["params"] = parameters;
242
243         let responseData = {command, request: messageObject};
244
245         if (InspectorBackend.activeTracer)
246             responseData.sendRequestTimestamp = performance.now();
247
248         let responsePromise = new Promise(function(resolve, reject) {
249             responseData.promise = {resolve, reject};
250         });
251
252         this._pendingResponses.set(sequenceId, responseData);
253         this._sendMessageToBackend(messageObject);
254
255         return responsePromise;
256     }
257
258     _sendMessageToBackend(messageObject)
259     {
260         for (let tracer of InspectorBackend.activeTracers)
261             tracer.logFrontendRequest(this, messageObject);
262
263         this.sendMessageToBackend(JSON.stringify(messageObject));
264     }
265
266     _flushPendingScripts()
267     {
268         console.assert(this._pendingResponses.size === 0);
269
270         let scriptsToRun = this._deferredCallbacks;
271         this._deferredCallbacks = [];
272         for (let script of scriptsToRun)
273             script.call(this);
274     }
275 };
276
277 InspectorBackend.BackendConnection = class InspectorBackendBackendConnection extends InspectorBackend.Connection
278 {
279     constructor()
280     {
281         super();
282
283         this._agents = InspectorBackend._agents;
284     }
285
286     sendMessageToBackend(message)
287     {
288         InspectorFrontendHost.sendMessageToBackend(message);
289     }
290 };
291
292 InspectorBackend.WorkerConnection = class InspectorBackendWorkerConnection extends InspectorBackend.Connection
293 {
294     constructor(workerId)
295     {
296         super();
297
298         this._workerId = workerId;
299
300         for (let [domain, agent] of Object.entries(InspectorBackend._agents)) {
301             let clone = Object.create(agent);
302             clone.connection = this;
303             if (agent.dispatcher)
304                 clone.dispatcher = new agent.dispatcher.constructor;
305             this._agents[domain] = clone;
306         }
307     }
308
309     sendMessageToBackend(message)
310     {
311         // Ignore errors if a worker went away quickly.
312         WorkerAgent.sendMessageToWorker(this._workerId, message).catch(function(){});
313     }
314 };
315
316 InspectorBackend.TargetConnection = class InspectorBackendTargetConnection extends InspectorBackend.Connection
317 {
318     constructor(targetId)
319     {
320         super();
321
322         this._targetId = targetId;
323
324         for (let [domain, agent] of Object.entries(InspectorBackend._agents)) {
325             let clone = Object.create(agent);
326             clone.connection = this;
327             if (agent.dispatcher)
328                 clone.dispatcher = new agent.dispatcher.constructor;
329             this._agents[domain] = clone;
330         }
331     }
332
333     sendMessageToBackend(message)
334     {
335         TargetAgent.sendMessageToTarget(this._targetId, message);
336     }
337 };
338
339 InspectorBackend.backendConnection = new InspectorBackend.BackendConnection;