OSDN Git Service

Merge WebKit at r73109: Initial merge by git.
[android-x86/external-webkit.git] / WebCore / inspector / front-end / ExtensionAPI.js
1 /*
2  * Copyright (C) 2010 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 WebInspector.injectedExtensionAPI = function(InjectedScriptHost, inspectedWindow, injectedScriptId)
32 {
33
34 // Here and below, all constructors are private to API implementation.
35 // For a public type Foo, if internal fields are present, these are on
36 // a private FooImpl type, an instance of FooImpl is used in a closure
37 // by Foo consutrctor to re-bind publicly exported members to an instance
38 // of Foo.
39
40 function EventSinkImpl(type, customDispatch)
41 {
42     this._type = type;
43     this._listeners = [];
44     this._customDispatch = customDispatch;
45 }
46
47 EventSinkImpl.prototype = {
48     addListener: function(callback)
49     {
50         if (typeof callback != "function")
51             throw new "addListener: callback is not a function";
52         if (this._listeners.length === 0)
53             extensionServer.sendRequest({ command: "subscribe", type: this._type });
54         this._listeners.push(callback);
55         extensionServer.registerHandler("notify-" + this._type, bind(this._dispatch, this));
56     },
57
58     removeListener: function(callback)
59     {
60         var listeners = this._listeners;
61
62         for (var i = 0; i < listeners.length; ++i) {
63             if (listeners[i] === callback) {
64                 listeners.splice(i, 1);
65                 break;
66             }
67         }
68         if (this._listeners.length === 0)
69             extensionServer.sendRequest({ command: "unsubscribe", type: this._type });
70     },
71
72     _fire: function()
73     {
74         var listeners = this._listeners.slice();
75         for (var i = 0; i < listeners.length; ++i)
76             listeners[i].apply(null, arguments);
77     },
78
79     _dispatch: function(request)
80     {
81          if (this._customDispatch)
82              this._customDispatch.call(this, request);
83          else
84              this._fire.apply(this, request.arguments);
85     }
86 }
87
88 function InspectorExtensionAPI()
89 {
90     this.audits = new Audits();
91     this.inspectedWindow = new InspectedWindow();
92     this.panels = new Panels();
93     this.resources = new Resources();
94
95     this.onReset = new EventSink("reset");
96 }
97
98 InspectorExtensionAPI.prototype = {
99     log: function(message)
100     {
101         extensionServer.sendRequest({ command: "log", message: message });
102     }
103 }
104
105 function Resources()
106 {
107     function resourceDispatch(request)
108     {
109         var resource = request.arguments[1];
110         resource.__proto__ = new Resource(request.arguments[0]);
111         this._fire(resource);
112     }
113     this.onFinished = new EventSink("resource-finished", resourceDispatch);
114 }
115
116 Resources.prototype = {
117     getHAR: function(callback)
118     {
119         function callbackWrapper(result)
120         {
121             var entries = (result && result.entries) || [];
122             for (var i = 0; i < entries.length; ++i) {
123                 entries[i].__proto__ = new Resource(entries[i]._resourceId);
124                 delete entries[i]._resourceId;
125             }
126             callback(result);
127         }
128         return extensionServer.sendRequest({ command: "getHAR" }, callback && callbackWrapper);
129     }
130 }
131
132 function ResourceImpl(id)
133 {
134     this._id = id;
135 }
136
137 ResourceImpl.prototype = {
138     getContent: function(callback)
139     {
140         function callbackWrapper(response)
141         {
142             callback(response.content, response.encoding);
143         }
144         extensionServer.sendRequest({ command: "getResourceContent", id: this._id }, callback && callbackWrapper);
145     }
146 };
147
148 function Panels()
149 {
150     var panels = {
151         elements: new ElementsPanel()
152     };
153
154     function panelGetter(name)
155     {
156         return panels[name];
157     }
158     for (var panel in panels)
159         this.__defineGetter__(panel, bind(panelGetter, null, panel));
160 }
161
162 Panels.prototype = {
163     create: function(title, iconURL, pageURL, callback)
164     {
165         var id = "extension-panel-" + extensionServer.nextObjectId();
166         var request = {
167             command: "createPanel",
168             id: id,
169             title: title,
170             icon: expandURL(iconURL),
171             url: expandURL(pageURL)
172         };
173         extensionServer.sendRequest(request, callback && bind(callback, this, new ExtensionPanel(id)));
174     }
175 }
176
177 function PanelImpl(id)
178 {
179     this._id = id;
180 }
181
182 function PanelWithSidebarImpl(id)
183 {
184     PanelImpl.call(this, id);
185 }
186
187 PanelWithSidebarImpl.prototype = {
188     createSidebarPane: function(title, url, callback)
189     {
190         var id = "extension-sidebar-" + extensionServer.nextObjectId();
191         var request = {
192             command: "createSidebarPane",
193             panel: this._id,
194             id: id,
195             title: title,
196             url: expandURL(url)
197         };
198         function callbackWrapper()
199         {
200             callback(new ExtensionSidebarPane(id));
201         }
202         extensionServer.sendRequest(request, callback && callbackWrapper);
203     },
204
205     createWatchExpressionSidebarPane: function(title, callback)
206     {
207         var id = "watch-sidebar-" + extensionServer.nextObjectId();
208         var request = {
209             command: "createWatchExpressionSidebarPane",
210             panel: this._id,
211             id: id,
212             title: title
213         };
214         function callbackWrapper()
215         {
216             callback(new WatchExpressionSidebarPane(id));
217         }
218         extensionServer.sendRequest(request, callback && callbackWrapper);
219     }
220 }
221
222 PanelWithSidebarImpl.prototype.__proto__ = PanelImpl.prototype;
223
224 function ElementsPanel()
225 {
226     var id = "elements";
227     PanelWithSidebar.call(this, id);
228     this.onSelectionChanged = new EventSink("panel-objectSelected-" + id);
229 }
230
231 function ExtensionPanel(id)
232 {
233     Panel.call(this, id);
234     this.onSearch = new EventSink("panel-search-" + id);
235 }
236
237 function ExtensionSidebarPaneImpl(id)
238 {
239     this._id = id;
240 }
241
242 ExtensionSidebarPaneImpl.prototype = {
243     setHeight: function(height)
244     {
245         extensionServer.sendRequest({ command: "setSidebarHeight", id: this._id, height: height });
246     }
247 }
248
249 function WatchExpressionSidebarPaneImpl(id)
250 {
251     ExtensionSidebarPaneImpl.call(this, id);
252     this.onUpdated = new EventSink("watch-sidebar-updated-" + id);
253 }
254
255 WatchExpressionSidebarPaneImpl.prototype = {
256     setExpression: function(expression, rootTitle)
257     {
258         extensionServer.sendRequest({ command: "setWatchSidebarContent", id: this._id, expression: expression, rootTitle: rootTitle, evaluateOnPage: true });
259     },
260
261     setObject: function(jsonObject, rootTitle)
262     {
263         extensionServer.sendRequest({ command: "setWatchSidebarContent", id: this._id, expression: jsonObject, rootTitle: rootTitle });
264     }
265 }
266
267 WatchExpressionSidebarPaneImpl.prototype.__proto__ = ExtensionSidebarPaneImpl.prototype;
268
269 function WatchExpressionSidebarPane(id)
270 {
271     var impl = new WatchExpressionSidebarPaneImpl(id);
272     ExtensionSidebarPane.call(this, id, impl);
273 }
274
275 function Audits()
276 {
277 }
278
279 Audits.prototype = {
280     addCategory: function(displayName, resultCount)
281     {
282         var id = "extension-audit-category-" + extensionServer.nextObjectId();
283         extensionServer.sendRequest({ command: "addAuditCategory", id: id, displayName: displayName, resultCount: resultCount });
284         return new AuditCategory(id);
285     }
286 }
287
288 function AuditCategoryImpl(id)
289 {
290     function auditResultDispatch(request)
291     {
292         var auditResult = new AuditResult(request.arguments[0]);
293         try {
294             this._fire(auditResult);
295         } catch (e) {
296             console.error("Uncaught exception in extension audit event handler: " + e);
297             auditResult.done();
298         }
299     }
300     this._id = id;
301     this.onAuditStarted = new EventSink("audit-started-" + id, auditResultDispatch);
302 }
303
304 function AuditResultImpl(id)
305 {
306     this._id = id;
307
308     var formatterTypes = [
309         "url",
310         "snippet",
311         "text"
312     ];
313     for (var i = 0; i < formatterTypes.length; ++i)
314         this[formatterTypes[i]] = bind(this._nodeFactory, null, formatterTypes[i]);
315 }
316
317 AuditResultImpl.prototype = {
318     addResult: function(displayName, description, severity, details)
319     {
320         // shorthand for specifying details directly in addResult().
321         if (details && !(details instanceof AuditResultNode))
322             details = details instanceof Array ? this.createNode.apply(this, details) : this.createNode(details);
323
324         var request = {
325             command: "addAuditResult",
326             resultId: this._id,
327             displayName: displayName,
328             description: description,
329             severity: severity,
330             details: details
331         };
332         extensionServer.sendRequest(request);
333     },
334
335     createResult: function()
336     {
337         var node = new AuditResultNode();
338         node.contents = Array.prototype.slice.call(arguments);
339         return node;
340     },
341
342     done: function()
343     {
344         extensionServer.sendRequest({ command: "stopAuditCategoryRun", resultId: this._id });
345     },
346
347     get Severity()
348     {
349         return apiPrivate.audits.Severity;
350     },
351
352     _nodeFactory: function(type)
353     {
354         return {
355             type: type,
356             arguments: Array.prototype.slice.call(arguments, 1)
357         };
358     }
359 }
360
361 function AuditResultNode(contents)
362 {
363     this.contents = contents;
364     this.children = [];
365     this.expanded = false;
366 }
367
368 AuditResultNode.prototype = {
369     addChild: function()
370     {
371         var node = AuditResultImpl.prototype.createResult.apply(null, arguments);
372         this.children.push(node);
373         return node;
374     }
375 };
376
377 function InspectedWindow()
378 {
379     this.onDOMContentLoaded = new EventSink("inspectedPageDOMContentLoaded");
380     this.onLoaded = new EventSink("inspectedPageLoaded");
381     this.onNavigated = new EventSink("inspectedURLChanged");
382 }
383
384 InspectedWindow.prototype = {
385     reload: function()
386     {
387         return extensionServer.sendRequest({ command: "reload" });
388     },
389
390     eval: function(expression, callback)
391     {
392         function callbackWrapper(result)
393         {
394             var value = result.value;
395             if (!result.isException)
396                 value = value === "undefined" ? undefined : JSON.parse(value);
397             callback(value, result.isException);
398         }
399         return extensionServer.sendRequest({ command: "evaluateOnInspectedPage", expression: expression }, callback && callbackWrapper);
400     }
401 }
402
403 function ExtensionServerClient()
404 {
405     this._callbacks = {};
406     this._handlers = {};
407     this._lastRequestId = 0;
408     this._lastObjectId = 0;
409
410     this.registerHandler("callback", bind(this._onCallback, this));
411
412     var channel = new MessageChannel();
413     this._port = channel.port1;
414     this._port.addEventListener("message", bind(this._onMessage, this), false);
415     this._port.start();
416
417     top.postMessage("registerExtension", [ channel.port2 ], "*");
418 }
419
420 ExtensionServerClient.prototype = {
421     sendRequest: function(message, callback)
422     {
423         if (typeof callback === "function")
424             message.requestId = this._registerCallback(callback);
425         return this._port.postMessage(message);
426     },
427
428     registerHandler: function(command, handler)
429     {
430         this._handlers[command] = handler;
431     },
432
433     nextObjectId: function()
434     {
435         return injectedScriptId + "_" + ++this._lastObjectId;
436     },
437
438     _registerCallback: function(callback)
439     {
440         var id = ++this._lastRequestId;
441         this._callbacks[id] = callback;
442         return id;
443     },
444
445     _onCallback: function(request)
446     {
447         if (request.requestId in this._callbacks) {
448             this._callbacks[request.requestId](request.result);
449             delete this._callbacks[request.requestId];
450         }
451     },
452
453     _onMessage: function(event)
454     {
455         var request = event.data;
456         var handler = this._handlers[request.command];
457         if (handler)
458             handler.call(this, request);
459     }
460 }
461
462 function expandURL(url)
463 {
464     if (!url)
465         return url;
466     if (/^[^/]+:/.exec(url)) // See if url has schema.
467         return url;
468     var baseURL = location.protocol + "//" + location.hostname + location.port;
469     if (/^\//.exec(url))
470         return baseURL + url;
471     return baseURL + location.pathname.replace(/\/[^/]*$/,"/") + url;
472 }
473
474 function bind(func, thisObject)
475 {
476     var args = Array.prototype.slice.call(arguments, 2);
477     return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); };
478 }
479
480 function populateInterfaceClass(interface, implementation)
481 {
482     for (var member in implementation) {
483         if (member.charAt(0) === "_")
484             continue;
485         var value = implementation[member];
486         interface[member] = typeof value === "function" ? bind(value, implementation)
487             : interface[member] = implementation[member];
488     }
489 }
490
491 function declareInterfaceClass(implConstructor)
492 {
493     return function()
494     {
495         var impl = { __proto__: implConstructor.prototype };
496         implConstructor.apply(impl, arguments);
497         populateInterfaceClass(this, impl);
498     }
499 }
500
501 var AuditCategory = declareInterfaceClass(AuditCategoryImpl);
502 var AuditResult = declareInterfaceClass(AuditResultImpl);
503 var EventSink = declareInterfaceClass(EventSinkImpl);
504 var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
505 var Panel = declareInterfaceClass(PanelImpl);
506 var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl);
507 var Resource = declareInterfaceClass(ResourceImpl);
508 var WatchExpressionSidebarPane = declareInterfaceClass(WatchExpressionSidebarPaneImpl);
509
510 var extensionServer = new ExtensionServerClient();
511
512 webInspector = new InspectorExtensionAPI();
513
514 }