Home | History | Annotate | Download | only in sdk
      1 /*
      2  * Copyright (C) 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      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
     13  * distribution.
     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.
     17  *
     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.
     29  */
     30 
     31 /**
     32  * @constructor
     33  */
     34 function InspectorBackendClass()
     35 {
     36     this._connection = null;
     37     this._agentPrototypes = {};
     38     this._dispatcherPrototypes = {};
     39     this._initialized = false;
     40     this._enums = {};
     41     this._initProtocolAgentsConstructor();
     42 }
     43 
     44 InspectorBackendClass._DevToolsErrorCode = -32000;
     45 
     46 InspectorBackendClass.prototype = {
     47 
     48     _initProtocolAgentsConstructor: function()
     49     {
     50         window.Protocol = {};
     51 
     52         /**
     53          * @constructor
     54          * @param {!Object.<string, !Object>} agentsMap
     55          */
     56         window.Protocol.Agents = function(agentsMap) {
     57             this._agentsMap = agentsMap;
     58         };
     59     },
     60 
     61     /**
     62      * @param {string} domain
     63      */
     64     _addAgentGetterMethodToProtocolAgentsPrototype: function(domain)
     65     {
     66         var upperCaseLength = 0;
     67         while (upperCaseLength < domain.length && domain[upperCaseLength].toLowerCase() !== domain[upperCaseLength])
     68             ++upperCaseLength;
     69 
     70         var methodName = domain.substr(0, upperCaseLength).toLowerCase() + domain.slice(upperCaseLength) + "Agent";
     71 
     72         /**
     73          * @this {Protocol.Agents}
     74          */
     75         function agentGetter()
     76         {
     77             return this._agentsMap[domain];
     78         }
     79 
     80         window.Protocol.Agents.prototype[methodName] = agentGetter;
     81 
     82         /**
     83          * @this {Protocol.Agents}
     84          */
     85         function registerDispatcher(dispatcher)
     86         {
     87             this.registerDispatcher(domain, dispatcher)
     88         }
     89 
     90         window.Protocol.Agents.prototype["register" + domain + "Dispatcher"] = registerDispatcher;
     91     },
     92 
     93     /**
     94      * @return {!InspectorBackendClass.Connection}
     95      */
     96     connection: function()
     97     {
     98         if (!this._connection)
     99             throw "Main connection was not initialized";
    100         return this._connection;
    101     },
    102 
    103     /**
    104      * @param {!InspectorBackendClass.MainConnection} connection
    105      */
    106     setConnection: function(connection)
    107     {
    108         this._connection = connection;
    109 
    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];
    114         }
    115     },
    116 
    117     /**
    118      * @param {string} domain
    119      * @return {!InspectorBackendClass.AgentPrototype}
    120      */
    121     _agentPrototype: function(domain)
    122     {
    123         if (!this._agentPrototypes[domain]) {
    124             this._agentPrototypes[domain] = new InspectorBackendClass.AgentPrototype(domain);
    125             this._addAgentGetterMethodToProtocolAgentsPrototype(domain);
    126         }
    127 
    128         return this._agentPrototypes[domain];
    129     },
    130 
    131     /**
    132      * @param {string} domain
    133      * @return {!InspectorBackendClass.DispatcherPrototype}
    134      */
    135     _dispatcherPrototype: function(domain)
    136     {
    137         if (!this._dispatcherPrototypes[domain])
    138             this._dispatcherPrototypes[domain] = new InspectorBackendClass.DispatcherPrototype();
    139         return this._dispatcherPrototypes[domain];
    140     },
    141 
    142     /**
    143      * @param {string} method
    144      * @param {!Array.<!Object>} signature
    145      * @param {!Array.<string>} replyArgs
    146      * @param {boolean} hasErrorData
    147      */
    148     registerCommand: function(method, signature, replyArgs, hasErrorData)
    149     {
    150         var domainAndMethod = method.split(".");
    151         this._agentPrototype(domainAndMethod[0]).registerCommand(domainAndMethod[1], signature, replyArgs, hasErrorData);
    152         this._initialized = true;
    153     },
    154 
    155     /**
    156      * @param {string} type
    157      * @param {!Object} values
    158      */
    159     registerEnum: function(type, values)
    160     {
    161         this._enums[type] = values;
    162         this._initialized = true;
    163     },
    164 
    165     /**
    166      * @param {string} eventName
    167      * @param {!Object} params
    168      */
    169     registerEvent: function(eventName, params)
    170     {
    171         var domain = eventName.split(".")[0];
    172         this._dispatcherPrototype(domain).registerEvent(eventName, params);
    173         this._initialized = true;
    174     },
    175 
    176     /**
    177      * @param {string} jsonUrl
    178      */
    179     loadFromJSONIfNeeded: function(jsonUrl)
    180     {
    181         if (this._initialized)
    182             return;
    183 
    184         var xhr = new XMLHttpRequest();
    185         xhr.open("GET", jsonUrl, false);
    186         xhr.send(null);
    187 
    188         var schema = JSON.parse(xhr.responseText);
    189         var code = InspectorBackendClass._generateCommands(schema);
    190         eval(code);
    191     },
    192 
    193     /**
    194      * @param {function(T)} clientCallback
    195      * @param {string} errorPrefix
    196      * @param {function(new:T,S)=} constructor
    197      * @param {T=} defaultValue
    198      * @return {function(?string, S)}
    199      * @template T,S
    200      */
    201     wrapClientCallback: function(clientCallback, errorPrefix, constructor, defaultValue)
    202     {
    203         /**
    204          * @param {?string} error
    205          * @param {S} value
    206          * @template S
    207          */
    208         function callbackWrapper(error, value)
    209         {
    210             if (error) {
    211                 console.error(errorPrefix + error);
    212                 clientCallback(defaultValue);
    213                 return;
    214             }
    215             if (constructor)
    216                 clientCallback(new constructor(value));
    217             else
    218                 clientCallback(value);
    219         }
    220         return callbackWrapper;
    221     }
    222 }
    223 
    224 /**
    225  * @param {*} schema
    226  * @return {string}
    227  */
    228 InspectorBackendClass._generateCommands = function(schema) {
    229     var jsTypes = { integer: "number", array: "object" };
    230     var rawTypes = {};
    231     var result = [];
    232 
    233     var domains = schema["domains"] || [];
    234     for (var i = 0; i < domains.length; ++i) {
    235         var domain = domains[i];
    236         for (var j = 0; domain.types && j < domain.types.length; ++j) {
    237             var type = domain.types[j];
    238             rawTypes[domain.domain + "." + type.id] = jsTypes[type.type] || type.type;
    239         }
    240     }
    241 
    242     function toUpperCase(groupIndex, group0, group1)
    243     {
    244         return [group0, group1][groupIndex].toUpperCase();
    245     }
    246     function generateEnum(enumName, items)
    247     {
    248         var members = []
    249         for (var m = 0; m < items.length; ++m) {
    250             var value = items[m];
    251             var name = value.replace(/-(\w)/g, toUpperCase.bind(null, 1)).toTitleCase();
    252             name = name.replace(/HTML|XML|WML|API/ig, toUpperCase.bind(null, 0));
    253             members.push(name + ": \"" + value +"\"");
    254         }
    255         return "InspectorBackend.registerEnum(\"" + enumName + "\", {" + members.join(", ") + "});";
    256     }
    257 
    258     for (var i = 0; i < domains.length; ++i) {
    259         var domain = domains[i];
    260 
    261         var types = domain["types"] || [];
    262         for (var j = 0; j < types.length; ++j) {
    263             var type = types[j];
    264             if ((type["type"] === "string") && type["enum"])
    265                 result.push(generateEnum(domain.domain + "." + type.id, type["enum"]));
    266             else if (type["type"] === "object") {
    267                 var properties = type["properties"] || [];
    268                 for (var k = 0; k < properties.length; ++k) {
    269                     var property = properties[k];
    270                     if ((property["type"] === "string") && property["enum"])
    271                         result.push(generateEnum(domain.domain + "." + type.id + property["name"].toTitleCase(), property["enum"]));
    272                 }
    273             }
    274         }
    275 
    276         var commands = domain["commands"] || [];
    277         for (var j = 0; j < commands.length; ++j) {
    278             var command = commands[j];
    279             var parameters = command["parameters"];
    280             var paramsText = [];
    281             for (var k = 0; parameters && k < parameters.length; ++k) {
    282                 var parameter = parameters[k];
    283 
    284                 var type;
    285                 if (parameter.type)
    286                     type = jsTypes[parameter.type] || parameter.type;
    287                 else {
    288                     var ref = parameter["$ref"];
    289                     if (ref.indexOf(".") !== -1)
    290                         type = rawTypes[ref];
    291                     else
    292                         type = rawTypes[domain.domain + "." + ref];
    293                 }
    294 
    295                 var text = "{\"name\": \"" + parameter.name + "\", \"type\": \"" + type + "\", \"optional\": " + (parameter.optional ? "true" : "false") + "}";
    296                 paramsText.push(text);
    297             }
    298 
    299             var returnsText = [];
    300             var returns = command["returns"] || [];
    301             for (var k = 0; k < returns.length; ++k) {
    302                 var parameter = returns[k];
    303                 returnsText.push("\"" + parameter.name + "\"");
    304             }
    305             var hasErrorData = String(Boolean(command.error));
    306             result.push("InspectorBackend.registerCommand(\"" + domain.domain + "." + command.name + "\", [" + paramsText.join(", ") + "], [" + returnsText.join(", ") + "], " + hasErrorData + ");");
    307         }
    308 
    309         for (var j = 0; domain.events && j < domain.events.length; ++j) {
    310             var event = domain.events[j];
    311             var paramsText = [];
    312             for (var k = 0; event.parameters && k < event.parameters.length; ++k) {
    313                 var parameter = event.parameters[k];
    314                 paramsText.push("\"" + parameter.name + "\"");
    315             }
    316             result.push("InspectorBackend.registerEvent(\"" + domain.domain + "." + event.name + "\", [" + paramsText.join(", ") + "]);");
    317         }
    318     }
    319     return result.join("\n");
    320 }
    321 
    322 /**
    323  *  @constructor
    324  *  @extends {WebInspector.Object}
    325  */
    326 InspectorBackendClass.Connection = function()
    327 {
    328     this._lastMessageId = 1;
    329     this._pendingResponsesCount = 0;
    330     this._agents = {};
    331     this._dispatchers = {};
    332     this._callbacks = {};
    333     this._initialize(InspectorBackend._agentPrototypes, InspectorBackend._dispatcherPrototypes);
    334     this._isConnected = true;
    335 }
    336 
    337 InspectorBackendClass.Connection.Events = {
    338     Disconnected: "Disconnected",
    339 }
    340 
    341 InspectorBackendClass.Connection.prototype = {
    342 
    343     /**
    344      * @param {!Object.<string, !InspectorBackendClass.AgentPrototype>} agentPrototypes
    345      * @param {!Object.<string, !InspectorBackendClass.DispatcherPrototype>} dispatcherPrototypes
    346      */
    347     _initialize: function(agentPrototypes, dispatcherPrototypes)
    348     {
    349         for (var domain in agentPrototypes) {
    350             this._agents[domain] = Object.create(agentPrototypes[domain]);
    351             this._agents[domain].setConnection(this);
    352         }
    353 
    354         for (var domain in dispatcherPrototypes)
    355             this._dispatchers[domain] = Object.create(dispatcherPrototypes[domain])
    356 
    357     },
    358 
    359     /**
    360      * @param {!Object} object
    361      */
    362     registerAgentsOn: function(object)
    363     {
    364         for (var domain in this._agents)
    365             object[domain + "Agent"]  = this._agents[domain];
    366     },
    367 
    368     /**
    369      * @return {number}
    370      */
    371     nextMessageId: function()
    372     {
    373         return this._lastMessageId++;
    374     },
    375 
    376     /**
    377      * @param {string} domain
    378      * @return {!InspectorBackendClass.AgentPrototype}
    379      */
    380     agent: function(domain)
    381     {
    382         return this._agents[domain];
    383     },
    384 
    385     /**
    386      * @return {!Object.<string, !Object>}
    387      */
    388     agentsMap: function()
    389     {
    390         return this._agents;
    391     },
    392 
    393     /**
    394      * @param {string} domain
    395      * @param {string} method
    396      * @param {?Object} params
    397      * @param {?function(*)} callback
    398      * @private
    399      */
    400     _wrapCallbackAndSendMessageObject: function(domain, method, params, callback)
    401     {
    402         if (!this._isConnected && callback) {
    403             this._dispatchConnectionErrorResponse(domain, method, callback);
    404             return;
    405         }
    406 
    407         var messageObject = {};
    408 
    409         var messageId = this.nextMessageId();
    410         messageObject.id = messageId;
    411 
    412         messageObject.method = method;
    413         if (params)
    414             messageObject.params = params;
    415 
    416         var wrappedCallback = this._wrap(callback, domain, method);
    417 
    418         if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
    419             this._dumpProtocolMessage("frontend: " + JSON.stringify(messageObject));
    420 
    421         this.sendMessage(messageObject);
    422         ++this._pendingResponsesCount;
    423         this._callbacks[messageId] = wrappedCallback;
    424     },
    425 
    426     /**
    427      * @param {?function(*)} callback
    428      * @param {string} method
    429      * @param {string} domain
    430      * @return {!function(*)}
    431      */
    432     _wrap: function(callback, domain, method)
    433     {
    434         if (!callback)
    435             callback = function() {};
    436 
    437         callback.methodName = method;
    438         callback.domain = domain;
    439         if (InspectorBackendClass.Options.dumpInspectorTimeStats)
    440             callback.sendRequestTime = Date.now();
    441 
    442         return callback;
    443     },
    444 
    445     /**
    446      * @param {!Object} messageObject
    447      */
    448     sendMessage: function(messageObject)
    449     {
    450         throw "Not implemented";
    451     },
    452 
    453     /**
    454      * @param {!Object} messageObject
    455      */
    456     reportProtocolError: function(messageObject)
    457     {
    458         console.error("Protocol Error: the message with wrong id. Message =  " + JSON.stringify(messageObject));
    459     },
    460 
    461     /**
    462      * @param {!Object|string} message
    463      */
    464     dispatch: function(message)
    465     {
    466         if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
    467             this._dumpProtocolMessage("backend: " + ((typeof message === "string") ? message : JSON.stringify(message)));
    468 
    469         var messageObject = /** @type {!Object} */ ((typeof message === "string") ? JSON.parse(message) : message);
    470 
    471         if ("id" in messageObject) { // just a response for some request
    472 
    473             var callback = this._callbacks[messageObject.id];
    474             if (!callback) {
    475                 this.reportProtocolError(messageObject);
    476                 return;
    477             }
    478 
    479             var processingStartTime;
    480             if (InspectorBackendClass.Options.dumpInspectorTimeStats)
    481                 processingStartTime = Date.now();
    482 
    483             this.agent(callback.domain).dispatchResponse(messageObject, callback.methodName, callback);
    484             --this._pendingResponsesCount;
    485             delete this._callbacks[messageObject.id];
    486 
    487             if (InspectorBackendClass.Options.dumpInspectorTimeStats)
    488                 console.log("time-stats: " + callback.methodName + " = " + (processingStartTime - callback.sendRequestTime) + " + " + (Date.now() - processingStartTime));
    489 
    490             if (this._scripts && !this._pendingResponsesCount)
    491                 this.runAfterPendingDispatches();
    492             return;
    493         } else {
    494             var method = messageObject.method.split(".");
    495             var domainName = method[0];
    496             if (!(domainName in this._dispatchers)) {
    497                 console.error("Protocol Error: the message " + messageObject.method + " is for non-existing domain '" + domainName + "'");
    498                 return;
    499             }
    500 
    501             this._dispatchers[domainName].dispatch(method[1], messageObject);
    502         }
    503 
    504     },
    505 
    506     /**
    507      * @param {string} domain
    508      * @param {!Object} dispatcher
    509      */
    510     registerDispatcher: function(domain, dispatcher)
    511     {
    512         if (!this._dispatchers[domain])
    513             return;
    514 
    515         this._dispatchers[domain].setDomainDispatcher(dispatcher);
    516     },
    517 
    518     /**
    519      * @param {string=} script
    520      */
    521     runAfterPendingDispatches: function(script)
    522     {
    523         if (!this._scripts)
    524             this._scripts = [];
    525 
    526         if (script)
    527             this._scripts.push(script);
    528 
    529         if (!this._pendingResponsesCount) {
    530             var scripts = this._scripts;
    531             this._scripts = [];
    532             for (var id = 0; id < scripts.length; ++id)
    533                 scripts[id].call(this);
    534         }
    535     },
    536 
    537     _dumpProtocolMessage: function(message)
    538     {
    539         console.log(message);
    540     },
    541 
    542     /**
    543      * @protected
    544      * @param {string} reason
    545      */
    546     connectionClosed: function(reason)
    547     {
    548         this._isConnected = false;
    549         this._runPendingCallbacks();
    550         this.dispatchEventToListeners(InspectorBackendClass.Connection.Events.Disconnected, {reason: reason});
    551     },
    552 
    553     _runPendingCallbacks: function()
    554     {
    555         var keys = Object.keys(this._callbacks).map(function(num) {return parseInt(num, 10)});
    556         for (var i = 0; i < keys.length; ++i) {
    557             var callback = this._callbacks[keys[i]];
    558             this._dispatchConnectionErrorResponse(callback.domain, callback.methodName, callback)
    559         }
    560         this._callbacks = {};
    561     },
    562 
    563     /**
    564      * @param {string} domain
    565      * @param {string} methodName
    566      * @param {!function(*)} callback
    567      */
    568     _dispatchConnectionErrorResponse: function(domain, methodName, callback)
    569     {
    570         var error = { message: "Connection is closed", code:  InspectorBackendClass._DevToolsErrorCode, data: null};
    571         var messageObject = {error: error};
    572         setTimeout(InspectorBackendClass.AgentPrototype.prototype.dispatchResponse.bind(this.agent(domain), messageObject, methodName, callback), 0);
    573     },
    574 
    575     /**
    576      * @return {boolean}
    577      */
    578     isClosed: function()
    579     {
    580         return !this._isConnected;
    581     },
    582 
    583     /**
    584      * @param {!Array.<string>} domains
    585      */
    586     suppressErrorsForDomains: function(domains)
    587     {
    588         domains.forEach(function(domain) { this._agents[domain].suppressErrorLogging(); }, this);
    589     },
    590 
    591     __proto__: WebInspector.Object.prototype
    592 
    593 }
    594 
    595 /**
    596  * @constructor
    597  * @extends {InspectorBackendClass.Connection}
    598  */
    599 InspectorBackendClass.MainConnection = function()
    600 {
    601     InspectorBackendClass.Connection.call(this);
    602     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessage, this._dispatchMessage, this);
    603 }
    604 
    605 InspectorBackendClass.MainConnection.prototype = {
    606 
    607     /**
    608      * @param {!Object} messageObject
    609      */
    610     sendMessage: function(messageObject)
    611     {
    612         var message = JSON.stringify(messageObject);
    613         InspectorFrontendHost.sendMessageToBackend(message);
    614     },
    615 
    616     /**
    617      * @param {!WebInspector.Event} event
    618      */
    619     _dispatchMessage: function(event)
    620     {
    621         this.dispatch(/** @type {!Object|string} */ (event.data));
    622     },
    623 
    624     __proto__: InspectorBackendClass.Connection.prototype
    625 }
    626 
    627 /**
    628  * @constructor
    629  * @extends {InspectorBackendClass.Connection}
    630  * @param {string} url
    631  * @param {!function(!InspectorBackendClass.Connection)} onConnectionReady
    632  */
    633 InspectorBackendClass.WebSocketConnection = function(url, onConnectionReady)
    634 {
    635     InspectorBackendClass.Connection.call(this);
    636     this._socket = new WebSocket(url);
    637     this._socket.onmessage = this._onMessage.bind(this);
    638     this._socket.onerror = this._onError.bind(this);
    639     this._socket.onopen = onConnectionReady.bind(null, this);
    640     this._socket.onclose = this.connectionClosed.bind(this, "websocket_closed");
    641 }
    642 
    643 /**
    644  * @param {string} url
    645  * @param {!function(!InspectorBackendClass.Connection)} onConnectionReady
    646  */
    647 InspectorBackendClass.WebSocketConnection.Create = function(url, onConnectionReady)
    648 {
    649     new InspectorBackendClass.WebSocketConnection(url, onConnectionReady);
    650 }
    651 
    652 InspectorBackendClass.WebSocketConnection.prototype = {
    653 
    654     /**
    655      * @param {!MessageEvent} message
    656      */
    657     _onMessage: function(message)
    658     {
    659         var data = /** @type {string} */ (message.data)
    660         this.dispatch(data);
    661     },
    662 
    663     /**
    664      * @param {!Event} error
    665      */
    666     _onError: function(error)
    667     {
    668         console.error(error);
    669     },
    670 
    671     /**
    672      * @param {!Object} messageObject
    673      */
    674     sendMessage: function(messageObject)
    675     {
    676         var message = JSON.stringify(messageObject);
    677         this._socket.send(message);
    678     },
    679 
    680     __proto__: InspectorBackendClass.Connection.prototype
    681 }
    682 
    683 
    684 /**
    685  * @constructor
    686  * @extends {InspectorBackendClass.Connection}
    687  */
    688 InspectorBackendClass.StubConnection = function()
    689 {
    690     InspectorBackendClass.Connection.call(this);
    691 }
    692 
    693 InspectorBackendClass.StubConnection.prototype = {
    694 
    695     /**
    696      * @param {!Object} messageObject
    697      */
    698     sendMessage: function(messageObject)
    699     {
    700         var message = JSON.stringify(messageObject);
    701         setTimeout(this._echoResponse.bind(this, messageObject), 0);
    702     },
    703 
    704     /**
    705      * @param {!Object} messageObject
    706      */
    707     _echoResponse: function(messageObject)
    708     {
    709         this.dispatch(messageObject)
    710     },
    711 
    712     __proto__: InspectorBackendClass.Connection.prototype
    713 }
    714 
    715 /**
    716  * @constructor
    717  * @param {string} domain
    718  */
    719 InspectorBackendClass.AgentPrototype = function(domain)
    720 {
    721     this._replyArgs = {};
    722     this._hasErrorData = {};
    723     this._domain = domain;
    724     this._suppressErrorLogging = false;
    725 }
    726 
    727 InspectorBackendClass.AgentPrototype.prototype = {
    728 
    729     /**
    730      * @param {!InspectorBackendClass.Connection} connection
    731      */
    732     setConnection: function(connection)
    733     {
    734         this._connection = connection;
    735     },
    736 
    737     /**
    738      * @param {string} methodName
    739      * @param {!Array.<!Object>} signature
    740      * @param {!Array.<string>} replyArgs
    741      * @param {boolean} hasErrorData
    742      */
    743     registerCommand: function(methodName, signature, replyArgs, hasErrorData)
    744     {
    745         var domainAndMethod = this._domain + "." + methodName;
    746 
    747         /**
    748          * @this {InspectorBackendClass.AgentPrototype}
    749          */
    750         function sendMessage(vararg)
    751         {
    752             var params = [domainAndMethod, signature].concat(Array.prototype.slice.call(arguments));
    753             InspectorBackendClass.AgentPrototype.prototype._sendMessageToBackend.apply(this, params);
    754         }
    755 
    756         this[methodName] = sendMessage;
    757 
    758         /**
    759          * @this {InspectorBackendClass.AgentPrototype}
    760          */
    761         function invoke(vararg)
    762         {
    763             var params = [domainAndMethod].concat(Array.prototype.slice.call(arguments));
    764             InspectorBackendClass.AgentPrototype.prototype._invoke.apply(this, params);
    765         }
    766 
    767         this["invoke_" + methodName] = invoke;
    768 
    769         this._replyArgs[domainAndMethod] = replyArgs;
    770         if (hasErrorData)
    771             this._hasErrorData[domainAndMethod] = true;
    772 
    773     },
    774 
    775     /**
    776      * @param {string} method
    777      * @param {!Array.<!Object>} signature
    778      * @param {*} vararg
    779      * @private
    780      */
    781     _sendMessageToBackend: function(method, signature, vararg)
    782     {
    783         var args = Array.prototype.slice.call(arguments, 2);
    784         var callback = (args.length && typeof args[args.length - 1] === "function") ? args.pop() : null;
    785 
    786         var params = {};
    787         var hasParams = false;
    788         for (var i = 0; i < signature.length; ++i) {
    789             var param = signature[i];
    790             var paramName = param["name"];
    791             var typeName = param["type"];
    792             var optionalFlag = param["optional"];
    793 
    794             if (!args.length && !optionalFlag) {
    795                 console.error("Protocol Error: Invalid number of arguments for method '" + method + "' call. It must have the following arguments '" + JSON.stringify(signature) + "'.");
    796                 return;
    797             }
    798 
    799             var value = args.shift();
    800             if (optionalFlag && typeof value === "undefined") {
    801                 continue;
    802             }
    803 
    804             if (typeof value !== typeName) {
    805                 console.error("Protocol Error: Invalid type of argument '" + paramName + "' for method '" + method + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'.");
    806                 return;
    807             }
    808 
    809             params[paramName] = value;
    810             hasParams = true;
    811         }
    812 
    813         if (args.length === 1 && !callback && (typeof args[0] !== "undefined")) {
    814             console.error("Protocol Error: Optional callback argument for method '" + method + "' call must be a function but its type is '" + typeof args[0] + "'.");
    815             return;
    816         }
    817 
    818         this._connection._wrapCallbackAndSendMessageObject(this._domain, method, hasParams ? params : null, callback);
    819     },
    820 
    821     /**
    822      * @param {string} method
    823      * @param {?Object} args
    824      * @param {?function(*)} callback
    825      */
    826     _invoke: function(method, args, callback)
    827     {
    828         this._connection._wrapCallbackAndSendMessageObject(this._domain, method, args, callback);
    829     },
    830 
    831     /**
    832      * @param {!Object} messageObject
    833      * @param {string} methodName
    834      * @param {function(!Array.<*>)} callback
    835      */
    836     dispatchResponse: function(messageObject, methodName, callback)
    837     {
    838         if (messageObject.error && messageObject.error.code !== InspectorBackendClass._DevToolsErrorCode && !InspectorBackendClass.Options.suppressRequestErrors && !this._suppressErrorLogging)
    839             console.error("Request with id = " + messageObject.id + " failed. " + JSON.stringify(messageObject.error));
    840 
    841         var argumentsArray = [];
    842         argumentsArray[0] = messageObject.error ? messageObject.error.message: null;
    843 
    844         if (this._hasErrorData[methodName])
    845             argumentsArray[1] = messageObject.error ? messageObject.error.data : null;
    846 
    847         if (messageObject.result) {
    848             var paramNames = this._replyArgs[methodName] || [];
    849             for (var i = 0; i < paramNames.length; ++i)
    850                 argumentsArray.push(messageObject.result[paramNames[i]]);
    851         }
    852 
    853         callback.apply(null, argumentsArray);
    854     },
    855 
    856     suppressErrorLogging: function()
    857     {
    858         this._suppressErrorLogging = true;
    859     }
    860 }
    861 
    862 /**
    863  * @constructor
    864  */
    865 InspectorBackendClass.DispatcherPrototype = function()
    866 {
    867     this._eventArgs = {};
    868     this._dispatcher = null;
    869 }
    870 
    871 InspectorBackendClass.DispatcherPrototype.prototype = {
    872 
    873     /**
    874      * @param {string} eventName
    875      * @param {!Object} params
    876      */
    877     registerEvent: function(eventName, params)
    878     {
    879         this._eventArgs[eventName] = params
    880     },
    881 
    882     /**
    883      * @param {!Object} dispatcher
    884      */
    885     setDomainDispatcher: function(dispatcher)
    886     {
    887         this._dispatcher = dispatcher;
    888     },
    889 
    890     /**
    891      * @param {string} functionName
    892      * @param {!Object} messageObject
    893      */
    894     dispatch: function(functionName, messageObject)
    895     {
    896         if (!this._dispatcher)
    897             return;
    898 
    899         if (!(functionName in this._dispatcher)) {
    900             console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + messageObject.method + "'");
    901             return;
    902         }
    903 
    904         if (!this._eventArgs[messageObject.method]) {
    905             console.error("Protocol Error: Attempted to dispatch an unspecified method '" + messageObject.method + "'");
    906             return;
    907         }
    908 
    909         var params = [];
    910         if (messageObject.params) {
    911             var paramNames = this._eventArgs[messageObject.method];
    912             for (var i = 0; i < paramNames.length; ++i)
    913                 params.push(messageObject.params[paramNames[i]]);
    914         }
    915 
    916         var processingStartTime;
    917         if (InspectorBackendClass.Options.dumpInspectorTimeStats)
    918             processingStartTime = Date.now();
    919 
    920         this._dispatcher[functionName].apply(this._dispatcher, params);
    921 
    922         if (InspectorBackendClass.Options.dumpInspectorTimeStats)
    923             console.log("time-stats: " + messageObject.method + " = " + (Date.now() - processingStartTime));
    924     }
    925 
    926 }
    927 
    928 InspectorBackendClass.Options = {
    929     dumpInspectorTimeStats: false,
    930     dumpInspectorProtocolMessages: false,
    931     suppressRequestErrors: false
    932 }
    933 
    934 InspectorBackend = new InspectorBackendClass();
    935