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