Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / 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("ExtensionRegistryStub.js");
33 importScript("ExtensionAPI.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 || id in WebInspector.panels)
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.showPanel already sanitizes input.
236         WebInspector.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.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(new WebInspector.UILocation(uiSourceCode, 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 = WebInspector.ConsoleMessage.create(
420             WebInspector.ConsoleMessage.MessageSource.JS,
421             level,
422             message.text,
423             WebInspector.ConsoleMessage.MessageType.Log,
424             message.url,
425             message.line);
426         WebInspector.console.addMessage(consoleMessage);
427     },
428
429     _makeConsoleMessage: function(message)
430     {
431         function convertLevel(level)
432         {
433             if (!level)
434                 return;
435             switch (level) {
436                 case WebInspector.ConsoleMessage.MessageLevel.Log:
437                     return WebInspector.extensionAPI.console.Severity.Log;
438                 case WebInspector.ConsoleMessage.MessageLevel.Warning:
439                     return WebInspector.extensionAPI.console.Severity.Warning;
440                 case WebInspector.ConsoleMessage.MessageLevel.Error:
441                     return WebInspector.extensionAPI.console.Severity.Error;
442                 case WebInspector.ConsoleMessage.MessageLevel.Debug:
443                     return WebInspector.extensionAPI.console.Severity.Debug;
444                 default:
445                     return WebInspector.extensionAPI.console.Severity.Log;
446             }
447         }
448         var result = {
449             severity: convertLevel(message.level),
450             text: message.text,
451         };
452         if (message.url)
453             result.url = message.url;
454         if (message.line)
455             result.line = message.line;
456         return result;
457     },
458
459     _onGetHAR: function()
460     {
461         var requests = WebInspector.networkLog.requests;
462         var harLog = (new WebInspector.HARLog(requests)).build();
463         for (var i = 0; i < harLog.entries.length; ++i)
464             harLog.entries[i]._requestId = this._requestId(requests[i]);
465         return harLog;
466     },
467
468     /**
469      * @param {!WebInspector.ContentProvider} contentProvider
470      */
471     _makeResource: function(contentProvider)
472     {
473         return {
474             url: contentProvider.contentURL(),
475             type: contentProvider.contentType().name()
476         };
477     },
478
479     /**
480      * @return {!Array.<!WebInspector.ContentProvider>}
481      */
482     _onGetPageResources: function()
483     {
484         var resources = {};
485
486         /**
487          * @this {WebInspector.ExtensionServer}
488          */
489         function pushResourceData(contentProvider)
490         {
491             if (!resources[contentProvider.contentURL()])
492                 resources[contentProvider.contentURL()] = this._makeResource(contentProvider);
493         }
494         var uiSourceCodes = WebInspector.workspace.uiSourceCodesForProjectType(WebInspector.projectTypes.Network);
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      */
503     _getResourceContent: function(contentProvider, message, port)
504     {
505         /**
506          * @param {?string} content
507          * @this {WebInspector.ExtensionServer}
508          */
509         function onContentAvailable(content)
510         {
511             var response = {
512                 encoding: (content === null) || contentProvider.contentType().isTextType() ? "" : "base64",
513                 content: content
514             };
515             this._dispatchCallback(message.requestId, port, response);
516         }
517
518         contentProvider.requestContent(onContentAvailable.bind(this));
519     },
520
521     _onGetRequestContent: function(message, port)
522     {
523         var request = this._requestById(message.id);
524         if (!request)
525             return this._status.E_NOTFOUND(message.id);
526         this._getResourceContent(request, message, port);
527     },
528
529     _onGetResourceContent: function(message, port)
530     {
531         var url = /** @type {string} */ (message.url);
532         var contentProvider = WebInspector.workspace.uiSourceCodeForOriginURL(url) || WebInspector.resourceForURL(url);
533         if (!contentProvider)
534             return this._status.E_NOTFOUND(url);
535         this._getResourceContent(contentProvider, message, port);
536     },
537
538     _onSetResourceContent: function(message, port)
539     {
540         /**
541          * @param {?Protocol.Error} error
542          * @this {WebInspector.ExtensionServer}
543          */
544         function callbackWrapper(error)
545         {
546             var response = error ? this._status.E_FAILED(error) : this._status.OK();
547             this._dispatchCallback(message.requestId, port, response);
548         }
549
550         var url = /** @type {string} */ (message.url);
551         var uiSourceCode = WebInspector.workspace.uiSourceCodeForOriginURL(url);
552         if (!uiSourceCode) {
553             var resource = WebInspector.resourceTreeModel.resourceForURL(url);
554             if (!resource)
555                 return this._status.E_NOTFOUND(url);
556             return this._status.E_NOTSUPPORTED("Resource is not editable")
557         }
558         uiSourceCode.setWorkingCopy(message.content);
559         if (message.commit)
560             uiSourceCode.commitWorkingCopy(callbackWrapper.bind(this));
561         else
562             callbackWrapper.call(this, null);
563     },
564
565     _requestId: function(request)
566     {
567         if (!request._extensionRequestId) {
568             request._extensionRequestId = ++this._lastRequestId;
569             this._requests[request._extensionRequestId] = request;
570         }
571         return request._extensionRequestId;
572     },
573
574     _requestById: function(id)
575     {
576         return this._requests[id];
577     },
578
579     _onAddAuditCategory: function(message, port)
580     {
581         var category = new WebInspector.ExtensionAuditCategory(port._extensionOrigin, message.id, message.displayName, message.resultCount);
582         if (WebInspector.panel("audits").getCategory(category.id))
583             return this._status.E_EXISTS(category.id);
584         this._clientObjects[message.id] = category;
585         // FIXME: register module manager extension instead of waking up audits module.
586         WebInspector.panel("audits").addCategory(category);
587     },
588
589     _onAddAuditResult: function(message)
590     {
591         var auditResult = this._clientObjects[message.resultId];
592         if (!auditResult)
593             return this._status.E_NOTFOUND(message.resultId);
594         try {
595             auditResult.addResult(message.displayName, message.description, message.severity, message.details);
596         } catch (e) {
597             return e;
598         }
599         return this._status.OK();
600     },
601
602     _onUpdateAuditProgress: function(message)
603     {
604         var auditResult = this._clientObjects[message.resultId];
605         if (!auditResult)
606             return this._status.E_NOTFOUND(message.resultId);
607         auditResult.updateProgress(Math.min(Math.max(0, message.progress), 1));
608     },
609
610     _onStopAuditCategoryRun: function(message)
611     {
612         var auditRun = this._clientObjects[message.resultId];
613         if (!auditRun)
614             return this._status.E_NOTFOUND(message.resultId);
615         auditRun.done();
616     },
617
618     _onForwardKeyboardEvent: function(message)
619     {
620         const Esc = "U+001B";
621
622         if (!message.ctrlKey && !message.altKey && !message.metaKey && !/^F\d+$/.test(message.keyIdentifier) && message.keyIdentifier !== Esc)
623             return;
624         // Fool around closure compiler -- it has its own notion of both KeyboardEvent constructor
625         // and initKeyboardEvent methods and overriding these in externs.js does not have effect.
626         var event = new window.KeyboardEvent(message.eventType, {
627             keyIdentifier: message.keyIdentifier,
628             location: message.location,
629             ctrlKey: message.ctrlKey,
630             altKey: message.altKey,
631             shiftKey: message.shiftKey,
632             metaKey: message.metaKey
633         });
634         document.dispatchEvent(event);
635     },
636
637     _dispatchCallback: function(requestId, port, result)
638     {
639         if (requestId)
640             port.postMessage({ command: "callback", requestId: requestId, result: result });
641     },
642
643     _initExtensions: function()
644     {
645         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ConsoleMessageAdded,
646             WebInspector.console, WebInspector.ConsoleModel.Events.MessageAdded, this._notifyConsoleMessageAdded);
647         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.NetworkRequestFinished,
648             WebInspector.networkManager, WebInspector.NetworkManager.EventTypes.RequestFinished, this._notifyRequestFinished);
649         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ResourceAdded,
650             WebInspector.workspace,
651             WebInspector.Workspace.Events.UISourceCodeAdded,
652             this._notifyResourceAdded);
653
654         /**
655          * @this {WebInspector.ExtensionServer}
656          */
657         function onElementsSubscriptionStarted()
658         {
659             WebInspector.notifications.addEventListener(WebInspector.NotificationService.Events.SelectedNodeChanged, this._notifyElementsSelectionChanged, this);
660         }
661
662         /**
663          * @this {WebInspector.ExtensionServer}
664          */
665         function onElementsSubscriptionStopped()
666         {
667             WebInspector.notifications.removeEventListener(WebInspector.NotificationService.Events.SelectedNodeChanged, this._notifyElementsSelectionChanged, this);
668         }
669
670         this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.PanelObjectSelected + "elements",
671             onElementsSubscriptionStarted.bind(this), onElementsSubscriptionStopped.bind(this));
672
673         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.PanelObjectSelected + "sources",
674             WebInspector.notifications,
675             WebInspector.SourceFrame.Events.SelectionChanged,
676             this._notifySourceFrameSelectionChanged);
677         this._registerResourceContentCommittedHandler(this._notifyUISourceCodeContentCommitted);
678
679         /**
680          * @this {WebInspector.ExtensionServer}
681          */
682         function onTimelineSubscriptionStarted()
683         {
684             WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded,
685                 this._notifyTimelineEventRecorded, this);
686             WebInspector.timelineManager.start();
687         }
688
689         /**
690          * @this {WebInspector.ExtensionServer}
691          */
692         function onTimelineSubscriptionStopped()
693         {
694             WebInspector.timelineManager.stop();
695             WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded,
696                 this._notifyTimelineEventRecorded, this);
697         }
698
699         this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.TimelineEventRecorded,
700             onTimelineSubscriptionStarted.bind(this), onTimelineSubscriptionStopped.bind(this));
701
702         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged,
703             this._inspectedURLChanged, this);
704
705         InspectorExtensionRegistry.getExtensionsAsync();
706     },
707
708     /**
709      * @param {!WebInspector.TextRange} textRange
710      */
711     _makeSourceSelection: function(textRange)
712     {
713         var sourcesPanel = WebInspector.inspectorView.panel("sources");
714         var selection = {
715             startLine: textRange.startLine,
716             startColumn: textRange.startColumn,
717             endLine: textRange.endLine,
718             endColumn: textRange.endColumn,
719             url: sourcesPanel.tabbedEditorContainer.currentFile().uri()
720         };
721
722         return selection;
723     },
724
725     _notifySourceFrameSelectionChanged: function(event)
726     {
727         this._postNotification(WebInspector.extensionAPI.Events.PanelObjectSelected + "sources", this._makeSourceSelection(event.data));
728     },
729
730     _notifyConsoleMessageAdded: function(event)
731     {
732         this._postNotification(WebInspector.extensionAPI.Events.ConsoleMessageAdded, this._makeConsoleMessage(event.data));
733     },
734
735     _notifyResourceAdded: function(event)
736     {
737         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
738         this._postNotification(WebInspector.extensionAPI.Events.ResourceAdded, this._makeResource(uiSourceCode));
739     },
740
741     _notifyUISourceCodeContentCommitted: function(event)
742     {
743         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode);
744         var content = /** @type {string} */ (event.data.content);
745         this._postNotification(WebInspector.extensionAPI.Events.ResourceContentCommitted, this._makeResource(uiSourceCode), content);
746     },
747
748     _notifyRequestFinished: function(event)
749     {
750         var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
751         this._postNotification(WebInspector.extensionAPI.Events.NetworkRequestFinished, this._requestId(request), (new WebInspector.HAREntry(request)).build());
752     },
753
754     _notifyElementsSelectionChanged: function()
755     {
756         this._postNotification(WebInspector.extensionAPI.Events.PanelObjectSelected + "elements");
757     },
758
759     _notifyTimelineEventRecorded: function(event)
760     {
761         this._postNotification(WebInspector.extensionAPI.Events.TimelineEventRecorded, event.data);
762     },
763
764     /**
765      * @param {!Array.<!ExtensionDescriptor>} extensionInfos
766      */
767     addExtensions: function(extensionInfos)
768     {
769         extensionInfos.forEach(this._addExtension, this);
770     },
771
772     /**
773      * @param {!ExtensionDescriptor} extensionInfo
774      */
775     _addExtension: function(extensionInfo)
776     {
777         const urlOriginRegExp = new RegExp("([^:]+:\/\/[^/]*)\/"); // Can't use regexp literal here, MinJS chokes on it.
778         var startPage = extensionInfo.startPage;
779         var name = extensionInfo.name;
780
781         try {
782             var originMatch = urlOriginRegExp.exec(startPage);
783             if (!originMatch) {
784                 console.error("Skipping extension with invalid URL: " + startPage);
785                 return false;
786             }
787             var extensionOrigin = originMatch[1];
788             if (!this._registeredExtensions[extensionOrigin]) {
789                 // See ExtensionAPI.js for details.
790                 InspectorFrontendHost.setInjectedScriptForOrigin(extensionOrigin, buildExtensionAPIInjectedScript(extensionInfo));
791                 this._registeredExtensions[extensionOrigin] = { name: name };
792             }
793             var iframe = document.createElement("iframe");
794             iframe.src = startPage;
795             iframe.style.display = "none";
796             document.body.appendChild(iframe);
797         } catch (e) {
798             console.error("Failed to initialize extension " + startPage + ":" + e);
799             return false;
800         }
801         return true;
802     },
803
804     _registerExtension: function(origin, port)
805     {
806         if (!this._registeredExtensions.hasOwnProperty(origin)) {
807             if (origin !== window.location.origin) // Just ignore inspector frames.
808                 console.error("Ignoring unauthorized client request from " + origin);
809             return;
810         }
811         port._extensionOrigin = origin;
812         port.addEventListener("message", this._onmessage.bind(this), false);
813         port.start();
814     },
815
816     _onWindowMessage: function(event)
817     {
818         if (event.data === "registerExtension")
819             this._registerExtension(event.origin, event.ports[0]);
820     },
821
822     _onmessage: function(event)
823     {
824         var message = event.data;
825         var result;
826
827         if (message.command in this._handlers)
828             result = this._handlers[message.command](message, event.target);
829         else
830             result = this._status.E_NOTSUPPORTED(message.command);
831
832         if (result && message.requestId)
833             this._dispatchCallback(message.requestId, event.target, result);
834     },
835
836     _registerHandler: function(command, callback)
837     {
838         console.assert(command);
839         this._handlers[command] = callback;
840     },
841
842     _registerSubscriptionHandler: function(eventTopic, onSubscribeFirst, onUnsubscribeLast)
843     {
844         this._subscriptionStartHandlers[eventTopic] = onSubscribeFirst;
845         this._subscriptionStopHandlers[eventTopic] = onUnsubscribeLast;
846     },
847
848     _registerAutosubscriptionHandler: function(eventTopic, eventTarget, frontendEventType, handler)
849     {
850         this._registerSubscriptionHandler(eventTopic,
851             eventTarget.addEventListener.bind(eventTarget, frontendEventType, handler, this),
852             eventTarget.removeEventListener.bind(eventTarget, frontendEventType, handler, this));
853     },
854
855     _registerResourceContentCommittedHandler: function(handler)
856     {
857         /**
858          * @this {WebInspector.ExtensionServer}
859          */
860         function addFirstEventListener()
861         {
862             WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, handler, this);
863             WebInspector.workspace.setHasResourceContentTrackingExtensions(true);
864         }
865
866         /**
867          * @this {WebInspector.ExtensionServer}
868          */
869         function removeLastEventListener()
870         {
871             WebInspector.workspace.setHasResourceContentTrackingExtensions(false);
872             WebInspector.workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, handler, this);
873         }
874
875         this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.ResourceContentCommitted,
876             addFirstEventListener.bind(this),
877             removeLastEventListener.bind(this));
878     },
879
880     _expandResourcePath: function(extensionPath, resourcePath)
881     {
882         if (!resourcePath)
883             return;
884         return extensionPath + this._normalizePath(resourcePath);
885     },
886
887     _normalizePath: function(path)
888     {
889         var source = path.split("/");
890         var result = [];
891
892         for (var i = 0; i < source.length; ++i) {
893             if (source[i] === ".")
894                 continue;
895             // Ignore empty path components resulting from //, as well as a leading and traling slashes.
896             if (source[i] === "")
897                 continue;
898             if (source[i] === "..")
899                 result.pop();
900             else
901                 result.push(source[i]);
902         }
903         return "/" + result.join("/");
904     },
905
906     /**
907      * @param {string} expression
908      * @param {boolean} exposeCommandLineAPI
909      * @param {boolean} returnByValue
910      * @param {?Object} options
911      * @param {string} securityOrigin
912      * @param {function(?string, !RuntimeAgent.RemoteObject, boolean=)} callback
913      * @return {!WebInspector.ExtensionStatus.Record|undefined}
914      */
915     evaluate: function(expression, exposeCommandLineAPI, returnByValue, options, securityOrigin, callback)
916     {
917         var contextId;
918
919         /**
920          * @param {string} url
921          * @return {boolean}
922          */
923         function resolveURLToFrame(url)
924         {
925             var found;
926             function hasMatchingURL(frame)
927             {
928                 found = (frame.url === url) ? frame : null;
929                 return found;
930             }
931             WebInspector.resourceTreeModel.frames().some(hasMatchingURL);
932             return found;
933         }
934
935         if (typeof options === "object") {
936             var frame = options.frameURL ? resolveURLToFrame(options.frameURL) : WebInspector.resourceTreeModel.mainFrame;
937             if (!frame) {
938                 if (options.frameURL)
939                     console.warn("evaluate: there is no frame with URL " + options.frameURL);
940                 else
941                     console.warn("evaluate: the main frame is not yet available");
942                 return this._status.E_NOTFOUND(options.frameURL || "<top>");
943             }
944
945             var contextSecurityOrigin;
946             if (options.useContentScriptContext)
947                 contextSecurityOrigin = securityOrigin;
948             else if (options.scriptExecutionContext)
949                 contextSecurityOrigin = options.scriptExecutionContext;
950
951             var frameContextList = WebInspector.runtimeModel.contextListByFrame(frame);
952             var context;
953             if (contextSecurityOrigin) {
954                 context = frameContextList.contextBySecurityOrigin(contextSecurityOrigin);
955                 if (!context) {
956                     console.warn("The JavaScript context " + contextSecurityOrigin + " was not found in the frame " + frame.url)
957                     return this._status.E_NOTFOUND(contextSecurityOrigin)
958                 }
959             } else {
960                 context = frameContextList.mainWorldContext();
961                 if (!context)
962                     return this._status.E_FAILED(frame.url + " has no execution context");
963             }
964
965             contextId = context.id;
966         }
967         RuntimeAgent.evaluate(expression, "extension", exposeCommandLineAPI, true, contextId, returnByValue, false, callback);
968     }
969 }
970
971 /**
972  * @constructor
973  * @param {string} name
974  * @param {string} title
975  * @param {!WebInspector.Panel} panel
976  * @implements {WebInspector.PanelDescriptor}
977  */
978 WebInspector.ExtensionServerPanelDescriptor = function(name, title, panel)
979 {
980     this._name = name;
981     this._title = title;
982     this._panel = panel;
983 }
984
985 WebInspector.ExtensionServerPanelDescriptor.prototype = {
986     /**
987      * @return {string}
988      */
989     name: function()
990     {
991         return this._name;
992     },
993
994     /**
995      * @return {string}
996      */
997     title: function()
998     {
999         return this._title;
1000     },
1001
1002     /**
1003      * @return {!WebInspector.Panel}
1004      */
1005     panel: function()
1006     {
1007         return this._panel;
1008     }
1009 }
1010
1011 /**
1012  * @constructor
1013  */
1014 WebInspector.ExtensionStatus = function()
1015 {
1016     /**
1017      * @param {string} code
1018      * @param {string} description
1019      * @return {!WebInspector.ExtensionStatus.Record}
1020      */
1021     function makeStatus(code, description)
1022     {
1023         var details = Array.prototype.slice.call(arguments, 2);
1024         var status = { code: code, description: description, details: details };
1025         if (code !== "OK") {
1026             status.isError = true;
1027             console.log("Extension server error: " + String.vsprintf(description, details));
1028         }
1029         return status;
1030     }
1031
1032     this.OK = makeStatus.bind(null, "OK", "OK");
1033     this.E_EXISTS = makeStatus.bind(null, "E_EXISTS", "Object already exists: %s");
1034     this.E_BADARG = makeStatus.bind(null, "E_BADARG", "Invalid argument %s: %s");
1035     this.E_BADARGTYPE = makeStatus.bind(null, "E_BADARGTYPE", "Invalid type for argument %s: got %s, expected %s");
1036     this.E_NOTFOUND = makeStatus.bind(null, "E_NOTFOUND", "Object not found: %s");
1037     this.E_NOTSUPPORTED = makeStatus.bind(null, "E_NOTSUPPORTED", "Object does not support requested operation: %s");
1038     this.E_PROTOCOLERROR = makeStatus.bind(null, "E_PROTOCOLERROR", "Inspector protocol error: %s");
1039     this.E_FAILED = makeStatus.bind(null, "E_FAILED", "Operation failed: %s");
1040 }
1041
1042 /**
1043  * @typedef {{code: string, description: string, details: !Array.<*>}}
1044  */
1045 WebInspector.ExtensionStatus.Record;
1046
1047 WebInspector.extensionAPI = {};
1048 defineCommonExtensionSymbols(WebInspector.extensionAPI);
1049
1050 importScript("ExtensionPanel.js");
1051 importScript("ExtensionView.js");