2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 function InspectorBackendClass()
36 this._connection = null;
37 this._agentPrototypes = {};
38 this._dispatcherPrototypes = {};
39 this._initialized = false;
41 this._initProtocolAgentsConstructor();
44 InspectorBackendClass._DevToolsErrorCode = -32000;
46 InspectorBackendClass.prototype = {
48 _initProtocolAgentsConstructor: function()
54 * @param {!Object.<string, !Object>} agentsMap
56 window.Protocol.Agents = function(agentsMap) {
57 this._agentsMap = agentsMap;
62 * @param {string} domain
64 _addAgentGetterMethodToProtocolAgentsPrototype: function(domain)
66 var upperCaseLength = 0;
67 while (upperCaseLength < domain.length && domain[upperCaseLength].toLowerCase() !== domain[upperCaseLength])
70 var methodName = domain.substr(0, upperCaseLength).toLowerCase() + domain.slice(upperCaseLength) + "Agent";
73 * @this {Protocol.Agents}
75 function agentGetter()
77 return this._agentsMap[domain];
80 window.Protocol.Agents.prototype[methodName] = agentGetter;
83 * @this {Protocol.Agents}
85 function registerDispatcher(dispatcher)
87 this.registerDispatcher(domain, dispatcher)
90 window.Protocol.Agents.prototype["register" + domain + "Dispatcher"] = registerDispatcher;
94 * @return {!InspectorBackendClass.Connection}
96 connection: function()
98 if (!this._connection)
99 throw "Main connection was not initialized";
100 return this._connection;
104 * @param {!InspectorBackendClass.MainConnection} connection
106 setConnection: function(connection)
108 this._connection = connection;
110 this._connection.registerAgentsOn(window);
111 for (var type in this._enums) {
112 var domainAndMethod = type.split(".");
113 window[domainAndMethod[0] + "Agent"][domainAndMethod[1]] = this._enums[type];
118 * @param {string} domain
119 * @return {!InspectorBackendClass.AgentPrototype}
121 _agentPrototype: function(domain)
123 if (!this._agentPrototypes[domain]) {
124 this._agentPrototypes[domain] = new InspectorBackendClass.AgentPrototype(domain);
125 this._addAgentGetterMethodToProtocolAgentsPrototype(domain);
128 return this._agentPrototypes[domain];
132 * @param {string} domain
133 * @return {!InspectorBackendClass.DispatcherPrototype}
135 _dispatcherPrototype: function(domain)
137 if (!this._dispatcherPrototypes[domain])
138 this._dispatcherPrototypes[domain] = new InspectorBackendClass.DispatcherPrototype();
139 return this._dispatcherPrototypes[domain];
143 * @param {string} method
144 * @param {!Array.<!Object>} signature
145 * @param {!Array.<string>} replyArgs
146 * @param {boolean} hasErrorData
148 registerCommand: function(method, signature, replyArgs, hasErrorData)
150 var domainAndMethod = method.split(".");
151 this._agentPrototype(domainAndMethod[0]).registerCommand(domainAndMethod[1], signature, replyArgs, hasErrorData);
152 this._initialized = true;
156 * @param {string} type
157 * @param {!Object} values
159 registerEnum: function(type, values)
161 this._enums[type] = values;
162 this._initialized = true;
166 * @param {string} eventName
167 * @param {!Object} params
169 registerEvent: function(eventName, params)
171 var domain = eventName.split(".")[0];
172 this._dispatcherPrototype(domain).registerEvent(eventName, params);
173 this._initialized = true;
177 * @param {string} domain
178 * @param {!Object} dispatcher
180 registerDomainDispatcher: function(domain, dispatcher)
182 this._connection.registerDispatcher(domain, dispatcher);
186 * @param {string} jsonUrl
188 loadFromJSONIfNeeded: function(jsonUrl)
190 if (this._initialized)
193 var xhr = new XMLHttpRequest();
194 xhr.open("GET", jsonUrl, false);
197 var schema = JSON.parse(xhr.responseText);
198 var code = InspectorBackendClass._generateCommands(schema);
203 * @param {function(T)} clientCallback
204 * @param {string} errorPrefix
205 * @param {function(new:T,S)=} constructor
206 * @param {T=} defaultValue
207 * @return {function(?string, S)}
210 wrapClientCallback: function(clientCallback, errorPrefix, constructor, defaultValue)
213 * @param {?string} error
217 function callbackWrapper(error, value)
220 console.error(errorPrefix + error);
221 clientCallback(defaultValue);
225 clientCallback(new constructor(value));
227 clientCallback(value);
229 return callbackWrapper;
237 InspectorBackendClass._generateCommands = function(schema) {
238 var jsTypes = { integer: "number", array: "object" };
242 var domains = schema["domains"] || [];
243 for (var i = 0; i < domains.length; ++i) {
244 var domain = domains[i];
245 for (var j = 0; domain.types && j < domain.types.length; ++j) {
246 var type = domain.types[j];
247 rawTypes[domain.domain + "." + type.id] = jsTypes[type.type] || type.type;
251 function toUpperCase(groupIndex, group0, group1)
253 return [group0, group1][groupIndex].toUpperCase();
255 function generateEnum(enumName, items)
258 for (var m = 0; m < items.length; ++m) {
259 var value = items[m];
260 var name = value.replace(/-(\w)/g, toUpperCase.bind(null, 1)).toTitleCase();
261 name = name.replace(/HTML|XML|WML|API/ig, toUpperCase.bind(null, 0));
262 members.push(name + ": \"" + value +"\"");
264 return "InspectorBackend.registerEnum(\"" + enumName + "\", {" + members.join(", ") + "});";
267 for (var i = 0; i < domains.length; ++i) {
268 var domain = domains[i];
270 var types = domain["types"] || [];
271 for (var j = 0; j < types.length; ++j) {
273 if ((type["type"] === "string") && type["enum"])
274 result.push(generateEnum(domain.domain + "." + type.id, type["enum"]));
275 else if (type["type"] === "object") {
276 var properties = type["properties"] || [];
277 for (var k = 0; k < properties.length; ++k) {
278 var property = properties[k];
279 if ((property["type"] === "string") && property["enum"])
280 result.push(generateEnum(domain.domain + "." + type.id + property["name"].toTitleCase(), property["enum"]));
285 var commands = domain["commands"] || [];
286 for (var j = 0; j < commands.length; ++j) {
287 var command = commands[j];
288 var parameters = command["parameters"];
290 for (var k = 0; parameters && k < parameters.length; ++k) {
291 var parameter = parameters[k];
295 type = jsTypes[parameter.type] || parameter.type;
297 var ref = parameter["$ref"];
298 if (ref.indexOf(".") !== -1)
299 type = rawTypes[ref];
301 type = rawTypes[domain.domain + "." + ref];
304 var text = "{\"name\": \"" + parameter.name + "\", \"type\": \"" + type + "\", \"optional\": " + (parameter.optional ? "true" : "false") + "}";
305 paramsText.push(text);
308 var returnsText = [];
309 var returns = command["returns"] || [];
310 for (var k = 0; k < returns.length; ++k) {
311 var parameter = returns[k];
312 returnsText.push("\"" + parameter.name + "\"");
314 var hasErrorData = String(Boolean(command.error));
315 result.push("InspectorBackend.registerCommand(\"" + domain.domain + "." + command.name + "\", [" + paramsText.join(", ") + "], [" + returnsText.join(", ") + "], " + hasErrorData + ");");
318 for (var j = 0; domain.events && j < domain.events.length; ++j) {
319 var event = domain.events[j];
321 for (var k = 0; event.parameters && k < event.parameters.length; ++k) {
322 var parameter = event.parameters[k];
323 paramsText.push("\"" + parameter.name + "\"");
325 result.push("InspectorBackend.registerEvent(\"" + domain.domain + "." + event.name + "\", [" + paramsText.join(", ") + "]);");
328 result.push("InspectorBackend.register" + domain.domain + "Dispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, \"" + domain.domain + "\");");
330 return result.join("\n");
335 * @extends {WebInspector.Object}
337 InspectorBackendClass.Connection = function()
339 this._lastMessageId = 1;
340 this._pendingResponsesCount = 0;
342 this._dispatchers = {};
343 this._callbacks = {};
344 this._initialize(InspectorBackend._agentPrototypes, InspectorBackend._dispatcherPrototypes);
345 this._isConnected = true;
348 InspectorBackendClass.Connection.Events = {
349 Disconnected: "Disconnected",
352 InspectorBackendClass.Connection.prototype = {
355 * @param {!Object.<string, !InspectorBackendClass.AgentPrototype>} agentPrototypes
356 * @param {!Object.<string, !InspectorBackendClass.DispatcherPrototype>} dispatcherPrototypes
358 _initialize: function(agentPrototypes, dispatcherPrototypes)
360 for (var domain in agentPrototypes) {
361 this._agents[domain] = Object.create(agentPrototypes[domain]);
362 this._agents[domain].setConnection(this);
365 for (var domain in dispatcherPrototypes)
366 this._dispatchers[domain] = Object.create(dispatcherPrototypes[domain])
371 * @param {!Object} object
373 registerAgentsOn: function(object)
375 for (var domain in this._agents)
376 object[domain + "Agent"] = this._agents[domain];
382 nextMessageId: function()
384 return this._lastMessageId++;
388 * @param {string} domain
389 * @return {!InspectorBackendClass.AgentPrototype}
391 agent: function(domain)
393 return this._agents[domain];
397 * @return {!Object.<string, !Object>}
399 agentsMap: function()
405 * @param {string} domain
406 * @param {string} method
407 * @param {?Object} params
408 * @param {?function(*)} callback
411 _wrapCallbackAndSendMessageObject: function(domain, method, params, callback)
413 if (!this._isConnected && callback) {
414 this._dispatchConnectionErrorResponse(domain, method, callback);
418 var messageObject = {};
420 var messageId = this.nextMessageId();
421 messageObject.id = messageId;
423 messageObject.method = method;
425 messageObject.params = params;
427 var wrappedCallback = this._wrap(callback, domain, method);
429 if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
430 this._dumpProtocolMessage("frontend: " + JSON.stringify(messageObject));
432 this.sendMessage(messageObject);
433 ++this._pendingResponsesCount;
434 this._callbacks[messageId] = wrappedCallback;
438 * @param {?function(*)} callback
439 * @param {string} method
440 * @param {string} domain
441 * @return {!function(*)}
443 _wrap: function(callback, domain, method)
446 callback = function() {};
448 callback.methodName = method;
449 callback.domain = domain;
450 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
451 callback.sendRequestTime = Date.now();
457 * @param {!Object} messageObject
459 sendMessage: function(messageObject)
461 throw "Not implemented";
465 * @param {!Object} messageObject
467 reportProtocolError: function(messageObject)
469 console.error("Protocol Error: the message with wrong id. Message = " + JSON.stringify(messageObject));
473 * @param {!Object|string} message
475 dispatch: function(message)
477 if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
478 this._dumpProtocolMessage("backend: " + ((typeof message === "string") ? message : JSON.stringify(message)));
480 var messageObject = /** @type {!Object} */ ((typeof message === "string") ? JSON.parse(message) : message);
482 if ("id" in messageObject) { // just a response for some request
484 var callback = this._callbacks[messageObject.id];
486 this.reportProtocolError(messageObject);
490 var processingStartTime;
491 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
492 processingStartTime = Date.now();
494 this.agent(callback.domain).dispatchResponse(messageObject, callback.methodName, callback);
495 --this._pendingResponsesCount;
496 delete this._callbacks[messageObject.id];
498 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
499 console.log("time-stats: " + callback.methodName + " = " + (processingStartTime - callback.sendRequestTime) + " + " + (Date.now() - processingStartTime));
501 if (this._scripts && !this._pendingResponsesCount)
502 this.runAfterPendingDispatches();
505 var method = messageObject.method.split(".");
506 var domainName = method[0];
507 if (!(domainName in this._dispatchers)) {
508 console.error("Protocol Error: the message " + messageObject.method + " is for non-existing domain '" + domainName + "'");
512 this._dispatchers[domainName].dispatch(method[1], messageObject);
518 * @param {string} domain
519 * @param {!Object} dispatcher
521 registerDispatcher: function(domain, dispatcher)
523 if (!this._dispatchers[domain])
526 this._dispatchers[domain].setDomainDispatcher(dispatcher);
530 * @param {string=} script
532 runAfterPendingDispatches: function(script)
538 this._scripts.push(script);
540 if (!this._pendingResponsesCount) {
541 var scripts = this._scripts;
543 for (var id = 0; id < scripts.length; ++id)
544 scripts[id].call(this);
548 _dumpProtocolMessage: function(message)
550 console.log(message);
555 * @param {string} reason
557 connectionClosed: function(reason)
559 this._isConnected = false;
560 this._runPendingCallbacks();
561 this.dispatchEventToListeners(InspectorBackendClass.Connection.Events.Disconnected, {reason: reason});
564 _runPendingCallbacks: function()
566 var keys = Object.keys(this._callbacks).map(function(num) {return parseInt(num, 10)});
567 for (var i = 0; i < keys.length; ++i) {
568 var callback = this._callbacks[keys[i]];
569 this._dispatchConnectionErrorResponse(callback.domain, callback.methodName, callback)
571 this._callbacks = {};
575 * @param {string} domain
576 * @param {string} methodName
577 * @param {!function(*)} callback
579 _dispatchConnectionErrorResponse: function(domain, methodName, callback)
581 var error = { message: "Connection is closed", code: InspectorBackendClass._DevToolsErrorCode, data: null};
582 var messageObject = {error: error};
583 setTimeout(InspectorBackendClass.AgentPrototype.prototype.dispatchResponse.bind(this.agent(domain), messageObject, methodName, callback), 0);
591 return !this._isConnected;
595 * @param {!Array.<string>} domains
597 suppressErrorsForDomains: function(domains)
599 domains.forEach(function(domain) { this._agents[domain].suppressErrorLogging(); }, this);
602 __proto__: WebInspector.Object.prototype
608 * @extends {InspectorBackendClass.Connection}
610 InspectorBackendClass.MainConnection = function()
612 InspectorBackendClass.Connection.call(this);
613 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessage, this._dispatchMessage, this);
616 InspectorBackendClass.MainConnection.prototype = {
619 * @param {!Object} messageObject
621 sendMessage: function(messageObject)
623 var message = JSON.stringify(messageObject);
624 InspectorFrontendHost.sendMessageToBackend(message);
628 * @param {!WebInspector.Event} event
630 _dispatchMessage: function(event)
632 this.dispatch(/** @type {!Object|string} */ (event.data));
635 __proto__: InspectorBackendClass.Connection.prototype
640 * @extends {InspectorBackendClass.Connection}
641 * @param {string} url
642 * @param {!function(!InspectorBackendClass.Connection)} onConnectionReady
644 InspectorBackendClass.WebSocketConnection = function(url, onConnectionReady)
646 InspectorBackendClass.Connection.call(this);
647 this._socket = new WebSocket(url);
648 this._socket.onmessage = this._onMessage.bind(this);
649 this._socket.onerror = this._onError.bind(this);
650 this._socket.onopen = onConnectionReady.bind(null, this);
651 this._socket.onclose = this.connectionClosed.bind(this, "websocket_closed");
655 * @param {string} url
656 * @param {!function(!InspectorBackendClass.Connection)} onConnectionReady
658 InspectorBackendClass.WebSocketConnection.Create = function(url, onConnectionReady)
660 new InspectorBackendClass.WebSocketConnection(url, onConnectionReady);
663 InspectorBackendClass.WebSocketConnection.prototype = {
666 * @param {!MessageEvent} message
668 _onMessage: function(message)
670 var data = /** @type {string} */ (message.data)
675 * @param {!Event} error
677 _onError: function(error)
679 console.error(error);
683 * @param {!Object} messageObject
685 sendMessage: function(messageObject)
687 var message = JSON.stringify(messageObject);
688 this._socket.send(message);
691 __proto__: InspectorBackendClass.Connection.prototype
697 * @extends {InspectorBackendClass.Connection}
699 InspectorBackendClass.StubConnection = function()
701 InspectorBackendClass.Connection.call(this);
704 InspectorBackendClass.StubConnection.prototype = {
707 * @param {!Object} messageObject
709 sendMessage: function(messageObject)
711 var message = JSON.stringify(messageObject);
712 setTimeout(this._echoResponse.bind(this, messageObject), 0);
716 * @param {!Object} messageObject
718 _echoResponse: function(messageObject)
720 this.dispatch(messageObject)
723 __proto__: InspectorBackendClass.Connection.prototype
728 * @param {string} domain
730 InspectorBackendClass.AgentPrototype = function(domain)
732 this._replyArgs = {};
733 this._hasErrorData = {};
734 this._domain = domain;
735 this._suppressErrorLogging = false;
738 InspectorBackendClass.AgentPrototype.prototype = {
741 * @param {!InspectorBackendClass.Connection} connection
743 setConnection: function(connection)
745 this._connection = connection;
749 * @param {string} methodName
750 * @param {!Array.<!Object>} signature
751 * @param {!Array.<string>} replyArgs
752 * @param {boolean} hasErrorData
754 registerCommand: function(methodName, signature, replyArgs, hasErrorData)
756 var domainAndMethod = this._domain + "." + methodName;
759 * @this {InspectorBackendClass.AgentPrototype}
761 function sendMessage(vararg)
763 var params = [domainAndMethod, signature].concat(Array.prototype.slice.call(arguments));
764 InspectorBackendClass.AgentPrototype.prototype._sendMessageToBackend.apply(this, params);
767 this[methodName] = sendMessage;
770 * @this {InspectorBackendClass.AgentPrototype}
772 function invoke(vararg)
774 var params = [domainAndMethod].concat(Array.prototype.slice.call(arguments));
775 InspectorBackendClass.AgentPrototype.prototype._invoke.apply(this, params);
778 this["invoke_" + methodName] = invoke;
780 this._replyArgs[domainAndMethod] = replyArgs;
782 this._hasErrorData[domainAndMethod] = true;
787 * @param {string} method
788 * @param {!Array.<!Object>} signature
792 _sendMessageToBackend: function(method, signature, vararg)
794 var args = Array.prototype.slice.call(arguments, 2);
795 var callback = (args.length && typeof args[args.length - 1] === "function") ? args.pop() : null;
798 var hasParams = false;
799 for (var i = 0; i < signature.length; ++i) {
800 var param = signature[i];
801 var paramName = param["name"];
802 var typeName = param["type"];
803 var optionalFlag = param["optional"];
805 if (!args.length && !optionalFlag) {
806 console.error("Protocol Error: Invalid number of arguments for method '" + method + "' call. It must have the following arguments '" + JSON.stringify(signature) + "'.");
810 var value = args.shift();
811 if (optionalFlag && typeof value === "undefined") {
815 if (typeof value !== typeName) {
816 console.error("Protocol Error: Invalid type of argument '" + paramName + "' for method '" + method + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'.");
820 params[paramName] = value;
824 if (args.length === 1 && !callback && (typeof args[0] !== "undefined")) {
825 console.error("Protocol Error: Optional callback argument for method '" + method + "' call must be a function but its type is '" + typeof args[0] + "'.");
829 this._connection._wrapCallbackAndSendMessageObject(this._domain, method, hasParams ? params : null, callback);
833 * @param {string} method
834 * @param {?Object} args
835 * @param {?function(*)} callback
837 _invoke: function(method, args, callback)
839 this._connection._wrapCallbackAndSendMessageObject(this._domain, method, args, callback);
843 * @param {!Object} messageObject
844 * @param {string} methodName
845 * @param {function(!Array.<*>)} callback
847 dispatchResponse: function(messageObject, methodName, callback)
849 if (messageObject.error && messageObject.error.code !== InspectorBackendClass._DevToolsErrorCode && !InspectorBackendClass.Options.suppressRequestErrors && !this._suppressErrorLogging)
850 console.error("Request with id = " + messageObject.id + " failed. " + JSON.stringify(messageObject.error));
852 var argumentsArray = [];
853 argumentsArray[0] = messageObject.error ? messageObject.error.message: null;
855 if (this._hasErrorData[methodName])
856 argumentsArray[1] = messageObject.error ? messageObject.error.data : null;
858 if (messageObject.result) {
859 var paramNames = this._replyArgs[methodName] || [];
860 for (var i = 0; i < paramNames.length; ++i)
861 argumentsArray.push(messageObject.result[paramNames[i]]);
864 callback.apply(null, argumentsArray);
867 suppressErrorLogging: function()
869 this._suppressErrorLogging = true;
876 InspectorBackendClass.DispatcherPrototype = function()
878 this._eventArgs = {};
879 this._dispatcher = null;
882 InspectorBackendClass.DispatcherPrototype.prototype = {
885 * @param {string} eventName
886 * @param {!Object} params
888 registerEvent: function(eventName, params)
890 this._eventArgs[eventName] = params
894 * @param {!Object} dispatcher
896 setDomainDispatcher: function(dispatcher)
898 this._dispatcher = dispatcher;
902 * @param {string} functionName
903 * @param {!Object} messageObject
905 dispatch: function(functionName, messageObject)
907 if (!this._dispatcher)
910 if (!(functionName in this._dispatcher)) {
911 console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + messageObject.method + "'");
915 if (!this._eventArgs[messageObject.method]) {
916 console.error("Protocol Error: Attempted to dispatch an unspecified method '" + messageObject.method + "'");
921 if (messageObject.params) {
922 var paramNames = this._eventArgs[messageObject.method];
923 for (var i = 0; i < paramNames.length; ++i)
924 params.push(messageObject.params[paramNames[i]]);
927 var processingStartTime;
928 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
929 processingStartTime = Date.now();
931 this._dispatcher[functionName].apply(this._dispatcher, params);
933 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
934 console.log("time-stats: " + messageObject.method + " = " + (Date.now() - processingStartTime));
939 InspectorBackendClass.Options = {
940 dumpInspectorTimeStats: false,
941 dumpInspectorProtocolMessages: false,
942 suppressRequestErrors: false
945 InspectorBackend = new InspectorBackendClass();