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