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