Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / InspectorBackend.js
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._lastCallbackId = 1;
37     this._pendingResponsesCount = 0;
38     this._callbacks = {};
39     this._domainDispatchers = {};
40     this._eventArgs = {};
41     this._replyArgs = {};
42     this._hasErrorData = {};
43
44     this.dumpInspectorTimeStats = false;
45     this.dumpInspectorProtocolMessages = false;
46     this._initialized = false;
47 }
48
49 InspectorBackendClass.prototype = {
50     /**
51      * @return {number}
52      */
53     nextCallbackId: function()
54     {
55         return this._lastCallbackId++;
56     },
57
58     _wrap: function(callback, method)
59     {
60         var callbackId = this.nextCallbackId();
61         if (!callback)
62             callback = function() {};
63
64         this._callbacks[callbackId] = callback;
65         callback.methodName = method;
66         if (this.dumpInspectorTimeStats)
67             callback.sendRequestTime = Date.now();
68
69         return callbackId;
70     },
71
72     _getAgent: function(domain)
73     {
74         var agentName = domain + "Agent";
75         if (!window[agentName])
76             window[agentName] = {};
77         return window[agentName];
78     },
79
80     registerCommand: function(method, signature, replyArgs, hasErrorData)
81     {
82         var domainAndMethod = method.split(".");
83         var agent = this._getAgent(domainAndMethod[0]);
84
85         agent[domainAndMethod[1]] = this._sendMessageToBackend.bind(this, method, signature);
86         agent[domainAndMethod[1]]["invoke"] = this._invoke.bind(this, method, signature);
87         this._replyArgs[method] = replyArgs;
88         if (hasErrorData)
89             this._hasErrorData[method] = true;
90
91         this._initialized = true;
92     },
93
94     registerEnum: function(type, values)
95     {
96         var domainAndMethod = type.split(".");
97         var agent = this._getAgent(domainAndMethod[0]);
98
99         agent[domainAndMethod[1]] = values;
100
101         this._initialized = true;
102     },
103
104     registerEvent: function(eventName, params)
105     {
106         this._eventArgs[eventName] = params;
107
108         this._initialized = true;
109     },
110
111     _invoke: function(method, signature, args, callback)
112     {
113         this._wrapCallbackAndSendMessageObject(method, args, callback);
114     },
115
116     _sendMessageToBackend: function(method, signature, vararg)
117     {
118         var args = Array.prototype.slice.call(arguments, 2);
119         var callback = (args.length && typeof args[args.length - 1] === "function") ? args.pop() : null;
120
121         var params = {};
122         var hasParams = false;
123         for (var i = 0; i < signature.length; ++i) {
124             var param = signature[i];
125             var paramName = param["name"];
126             var typeName = param["type"];
127             var optionalFlag = param["optional"];
128
129             if (!args.length && !optionalFlag) {
130                 console.error("Protocol Error: Invalid number of arguments for method '" + method + "' call. It must have the following arguments '" + JSON.stringify(signature) + "'.");
131                 return;
132             }
133
134             var value = args.shift();
135             if (optionalFlag && typeof value === "undefined") {
136                 continue;
137             }
138
139             if (typeof value !== typeName) {
140                 console.error("Protocol Error: Invalid type of argument '" + paramName + "' for method '" + method + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'.");
141                 return;
142             }
143
144             params[paramName] = value;
145             hasParams = true;
146         }
147
148         if (args.length === 1 && !callback) {
149             if (typeof args[0] !== "undefined") {
150                 console.error("Protocol Error: Optional callback argument for method '" + method + "' call must be a function but its type is '" + typeof args[0] + "'.");
151                 return;
152             }
153         }
154
155         this._wrapCallbackAndSendMessageObject(method, hasParams ? params : null, callback);
156     },
157
158     _wrapCallbackAndSendMessageObject: function(method, params, callback)
159     {
160         var messageObject = {};
161         messageObject.method = method;
162         if (params)
163             messageObject.params = params;
164         messageObject.id = this._wrap(callback, method);
165
166         if (this.dumpInspectorProtocolMessages)
167             console.log("frontend: " + JSON.stringify(messageObject));
168
169         ++this._pendingResponsesCount;
170         this.sendMessageObjectToBackend(messageObject);
171     },
172
173     sendMessageObjectToBackend: function(messageObject)
174     {
175         var message = JSON.stringify(messageObject);
176         InspectorFrontendHost.sendMessageToBackend(message);
177     },
178
179     registerDomainDispatcher: function(domain, dispatcher)
180     {
181         this._domainDispatchers[domain] = dispatcher;
182     },
183
184     dispatch: function(message)
185     {
186         if (this.dumpInspectorProtocolMessages)
187             console.log("backend: " + ((typeof message === "string") ? message : JSON.stringify(message)));
188
189         var messageObject = (typeof message === "string") ? JSON.parse(message) : message;
190
191         if ("id" in messageObject) { // just a response for some request
192             if (messageObject.error) {
193                 if (messageObject.error.code !== -32000)
194                     this.reportProtocolError(messageObject);
195             }
196
197             var callback = this._callbacks[messageObject.id];
198             if (callback) {
199                 var argumentsArray = [ null ];
200                 if (messageObject.error) {
201                     argumentsArray[0] = messageObject.error.message;
202                 }
203                 if (this._hasErrorData[callback.methodName]) {
204                     argumentsArray.push(null);
205                     if (messageObject.error)
206                         argumentsArray[1] = messageObject.error.data;
207                 }
208                 if (messageObject.result) {
209                     var paramNames = this._replyArgs[callback.methodName];
210                     if (paramNames) {
211                         for (var i = 0; i < paramNames.length; ++i)
212                             argumentsArray.push(messageObject.result[paramNames[i]]);
213                     }
214                 }
215
216                 var processingStartTime;
217                 if (this.dumpInspectorTimeStats && callback.methodName)
218                     processingStartTime = Date.now();
219
220                 callback.apply(null, argumentsArray);
221                 --this._pendingResponsesCount;
222                 delete this._callbacks[messageObject.id];
223
224                 if (this.dumpInspectorTimeStats && callback.methodName)
225                     console.log("time-stats: " + callback.methodName + " = " + (processingStartTime - callback.sendRequestTime) + " + " + (Date.now() - processingStartTime));
226             }
227
228             if (this._scripts && !this._pendingResponsesCount)
229                 this.runAfterPendingDispatches();
230
231             return;
232         } else {
233             var method = messageObject.method.split(".");
234             var domainName = method[0];
235             var functionName = method[1];
236             if (!(domainName in this._domainDispatchers)) {
237                 console.error("Protocol Error: the message " + messageObject.method + " is for non-existing domain '" + domainName + "'");
238                 return;
239             }
240             var dispatcher = this._domainDispatchers[domainName];
241             if (!(functionName in dispatcher)) {
242                 console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + messageObject.method + "'");
243                 return;
244             }
245
246             if (!this._eventArgs[messageObject.method]) {
247                 console.error("Protocol Error: Attempted to dispatch an unspecified method '" + messageObject.method + "'");
248                 return;
249             }
250
251             var params = [];
252             if (messageObject.params) {
253                 var paramNames = this._eventArgs[messageObject.method];
254                 for (var i = 0; i < paramNames.length; ++i)
255                     params.push(messageObject.params[paramNames[i]]);
256             }
257
258             var processingStartTime;
259             if (this.dumpInspectorTimeStats)
260                 processingStartTime = Date.now();
261
262             dispatcher[functionName].apply(dispatcher, params);
263
264             if (this.dumpInspectorTimeStats)
265                 console.log("time-stats: " + messageObject.method + " = " + (Date.now() - processingStartTime));
266         }
267     },
268
269     reportProtocolError: function(messageObject)
270     {
271         console.error("Request with id = " + messageObject.id + " failed. " + JSON.stringify(messageObject.error));
272     },
273
274     /**
275      * @param {string=} script
276      */
277     runAfterPendingDispatches: function(script)
278     {
279         if (!this._scripts)
280             this._scripts = [];
281
282         if (script)
283             this._scripts.push(script);
284
285         if (!this._pendingResponsesCount) {
286             var scripts = this._scripts;
287             this._scripts = []
288             for (var id = 0; id < scripts.length; ++id)
289                  scripts[id].call(this);
290         }
291     },
292
293     loadFromJSONIfNeeded: function(jsonUrl)
294     {
295         if (this._initialized)
296             return;
297
298         var xhr = new XMLHttpRequest();
299         xhr.open("GET", jsonUrl, false);
300         xhr.send(null);
301
302         var schema = JSON.parse(xhr.responseText);
303         var code = InspectorBackendClass._generateCommands(schema);
304         eval(code);
305     },
306
307     /**
308      * @param {function(T)} clientCallback
309      * @param {string} errorPrefix
310      * @param {function(new:T,S)=} constructor
311      * @param {T=} defaultValue
312      * @return {function(?string, S)}
313      * @template T,S
314      */
315     wrapClientCallback: function(clientCallback, errorPrefix, constructor, defaultValue)
316     {
317         /**
318          * @param {?string} error
319          * @param {S} value
320          * @template S
321          */
322         function callbackWrapper(error, value)
323         {
324             if (error) {
325                 console.error(errorPrefix + error);
326                 clientCallback(defaultValue);
327                 return;
328             }
329             if (constructor)
330                 clientCallback(new constructor(value));
331             else
332                 clientCallback(value);
333         }
334         return callbackWrapper;
335     }
336 }
337
338 /**
339  * @param {*} schema
340  * @return {string}
341  */
342 InspectorBackendClass._generateCommands = function(schema) {
343     var jsTypes = { integer: "number", array: "object" };
344     var rawTypes = {};
345     var result = [];
346
347     var domains = schema["domains"] || [];
348     for (var i = 0; i < domains.length; ++i) {
349         var domain = domains[i];
350         for (var j = 0; domain.types && j < domain.types.length; ++j) {
351             var type = domain.types[j];
352             rawTypes[domain.domain + "." + type.id] = jsTypes[type.type] || type.type;
353         }
354     }
355
356     function toUpperCase(groupIndex, group0, group1)
357     {
358         return [group0, group1][groupIndex].toUpperCase();
359     }
360     function generateEnum(enumName, items)
361     {
362         var members = []
363         for (var m = 0; m < items.length; ++m) {
364             var value = items[m];
365             var name = value.replace(/-(\w)/g, toUpperCase.bind(null, 1)).toTitleCase();
366             name = name.replace(/HTML|XML|WML|API/ig, toUpperCase.bind(null, 0));
367             members.push(name + ": \"" + value +"\"");
368         }
369         return "InspectorBackend.registerEnum(\"" + enumName + "\", {" + members.join(", ") + "});";
370     }
371
372     for (var i = 0; i < domains.length; ++i) {
373         var domain = domains[i];
374
375         var types = domain["types"] || [];
376         for (var j = 0; j < types.length; ++j) {
377             var type = types[j];
378             if ((type["type"] === "string") && type["enum"])
379                 result.push(generateEnum(domain.domain + "." + type.id, type["enum"]));
380             else if (type["type"] === "object") {
381                 var properties = type["properties"] || [];
382                 for (var k = 0; k < properties.length; ++k) {
383                     var property = properties[k];
384                     if ((property["type"] === "string") && property["enum"])
385                         result.push(generateEnum(domain.domain + "." + type.id + property["name"].toTitleCase(), property["enum"]));
386                 }
387             }
388         }
389
390         var commands = domain["commands"] || [];
391         for (var j = 0; j < commands.length; ++j) {
392             var command = commands[j];
393             var parameters = command["parameters"];
394             var paramsText = [];
395             for (var k = 0; parameters && k < parameters.length; ++k) {
396                 var parameter = parameters[k];
397
398                 var type;
399                 if (parameter.type)
400                     type = jsTypes[parameter.type] || parameter.type;
401                 else {
402                     var ref = parameter["$ref"];
403                     if (ref.indexOf(".") !== -1)
404                         type = rawTypes[ref];
405                     else
406                         type = rawTypes[domain.domain + "." + ref];
407                 }
408
409                 var text = "{\"name\": \"" + parameter.name + "\", \"type\": \"" + type + "\", \"optional\": " + (parameter.optional ? "true" : "false") + "}";
410                 paramsText.push(text);
411             }
412
413             var returnsText = [];
414             var returns = command["returns"] || [];
415             for (var k = 0; k < returns.length; ++k) {
416                 var parameter = returns[k];
417                 returnsText.push("\"" + parameter.name + "\"");
418             }
419             var hasErrorData = String(Boolean(command.error));
420             result.push("InspectorBackend.registerCommand(\"" + domain.domain + "." + command.name + "\", [" + paramsText.join(", ") + "], [" + returnsText.join(", ") + "], " + hasErrorData + ");");
421         }
422
423         for (var j = 0; domain.events && j < domain.events.length; ++j) {
424             var event = domain.events[j];
425             var paramsText = [];
426             for (var k = 0; event.parameters && k < event.parameters.length; ++k) {
427                 var parameter = event.parameters[k];
428                 paramsText.push("\"" + parameter.name + "\"");
429             }
430             result.push("InspectorBackend.registerEvent(\"" + domain.domain + "." + event.name + "\", [" + paramsText.join(", ") + "]);");
431         }
432
433         result.push("InspectorBackend.register" + domain.domain + "Dispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, \"" + domain.domain + "\");");
434     }
435     return result.join("\n");
436 }
437
438 InspectorBackend = new InspectorBackendClass();