Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / extensions / ExtensionAPI.js
1 /*
2  * Copyright (C) 2012 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 function defineCommonExtensionSymbols(apiPrivate)
32 {
33     if (!apiPrivate.audits)
34         apiPrivate.audits = {};
35     apiPrivate.audits.Severity = {
36         Info: "info",
37         Warning: "warning",
38         Severe: "severe"
39     };
40
41     if (!apiPrivate.console)
42         apiPrivate.console = {};
43     apiPrivate.console.Severity = {
44         Debug: "debug",
45         Log: "log",
46         Warning: "warning",
47         Error: "error"
48     };
49
50     if (!apiPrivate.panels)
51         apiPrivate.panels = {};
52     apiPrivate.panels.SearchAction = {
53         CancelSearch: "cancelSearch",
54         PerformSearch: "performSearch",
55         NextSearchResult: "nextSearchResult",
56         PreviousSearchResult: "previousSearchResult"
57     };
58
59     apiPrivate.Events = {
60         AuditStarted: "audit-started-",
61         ButtonClicked: "button-clicked-",
62         ConsoleMessageAdded: "console-message-added",
63         PanelObjectSelected: "panel-objectSelected-",
64         NetworkRequestFinished: "network-request-finished",
65         OpenResource: "open-resource",
66         PanelSearch: "panel-search-",
67         ResourceAdded: "resource-added",
68         ResourceContentCommitted: "resource-content-committed",
69         TimelineEventRecorded: "timeline-event-recorded",
70         ViewShown: "view-shown-",
71         ViewHidden: "view-hidden-"
72     };
73
74     apiPrivate.Commands = {
75         AddAuditCategory: "addAuditCategory",
76         AddAuditResult: "addAuditResult",
77         AddConsoleMessage: "addConsoleMessage",
78         AddRequestHeaders: "addRequestHeaders",
79         ApplyStyleSheet: "applyStyleSheet",
80         CreatePanel: "createPanel",
81         CreateSidebarPane: "createSidebarPane",
82         CreateStatusBarButton: "createStatusBarButton",
83         EvaluateOnInspectedPage: "evaluateOnInspectedPage",
84         ForwardKeyboardEvent: "_forwardKeyboardEvent",
85         GetConsoleMessages: "getConsoleMessages",
86         GetHAR: "getHAR",
87         GetPageResources: "getPageResources",
88         GetRequestContent: "getRequestContent",
89         GetResourceContent: "getResourceContent",
90         InspectedURLChanged: "inspectedURLChanged",
91         OpenResource: "openResource",
92         Reload: "Reload",
93         Subscribe: "subscribe",
94         SetOpenResourceHandler: "setOpenResourceHandler",
95         SetResourceContent: "setResourceContent",
96         SetSidebarContent: "setSidebarContent",
97         SetSidebarHeight: "setSidebarHeight",
98         SetSidebarPage: "setSidebarPage",
99         ShowPanel: "showPanel",
100         StopAuditCategoryRun: "stopAuditCategoryRun",
101         Unsubscribe: "unsubscribe",
102         UpdateAuditProgress: "updateAuditProgress",
103         UpdateButton: "updateButton"
104     };
105 }
106
107 /**
108  * @param {number} injectedScriptId
109  * @return {!Object}
110  */
111 function injectedExtensionAPI(injectedScriptId)
112 {
113
114 var apiPrivate = {};
115
116 defineCommonExtensionSymbols(apiPrivate);
117
118 var commands = apiPrivate.Commands;
119 var events = apiPrivate.Events;
120 var userAction = false;
121
122 // Here and below, all constructors are private to API implementation.
123 // For a public type Foo, if internal fields are present, these are on
124 // a private FooImpl type, an instance of FooImpl is used in a closure
125 // by Foo consutrctor to re-bind publicly exported members to an instance
126 // of Foo.
127
128 /**
129  * @constructor
130  */
131 function EventSinkImpl(type, customDispatch)
132 {
133     this._type = type;
134     this._listeners = [];
135     this._customDispatch = customDispatch;
136 }
137
138 EventSinkImpl.prototype = {
139     addListener: function(callback)
140     {
141         if (typeof callback !== "function")
142             throw "addListener: callback is not a function";
143         if (this._listeners.length === 0)
144             extensionServer.sendRequest({ command: commands.Subscribe, type: this._type });
145         this._listeners.push(callback);
146         extensionServer.registerHandler("notify-" + this._type, this._dispatch.bind(this));
147     },
148
149     removeListener: function(callback)
150     {
151         var listeners = this._listeners;
152
153         for (var i = 0; i < listeners.length; ++i) {
154             if (listeners[i] === callback) {
155                 listeners.splice(i, 1);
156                 break;
157             }
158         }
159         if (this._listeners.length === 0)
160             extensionServer.sendRequest({ command: commands.Unsubscribe, type: this._type });
161     },
162
163     /**
164      * @param {...} vararg
165      */
166     _fire: function(vararg)
167     {
168         var listeners = this._listeners.slice();
169         for (var i = 0; i < listeners.length; ++i)
170             listeners[i].apply(null, arguments);
171     },
172
173     _dispatch: function(request)
174     {
175          if (this._customDispatch)
176              this._customDispatch.call(this, request);
177          else
178              this._fire.apply(this, request.arguments);
179     }
180 }
181
182 /**
183  * @constructor
184  */
185 function InspectorExtensionAPI()
186 {
187     this.audits = new Audits();
188     this.inspectedWindow = new InspectedWindow();
189     this.panels = new Panels();
190     this.network = new Network();
191     defineDeprecatedProperty(this, "webInspector", "resources", "network");
192     this.timeline = new Timeline();
193     this.console = new ConsoleAPI();
194 }
195
196 /**
197  * @constructor
198  */
199 function ConsoleAPI()
200 {
201     this.onMessageAdded = new EventSink(events.ConsoleMessageAdded);
202 }
203
204 ConsoleAPI.prototype = {
205     getMessages: function(callback)
206     {
207         extensionServer.sendRequest({ command: commands.GetConsoleMessages }, callback);
208     },
209
210     addMessage: function(severity, text, url, line)
211     {
212         extensionServer.sendRequest({ command: commands.AddConsoleMessage, severity: severity, text: text, url: url, line: line });
213     },
214
215     get Severity()
216     {
217         return apiPrivate.console.Severity;
218     }
219 }
220
221 /**
222  * @constructor
223  */
224 function Network()
225 {
226     /**
227      * @this {EventSinkImpl}
228      */
229     function dispatchRequestEvent(message)
230     {
231         var request = message.arguments[1];
232         request.__proto__ = new Request(message.arguments[0]);
233         this._fire(request);
234     }
235     this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
236     defineDeprecatedProperty(this, "network", "onFinished", "onRequestFinished");
237     this.onNavigated = new EventSink(events.InspectedURLChanged);
238 }
239
240 Network.prototype = {
241     getHAR: function(callback)
242     {
243         function callbackWrapper(result)
244         {
245             var entries = (result && result.entries) || [];
246             for (var i = 0; i < entries.length; ++i) {
247                 entries[i].__proto__ = new Request(entries[i]._requestId);
248                 delete entries[i]._requestId;
249             }
250             callback(result);
251         }
252         extensionServer.sendRequest({ command: commands.GetHAR }, callback && callbackWrapper);
253     },
254
255     addRequestHeaders: function(headers)
256     {
257         extensionServer.sendRequest({ command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname });
258     }
259 }
260
261 /**
262  * @constructor
263  */
264 function RequestImpl(id)
265 {
266     this._id = id;
267 }
268
269 RequestImpl.prototype = {
270     getContent: function(callback)
271     {
272         function callbackWrapper(response)
273         {
274             callback(response.content, response.encoding);
275         }
276         extensionServer.sendRequest({ command: commands.GetRequestContent, id: this._id }, callback && callbackWrapper);
277     }
278 }
279
280 /**
281  * @constructor
282  */
283 function Panels()
284 {
285     var panels = {
286         elements: new ElementsPanel(),
287         sources: new SourcesPanel(),
288     };
289
290     function panelGetter(name)
291     {
292         return panels[name];
293     }
294     for (var panel in panels)
295         this.__defineGetter__(panel, panelGetter.bind(null, panel));
296     this.applyStyleSheet = function(styleSheet) { extensionServer.sendRequest({ command: commands.ApplyStyleSheet, styleSheet: styleSheet }); };
297 }
298
299 Panels.prototype = {
300     create: function(title, icon, page, callback)
301     {
302         var id = "extension-panel-" + extensionServer.nextObjectId();
303         var request = {
304             command: commands.CreatePanel,
305             id: id,
306             title: title,
307             icon: icon,
308             page: page
309         };
310         extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
311     },
312
313     setOpenResourceHandler: function(callback)
314     {
315         var hadHandler = extensionServer.hasHandler(events.OpenResource);
316
317         function callbackWrapper(message)
318         {
319             // Allow the panel to show itself when handling the event.
320             userAction = true;
321             try {
322                 callback.call(null, new Resource(message.resource), message.lineNumber);
323             } finally {
324                 userAction = false;
325             }
326         }
327
328         if (!callback)
329             extensionServer.unregisterHandler(events.OpenResource);
330         else
331             extensionServer.registerHandler(events.OpenResource, callbackWrapper);
332
333         // Only send command if we either removed an existing handler or added handler and had none before.
334         if (hadHandler === !callback)
335             extensionServer.sendRequest({ command: commands.SetOpenResourceHandler, "handlerPresent": !!callback });
336     },
337
338     openResource: function(url, lineNumber, callback)
339     {
340         extensionServer.sendRequest({ command: commands.OpenResource, "url": url, "lineNumber": lineNumber }, callback);
341     },
342
343     get SearchAction()
344     {
345         return apiPrivate.panels.SearchAction;
346     }
347 }
348
349 /**
350  * @constructor
351  */
352 function ExtensionViewImpl(id)
353 {
354     this._id = id;
355
356     /**
357      * @this {EventSinkImpl}
358      */
359     function dispatchShowEvent(message)
360     {
361         var frameIndex = message.arguments[0];
362         if (typeof frameIndex === "number")
363             this._fire(window.parent.frames[frameIndex]);
364         else
365             this._fire();
366     }
367
368     if (id) {
369         this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
370         this.onHidden = new EventSink(events.ViewHidden + id);
371     }
372 }
373
374 /**
375  * @constructor
376  * @extends {ExtensionViewImpl}
377  * @param {string} hostPanelName
378  */
379 function PanelWithSidebarImpl(hostPanelName)
380 {
381     ExtensionViewImpl.call(this, null);
382     this._hostPanelName = hostPanelName;
383     this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
384 }
385
386 PanelWithSidebarImpl.prototype = {
387     createSidebarPane: function(title, callback)
388     {
389         var id = "extension-sidebar-" + extensionServer.nextObjectId();
390         var request = {
391             command: commands.CreateSidebarPane,
392             panel: this._hostPanelName,
393             id: id,
394             title: title
395         };
396         function callbackWrapper()
397         {
398             callback(new ExtensionSidebarPane(id));
399         }
400         extensionServer.sendRequest(request, callback && callbackWrapper);
401     },
402
403     __proto__: ExtensionViewImpl.prototype
404 }
405
406 function declareInterfaceClass(implConstructor)
407 {
408     return function()
409     {
410         var impl = { __proto__: implConstructor.prototype };
411         implConstructor.apply(impl, arguments);
412         populateInterfaceClass(this, impl);
413     }
414 }
415
416 function defineDeprecatedProperty(object, className, oldName, newName)
417 {
418     var warningGiven = false;
419     function getter()
420     {
421         if (!warningGiven) {
422             console.warn(className + "." + oldName + " is deprecated. Use " + className + "." + newName + " instead");
423             warningGiven = true;
424         }
425         return object[newName];
426     }
427     object.__defineGetter__(oldName, getter);
428 }
429
430 function extractCallbackArgument(args)
431 {
432     var lastArgument = args[args.length - 1];
433     return typeof lastArgument === "function" ? lastArgument : undefined;
434 }
435
436 var AuditCategory = declareInterfaceClass(AuditCategoryImpl);
437 var AuditResult = declareInterfaceClass(AuditResultImpl);
438 var Button = declareInterfaceClass(ButtonImpl);
439 var EventSink = declareInterfaceClass(EventSinkImpl);
440 var ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
441 var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
442 var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl);
443 var Request = declareInterfaceClass(RequestImpl);
444 var Resource = declareInterfaceClass(ResourceImpl);
445 var Timeline = declareInterfaceClass(TimelineImpl);
446
447 /**
448  * @constructor
449  * @extends {PanelWithSidebar}
450  */
451 function ElementsPanel()
452 {
453     PanelWithSidebar.call(this, "elements");
454 }
455
456 ElementsPanel.prototype = {
457     __proto__: PanelWithSidebar.prototype
458 }
459
460 /**
461  * @constructor
462  * @extends {PanelWithSidebar}
463  */
464 function SourcesPanel()
465 {
466     PanelWithSidebar.call(this, "sources");
467 }
468
469 SourcesPanel.prototype = {
470     __proto__: PanelWithSidebar.prototype
471 }
472
473 /**
474  * @constructor
475  * @extends {ExtensionViewImpl}
476  */
477 function ExtensionPanelImpl(id)
478 {
479     ExtensionViewImpl.call(this, id);
480     this.onSearch = new EventSink(events.PanelSearch + id);
481 }
482
483 ExtensionPanelImpl.prototype = {
484     /**
485      * @return {!Object}
486      */
487     createStatusBarButton: function(iconPath, tooltipText, disabled)
488     {
489         var id = "button-" + extensionServer.nextObjectId();
490         var request = {
491             command: commands.CreateStatusBarButton,
492             panel: this._id,
493             id: id,
494             icon: iconPath,
495             tooltip: tooltipText,
496             disabled: !!disabled
497         };
498         extensionServer.sendRequest(request);
499         return new Button(id);
500     },
501
502     show: function()
503     {
504         if (!userAction)
505             return;
506
507         var request = {
508             command: commands.ShowPanel,
509             id: this._id
510         };
511         extensionServer.sendRequest(request);
512     },
513
514     __proto__: ExtensionViewImpl.prototype
515 }
516
517 /**
518  * @constructor
519  * @extends {ExtensionViewImpl}
520  */
521 function ExtensionSidebarPaneImpl(id)
522 {
523     ExtensionViewImpl.call(this, id);
524 }
525
526 ExtensionSidebarPaneImpl.prototype = {
527     setHeight: function(height)
528     {
529         extensionServer.sendRequest({ command: commands.SetSidebarHeight, id: this._id, height: height });
530     },
531
532     setExpression: function(expression, rootTitle, evaluateOptions)
533     {
534         var request = {
535             command: commands.SetSidebarContent,
536             id: this._id,
537             expression: expression,
538             rootTitle: rootTitle,
539             evaluateOnPage: true,
540         };
541         if (typeof evaluateOptions === "object")
542             request.evaluateOptions = evaluateOptions;
543         extensionServer.sendRequest(request, extractCallbackArgument(arguments));
544     },
545
546     setObject: function(jsonObject, rootTitle, callback)
547     {
548         extensionServer.sendRequest({ command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle }, callback);
549     },
550
551     setPage: function(page)
552     {
553         extensionServer.sendRequest({ command: commands.SetSidebarPage, id: this._id, page: page });
554     },
555
556     __proto__: ExtensionViewImpl.prototype
557 }
558
559 /**
560  * @constructor
561  */
562 function ButtonImpl(id)
563 {
564     this._id = id;
565     this.onClicked = new EventSink(events.ButtonClicked + id);
566 }
567
568 ButtonImpl.prototype = {
569     update: function(iconPath, tooltipText, disabled)
570     {
571         var request = {
572             command: commands.UpdateButton,
573             id: this._id,
574             icon: iconPath,
575             tooltip: tooltipText,
576             disabled: !!disabled
577         };
578         extensionServer.sendRequest(request);
579     }
580 };
581
582 /**
583  * @constructor
584  */
585 function Audits()
586 {
587 }
588
589 Audits.prototype = {
590     /**
591      * @return {!AuditCategory}
592      */
593     addCategory: function(displayName, resultCount)
594     {
595         var id = "extension-audit-category-" + extensionServer.nextObjectId();
596         if (typeof resultCount !== "undefined")
597             console.warn("Passing resultCount to audits.addCategory() is deprecated. Use AuditResult.updateProgress() instead.");
598         extensionServer.sendRequest({ command: commands.AddAuditCategory, id: id, displayName: displayName, resultCount: resultCount });
599         return new AuditCategory(id);
600     }
601 }
602
603 /**
604  * @constructor
605  */
606 function AuditCategoryImpl(id)
607 {
608     /**
609      * @this {EventSinkImpl}
610      */
611     function dispatchAuditEvent(request)
612     {
613         var auditResult = new AuditResult(request.arguments[0]);
614         try {
615             this._fire(auditResult);
616         } catch (e) {
617             console.error("Uncaught exception in extension audit event handler: " + e);
618             auditResult.done();
619         }
620     }
621     this._id = id;
622     this.onAuditStarted = new EventSink(events.AuditStarted + id, dispatchAuditEvent);
623 }
624
625 /**
626  * @constructor
627  */
628 function AuditResultImpl(id)
629 {
630     this._id = id;
631
632     this.createURL = this._nodeFactory.bind(this, "url");
633     this.createSnippet = this._nodeFactory.bind(this, "snippet");
634     this.createText = this._nodeFactory.bind(this, "text");
635     this.createObject = this._nodeFactory.bind(this, "object");
636     this.createNode = this._nodeFactory.bind(this, "node");
637 }
638
639 AuditResultImpl.prototype = {
640     addResult: function(displayName, description, severity, details)
641     {
642         // shorthand for specifying details directly in addResult().
643         if (details && !(details instanceof AuditResultNode))
644             details = new AuditResultNode(details instanceof Array ? details : [details]);
645
646         var request = {
647             command: commands.AddAuditResult,
648             resultId: this._id,
649             displayName: displayName,
650             description: description,
651             severity: severity,
652             details: details
653         };
654         extensionServer.sendRequest(request);
655     },
656
657     /**
658      * @return {!Object}
659      */
660     createResult: function()
661     {
662         return new AuditResultNode(Array.prototype.slice.call(arguments));
663     },
664
665     updateProgress: function(worked, totalWork)
666     {
667         extensionServer.sendRequest({ command: commands.UpdateAuditProgress, resultId: this._id, progress: worked / totalWork });
668     },
669
670     done: function()
671     {
672         extensionServer.sendRequest({ command: commands.StopAuditCategoryRun, resultId: this._id });
673     },
674
675     /**
676      * @type {!Object.<string, string>}
677      */
678     get Severity()
679     {
680         return apiPrivate.audits.Severity;
681     },
682
683     /**
684      * @return {!{type: string, arguments: !Array.<string|number>}}
685      */
686     createResourceLink: function(url, lineNumber)
687     {
688         return {
689             type: "resourceLink",
690             arguments: [url, lineNumber && lineNumber - 1]
691         };
692     },
693
694     /**
695      * @return {!{type: string, arguments: !Array.<string|number>}}
696      */
697     _nodeFactory: function(type)
698     {
699         return {
700             type: type,
701             arguments: Array.prototype.slice.call(arguments, 1)
702         };
703     }
704 }
705
706 /**
707  * @constructor
708  */
709 function AuditResultNode(contents)
710 {
711     this.contents = contents;
712     this.children = [];
713     this.expanded = false;
714 }
715
716 AuditResultNode.prototype = {
717     /**
718      * @return {!Object}
719      */
720     addChild: function()
721     {
722         var node = new AuditResultNode(Array.prototype.slice.call(arguments));
723         this.children.push(node);
724         return node;
725     }
726 };
727
728 /**
729  * @constructor
730  */
731 function InspectedWindow()
732 {
733     /**
734      * @this {EventSinkImpl}
735      */
736     function dispatchResourceEvent(message)
737     {
738         this._fire(new Resource(message.arguments[0]));
739     }
740
741     /**
742      * @this {EventSinkImpl}
743      */
744     function dispatchResourceContentEvent(message)
745     {
746         this._fire(new Resource(message.arguments[0]), message.arguments[1]);
747     }
748
749     this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
750     this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
751 }
752
753 InspectedWindow.prototype = {
754     reload: function(optionsOrUserAgent)
755     {
756         var options = null;
757         if (typeof optionsOrUserAgent === "object")
758             options = optionsOrUserAgent;
759         else if (typeof optionsOrUserAgent === "string") {
760             options = { userAgent: optionsOrUserAgent };
761             console.warn("Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. " +
762                          "Use inspectedWindow.reload({ userAgent: value}) instead.");
763         }
764         extensionServer.sendRequest({ command: commands.Reload, options: options });
765     },
766
767     /**
768      * @return {?Object}
769      */
770     eval: function(expression, evaluateOptions)
771     {
772         var callback = extractCallbackArgument(arguments);
773         function callbackWrapper(result)
774         {
775             if (result.isError || result.isException)
776                 callback(undefined, result);
777             else
778                 callback(result.value);
779         }
780         var request = {
781             command: commands.EvaluateOnInspectedPage,
782             expression: expression
783         };
784         if (typeof evaluateOptions === "object")
785             request.evaluateOptions = evaluateOptions;
786         extensionServer.sendRequest(request, callback && callbackWrapper);
787         return null;
788     },
789
790     getResources: function(callback)
791     {
792         function wrapResource(resourceData)
793         {
794             return new Resource(resourceData);
795         }
796         function callbackWrapper(resources)
797         {
798             callback(resources.map(wrapResource));
799         }
800         extensionServer.sendRequest({ command: commands.GetPageResources }, callback && callbackWrapper);
801     }
802 }
803
804 /**
805  * @constructor
806  */
807 function ResourceImpl(resourceData)
808 {
809     this._url = resourceData.url
810     this._type = resourceData.type;
811 }
812
813 ResourceImpl.prototype = {
814     get url()
815     {
816         return this._url;
817     },
818
819     get type()
820     {
821         return this._type;
822     },
823
824     getContent: function(callback)
825     {
826         function callbackWrapper(response)
827         {
828             callback(response.content, response.encoding);
829         }
830
831         extensionServer.sendRequest({ command: commands.GetResourceContent, url: this._url }, callback && callbackWrapper);
832     },
833
834     setContent: function(content, commit, callback)
835     {
836         extensionServer.sendRequest({ command: commands.SetResourceContent, url: this._url, content: content, commit: commit }, callback);
837     }
838 }
839
840 /**
841  * @constructor
842  */
843 function TimelineImpl()
844 {
845     this.onEventRecorded = new EventSink(events.TimelineEventRecorded);
846 }
847
848 var keyboardEventRequestQueue = [];
849 var forwardTimer = null;
850
851 function forwardKeyboardEvent(event)
852 {
853     const Esc = "U+001B";
854     // We only care about global hotkeys, not about random text
855     if (!event.ctrlKey && !event.altKey && !event.metaKey && !/^F\d+$/.test(event.keyIdentifier) && event.keyIdentifier !== Esc)
856         return;
857     var requestPayload = {
858         eventType: event.type,
859         ctrlKey: event.ctrlKey,
860         altKey: event.altKey,
861         metaKey: event.metaKey,
862         keyIdentifier: event.keyIdentifier,
863         location: event.location,
864         keyCode: event.keyCode
865     };
866     keyboardEventRequestQueue.push(requestPayload);
867     if (!forwardTimer)
868         forwardTimer = setTimeout(forwardEventQueue, 0);
869 }
870
871 function forwardEventQueue()
872 {
873     forwardTimer = null;
874     var request = {
875         command: commands.ForwardKeyboardEvent,
876         entries: keyboardEventRequestQueue
877     };
878     extensionServer.sendRequest(request);
879     keyboardEventRequestQueue = [];
880 }
881
882 document.addEventListener("keydown", forwardKeyboardEvent, false);
883 document.addEventListener("keypress", forwardKeyboardEvent, false);
884
885 /**
886  * @constructor
887  */
888 function ExtensionServerClient()
889 {
890     this._callbacks = {};
891     this._handlers = {};
892     this._lastRequestId = 0;
893     this._lastObjectId = 0;
894
895     this.registerHandler("callback", this._onCallback.bind(this));
896
897     var channel = new MessageChannel();
898     this._port = channel.port1;
899     this._port.addEventListener("message", this._onMessage.bind(this), false);
900     this._port.start();
901
902     window.parent.postMessage("registerExtension", [ channel.port2 ], "*");
903 }
904
905 ExtensionServerClient.prototype = {
906     /**
907      * @param {function()=} callback
908      */
909     sendRequest: function(message, callback)
910     {
911         if (typeof callback === "function")
912             message.requestId = this._registerCallback(callback);
913         this._port.postMessage(message);
914     },
915
916     /**
917      * @return {boolean}
918      */
919     hasHandler: function(command)
920     {
921         return !!this._handlers[command];
922     },
923
924     registerHandler: function(command, handler)
925     {
926         this._handlers[command] = handler;
927     },
928
929     unregisterHandler: function(command)
930     {
931         delete this._handlers[command];
932     },
933
934     /**
935      * @return {string}
936      */
937     nextObjectId: function()
938     {
939         return injectedScriptId + "_" + ++this._lastObjectId;
940     },
941
942     _registerCallback: function(callback)
943     {
944         var id = ++this._lastRequestId;
945         this._callbacks[id] = callback;
946         return id;
947     },
948
949     _onCallback: function(request)
950     {
951         if (request.requestId in this._callbacks) {
952             var callback = this._callbacks[request.requestId];
953             delete this._callbacks[request.requestId];
954             callback(request.result);
955         }
956     },
957
958     _onMessage: function(event)
959     {
960         var request = event.data;
961         var handler = this._handlers[request.command];
962         if (handler)
963             handler.call(this, request);
964     }
965 }
966
967 function populateInterfaceClass(interface, implementation)
968 {
969     for (var member in implementation) {
970         if (member.charAt(0) === "_")
971             continue;
972         var descriptor = null;
973         // Traverse prototype chain until we find the owner.
974         for (var owner = implementation; owner && !descriptor; owner = owner.__proto__)
975             descriptor = Object.getOwnPropertyDescriptor(owner, member);
976         if (!descriptor)
977             continue;
978         if (typeof descriptor.value === "function")
979             interface[member] = descriptor.value.bind(implementation);
980         else if (typeof descriptor.get === "function")
981             interface.__defineGetter__(member, descriptor.get.bind(implementation));
982         else
983             Object.defineProperty(interface, member, descriptor);
984     }
985 }
986
987 // extensionServer is a closure variable defined by the glue below -- make sure we fail if it's not there.
988 if (!extensionServer)
989     extensionServer = new ExtensionServerClient();
990
991 return new InspectorExtensionAPI();
992 }
993
994 /**
995  * @suppress {checkVars, checkTypes}
996  */
997 function platformExtensionAPI(coreAPI)
998 {
999     function getTabId()
1000     {
1001         return tabId;
1002     }
1003     chrome = window.chrome || {};
1004     // Override chrome.devtools as a workaround for a error-throwing getter being exposed
1005     // in extension pages loaded into a non-extension process (only happens for remote client
1006     // extensions)
1007     var devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, "devtools");
1008     if (!devtools_descriptor || devtools_descriptor.get)
1009         Object.defineProperty(chrome, "devtools", { value: {}, enumerable: true });
1010     // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
1011     chrome.devtools.inspectedWindow = {};
1012     chrome.devtools.inspectedWindow.__defineGetter__("tabId", getTabId);
1013     chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
1014     chrome.devtools.network = coreAPI.network;
1015     chrome.devtools.panels = coreAPI.panels;
1016
1017     // default to expose experimental APIs for now.
1018     if (extensionInfo.exposeExperimentalAPIs !== false) {
1019         chrome.experimental = chrome.experimental || {};
1020         chrome.experimental.devtools = chrome.experimental.devtools || {};
1021
1022         var properties = Object.getOwnPropertyNames(coreAPI);
1023         for (var i = 0; i < properties.length; ++i) {
1024             var descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
1025             Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
1026         }
1027         chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
1028     }
1029     if (extensionInfo.exposeWebInspectorNamespace)
1030         window.webInspector = coreAPI;
1031 }
1032
1033 /**
1034  * @param {!ExtensionDescriptor} extensionInfo
1035  * @return {string}
1036  */
1037 function buildPlatformExtensionAPI(extensionInfo)
1038 {
1039     return "var extensionInfo = " + JSON.stringify(extensionInfo) + ";" +
1040        "var tabId = " + WebInspector._inspectedTabId + ";" +
1041        platformExtensionAPI.toString();
1042 }
1043
1044 /**
1045  * @param {!ExtensionDescriptor} extensionInfo
1046  * @return {string}
1047  */
1048 function buildExtensionAPIInjectedScript(extensionInfo)
1049 {
1050     return "(function(injectedScriptId){ " +
1051         "var extensionServer;" +
1052         defineCommonExtensionSymbols.toString() + ";" +
1053         injectedExtensionAPI.toString() + ";" +
1054         buildPlatformExtensionAPI(extensionInfo) + ";" +
1055         "platformExtensionAPI(injectedExtensionAPI(injectedScriptId));" +
1056         "return {};" +
1057         "})";
1058 }