Web Inspector: Keep Web Inspector window alive across process swaps (PSON) (Local...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Protocol / InspectorBackend.js
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  * Copyright (C) 2013, 2015, 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 InspectorBackendClass = class InspectorBackendClass
34 {
35     constructor()
36     {
37         this._agents = {};
38
39         this._customTracer = null;
40         this._defaultTracer = new WI.LoggingProtocolTracer;
41         this._activeTracers = [this._defaultTracer];
42
43         this._supportedDomainsForDebuggableType = new Map;
44
45         for (let debuggableType of Object.values(WI.DebuggableType))
46             this._supportedDomainsForDebuggableType.set(debuggableType, []);
47
48         WI.settings.autoLogProtocolMessages.addEventListener(WI.Setting.Event.Changed, this._startOrStopAutomaticTracing, this);
49         WI.settings.autoLogTimeStats.addEventListener(WI.Setting.Event.Changed, this._startOrStopAutomaticTracing, this);
50         this._startOrStopAutomaticTracing();
51
52         this.currentDispatchState = {
53             event: null,
54             request: null,
55             response: null,
56         };
57     }
58
59     // Public
60
61     // This should be used for feature checking if something exists in the protocol
62     // regardless of whether or not the domain is active for a specific target.
63     get domains()
64     {
65         return this._agents;
66     }
67
68     // It's still possible to set this flag on InspectorBackend to just
69     // dump protocol traffic as it happens. For more complex uses of
70     // protocol data, install a subclass of WI.ProtocolTracer.
71     set dumpInspectorProtocolMessages(value)
72     {
73         // Implicitly cause automatic logging to start if it's allowed.
74         WI.settings.autoLogProtocolMessages.value = value;
75
76         this._defaultTracer.dumpMessagesToConsole = value;
77     }
78
79     get dumpInspectorProtocolMessages()
80     {
81         return WI.settings.autoLogProtocolMessages.value;
82     }
83
84     set dumpInspectorTimeStats(value)
85     {
86         WI.settings.autoLogTimeStats.value = value;
87
88         if (!this.dumpInspectorProtocolMessages)
89             this.dumpInspectorProtocolMessages = true;
90
91         this._defaultTracer.dumpTimingDataToConsole = value;
92     }
93
94     get dumpInspectorTimeStats()
95     {
96         return WI.settings.autoLogTimeStats.value;
97     }
98
99     set customTracer(tracer)
100     {
101         console.assert(!tracer || tracer instanceof WI.ProtocolTracer, tracer);
102         console.assert(!tracer || tracer !== this._defaultTracer, tracer);
103
104         // Bail early if no state change is to be made.
105         if (!tracer && !this._customTracer)
106             return;
107
108         if (tracer === this._customTracer)
109             return;
110
111         if (tracer === this._defaultTracer)
112             return;
113
114         if (this._customTracer)
115             this._customTracer.logFinished();
116
117         this._customTracer = tracer;
118         this._activeTracers = [this._defaultTracer];
119
120         if (this._customTracer) {
121             this._customTracer.logStarted();
122             this._activeTracers.push(this._customTracer);
123         }
124     }
125
126     get activeTracers()
127     {
128         return this._activeTracers;
129     }
130
131     registerCommand(qualifiedName, callSignature, replySignature)
132     {
133         var [domainName, commandName] = qualifiedName.split(".");
134         var agent = this._agentForDomain(domainName);
135         agent.addCommand(InspectorBackend.Command.create(agent, qualifiedName, callSignature, replySignature));
136     }
137
138     registerEnum(qualifiedName, enumValues)
139     {
140         var [domainName, enumName] = qualifiedName.split(".");
141         var agent = this._agentForDomain(domainName);
142         agent.addEnum(enumName, enumValues);
143     }
144
145     registerEvent(qualifiedName, signature)
146     {
147         var [domainName, eventName] = qualifiedName.split(".");
148         var agent = this._agentForDomain(domainName);
149         agent.addEvent(new InspectorBackend.Event(eventName, signature));
150     }
151
152     registerDomainDispatcher(domainName, dispatcher)
153     {
154         var agent = this._agentForDomain(domainName);
155         agent.dispatcher = dispatcher;
156     }
157
158     dispatch(message)
159     {
160         InspectorBackend.backendConnection.dispatch(message);
161     }
162
163     runAfterPendingDispatches(script)
164     {
165         // FIXME: Should this respect pending dispatches in all connections?
166         WI.mainTarget.connection.runAfterPendingDispatches(script);
167     }
168
169     activateDomain(domainName, activationDebuggableTypes)
170     {
171         let supportedDebuggableTypes = activationDebuggableTypes || Object.values(WI.DebuggableType);
172         for (let debuggableType of supportedDebuggableTypes)
173             this._supportedDomainsForDebuggableType.get(debuggableType).push(domainName);
174
175         // FIXME: For proper multi-target support we should eliminate all uses of
176         // `window.FooAgent` and `unprefixed FooAgent` in favor of either:
177         //   - Per-target: `target.FooAgent`
178         //   - Global feature check: `InspectorBackend.domains.Foo`
179         if (!activationDebuggableTypes || activationDebuggableTypes.includes(InspectorFrontendHost.debuggableType())) {
180             let agent = this._agents[domainName];
181             agent.activate();
182             return agent;
183         }
184
185         return null;
186     }
187
188     supportedDomainsForDebuggableType(type)
189     {
190         console.assert(Object.values(WI.DebuggableType).includes(type), "Unknown debuggable type", type);
191
192         return this._supportedDomainsForDebuggableType.get(type);
193     }
194
195     // Private
196
197     _startOrStopAutomaticTracing()
198     {
199         this._defaultTracer.dumpMessagesToConsole = this.dumpInspectorProtocolMessages;
200         this._defaultTracer.dumpTimingDataToConsole = this.dumpTimingDataToConsole;
201     }
202
203     _agentForDomain(domainName)
204     {
205         if (this._agents[domainName])
206             return this._agents[domainName];
207
208         var agent = new InspectorBackend.Agent(domainName);
209         this._agents[domainName] = agent;
210         return agent;
211     }
212 };
213
214 InspectorBackend = new InspectorBackendClass;
215
216 InspectorBackend.Agent = class InspectorBackendAgent
217 {
218     constructor(domainName)
219     {
220         this._domainName = domainName;
221
222         // Default connection is the main connection.
223         this._connection = InspectorBackend.backendConnection;
224         this._dispatcher = null;
225
226         // Agents are always created, but are only useable after they are activated.
227         this._active = false;
228
229         // Commands are stored directly on the Agent instance using their unqualified
230         // method name as the property. Thus, callers can write: FooAgent.methodName().
231         // Enums are stored similarly based on the unqualified type name.
232         this._events = {};
233     }
234
235     // Public
236
237     get domainName()
238     {
239         return this._domainName;
240     }
241
242     get active()
243     {
244         return this._active;
245     }
246
247     get connection()
248     {
249         return this._connection;
250     }
251
252     set connection(connection)
253     {
254         this._connection = connection;
255     }
256
257     get dispatcher()
258     {
259         return this._dispatcher;
260     }
261
262     set dispatcher(value)
263     {
264         this._dispatcher = value;
265     }
266
267     addEnum(enumName, enumValues)
268     {
269         this[enumName] = enumValues;
270     }
271
272     addCommand(command)
273     {
274         this[command.commandName] = command;
275     }
276
277     addEvent(event)
278     {
279         this._events[event.eventName] = event;
280     }
281
282     getEvent(eventName)
283     {
284         return this._events[eventName];
285     }
286
287     hasEvent(eventName)
288     {
289         return eventName in this._events;
290     }
291
292     hasEventParameter(eventName, eventParameterName)
293     {
294         let event = this._events[eventName];
295         return event && event.parameterNames.includes(eventParameterName);
296     }
297
298     activate()
299     {
300         this._active = true;
301         window[this._domainName + "Agent"] = this;
302     }
303
304     dispatchEvent(eventName, eventArguments)
305     {
306         if (!this._dispatcher) {
307             console.error(`No domain dispatcher registered for domain '${this._domainName}', for event '${this._domainName}.${eventName}'`);
308             return false;
309         }
310
311         if (!(eventName in this._dispatcher)) {
312             console.error(`Protocol Error: Attempted to dispatch an unimplemented method '${this._domainName}.${eventName}'`);
313             return false;
314         }
315
316         this._dispatcher[eventName].apply(this._dispatcher, eventArguments);
317         return true;
318     }
319 };
320
321 // InspectorBackend.Command can't use ES6 classes because of its trampoline nature.
322 // But we can use strict mode to get stricter handling of the code inside its functions.
323 InspectorBackend.Command = function(agent, qualifiedName, callSignature, replySignature)
324 {
325     "use strict";
326
327     this._agent = agent;
328     this._instance = this;
329
330     let [domainName, commandName] = qualifiedName.split(".");
331     this._qualifiedName = qualifiedName;
332     this._commandName = commandName;
333     this._callSignature = callSignature || [];
334     this._replySignature = replySignature || [];
335 };
336
337 InspectorBackend.Command.create = function(agent, commandName, callSignature, replySignature)
338 {
339     "use strict";
340
341     let instance = new InspectorBackend.Command(agent, commandName, callSignature, replySignature);
342
343     function callable() {
344         console.assert(this instanceof InspectorBackend.Agent);
345         return instance._invokeWithArguments.call(instance, this, Array.from(arguments));
346     }
347
348     callable._instance = instance;
349     Object.setPrototypeOf(callable, InspectorBackend.Command.prototype);
350
351     return callable;
352 };
353
354 // As part of the workaround to make commands callable, these functions use |this._instance|.
355 // |this| could refer to the callable trampoline, or the InspectorBackend.Command instance.
356 InspectorBackend.Command.prototype = {
357     __proto__: Function.prototype,
358
359     // Public
360
361     get qualifiedName()
362     {
363         return this._instance._qualifiedName;
364     },
365
366     get commandName()
367     {
368         return this._instance._commandName;
369     },
370
371     get callSignature()
372     {
373         return this._instance._callSignature;
374     },
375
376     get replySignature()
377     {
378         return this._instance._replySignature;
379     },
380
381     invoke(commandArguments, callback, agent)
382     {
383         "use strict";
384
385         agent = agent || this._instance._agent;
386
387         if (typeof callback === "function")
388             agent._connection._sendCommandToBackendWithCallback(this._instance, commandArguments, callback);
389         else
390             return agent._connection._sendCommandToBackendExpectingPromise(this._instance, commandArguments);
391     },
392
393     supports(parameterName)
394     {
395         "use strict";
396
397         return this._instance.callSignature.some((parameter) => parameter["name"] === parameterName);
398     },
399
400     // Private
401
402     _invokeWithArguments(agent, commandArguments)
403     {
404         "use strict";
405
406         let instance = this._instance;
407         let callback = typeof commandArguments.lastValue === "function" ? commandArguments.pop() : null;
408
409         function deliverFailure(message) {
410             console.error(`Protocol Error: ${message}`);
411             if (callback)
412                 setTimeout(callback.bind(null, message), 0);
413             else
414                 return Promise.reject(new Error(message));
415         }
416
417         let parameters = {};
418         for (let parameter of instance.callSignature) {
419             let parameterName = parameter["name"];
420             let typeName = parameter["type"];
421             let optionalFlag = parameter["optional"];
422
423             if (!commandArguments.length && !optionalFlag)
424                 return deliverFailure(`Invalid number of arguments for command '${instance.qualifiedName}'.`);
425
426             let value = commandArguments.shift();
427             if (optionalFlag && value === undefined)
428                 continue;
429
430             if (typeof value !== typeName)
431                 return deliverFailure(`Invalid type of argument '${parameterName}' for command '${instance.qualifiedName}' call. It must be '${typeName}' but it is '${typeof value}'.`);
432
433             parameters[parameterName] = value;
434         }
435
436         if (!callback && commandArguments.length === 1 && commandArguments[0] !== undefined)
437             return deliverFailure(`Protocol Error: Optional callback argument for command '${instance.qualifiedName}' call must be a function but its type is '${typeof commandArguments[0]}'.`);
438
439         if (callback)
440             agent._connection._sendCommandToBackendWithCallback(instance, parameters, callback);
441         else
442             return agent._connection._sendCommandToBackendExpectingPromise(instance, parameters);
443     }
444 };
445
446 InspectorBackend.Event = class Event
447 {
448     constructor(eventName, parameterNames)
449     {
450         this.eventName = eventName;
451         this.parameterNames = parameterNames;
452     }
453 };