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.prototype = {
46 _initProtocolAgentsConstructor: function()
52 * @param {!Object.<string, !Object>} agentsMap
54 window.Protocol.Agents = function(agentsMap) {
55 this._agentsMap = agentsMap;
60 * @param {string} domain
62 _addAgentGetterMethodToProtocolAgentsPrototype: function(domain)
64 var upperCaseLength = 0;
65 while (upperCaseLength < domain.length && domain[upperCaseLength].toLowerCase() !== domain[upperCaseLength])
68 var methodName = domain.substr(0, upperCaseLength).toLowerCase() + domain.slice(upperCaseLength) + "Agent";
71 * @this {Protocol.Agents}
73 function agentGetter()
75 return this._agentsMap[domain];
78 window.Protocol.Agents.prototype[methodName] = agentGetter;
81 * @this {Protocol.Agents}
83 function registerDispatcher(dispatcher)
85 this.registerDispatcher(domain, dispatcher)
88 window.Protocol.Agents.prototype["register" + domain + "Dispatcher"] = registerDispatcher;
92 * @return {!InspectorBackendClass.Connection}
94 connection: function()
96 if (!this._connection)
97 throw "Main connection was not initialized";
98 return this._connection;
102 * @param {!InspectorBackendClass.MainConnection} connection
104 setConnection: function(connection)
106 this._connection = connection;
108 this._connection.registerAgentsOn(window);
109 for (var type in this._enums) {
110 var domainAndMethod = type.split(".");
111 window[domainAndMethod[0] + "Agent"][domainAndMethod[1]] = this._enums[type];
116 * @param {string} domain
117 * @return {!InspectorBackendClass.AgentPrototype}
119 _agentPrototype: function(domain)
121 if (!this._agentPrototypes[domain]) {
122 this._agentPrototypes[domain] = new InspectorBackendClass.AgentPrototype(domain);
123 this._addAgentGetterMethodToProtocolAgentsPrototype(domain);
126 return this._agentPrototypes[domain];
130 * @param {string} domain
131 * @return {!InspectorBackendClass.DispatcherPrototype}
133 _dispatcherPrototype: function(domain)
135 if (!this._dispatcherPrototypes[domain])
136 this._dispatcherPrototypes[domain] = new InspectorBackendClass.DispatcherPrototype();
137 return this._dispatcherPrototypes[domain];
141 * @param {string} method
142 * @param {!Array.<!Object>} signature
143 * @param {!Array.<string>} replyArgs
144 * @param {boolean} hasErrorData
146 registerCommand: function(method, signature, replyArgs, hasErrorData)
148 var domainAndMethod = method.split(".");
149 this._agentPrototype(domainAndMethod[0]).registerCommand(domainAndMethod[1], signature, replyArgs, hasErrorData);
150 this._initialized = true;
154 * @param {string} type
155 * @param {!Object} values
157 registerEnum: function(type, values)
159 this._enums[type] = values;
160 this._initialized = true;
164 * @param {string} eventName
165 * @param {!Object} params
167 registerEvent: function(eventName, params)
169 var domain = eventName.split(".")[0];
170 this._dispatcherPrototype(domain).registerEvent(eventName, params);
171 this._initialized = true;
175 * @param {string} domain
176 * @param {!Object} dispatcher
178 registerDomainDispatcher: function(domain, dispatcher)
180 this._connection.registerDispatcher(domain, dispatcher);
184 * @param {string} jsonUrl
186 loadFromJSONIfNeeded: function(jsonUrl)
188 if (this._initialized)
191 var xhr = new XMLHttpRequest();
192 xhr.open("GET", jsonUrl, false);
195 var schema = JSON.parse(xhr.responseText);
196 var code = InspectorBackendClass._generateCommands(schema);
201 * @param {function(T)} clientCallback
202 * @param {string} errorPrefix
203 * @param {function(new:T,S)=} constructor
204 * @param {T=} defaultValue
205 * @return {function(?string, S)}
208 wrapClientCallback: function(clientCallback, errorPrefix, constructor, defaultValue)
211 * @param {?string} error
215 function callbackWrapper(error, value)
218 console.error(errorPrefix + error);
219 clientCallback(defaultValue);
223 clientCallback(new constructor(value));
225 clientCallback(value);
227 return callbackWrapper;
235 InspectorBackendClass._generateCommands = function(schema) {
236 var jsTypes = { integer: "number", array: "object" };
240 var domains = schema["domains"] || [];
241 for (var i = 0; i < domains.length; ++i) {
242 var domain = domains[i];
243 for (var j = 0; domain.types && j < domain.types.length; ++j) {
244 var type = domain.types[j];
245 rawTypes[domain.domain + "." + type.id] = jsTypes[type.type] || type.type;
249 function toUpperCase(groupIndex, group0, group1)
251 return [group0, group1][groupIndex].toUpperCase();
253 function generateEnum(enumName, items)
256 for (var m = 0; m < items.length; ++m) {
257 var value = items[m];
258 var name = value.replace(/-(\w)/g, toUpperCase.bind(null, 1)).toTitleCase();
259 name = name.replace(/HTML|XML|WML|API/ig, toUpperCase.bind(null, 0));
260 members.push(name + ": \"" + value +"\"");
262 return "InspectorBackend.registerEnum(\"" + enumName + "\", {" + members.join(", ") + "});";
265 for (var i = 0; i < domains.length; ++i) {
266 var domain = domains[i];
268 var types = domain["types"] || [];
269 for (var j = 0; j < types.length; ++j) {
271 if ((type["type"] === "string") && type["enum"])
272 result.push(generateEnum(domain.domain + "." + type.id, type["enum"]));
273 else if (type["type"] === "object") {
274 var properties = type["properties"] || [];
275 for (var k = 0; k < properties.length; ++k) {
276 var property = properties[k];
277 if ((property["type"] === "string") && property["enum"])
278 result.push(generateEnum(domain.domain + "." + type.id + property["name"].toTitleCase(), property["enum"]));
283 var commands = domain["commands"] || [];
284 for (var j = 0; j < commands.length; ++j) {
285 var command = commands[j];
286 var parameters = command["parameters"];
288 for (var k = 0; parameters && k < parameters.length; ++k) {
289 var parameter = parameters[k];
293 type = jsTypes[parameter.type] || parameter.type;
295 var ref = parameter["$ref"];
296 if (ref.indexOf(".") !== -1)
297 type = rawTypes[ref];
299 type = rawTypes[domain.domain + "." + ref];
302 var text = "{\"name\": \"" + parameter.name + "\", \"type\": \"" + type + "\", \"optional\": " + (parameter.optional ? "true" : "false") + "}";
303 paramsText.push(text);
306 var returnsText = [];
307 var returns = command["returns"] || [];
308 for (var k = 0; k < returns.length; ++k) {
309 var parameter = returns[k];
310 returnsText.push("\"" + parameter.name + "\"");
312 var hasErrorData = String(Boolean(command.error));
313 result.push("InspectorBackend.registerCommand(\"" + domain.domain + "." + command.name + "\", [" + paramsText.join(", ") + "], [" + returnsText.join(", ") + "], " + hasErrorData + ");");
316 for (var j = 0; domain.events && j < domain.events.length; ++j) {
317 var event = domain.events[j];
319 for (var k = 0; event.parameters && k < event.parameters.length; ++k) {
320 var parameter = event.parameters[k];
321 paramsText.push("\"" + parameter.name + "\"");
323 result.push("InspectorBackend.registerEvent(\"" + domain.domain + "." + event.name + "\", [" + paramsText.join(", ") + "]);");
326 result.push("InspectorBackend.register" + domain.domain + "Dispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, \"" + domain.domain + "\");");
328 return result.join("\n");
333 * @extends {WebInspector.Object}
335 InspectorBackendClass.Connection = function()
337 this._lastMessageId = 1;
338 this._pendingResponsesCount = 0;
340 this._dispatchers = {};
341 this._callbacks = {};
342 this._initialize(InspectorBackend._agentPrototypes, InspectorBackend._dispatcherPrototypes);
345 InspectorBackendClass.Connection.Events = {
346 Disconnected: "Disconnected",
349 InspectorBackendClass.Connection.prototype = {
352 * @param {!Object.<string, !InspectorBackendClass.AgentPrototype>} agentPrototypes
353 * @param {!Object.<string, !InspectorBackendClass.DispatcherPrototype>} dispatcherPrototypes
355 _initialize: function(agentPrototypes, dispatcherPrototypes)
357 for (var domain in agentPrototypes) {
358 this._agents[domain] = Object.create(agentPrototypes[domain]);
359 this._agents[domain].setConnection(this);
362 for (var domain in dispatcherPrototypes)
363 this._dispatchers[domain] = Object.create(dispatcherPrototypes[domain])
368 * @param {!Object} object
370 registerAgentsOn: function(object)
372 for (var domain in this._agents)
373 object[domain + "Agent"] = this._agents[domain];
379 nextMessageId: function()
381 return this._lastMessageId++;
385 * @param {string} domain
386 * @return {!InspectorBackendClass.AgentPrototype}
388 agent: function(domain)
390 return this._agents[domain];
394 * @return {!Object.<string, !Object>}
396 agentsMap: function()
402 * @param {string} domain
403 * @param {string} method
404 * @param {?Object} params
405 * @param {?function(*)} callback
408 _wrapCallbackAndSendMessageObject: function(domain, method, params, callback)
410 var messageObject = {};
411 messageObject.method = method;
413 messageObject.params = params;
415 var wrappedCallback = this._wrap(callback, domain, method);
417 var messageId = this.nextMessageId();
418 messageObject.id = messageId;
420 if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
421 console.log("frontend: " + JSON.stringify(messageObject));
423 this.sendMessage(messageObject);
424 ++this._pendingResponsesCount;
425 this._callbacks[messageId] = wrappedCallback;
429 * @param {?function(*)} callback
430 * @param {string} method
431 * @param {string} domain
432 * @return {!function(*)}
434 _wrap: function(callback, domain, method)
437 callback = function() {};
439 callback.methodName = method;
440 callback.domain = domain;
441 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
442 callback.sendRequestTime = Date.now();
448 * @param {!Object} messageObject
450 sendMessage: function(messageObject)
452 throw "Not implemented";
456 * @param {!Object} messageObject
458 reportProtocolError: function(messageObject)
460 console.error("Protocol Error: the message with wrong id. Message = " + JSON.stringify(messageObject));
464 * @param {!Object|string} message
466 dispatch: function(message)
468 if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
469 console.log("backend: " + ((typeof message === "string") ? message : JSON.stringify(message)));
471 var messageObject = /** @type {!Object} */ ((typeof message === "string") ? JSON.parse(message) : message);
473 if ("id" in messageObject) { // just a response for some request
475 var callback = this._callbacks[messageObject.id];
477 this.reportProtocolError(messageObject);
481 var processingStartTime;
482 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
483 processingStartTime = Date.now();
485 this.agent(callback.domain).dispatchResponse(messageObject.id, messageObject, callback.methodName, callback);
486 --this._pendingResponsesCount;
487 delete this._callbacks[messageObject.id];
489 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
490 console.log("time-stats: " + callback.methodName + " = " + (processingStartTime - callback.sendRequestTime) + " + " + (Date.now() - processingStartTime));
492 if (this._scripts && !this._pendingResponsesCount)
493 this.runAfterPendingDispatches();
496 var method = messageObject.method.split(".");
497 var domainName = method[0];
498 if (!(domainName in this._dispatchers)) {
499 console.error("Protocol Error: the message " + messageObject.method + " is for non-existing domain '" + domainName + "'");
503 this._dispatchers[domainName].dispatch(method[1], messageObject);
510 * @param {string} domain
511 * @param {!Object} dispatcher
513 registerDispatcher: function(domain, dispatcher)
515 if (!this._dispatchers[domain])
518 this._dispatchers[domain].setDomainDispatcher(dispatcher);
522 * @param {string=} script
524 runAfterPendingDispatches: function(script)
530 this._scripts.push(script);
532 if (!this._pendingResponsesCount) {
533 var scripts = this._scripts;
535 for (var id = 0; id < scripts.length; ++id)
536 scripts[id].call(this);
541 * @param {string} reason
543 fireDisconnected: function(reason)
545 this.dispatchEventToListeners(InspectorBackendClass.Connection.Events.Disconnected, {reason: reason});
548 __proto__: WebInspector.Object.prototype
554 * @extends {InspectorBackendClass.Connection}
555 * @param {!function(!InspectorBackendClass.Connection)} onConnectionReady
557 InspectorBackendClass.MainConnection = function(onConnectionReady)
559 InspectorBackendClass.Connection.call(this);
560 onConnectionReady(this);
563 InspectorBackendClass.MainConnection.prototype = {
566 * @param {!Object} messageObject
568 sendMessage: function(messageObject)
570 var message = JSON.stringify(messageObject);
571 InspectorFrontendHost.sendMessageToBackend(message);
574 __proto__: InspectorBackendClass.Connection.prototype
579 * @extends {InspectorBackendClass.Connection}
580 * @param {string} url
581 * @param {!function(!InspectorBackendClass.Connection)} onConnectionReady
583 InspectorBackendClass.WebSocketConnection = function(url, onConnectionReady)
585 InspectorBackendClass.Connection.call(this);
586 this._socket = new WebSocket(url);
587 this._socket.onmessage = this._onMessage.bind(this);
588 this._socket.onerror = this._onError.bind(this);
589 this._socket.onopen = onConnectionReady.bind(null, this);
590 this._socket.onclose = this.fireDisconnected.bind(this, "websocket_closed");
593 InspectorBackendClass.WebSocketConnection.prototype = {
596 * @param {!MessageEvent} message
598 _onMessage: function(message)
600 var data = /** @type {string} */ (message.data)
605 * @param {!Event} error
607 _onError: function(error)
609 console.error(error);
613 * @param {!Object} messageObject
615 sendMessage: function(messageObject)
617 var message = JSON.stringify(messageObject);
618 this._socket.send(message);
621 __proto__: InspectorBackendClass.Connection.prototype
627 * @extends {InspectorBackendClass.Connection}
628 * @param {!function(!InspectorBackendClass.Connection)} onConnectionReady
630 InspectorBackendClass.StubConnection = function(onConnectionReady)
632 InspectorBackendClass.Connection.call(this);
633 onConnectionReady(this);
636 InspectorBackendClass.StubConnection.prototype = {
639 * @param {!Object} messageObject
641 sendMessage: function(messageObject)
643 var message = JSON.stringify(messageObject);
644 setTimeout(this._echoResponse.bind(this, messageObject), 0);
648 * @param {!Object} messageObject
650 _echoResponse: function(messageObject)
652 this.dispatch(messageObject)
655 __proto__: InspectorBackendClass.Connection.prototype
660 * @param {string} domain
662 InspectorBackendClass.AgentPrototype = function(domain)
664 this._replyArgs = {};
665 this._hasErrorData = {};
666 this._domain = domain;
669 InspectorBackendClass.AgentPrototype.prototype = {
672 * @param {!InspectorBackendClass.Connection} connection
674 setConnection: function(connection)
676 this._connection = connection;
680 * @param {string} methodName
681 * @param {!Array.<!Object>} signature
682 * @param {!Array.<string>} replyArgs
683 * @param {boolean} hasErrorData
685 registerCommand: function(methodName, signature, replyArgs, hasErrorData)
687 var domainAndMethod = this._domain + "." + methodName;
690 * @this {InspectorBackendClass.AgentPrototype}
692 function sendMessage(vararg)
694 var params = [domainAndMethod, signature].concat(Array.prototype.slice.call(arguments));
695 InspectorBackendClass.AgentPrototype.prototype._sendMessageToBackend.apply(this, params);
698 this[methodName] = sendMessage;
701 * @this {InspectorBackendClass.AgentPrototype}
703 function invoke(vararg)
705 var params = [domainAndMethod].concat(Array.prototype.slice.call(arguments));
706 InspectorBackendClass.AgentPrototype.prototype._invoke.apply(this, params);
709 this["invoke_" + methodName] = invoke;
711 this._replyArgs[domainAndMethod] = replyArgs;
713 this._hasErrorData[domainAndMethod] = true;
718 * @param {string} method
719 * @param {!Array.<!Object>} signature
723 _sendMessageToBackend: function(method, signature, vararg)
725 var args = Array.prototype.slice.call(arguments, 2);
726 var callback = (args.length && typeof args[args.length - 1] === "function") ? args.pop() : null;
729 var hasParams = false;
730 for (var i = 0; i < signature.length; ++i) {
731 var param = signature[i];
732 var paramName = param["name"];
733 var typeName = param["type"];
734 var optionalFlag = param["optional"];
736 if (!args.length && !optionalFlag) {
737 console.error("Protocol Error: Invalid number of arguments for method '" + method + "' call. It must have the following arguments '" + JSON.stringify(signature) + "'.");
741 var value = args.shift();
742 if (optionalFlag && typeof value === "undefined") {
746 if (typeof value !== typeName) {
747 console.error("Protocol Error: Invalid type of argument '" + paramName + "' for method '" + method + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'.");
751 params[paramName] = value;
755 if (args.length === 1 && !callback && (typeof args[0] !== "undefined")) {
756 console.error("Protocol Error: Optional callback argument for method '" + method + "' call must be a function but its type is '" + typeof args[0] + "'.");
760 this._connection._wrapCallbackAndSendMessageObject(this._domain, method, hasParams ? params : null, callback);
764 * @param {string} method
765 * @param {?Object} args
766 * @param {?function(*)} callback
768 _invoke: function(method, args, callback)
770 this._connection._wrapCallbackAndSendMessageObject(this._domain, method, args, callback);
774 * @param {number} messageId
775 * @param {!Object} messageObject
777 dispatchResponse: function(messageId, messageObject, methodName, callback)
779 if (messageObject.error && messageObject.error.code !== -32000)
780 console.error("Request with id = " + messageObject.id + " failed. " + JSON.stringify(messageObject.error));
782 var argumentsArray = [];
783 argumentsArray[0] = messageObject.error ? messageObject.error.message: null;
785 if (this._hasErrorData[methodName])
786 argumentsArray[1] = messageObject.error ? messageObject.error.data : null;
788 if (messageObject.result) {
789 var paramNames = this._replyArgs[methodName] || [];
790 for (var i = 0; i < paramNames.length; ++i)
791 argumentsArray.push(messageObject.result[paramNames[i]]);
794 callback.apply(null, argumentsArray);
801 InspectorBackendClass.DispatcherPrototype = function()
803 this._eventArgs = {};
804 this._dispatcher = null;
807 InspectorBackendClass.DispatcherPrototype.prototype = {
810 * @param {string} eventName
811 * @param {!Object} params
813 registerEvent: function(eventName, params)
815 this._eventArgs[eventName] = params
819 * @param {!Object} dispatcher
821 setDomainDispatcher: function(dispatcher)
823 this._dispatcher = dispatcher;
827 * @param {string} functionName
828 * @param {!Object} messageObject
830 dispatch: function(functionName, messageObject)
832 if (!this._dispatcher)
835 if (!(functionName in this._dispatcher)) {
836 console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + messageObject.method + "'");
840 if (!this._eventArgs[messageObject.method]) {
841 console.error("Protocol Error: Attempted to dispatch an unspecified method '" + messageObject.method + "'");
846 if (messageObject.params) {
847 var paramNames = this._eventArgs[messageObject.method];
848 for (var i = 0; i < paramNames.length; ++i)
849 params.push(messageObject.params[paramNames[i]]);
852 var processingStartTime;
853 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
854 processingStartTime = Date.now();
856 this._dispatcher[functionName].apply(this._dispatcher, params);
858 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
859 console.log("time-stats: " + messageObject.method + " = " + (Date.now() - processingStartTime));
864 InspectorBackendClass.Options = {
865 dumpInspectorTimeStats: false,
866 dumpInspectorProtocolMessages: false
869 InspectorBackend = new InspectorBackendClass();