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