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