2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
4 * Copyright (C) 2014 University of Washington.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
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
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.
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.
33 InspectorBackendClass = class InspectorBackendClass
37 this._lastSequenceId = 1;
38 this._pendingResponses = new Map;
40 this._deferredScripts = [];
41 this._activeTracer = null;
42 this._automaticTracer = null;
44 this._dumpInspectorTimeStats = false;
46 let setting = WebInspector.autoLogProtocolMessagesSetting = new WebInspector.Setting("auto-collect-protocol-messages", false);
47 setting.addEventListener(WebInspector.Setting.Event.Changed, this._startOrStopAutomaticTracing.bind(this))
48 this._startOrStopAutomaticTracing();
53 // It's still possible to set this flag on InspectorBackend to just
54 // dump protocol traffic as it happens. For more complex uses of
55 // protocol data, install a subclass of WebInspector.ProtocolTracer.
56 set dumpInspectorProtocolMessages(value)
58 // Implicitly cause automatic logging to start if it's allowed.
59 let setting = WebInspector.autoLogProtocolMessagesSetting;
60 setting.value = value;
62 if (this.activeTracer !== this._automaticTracer)
65 if (this.activeTracer)
66 this.activeTracer.dumpMessagesToConsole = value;
69 get dumpInspectorProtocolMessages()
71 return !!this._automaticTracer;
74 set dumpInspectorTimeStats(value)
76 if (!this.dumpInspectorProtocolMessages)
77 this.dumpInspectorProtocolMessages = true;
79 if (this.activeTracer !== this._automaticTracer)
82 if (this.activeTracer)
83 this.activeTracer.dumpTimingDataToConsole = value;
86 get dumpInspectorTimeStats()
88 return this._dumpInspectorTimeStats;
91 set activeTracer(tracer)
93 console.assert(!tracer || tracer instanceof WebInspector.ProtocolTracer);
95 // Bail early if no state change is to be made.
96 if (!tracer && !this._activeTracer)
99 if (tracer === this._activeTracer)
102 // Don't allow an automatic tracer to dislodge a custom tracer.
103 if (this._activeTracer && tracer === this._automaticTracer)
106 if (this.activeTracer)
107 this.activeTracer.logFinished();
109 if (this._activeTracer === this._automaticTracer)
110 this._automaticTracer = null;
112 this._activeTracer = tracer;
113 if (this.activeTracer)
114 this.activeTracer.logStarted();
116 // If the custom tracer was removed and automatic tracing is enabled,
117 // then create a new automatic tracer and install it in its place.
118 this._startOrStopAutomaticTracing();
124 return this._activeTracer || null;
127 registerCommand(qualifiedName, callSignature, replySignature)
129 var [domainName, commandName] = qualifiedName.split(".");
130 var agent = this._agentForDomain(domainName);
131 agent.addCommand(InspectorBackend.Command.create(this, qualifiedName, callSignature, replySignature));
134 registerEnum(qualifiedName, enumValues)
136 var [domainName, enumName] = qualifiedName.split(".");
137 var agent = this._agentForDomain(domainName);
138 agent.addEnum(enumName, enumValues);
141 registerEvent(qualifiedName, signature)
143 var [domainName, eventName] = qualifiedName.split(".");
144 var agent = this._agentForDomain(domainName);
145 agent.addEvent(new InspectorBackend.Event(eventName, signature));
148 registerDomainDispatcher(domainName, dispatcher)
150 var agent = this._agentForDomain(domainName);
151 agent.dispatcher = dispatcher;
156 let messageObject = (typeof message === "string") ? JSON.parse(message) : message;
158 if ("id" in messageObject)
159 this._dispatchResponse(messageObject);
161 this._dispatchEvent(messageObject);
164 runAfterPendingDispatches(script)
166 console.assert(script);
167 console.assert(typeof script === "function");
169 if (!this._pendingResponses.size)
172 this._deferredScripts.push(script);
175 activateDomain(domainName, activationDebuggableType)
177 if (!activationDebuggableType || InspectorFrontendHost.debuggableType() === activationDebuggableType) {
178 var agent = this._agents[domainName];
188 _startOrStopAutomaticTracing()
190 let setting = WebInspector.autoLogProtocolMessagesSetting;
192 // Bail if there is no state transition to be made.
193 if (!(setting.value ^ !!this.activeTracer))
196 if (!setting.value) {
197 if (this.activeTracer === this._automaticTracer)
198 this.activeTracer = null;
200 this._automaticTracer = null;
202 this._automaticTracer = new WebInspector.LoggingProtocolTracer;
203 this._automaticTracer.dumpMessagesToConsole = this.dumpInspectorProtocolMessages;
204 this._automaticTracer.dumpTimingDataToConsole = this.dumpTimingDataToConsole;
205 // This will be ignored if a custom tracer is installed.
206 this.activeTracer = this._automaticTracer;
210 _agentForDomain(domainName)
212 if (this._agents[domainName])
213 return this._agents[domainName];
215 var agent = new InspectorBackend.Agent(domainName);
216 this._agents[domainName] = agent;
220 _sendCommandToBackendWithCallback(command, parameters, callback)
222 let sequenceId = this._lastSequenceId++;
224 let messageObject = {
226 "method": command.qualifiedName,
229 if (Object.keys(parameters).length)
230 messageObject["params"] = parameters;
232 let responseData = {command, callback};
234 if (this.activeTracer)
235 responseData.sendRequestTimestamp = timestamp();
237 this._pendingResponses.set(sequenceId, responseData);
238 this._sendMessageToBackend(messageObject);
241 _sendCommandToBackendExpectingPromise(command, parameters)
243 let sequenceId = this._lastSequenceId++;
245 let messageObject = {
247 "method": command.qualifiedName,
250 if (Object.keys(parameters).length)
251 messageObject["params"] = parameters;
253 let responseData = {command};
255 if (this.activeTracer)
256 responseData.sendRequestTimestamp = timestamp();
258 let responsePromise = new Promise(function(resolve, reject) {
259 responseData.promise = {resolve, reject};
262 this._pendingResponses.set(sequenceId, responseData);
263 this._sendMessageToBackend(messageObject);
265 return responsePromise;
268 _sendMessageToBackend(messageObject)
270 let stringifiedMessage = JSON.stringify(messageObject);
271 if (this.activeTracer)
272 this.activeTracer.logFrontendRequest(stringifiedMessage);
274 InspectorFrontendHost.sendMessageToBackend(stringifiedMessage);
277 _dispatchResponse(messageObject)
279 console.assert(this._pendingResponses.size >= 0);
281 if (messageObject["error"]) {
282 if (messageObject["error"].code !== -32000)
283 this._reportProtocolError(messageObject);
286 let sequenceId = messageObject["id"];
287 console.assert(this._pendingResponses.has(sequenceId), sequenceId, this._pendingResponses);
289 let responseData = this._pendingResponses.take(sequenceId);
290 let {command, callback, promise} = responseData;
292 let processingStartTimestamp;
293 if (this.activeTracer) {
294 processingStartTimestamp = timestamp();
295 this.activeTracer.logWillHandleResponse(JSON.stringify(messageObject));
298 if (typeof callback === "function")
299 this._dispatchResponseToCallback(command, messageObject, callback);
300 else if (typeof promise === "object")
301 this._dispatchResponseToPromise(command, messageObject, promise);
303 console.error("Received a command response without a corresponding callback or promise.", messageObject, command);
305 if (this.activeTracer) {
306 let processingTime = (timestamp() - processingStartTimestamp).toFixed(3);
307 let roundTripTime = (processingStartTimestamp - responseData.sendRequestTimestamp).toFixed(3);
308 this.activeTracer.logDidHandleResponse(JSON.stringify(messageObject), {rtt: roundTripTime, dispatch: processingTime});
311 if (this._deferredScripts.length && !this._pendingResponses.size)
312 this._flushPendingScripts();
315 _dispatchResponseToCallback(command, messageObject, callback)
317 let callbackArguments = [];
318 callbackArguments.push(messageObject["error"] ? messageObject["error"].message : null);
320 if (messageObject["result"]) {
321 for (var parameterName of command.replySignature)
322 callbackArguments.push(messageObject["result"][parameterName]);
326 callback.apply(null, callbackArguments);
328 console.error("Uncaught exception in inspector page while dispatching callback for command " + command.qualifiedName, e);
332 _dispatchResponseToPromise(command, messageObject, promise)
334 let {resolve, reject} = promise;
335 if (messageObject["error"])
336 reject(new Error(messageObject["error"].message));
338 resolve(messageObject["result"]);
341 _dispatchEvent(messageObject)
343 let qualifiedName = messageObject["method"];
344 let [domainName, eventName] = qualifiedName.split(".");
345 if (!(domainName in this._agents)) {
346 console.error("Protocol Error: Attempted to dispatch method '" + eventName + "' for non-existing domain '" + domainName + "'");
350 let agent = this._agentForDomain(domainName);
352 console.error("Protocol Error: Attempted to dispatch method for domain '" + domainName + "' which exists but is not active.");
356 let event = agent.getEvent(eventName);
358 console.error("Protocol Error: Attempted to dispatch an unspecified method '" + qualifiedName + "'");
362 let eventArguments = [];
363 if (messageObject["params"])
364 eventArguments = event.parameterNames.map((name) => messageObject["params"][name]);
366 let processingStartTimestamp;
367 if (this.activeTracer) {
368 processingStartTimestamp = timestamp();
369 this.activeTracer.logWillHandleEvent(JSON.stringify(messageObject));
373 agent.dispatchEvent(eventName, eventArguments);
375 console.error("Uncaught exception in inspector page while handling event " + qualifiedName, e);
376 if (this.activeTracer)
377 this.activeTracer.logFrontendException(JSON.stringify(messageObject), e);
380 if (this.activeTracer) {
381 let processingTime = (timestamp() - processingStartTimestamp).toFixed(3);
382 this.activeTracer.logDidHandleEvent(JSON.stringify(messageObject), {dispatch: processingTime});
386 _reportProtocolError(messageObject)
388 console.error("Request with id = " + messageObject["id"] + " failed. " + JSON.stringify(messageObject["error"]));
391 _flushPendingScripts()
393 console.assert(this._pendingResponses.size === 0);
395 let scriptsToRun = this._deferredScripts;
396 this._deferredScripts = [];
397 for (let script of scriptsToRun)
402 InspectorBackend = new InspectorBackendClass;
404 InspectorBackend.Agent = class InspectorBackendAgent
406 constructor(domainName)
408 this._domainName = domainName;
410 // Agents are always created, but are only useable after they are activated.
411 this._active = false;
413 // Commands are stored directly on the Agent instance using their unqualified
414 // method name as the property. Thus, callers can write: FooAgent.methodName().
415 // Enums are stored similarly based on the unqualified type name.
423 return this._domainName;
431 set dispatcher(value)
433 this._dispatcher = value;
436 addEnum(enumName, enumValues)
438 this[enumName] = enumValues;
443 this[command.commandName] = command;
448 this._events[event.eventName] = event;
453 return this._events[eventName];
458 return eventName in this._events;
464 window[this._domainName + "Agent"] = this;
467 dispatchEvent(eventName, eventArguments)
469 if (!(eventName in this._dispatcher)) {
470 console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + this._domainName + "." + eventName + "'");
474 this._dispatcher[eventName].apply(this._dispatcher, eventArguments);
479 // InspectorBackend.Command can't use ES6 classes because of its trampoline nature.
480 // But we can use strict mode to get stricter handling of the code inside its functions.
481 InspectorBackend.Command = function(backend, qualifiedName, callSignature, replySignature)
485 this._backend = backend;
486 this._instance = this;
488 var [domainName, commandName] = qualifiedName.split(".");
489 this._qualifiedName = qualifiedName;
490 this._commandName = commandName;
491 this._callSignature = callSignature || [];
492 this._replySignature = replySignature || [];
495 InspectorBackend.Command.create = function(backend, commandName, callSignature, replySignature)
499 var instance = new InspectorBackend.Command(backend, commandName, callSignature, replySignature);
501 function callable() {
502 return instance._invokeWithArguments.apply(instance, arguments);
505 callable._instance = instance;
506 Object.setPrototypeOf(callable, InspectorBackend.Command.prototype);
511 // As part of the workaround to make commands callable, these functions use |this._instance|.
512 // |this| could refer to the callable trampoline, or the InspectorBackend.Command instance.
513 InspectorBackend.Command.prototype = {
514 __proto__: Function.prototype,
520 return this._instance._qualifiedName;
525 return this._instance._commandName;
530 return this._instance._callSignature;
535 return this._instance._replySignature;
538 invoke: function(commandArguments, callback)
542 let instance = this._instance;
544 if (typeof callback === "function")
545 instance._backend._sendCommandToBackendWithCallback(instance, commandArguments, callback);
547 return instance._backend._sendCommandToBackendExpectingPromise(instance, commandArguments);
550 supports: function(parameterName)
554 var instance = this._instance;
555 return instance.callSignature.some(function(parameter) {
556 return parameter["name"] === parameterName;
562 _invokeWithArguments: function()
566 let instance = this._instance;
567 let commandArguments = Array.from(arguments);
568 let callback = typeof commandArguments.lastValue === "function" ? commandArguments.pop() : null;
570 function deliverFailure(message) {
571 console.error(`Protocol Error: ${message}`);
573 setTimeout(callback.bind(null, message), 0);
575 return Promise.reject(new Error(message));
579 for (let parameter of instance.callSignature) {
580 let parameterName = parameter["name"];
581 let typeName = parameter["type"];
582 let optionalFlag = parameter["optional"];
584 if (!commandArguments.length && !optionalFlag)
585 return deliverFailure(`Invalid number of arguments for command '${instance.qualifiedName}'.`);
587 let value = commandArguments.shift();
588 if (optionalFlag && value === undefined)
591 if (typeof value !== typeName)
592 return deliverFailure(`Invalid type of argument '${parameterName}' for command '${instance.qualifiedName}' call. It must be '${typeName}' but it is '${typeof value}'.`);
594 parameters[parameterName] = value;
597 if (!callback && commandArguments.length === 1 && commandArguments[0] !== undefined)
598 return deliverFailure(`Protocol Error: Optional callback argument for command '${instance.qualifiedName}' call must be a function but its type is '${typeof args[0]}'.`);
601 instance._backend._sendCommandToBackendWithCallback(instance, parameters, callback);
603 return instance._backend._sendCommandToBackendExpectingPromise(instance, parameters);
607 InspectorBackend.Event = class Event
609 constructor(eventName, parameterNames)
611 this.eventName = eventName;
612 this.parameterNames = parameterNames;