Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / extensions / ExtensionServer.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  * @implements {WebInspector.ExtensionServerAPI}
34  */
35 WebInspector.ExtensionServer = function()
36 {
37     this._clientObjects = {};
38     this._handlers = {};
39     this._subscribers = {};
40     this._subscriptionStartHandlers = {};
41     this._subscriptionStopHandlers = {};
42     this._extraHeaders = {};
43     this._requests = {};
44     this._lastRequestId = 0;
45     this._registeredExtensions = {};
46     this._status = new WebInspector.ExtensionStatus();
47
48     var commands = WebInspector.extensionAPI.Commands;
49
50     this._registerHandler(commands.AddAuditCategory, this._onAddAuditCategory.bind(this));
51     this._registerHandler(commands.AddAuditResult, this._onAddAuditResult.bind(this));
52     this._registerHandler(commands.AddConsoleMessage, this._onAddConsoleMessage.bind(this));
53     this._registerHandler(commands.AddRequestHeaders, this._onAddRequestHeaders.bind(this));
54     this._registerHandler(commands.ApplyStyleSheet, this._onApplyStyleSheet.bind(this));
55     this._registerHandler(commands.CreatePanel, this._onCreatePanel.bind(this));
56     this._registerHandler(commands.CreateSidebarPane, this._onCreateSidebarPane.bind(this));
57     this._registerHandler(commands.CreateStatusBarButton, this._onCreateStatusBarButton.bind(this));
58     this._registerHandler(commands.EvaluateOnInspectedPage, this._onEvaluateOnInspectedPage.bind(this));
59     this._registerHandler(commands.ForwardKeyboardEvent, this._onForwardKeyboardEvent.bind(this));
60     this._registerHandler(commands.GetHAR, this._onGetHAR.bind(this));
61     this._registerHandler(commands.GetConsoleMessages, this._onGetConsoleMessages.bind(this));
62     this._registerHandler(commands.GetPageResources, this._onGetPageResources.bind(this));
63     this._registerHandler(commands.GetRequestContent, this._onGetRequestContent.bind(this));
64     this._registerHandler(commands.GetResourceContent, this._onGetResourceContent.bind(this));
65     this._registerHandler(commands.Reload, this._onReload.bind(this));
66     this._registerHandler(commands.SetOpenResourceHandler, this._onSetOpenResourceHandler.bind(this));
67     this._registerHandler(commands.SetResourceContent, this._onSetResourceContent.bind(this));
68     this._registerHandler(commands.SetSidebarHeight, this._onSetSidebarHeight.bind(this));
69     this._registerHandler(commands.SetSidebarContent, this._onSetSidebarContent.bind(this));
70     this._registerHandler(commands.SetSidebarPage, this._onSetSidebarPage.bind(this));
71     this._registerHandler(commands.ShowPanel, this._onShowPanel.bind(this));
72     this._registerHandler(commands.StopAuditCategoryRun, this._onStopAuditCategoryRun.bind(this));
73     this._registerHandler(commands.Subscribe, this._onSubscribe.bind(this));
74     this._registerHandler(commands.OpenResource, this._onOpenResource.bind(this));
75     this._registerHandler(commands.Unsubscribe, this._onUnsubscribe.bind(this));
76     this._registerHandler(commands.UpdateButton, this._onUpdateButton.bind(this));
77     this._registerHandler(commands.UpdateAuditProgress, this._onUpdateAuditProgress.bind(this));
78     window.addEventListener("message", this._onWindowMessage.bind(this), false);
79
80     this._initExtensions();
81 }
82
83 WebInspector.ExtensionServer.prototype = {
84     /**
85      * @return {boolean}
86      */
87     hasExtensions: function()
88     {
89         return !!Object.keys(this._registeredExtensions).length;
90     },
91
92     notifySearchAction: function(panelId, action, searchString)
93     {
94         this._postNotification(WebInspector.extensionAPI.Events.PanelSearch + panelId, action, searchString);
95     },
96
97     notifyViewShown: function(identifier, frameIndex)
98     {
99         this._postNotification(WebInspector.extensionAPI.Events.ViewShown + identifier, frameIndex);
100     },
101
102     notifyViewHidden: function(identifier)
103     {
104         this._postNotification(WebInspector.extensionAPI.Events.ViewHidden + identifier);
105     },
106
107     notifyButtonClicked: function(identifier)
108     {
109         this._postNotification(WebInspector.extensionAPI.Events.ButtonClicked + identifier);
110     },
111
112     _inspectedURLChanged: function(event)
113     {
114         this._requests = {};
115         var url = event.data;
116         this._postNotification(WebInspector.extensionAPI.Events.InspectedURLChanged, url);
117     },
118
119     startAuditRun: function(category, auditRun)
120     {
121         this._clientObjects[auditRun.id] = auditRun;
122         this._postNotification("audit-started-" + category.id, auditRun.id);
123     },
124
125     stopAuditRun: function(auditRun)
126     {
127         delete this._clientObjects[auditRun.id];
128     },
129
130     /**
131      * @param {string} type
132      * @return {boolean}
133      */
134     hasSubscribers: function(type)
135     {
136         return !!this._subscribers[type];
137     },
138
139     /**
140      * @param {string} type
141      * @param {...*} vararg
142      */
143     _postNotification: function(type, vararg)
144     {
145         var subscribers = this._subscribers[type];
146         if (!subscribers)
147             return;
148         var message = {
149             command: "notify-" + type,
150             arguments: Array.prototype.slice.call(arguments, 1)
151         };
152         for (var i = 0; i < subscribers.length; ++i)
153             subscribers[i].postMessage(message);
154     },
155
156     _onSubscribe: function(message, port)
157     {
158         var subscribers = this._subscribers[message.type];
159         if (subscribers)
160             subscribers.push(port);
161         else {
162             this._subscribers[message.type] = [ port ];
163             if (this._subscriptionStartHandlers[message.type])
164                 this._subscriptionStartHandlers[message.type]();
165         }
166     },
167
168     _onUnsubscribe: function(message, port)
169     {
170         var subscribers = this._subscribers[message.type];
171         if (!subscribers)
172             return;
173         subscribers.remove(port);
174         if (!subscribers.length) {
175             delete this._subscribers[message.type];
176             if (this._subscriptionStopHandlers[message.type])
177                 this._subscriptionStopHandlers[message.type]();
178         }
179     },
180
181     _onAddRequestHeaders: function(message)
182     {
183         var id = message.extensionId;
184         if (typeof id !== "string")
185             return this._status.E_BADARGTYPE("extensionId", typeof id, "string");
186         var extensionHeaders = this._extraHeaders[id];
187         if (!extensionHeaders) {
188             extensionHeaders = {};
189             this._extraHeaders[id] = extensionHeaders;
190         }
191         for (var name in message.headers)
192             extensionHeaders[name] = message.headers[name];
193         var allHeaders = /** @type {!NetworkAgent.Headers} */ ({});
194         for (var extension in this._extraHeaders) {
195             var headers = this._extraHeaders[extension];
196             for (name in headers) {
197                 if (typeof headers[name] === "string")
198                     allHeaders[name] = headers[name];
199             }
200         }
201         NetworkAgent.setExtraHTTPHeaders(allHeaders);
202     },
203
204     _onApplyStyleSheet: function(message)
205     {
206         if (!WebInspector.experimentsSettings.applyCustomStylesheet.isEnabled())
207             return;
208         var styleSheet = document.createElement("style");
209         styleSheet.textContent = message.styleSheet;
210         document.head.appendChild(styleSheet);
211     },
212
213     _onCreatePanel: function(message, port)
214     {
215         var id = message.id;
216         // The ids are generated on the client API side and must be unique, so the check below
217         // shouldn't be hit unless someone is bypassing the API.
218         if (id in this._clientObjects || WebInspector.inspectorView.hasPanel(id))
219             return this._status.E_EXISTS(id);
220
221         var page = this._expandResourcePath(port._extensionOrigin, message.page);
222         var panelDescriptor = new WebInspector.ExtensionServerPanelDescriptor(id, message.title, new WebInspector.ExtensionPanel(id, page));
223         this._clientObjects[id] = panelDescriptor.panel();
224         WebInspector.inspectorView.addPanel(panelDescriptor);
225         return this._status.OK();
226     },
227
228     _onShowPanel: function(message)
229     {
230         // Note: WebInspector.inspectorView.showPanel already sanitizes input.
231         WebInspector.inspectorView.showPanel(message.id);
232     },
233
234     _onCreateStatusBarButton: function(message, port)
235     {
236         var panel = this._clientObjects[message.panel];
237         if (!panel || !(panel instanceof WebInspector.ExtensionPanel))
238             return this._status.E_NOTFOUND(message.panel);
239         var button = new WebInspector.ExtensionButton(message.id, this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled);
240         this._clientObjects[message.id] = button;
241         panel.addStatusBarItem(button.element);
242         return this._status.OK();
243     },
244
245     _onUpdateButton: function(message, port)
246     {
247         var button = this._clientObjects[message.id];
248         if (!button || !(button instanceof WebInspector.ExtensionButton))
249             return this._status.E_NOTFOUND(message.id);
250         button.update(this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled);
251         return this._status.OK();
252     },
253
254     _onCreateSidebarPane: function(message)
255     {
256         var panel = WebInspector.inspectorView.panel(message.panel);
257         if (!panel)
258             return this._status.E_NOTFOUND(message.panel);
259         if (!panel.addExtensionSidebarPane)
260             return this._status.E_NOTSUPPORTED();
261         var id = message.id;
262         var sidebar = new WebInspector.ExtensionSidebarPane(message.title, id);
263         this._clientObjects[id] = sidebar;
264         panel.addExtensionSidebarPane(id, sidebar);
265
266         return this._status.OK();
267     },
268
269     _onSetSidebarHeight: function(message)
270     {
271         var sidebar = this._clientObjects[message.id];
272         if (!sidebar)
273             return this._status.E_NOTFOUND(message.id);
274         sidebar.setHeight(message.height);
275         return this._status.OK();
276     },
277
278     _onSetSidebarContent: function(message, port)
279     {
280         var sidebar = this._clientObjects[message.id];
281         if (!sidebar)
282             return this._status.E_NOTFOUND(message.id);
283
284         /**
285          * @this {WebInspector.ExtensionServer}
286          */
287         function callback(error)
288         {
289             var result = error ? this._status.E_FAILED(error) : this._status.OK();
290             this._dispatchCallback(message.requestId, port, result);
291         }
292         if (message.evaluateOnPage)
293             return sidebar.setExpression(message.expression, message.rootTitle, message.evaluateOptions, port._extensionOrigin, callback.bind(this));
294         sidebar.setObject(message.expression, message.rootTitle, callback.bind(this));
295     },
296
297     _onSetSidebarPage: function(message, port)
298     {
299         var sidebar = this._clientObjects[message.id];
300         if (!sidebar)
301             return this._status.E_NOTFOUND(message.id);
302         sidebar.setPage(this._expandResourcePath(port._extensionOrigin, message.page));
303     },
304
305     _onOpenResource: function(message)
306     {
307         var uiSourceCode = WebInspector.workspace.uiSourceCodeForURL(message.url);
308         if (uiSourceCode) {
309             WebInspector.Revealer.reveal(uiSourceCode.uiLocation(message.lineNumber, 0));
310             return this._status.OK();
311         }
312
313         var resource = WebInspector.resourceForURL(message.url);
314         if (resource) {
315             WebInspector.Revealer.reveal(resource, message.lineNumber);
316             return this._status.OK();
317         }
318
319         var request = WebInspector.networkLog.requestForURL(message.url);
320         if (request) {
321             WebInspector.Revealer.reveal(request);
322             return this._status.OK();
323         }
324
325         return this._status.E_NOTFOUND(message.url);
326     },
327
328     _onSetOpenResourceHandler: function(message, port)
329     {
330         var name = this._registeredExtensions[port._extensionOrigin].name || ("Extension " + port._extensionOrigin);
331         if (message.handlerPresent)
332             WebInspector.openAnchorLocationRegistry.registerHandler(name, this._handleOpenURL.bind(this, port));
333         else
334             WebInspector.openAnchorLocationRegistry.unregisterHandler(name);
335     },
336
337     _handleOpenURL: function(port, details)
338     {
339         var url = /** @type {string} */ (details.url);
340         var contentProvider = WebInspector.workspace.uiSourceCodeForOriginURL(url) || WebInspector.resourceForURL(url);
341         if (!contentProvider)
342             return false;
343
344         var lineNumber = details.lineNumber;
345         if (typeof lineNumber === "number")
346             lineNumber += 1;
347         port.postMessage({
348             command: "open-resource",
349             resource: this._makeResource(contentProvider),
350             lineNumber: lineNumber
351         });
352         return true;
353     },
354
355     _onReload: function(message)
356     {
357         var options = /** @type {!ExtensionReloadOptions} */ (message.options || {});
358         NetworkAgent.setUserAgentOverride(typeof options.userAgent === "string" ? options.userAgent : "");
359         var injectedScript;
360         if (options.injectedScript)
361             injectedScript = "(function(){" + options.injectedScript + "})()";
362         var preprocessingScript = options.preprocessingScript;
363         WebInspector.resourceTreeModel.reloadPage(!!options.ignoreCache, injectedScript, preprocessingScript);
364         return this._status.OK();
365     },
366
367     _onEvaluateOnInspectedPage: function(message, port)
368     {
369         /**
370          * @param {?Protocol.Error} error
371          * @param {?RuntimeAgent.RemoteObject} resultPayload
372          * @param {boolean=} wasThrown
373          * @this {WebInspector.ExtensionServer}
374          */
375         function callback(error, resultPayload, wasThrown)
376         {
377             var result;
378             if (error || !resultPayload)
379                 result = this._status.E_PROTOCOLERROR(error.toString());
380             else if (wasThrown)
381                 result = { isException: true, value: resultPayload.description };
382             else
383                 result = { value: resultPayload.value };
384
385             this._dispatchCallback(message.requestId, port, result);
386         }
387         return this.evaluate(message.expression, true, true, message.evaluateOptions, port._extensionOrigin, callback.bind(this));
388     },
389
390     _onGetConsoleMessages: function()
391     {
392         return WebInspector.multitargetConsoleModel.messages().map(this._makeConsoleMessage);
393     },
394
395     _onAddConsoleMessage: function(message)
396     {
397         function convertSeverity(level)
398         {
399             switch (level) {
400                 case WebInspector.extensionAPI.console.Severity.Log:
401                     return WebInspector.ConsoleMessage.MessageLevel.Log;
402                 case WebInspector.extensionAPI.console.Severity.Warning:
403                     return WebInspector.ConsoleMessage.MessageLevel.Warning;
404                 case WebInspector.extensionAPI.console.Severity.Error:
405                     return WebInspector.ConsoleMessage.MessageLevel.Error;
406                 case WebInspector.extensionAPI.console.Severity.Debug:
407                     return WebInspector.ConsoleMessage.MessageLevel.Debug;
408             }
409         }
410         var level = convertSeverity(message.severity);
411         if (!level)
412             return this._status.E_BADARG("message.severity", message.severity);
413
414         var mainTarget = WebInspector.targetManager.mainTarget();
415         var consoleMessage = new WebInspector.ConsoleMessage(
416             mainTarget,
417             WebInspector.ConsoleMessage.MessageSource.JS,
418             level,
419             message.text,
420             WebInspector.ConsoleMessage.MessageType.Log,
421             message.url,
422             message.line);
423         mainTarget.consoleModel.addMessage(consoleMessage);
424     },
425
426     _makeConsoleMessage: function(message)
427     {
428         function convertLevel(level)
429         {
430             if (!level)
431                 return;
432             switch (level) {
433                 case WebInspector.ConsoleMessage.MessageLevel.Log:
434                     return WebInspector.extensionAPI.console.Severity.Log;
435                 case WebInspector.ConsoleMessage.MessageLevel.Warning:
436                     return WebInspector.extensionAPI.console.Severity.Warning;
437                 case WebInspector.ConsoleMessage.MessageLevel.Error:
438                     return WebInspector.extensionAPI.console.Severity.Error;
439                 case WebInspector.ConsoleMessage.MessageLevel.Debug:
440                     return WebInspector.extensionAPI.console.Severity.Debug;
441                 default:
442                     return WebInspector.extensionAPI.console.Severity.Log;
443             }
444         }
445         var result = {
446             severity: convertLevel(message.level),
447             text: message.messageText,
448         };
449         if (message.url)
450             result.url = message.url;
451         if (message.line)
452             result.line = message.line;
453         return result;
454     },
455
456     _onGetHAR: function()
457     {
458         // Wake up the "network" module for HAR operations.
459         WebInspector.inspectorView.panel("network");
460         var requests = WebInspector.networkLog.requests;
461         var harLog = (new WebInspector.HARLog(requests)).build();
462         for (var i = 0; i < harLog.entries.length; ++i)
463             harLog.entries[i]._requestId = this._requestId(requests[i]);
464         return harLog;
465     },
466
467     /**
468      * @param {!WebInspector.ContentProvider} contentProvider
469      */
470     _makeResource: function(contentProvider)
471     {
472         return {
473             url: contentProvider.contentURL(),
474             type: contentProvider.contentType().name()
475         };
476     },
477
478     /**
479      * @return {!Array.<!WebInspector.ContentProvider>}
480      */
481     _onGetPageResources: function()
482     {
483         var resources = {};
484
485         /**
486          * @this {WebInspector.ExtensionServer}
487          */
488         function pushResourceData(contentProvider)
489         {
490             if (!resources[contentProvider.contentURL()])
491                 resources[contentProvider.contentURL()] = this._makeResource(contentProvider);
492         }
493         var uiSourceCodes = WebInspector.workspace.uiSourceCodesForProjectType(WebInspector.projectTypes.Network);
494         uiSourceCodes = uiSourceCodes.concat(WebInspector.workspace.uiSourceCodesForProjectType(WebInspector.projectTypes.ContentScripts));
495         uiSourceCodes.forEach(pushResourceData.bind(this));
496         WebInspector.resourceTreeModel.forAllResources(pushResourceData.bind(this));
497         return Object.values(resources);
498     },
499
500     /**
501      * @param {!WebInspector.ContentProvider} contentProvider
502      * @param {!Object} message
503      * @param {!MessagePort} port
504      */
505     _getResourceContent: function(contentProvider, message, port)
506     {
507         /**
508          * @param {?string} content
509          * @this {WebInspector.ExtensionServer}
510          */
511         function onContentAvailable(content)
512         {
513             var response = {
514                 encoding: (content === null) || contentProvider.contentType().isTextType() ? "" : "base64",
515                 content: content
516             };
517             this._dispatchCallback(message.requestId, port, response);
518         }
519
520         contentProvider.requestContent(onContentAvailable.bind(this));
521     },
522
523     _onGetRequestContent: function(message, port)
524     {
525         var request = this._requestById(message.id);
526         if (!request)
527             return this._status.E_NOTFOUND(message.id);
528         this._getResourceContent(request, message, port);
529     },
530
531     _onGetResourceContent: function(message, port)
532     {
533         var url = /** @type {string} */ (message.url);
534         var contentProvider = WebInspector.workspace.uiSourceCodeForOriginURL(url) || WebInspector.resourceForURL(url);
535         if (!contentProvider)
536             return this._status.E_NOTFOUND(url);
537         this._getResourceContent(contentProvider, message, port);
538     },
539
540     _onSetResourceContent: function(message, port)
541     {
542         /**
543          * @param {?Protocol.Error} error
544          * @this {WebInspector.ExtensionServer}
545          */
546         function callbackWrapper(error)
547         {
548             var response = error ? this._status.E_FAILED(error) : this._status.OK();
549             this._dispatchCallback(message.requestId, port, response);
550         }
551
552         var url = /** @type {string} */ (message.url);
553         var uiSourceCode = WebInspector.workspace.uiSourceCodeForOriginURL(url);
554         if (!uiSourceCode) {
555             var resource = WebInspector.resourceTreeModel.resourceForURL(url);
556             if (!resource)
557                 return this._status.E_NOTFOUND(url);
558             return this._status.E_NOTSUPPORTED("Resource is not editable")
559         }
560         uiSourceCode.setWorkingCopy(message.content);
561         if (message.commit)
562             uiSourceCode.commitWorkingCopy();
563         callbackWrapper.call(this, null);
564     },
565
566     _requestId: function(request)
567     {
568         if (!request._extensionRequestId) {
569             request._extensionRequestId = ++this._lastRequestId;
570             this._requests[request._extensionRequestId] = request;
571         }
572         return request._extensionRequestId;
573     },
574
575     _requestById: function(id)
576     {
577         return this._requests[id];
578     },
579
580     _onAddAuditCategory: function(message, port)
581     {
582         var category = new WebInspector.ExtensionAuditCategory(port._extensionOrigin, message.id, message.displayName, message.resultCount);
583         if (WebInspector.inspectorView.panel("audits").getCategory(category.id))
584             return this._status.E_EXISTS(category.id);
585         this._clientObjects[message.id] = category;
586         // FIXME: register module manager extension instead of waking up audits module.
587         WebInspector.inspectorView.panel("audits").addCategory(category);
588     },
589
590     _onAddAuditResult: function(message)
591     {
592         var auditResult = this._clientObjects[message.resultId];
593         if (!auditResult)
594             return this._status.E_NOTFOUND(message.resultId);
595         try {
596             auditResult.addResult(message.displayName, message.description, message.severity, message.details);
597         } catch (e) {
598             return e;
599         }
600         return this._status.OK();
601     },
602
603     _onUpdateAuditProgress: function(message)
604     {
605         var auditResult = this._clientObjects[message.resultId];
606         if (!auditResult)
607             return this._status.E_NOTFOUND(message.resultId);
608         auditResult.updateProgress(Math.min(Math.max(0, message.progress), 1));
609     },
610
611     _onStopAuditCategoryRun: function(message)
612     {
613         var auditRun = this._clientObjects[message.resultId];
614         if (!auditRun)
615             return this._status.E_NOTFOUND(message.resultId);
616         auditRun.done();
617     },
618
619     _onForwardKeyboardEvent: function(message)
620     {
621         const Esc = "U+001B";
622         message.entries.forEach(handleEventEntry);
623
624         function handleEventEntry(entry)
625         {
626             if (!entry.ctrlKey && !entry.altKey && !entry.metaKey && !/^F\d+$/.test(entry.keyIdentifier) && entry.keyIdentifier !== Esc)
627                 return;
628             // Fool around closure compiler -- it has its own notion of both KeyboardEvent constructor
629             // and initKeyboardEvent methods and overriding these in externs.js does not have effect.
630             var event = new window.KeyboardEvent(entry.eventType, {
631                 keyIdentifier: entry.keyIdentifier,
632                 location: entry.location,
633                 ctrlKey: entry.ctrlKey,
634                 altKey: entry.altKey,
635                 shiftKey: entry.shiftKey,
636                 metaKey: entry.metaKey
637             });
638             event.__keyCode = keyCodeForEntry(entry);
639             document.dispatchEvent(event);
640         }
641
642         function keyCodeForEntry(entry)
643         {
644             var keyCode = entry.keyCode;
645             if (!keyCode) {
646                 // This is required only for synthetic events (e.g. dispatched in tests).
647                 var match = entry.keyIdentifier.match(/^U\+([\dA-Fa-f]+)$/);
648                 if (match)
649                     keyCode = parseInt(match[1], 16);
650             }
651             return keyCode || 0;
652         }
653     },
654
655     _dispatchCallback: function(requestId, port, result)
656     {
657         if (requestId)
658             port.postMessage({ command: "callback", requestId: requestId, result: result });
659     },
660
661     _initExtensions: function()
662     {
663         this._registerAutosubscriptionTargetManagerHandler(WebInspector.extensionAPI.Events.ConsoleMessageAdded,
664             WebInspector.ConsoleModel, WebInspector.ConsoleModel.Events.MessageAdded, this._notifyConsoleMessageAdded);
665         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ResourceAdded,
666             WebInspector.workspace, WebInspector.Workspace.Events.UISourceCodeAdded, this._notifyResourceAdded);
667         this._registerAutosubscriptionTargetManagerHandler(WebInspector.extensionAPI.Events.NetworkRequestFinished,
668             WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestFinished, this._notifyRequestFinished);
669
670         /**
671          * @this {WebInspector.ExtensionServer}
672          */
673         function onElementsSubscriptionStarted()
674         {
675             WebInspector.notifications.addEventListener(WebInspector.NotificationService.Events.SelectedNodeChanged, this._notifyElementsSelectionChanged, this);
676         }
677
678         /**
679          * @this {WebInspector.ExtensionServer}
680          */
681         function onElementsSubscriptionStopped()
682         {
683             WebInspector.notifications.removeEventListener(WebInspector.NotificationService.Events.SelectedNodeChanged, this._notifyElementsSelectionChanged, this);
684         }
685
686         this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.PanelObjectSelected + "elements",
687             onElementsSubscriptionStarted.bind(this), onElementsSubscriptionStopped.bind(this));
688
689         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.PanelObjectSelected + "sources",
690             WebInspector.notifications,
691             WebInspector.SourceFrame.Events.SelectionChanged,
692             this._notifySourceFrameSelectionChanged);
693         this._registerResourceContentCommittedHandler(this._notifyUISourceCodeContentCommitted);
694
695         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged,
696             this._inspectedURLChanged, this);
697
698         InspectorExtensionRegistry.getExtensionsAsync();
699     },
700
701     /**
702      * @param {!WebInspector.TextRange} textRange
703      */
704     _makeSourceSelection: function(textRange)
705     {
706         var sourcesPanel = WebInspector.inspectorView.panel("sources");
707         var selection = {
708             startLine: textRange.startLine,
709             startColumn: textRange.startColumn,
710             endLine: textRange.endLine,
711             endColumn: textRange.endColumn,
712             url: sourcesPanel.sourcesView().currentUISourceCode().uri()
713         };
714
715         return selection;
716     },
717
718     _notifySourceFrameSelectionChanged: function(event)
719     {
720         this._postNotification(WebInspector.extensionAPI.Events.PanelObjectSelected + "sources", this._makeSourceSelection(event.data));
721     },
722
723     _notifyConsoleMessageAdded: function(event)
724     {
725         this._postNotification(WebInspector.extensionAPI.Events.ConsoleMessageAdded, this._makeConsoleMessage(event.data));
726     },
727
728     _notifyResourceAdded: function(event)
729     {
730         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
731         this._postNotification(WebInspector.extensionAPI.Events.ResourceAdded, this._makeResource(uiSourceCode));
732     },
733
734     _notifyUISourceCodeContentCommitted: function(event)
735     {
736         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode);
737         var content = /** @type {string} */ (event.data.content);
738         this._postNotification(WebInspector.extensionAPI.Events.ResourceContentCommitted, this._makeResource(uiSourceCode), content);
739     },
740
741     _notifyRequestFinished: function(event)
742     {
743         var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
744         // Wake up the "network" module for HAR operations.
745         WebInspector.inspectorView.panel("network");
746         this._postNotification(WebInspector.extensionAPI.Events.NetworkRequestFinished, this._requestId(request), (new WebInspector.HAREntry(request)).build());
747     },
748
749     _notifyElementsSelectionChanged: function()
750     {
751         this._postNotification(WebInspector.extensionAPI.Events.PanelObjectSelected + "elements");
752     },
753
754     /**
755      * @param {!Array.<!ExtensionDescriptor>} extensionInfos
756      */
757     addExtensions: function(extensionInfos)
758     {
759         extensionInfos.forEach(this._addExtension, this);
760     },
761
762     /**
763      * @param {!ExtensionDescriptor} extensionInfo
764      */
765     _addExtension: function(extensionInfo)
766     {
767         const urlOriginRegExp = new RegExp("([^:]+:\/\/[^/]*)\/"); // Can't use regexp literal here, MinJS chokes on it.
768         var startPage = extensionInfo.startPage;
769         var name = extensionInfo.name;
770
771         try {
772             var originMatch = urlOriginRegExp.exec(startPage);
773             if (!originMatch) {
774                 console.error("Skipping extension with invalid URL: " + startPage);
775                 return false;
776             }
777             var extensionOrigin = originMatch[1];
778             if (!this._registeredExtensions[extensionOrigin]) {
779                 // See ExtensionAPI.js for details.
780                 InspectorFrontendHost.setInjectedScriptForOrigin(extensionOrigin, buildExtensionAPIInjectedScript(extensionInfo));
781                 this._registeredExtensions[extensionOrigin] = { name: name };
782             }
783             var iframe = document.createElement("iframe");
784             iframe.src = startPage;
785             iframe.style.display = "none";
786             document.body.appendChild(iframe);
787         } catch (e) {
788             console.error("Failed to initialize extension " + startPage + ":" + e);
789             return false;
790         }
791         return true;
792     },
793
794     _registerExtension: function(origin, port)
795     {
796         if (!this._registeredExtensions.hasOwnProperty(origin)) {
797             if (origin !== window.location.origin) // Just ignore inspector frames.
798                 console.error("Ignoring unauthorized client request from " + origin);
799             return;
800         }
801         port._extensionOrigin = origin;
802         port.addEventListener("message", this._onmessage.bind(this), false);
803         port.start();
804     },
805
806     _onWindowMessage: function(event)
807     {
808         if (event.data === "registerExtension")
809             this._registerExtension(event.origin, event.ports[0]);
810     },
811
812     _onmessage: function(event)
813     {
814         var message = event.data;
815         var result;
816
817         if (message.command in this._handlers)
818             result = this._handlers[message.command](message, event.target);
819         else
820             result = this._status.E_NOTSUPPORTED(message.command);
821
822         if (result && message.requestId)
823             this._dispatchCallback(message.requestId, event.target, result);
824     },
825
826     _registerHandler: function(command, callback)
827     {
828         console.assert(command);
829         this._handlers[command] = callback;
830     },
831
832     _registerSubscriptionHandler: function(eventTopic, onSubscribeFirst, onUnsubscribeLast)
833     {
834         this._subscriptionStartHandlers[eventTopic] = onSubscribeFirst;
835         this._subscriptionStopHandlers[eventTopic] = onUnsubscribeLast;
836     },
837
838     /**
839      * @param {string} eventTopic
840      * @param {!Object} eventTarget
841      * @param {string} frontendEventType
842      * @param {function(!WebInspector.Event)} handler
843      */
844     _registerAutosubscriptionHandler: function(eventTopic, eventTarget, frontendEventType, handler)
845     {
846         this._registerSubscriptionHandler(eventTopic,
847             eventTarget.addEventListener.bind(eventTarget, frontendEventType, handler, this),
848             eventTarget.removeEventListener.bind(eventTarget, frontendEventType, handler, this));
849     },
850
851     /**
852      * @param {string} eventTopic
853      * @param {!Function} modelClass
854      * @param {string} frontendEventType
855      * @param {function(!WebInspector.Event)} handler
856      */
857     _registerAutosubscriptionTargetManagerHandler: function(eventTopic, modelClass, frontendEventType, handler)
858     {
859         this._registerSubscriptionHandler(eventTopic,
860             WebInspector.targetManager.addModelListener.bind(WebInspector.targetManager, modelClass, frontendEventType, handler, this),
861             WebInspector.targetManager.removeModelListener.bind(WebInspector.targetManager, modelClass, frontendEventType, handler, this));
862     },
863
864     _registerResourceContentCommittedHandler: function(handler)
865     {
866         /**
867          * @this {WebInspector.ExtensionServer}
868          */
869         function addFirstEventListener()
870         {
871             WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, handler, this);
872             WebInspector.workspace.setHasResourceContentTrackingExtensions(true);
873         }
874
875         /**
876          * @this {WebInspector.ExtensionServer}
877          */
878         function removeLastEventListener()
879         {
880             WebInspector.workspace.setHasResourceContentTrackingExtensions(false);
881             WebInspector.workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, handler, this);
882         }
883
884         this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.ResourceContentCommitted,
885             addFirstEventListener.bind(this),
886             removeLastEventListener.bind(this));
887     },
888
889     _expandResourcePath: function(extensionPath, resourcePath)
890     {
891         if (!resourcePath)
892             return;
893         return extensionPath + this._normalizePath(resourcePath);
894     },
895
896     _normalizePath: function(path)
897     {
898         var source = path.split("/");
899         var result = [];
900
901         for (var i = 0; i < source.length; ++i) {
902             if (source[i] === ".")
903                 continue;
904             // Ignore empty path components resulting from //, as well as a leading and traling slashes.
905             if (source[i] === "")
906                 continue;
907             if (source[i] === "..")
908                 result.pop();
909             else
910                 result.push(source[i]);
911         }
912         return "/" + result.join("/");
913     },
914
915     /**
916      * @param {string} expression
917      * @param {boolean} exposeCommandLineAPI
918      * @param {boolean} returnByValue
919      * @param {?Object} options
920      * @param {string} securityOrigin
921      * @param {function(?string, !RuntimeAgent.RemoteObject, boolean=)} callback
922      * @return {!WebInspector.ExtensionStatus.Record|undefined}
923      */
924     evaluate: function(expression, exposeCommandLineAPI, returnByValue, options, securityOrigin, callback)
925     {
926         var contextId;
927
928         /**
929          * @param {string} url
930          * @return {boolean}
931          */
932         function resolveURLToFrame(url)
933         {
934             var found;
935             function hasMatchingURL(frame)
936             {
937                 found = (frame.url === url) ? frame : null;
938                 return found;
939             }
940             WebInspector.resourceTreeModel.frames().some(hasMatchingURL);
941             return found;
942         }
943
944         if (typeof options === "object") {
945             var frame = options.frameURL ? resolveURLToFrame(options.frameURL) : WebInspector.resourceTreeModel.mainFrame;
946             if (!frame) {
947                 if (options.frameURL)
948                     console.warn("evaluate: there is no frame with URL " + options.frameURL);
949                 else
950                     console.warn("evaluate: the main frame is not yet available");
951                 return this._status.E_NOTFOUND(options.frameURL || "<top>");
952             }
953
954             var contextSecurityOrigin;
955             if (options.useContentScriptContext)
956                 contextSecurityOrigin = securityOrigin;
957             else if (options.scriptExecutionContext)
958                 contextSecurityOrigin = options.scriptExecutionContext;
959
960             var context;
961             var executionContexts = WebInspector.runtimeModel.executionContexts();
962             if (contextSecurityOrigin) {
963                 for (var i = 0; i < executionContexts.length; ++i) {
964                     var executionContext = executionContexts[i];
965                     if (executionContext.frameId === frame.id && executionContext.name === contextSecurityOrigin && !executionContext.isMainWorldContext)
966                         context = executionContext;
967
968                 }
969                 if (!context) {
970                     console.warn("The JavaScript context " + contextSecurityOrigin + " was not found in the frame " + frame.url)
971                     return this._status.E_NOTFOUND(contextSecurityOrigin)
972                 }
973             } else {
974                 for (var i = 0; i < executionContexts.length; ++i) {
975                     var executionContext = executionContexts[i];
976                     if (executionContext.frameId === frame.id && executionContext.isMainWorldContext)
977                         context = executionContext;
978
979                 }
980                 if (!context)
981                     return this._status.E_FAILED(frame.url + " has no execution context");
982             }
983
984             contextId = context.id;
985         }
986         RuntimeAgent.evaluate(expression, "extension", exposeCommandLineAPI, true, contextId, returnByValue, false, callback);
987     }
988 }
989
990 /**
991  * @constructor
992  * @param {string} name
993  * @param {string} title
994  * @param {!WebInspector.Panel} panel
995  * @implements {WebInspector.PanelDescriptor}
996  */
997 WebInspector.ExtensionServerPanelDescriptor = function(name, title, panel)
998 {
999     this._name = name;
1000     this._title = title;
1001     this._panel = panel;
1002 }
1003
1004 WebInspector.ExtensionServerPanelDescriptor.prototype = {
1005     /**
1006      * @return {string}
1007      */
1008     name: function()
1009     {
1010         return this._name;
1011     },
1012
1013     /**
1014      * @return {string}
1015      */
1016     title: function()
1017     {
1018         return this._title;
1019     },
1020
1021     /**
1022      * @return {!WebInspector.Panel}
1023      */
1024     panel: function()
1025     {
1026         return this._panel;
1027     }
1028 }
1029
1030 /**
1031  * @constructor
1032  */
1033 WebInspector.ExtensionStatus = function()
1034 {
1035     /**
1036      * @param {string} code
1037      * @param {string} description
1038      * @return {!WebInspector.ExtensionStatus.Record}
1039      */
1040     function makeStatus(code, description)
1041     {
1042         var details = Array.prototype.slice.call(arguments, 2);
1043         var status = { code: code, description: description, details: details };
1044         if (code !== "OK") {
1045             status.isError = true;
1046             console.log("Extension server error: " + String.vsprintf(description, details));
1047         }
1048         return status;
1049     }
1050
1051     this.OK = makeStatus.bind(null, "OK", "OK");
1052     this.E_EXISTS = makeStatus.bind(null, "E_EXISTS", "Object already exists: %s");
1053     this.E_BADARG = makeStatus.bind(null, "E_BADARG", "Invalid argument %s: %s");
1054     this.E_BADARGTYPE = makeStatus.bind(null, "E_BADARGTYPE", "Invalid type for argument %s: got %s, expected %s");
1055     this.E_NOTFOUND = makeStatus.bind(null, "E_NOTFOUND", "Object not found: %s");
1056     this.E_NOTSUPPORTED = makeStatus.bind(null, "E_NOTSUPPORTED", "Object does not support requested operation: %s");
1057     this.E_PROTOCOLERROR = makeStatus.bind(null, "E_PROTOCOLERROR", "Inspector protocol error: %s");
1058     this.E_FAILED = makeStatus.bind(null, "E_FAILED", "Operation failed: %s");
1059 }
1060
1061 /**
1062  * @typedef {{code: string, description: string, details: !Array.<*>}}
1063  */
1064 WebInspector.ExtensionStatus.Record;
1065
1066 WebInspector.extensionAPI = {};
1067 defineCommonExtensionSymbols(WebInspector.extensionAPI);