| Index: Source/devtools/front_end/extensions/ExtensionAPI.js
 | 
| diff --git a/Source/devtools/front_end/extensions/ExtensionAPI.js b/Source/devtools/front_end/extensions/ExtensionAPI.js
 | 
| index 9eb5179f1a4556952a491fe500344287bc9d3499..46fb9cd94d066e8f80f50e5fa316175dc32d77b6 100644
 | 
| --- a/Source/devtools/front_end/extensions/ExtensionAPI.js
 | 
| +++ b/Source/devtools/front_end/extensions/ExtensionAPI.js
 | 
| @@ -50,11 +50,13 @@ function defineCommonExtensionSymbols(apiPrivate)
 | 
|      apiPrivate.Events = {
 | 
|          AuditStarted: "audit-started-",
 | 
|          ButtonClicked: "button-clicked-",
 | 
| +        ContextMenuClicked: "context-menu-clicked-",
 | 
|          PanelObjectSelected: "panel-objectSelected-",
 | 
|          NetworkRequestFinished: "network-request-finished",
 | 
|          OpenResource: "open-resource",
 | 
|          PanelSearch: "panel-search-",
 | 
|          ResourceAdded: "resource-added",
 | 
| +        ResourceContentEdited: "resource-content-edited",
 | 
|          ResourceContentCommitted: "resource-content-committed",
 | 
|          ViewShown: "view-shown-",
 | 
|          ViewHidden: "view-hidden-"
 | 
| @@ -68,6 +70,7 @@ function defineCommonExtensionSymbols(apiPrivate)
 | 
|          CreatePanel: "createPanel",
 | 
|          CreateSidebarPane: "createSidebarPane",
 | 
|          CreateToolbarButton: "createToolbarButton",
 | 
| +        DisplaySearchResults: "displaySearchResults",
 | 
|          EvaluateOnInspectedPage: "evaluateOnInspectedPage",
 | 
|          ForwardKeyboardEvent: "_forwardKeyboardEvent",
 | 
|          GetHAR: "getHAR",
 | 
| @@ -75,11 +78,14 @@ function defineCommonExtensionSymbols(apiPrivate)
 | 
|          GetRequestContent: "getRequestContent",
 | 
|          GetResourceContent: "getResourceContent",
 | 
|          InspectedURLChanged: "inspectedURLChanged",
 | 
| +        NewMimeType: "newMimeType",
 | 
|          OpenResource: "openResource",
 | 
| +        RegisterLanguageService: "registerLanguageService",
 | 
|          Reload: "Reload",
 | 
|          Subscribe: "subscribe",
 | 
|          SetOpenResourceHandler: "setOpenResourceHandler",
 | 
|          SetResourceContent: "setResourceContent",
 | 
| +        SetResourceLineMessages: "setResourceLineMessages",
 | 
|          SetSidebarContent: "setSidebarContent",
 | 
|          SetSidebarHeight: "setSidebarHeight",
 | 
|          SetSidebarPage: "setSidebarPage",
 | 
| @@ -176,10 +182,177 @@ function InspectorExtensionAPI()
 | 
|      this.inspectedWindow = new InspectedWindow();
 | 
|      this.panels = new Panels();
 | 
|      this.network = new Network();
 | 
| +    this.language = new Language();
 | 
|      defineDeprecatedProperty(this, "webInspector", "resources", "network");
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| + * @interface
 | 
| + */
 | 
| +function ServiceProvider(){}
 | 
| +ServiceProvider.prototype = {
 | 
| +    /**
 | 
| +     * @param {string} text
 | 
| +     * @param {{source: string, line: number, column: number}=} location
 | 
| +     * @return {!Promise<string>}
 | 
| +     */
 | 
| +    transpile: function(text, location){ return Promise.reject(); },
 | 
| +    /**
 | 
| +     * @param {{source: string, line: number, column: number}} location
 | 
| +     * @return {!Promise<!Array<{text: string, callback: function(), category: (string|undefined)}>>}
 | 
| +     */
 | 
| +    populateContextMenu: function(location){ return Promise.reject(); },
 | 
| +
 | 
| +    /**
 | 
| +     * @param {{source: string, line: number, column: number}} location
 | 
| +     * @param {string} prefix
 | 
| +     * @return {!Promise<!Array<{text: string, icon: (string|undefined), details: function(): !Promise<{detail: string, description: string}>}>>}
 | 
| +     */
 | 
| +    completions: function(location, prefix) { return Promise.reject(); },
 | 
| +    
 | 
| +    /**
 | 
| +     * @param {string} content
 | 
| +     * @param {{line: number, column: number}} cursor
 | 
| +     * @param {string=} prefix
 | 
| +     * @param {{source: string, line: number, column: number}=} context
 | 
| +     * @return {!Promise<!Array<{text: string, icon: (string|undefined), details: function(): !Promise<{detail: string, description: string}>}>>}
 | 
| +     */
 | 
| +    debuggerCompletions: function(content, cursor, prefix, context) { return Promise.reject(); }
 | 
| +}
 | 
| +
 | 
| +/**
 | 
| + * @constructor
 | 
| + */
 | 
| +function Language() {
 | 
| +}
 | 
| +Language.prototype = {
 | 
| +    /**
 | 
| +     * @param {string} extensionOrName
 | 
| +     * @param {string} mime
 | 
| +     * @param {boolean=} byName
 | 
| +     */
 | 
| +    newMimeType: function(extensionOrName, mime, byName) {
 | 
| +        extensionServer.sendRequest({command: commands.NewMimeType, extensionOrName: extensionOrName, mime: mime, byName: byName});
 | 
| +    },
 | 
| +    
 | 
| +    /**
 | 
| +     * @param {string} name
 | 
| +     * @param {!Array<string>} mimes
 | 
| +     * @param {!ServiceProvider} service
 | 
| +     * @param {?=} simplemode
 | 
| +     */
 | 
| +    register: function(name, mimes, service, simplemode) {
 | 
| +        var capabilities = Object.keys(service);
 | 
| +        if (Object.getPrototypeOf(service) !== Object.getPrototypeOf({})) { //handle classes
 | 
| +            var serviceWalker = service;
 | 
| +            while ((serviceWalker = Object.getPrototypeOf(serviceWalker)) && serviceWalker !== Object.getPrototypeOf({})) {
 | 
| +                capabilities = capabilities.concat(Object.getOwnPropertyNames(serviceWalker));
 | 
| +            }
 | 
| +        }
 | 
| +        capabilities = Array.from(/** @type {!Iterator<string>} */(new Set(capabilities.filter(f => (f !== "constructor") && (typeof service[f] === "function")))));
 | 
| +        
 | 
| +        extensionServer.sendRequest({
 | 
| +            command: commands.RegisterLanguageService,
 | 
| +            name: name,
 | 
| +            mimes: mimes,
 | 
| +            capabilities: capabilities,
 | 
| +            simplemode: simplemode
 | 
| +        });
 | 
| +
 | 
| +        extensionServer.registerHandler("transpile-"+name, function(request) {
 | 
| +            if (!service.transpile) {
 | 
| +                return extensionServer.sendRequest({command: "callback", requestId: request.requestId, result: request.input});
 | 
| +            }
 | 
| +            service.transpile(request.input, request.location).then(function(transpiled) {
 | 
| +                extensionServer.sendRequest({command: "callback", requestId: request.requestId, result: transpiled});
 | 
| +            })
 | 
| +            .catch(function(err) {
 | 
| +                extensionServer.sendRequest({command: "callback", requestId: request.requestId});
 | 
| +                console.error(err);
 | 
| +            });
 | 
| +        });
 | 
| +
 | 
| +        extensionServer.registerHandler("populateContextMenu-"+name, function(request) {
 | 
| +            if (!service.populateContextMenu) {
 | 
| +                return extensionServer.sendRequest({command: "callback", requestId: request.requestId});
 | 
| +            }
 | 
| +            var eventName ="notify-"+apiPrivate.Events.ContextMenuClicked;
 | 
| +            service.populateContextMenu(request.location).then(function(options) {
 | 
| +                var opts = options.map(function(item, i) { return {text: item.text, id: i, category: item.category} });
 | 
| +                extensionServer.sendRequest({command: "callback", requestId: request.requestId, result: opts});
 | 
| +
 | 
| +                if (extensionServer.hasHandler(eventName)) {
 | 
| +                    extensionServer.unregisterHandler(eventName);
 | 
| +                }
 | 
| +                extensionServer.registerHandler(eventName, handleClick);
 | 
| +
 | 
| +                function handleClick(event) {
 | 
| +                    extensionServer.unregisterHandler(eventName);
 | 
| +                    if (options[event.id] && options[event.id].callback) {
 | 
| +                        options[event.id].callback();
 | 
| +                    }
 | 
| +                }
 | 
| +            })
 | 
| +            .catch(function(err) {
 | 
| +                extensionServer.sendRequest({command: "callback", requestId: request.requestId});
 | 
| +                console.error(err);
 | 
| +            });
 | 
| +        });
 | 
| +        
 | 
| +        function handleCompletionsResponse(request) {
 | 
| +            var commandName = "completionDetails-"+name;
 | 
| +            return function(completions) {
 | 
| +                var comps = completions.map(function(item, i) { return {text: item.text, icon: item.icon, id: i} });
 | 
| +
 | 
| +                extensionServer.sendRequest({command: "callback", requestId: request.requestId, result: comps});
 | 
| +
 | 
| +                if (extensionServer.hasHandler(commandName)) {
 | 
| +                    extensionServer.unregisterHandler(commandName);
 | 
| +                }
 | 
| +                extensionServer.registerHandler(commandName, handlerHover);
 | 
| +
 | 
| +                function handlerHover(event) {
 | 
| +                    if (completions[event.id] && completions[event.id].details) {
 | 
| +                        completions[event.id].details().then(function(details) {
 | 
| +                            extensionServer.sendRequest({command: "callback", requestId: event.requestId, result: details});
 | 
| +                        }).catch(function(err) {
 | 
| +                            extensionServer.sendRequest({command: "callback", requestId: event.requestId});
 | 
| +                            console.error(err);
 | 
| +                        });
 | 
| +                    } else {
 | 
| +                        extensionServer.sendRequest({command: "callback", requestId: event.requestId});
 | 
| +                    }
 | 
| +                }
 | 
| +            }
 | 
| +        }
 | 
| +
 | 
| +        extensionServer.registerHandler("completions-"+name, function(request) {
 | 
| +            if (!service.completions) {
 | 
| +                return extensionServer.sendRequest({command: "callback", requestId: request.requestId});
 | 
| +            }
 | 
| +            service.completions(request.location, request.prefix)
 | 
| +            .then(handleCompletionsResponse(request))
 | 
| +            .catch(function(err) {
 | 
| +                extensionServer.sendRequest({command: "callback", requestId: request.requestId});
 | 
| +                console.error(err);
 | 
| +            });
 | 
| +        });
 | 
| +        
 | 
| +        extensionServer.registerHandler("debuggerCompletions-"+name, function(request) {
 | 
| +            if (!service.debuggerCompletions) {
 | 
| +                return extensionServer.sendRequest({command: "callback", requestId: request.requestId});
 | 
| +            }
 | 
| +            service.debuggerCompletions(request.content, request.cursor, request.prefix, request.context)
 | 
| +            .then(handleCompletionsResponse(request))
 | 
| +            .catch(function(err) {
 | 
| +                extensionServer.sendRequest({command: "callback", requestId: request.requestId});
 | 
| +                console.error(err);
 | 
| +            });
 | 
| +        });
 | 
| +    }
 | 
| +}
 | 
| +
 | 
| +/**
 | 
|   * @constructor
 | 
|   */
 | 
|  function Network()
 | 
| @@ -427,6 +600,9 @@ function SourcesPanel()
 | 
|  }
 | 
|  
 | 
|  SourcesPanel.prototype = {
 | 
| +    displaySearchResults: function(results) {
 | 
| +        extensionServer.sendRequest({command: commands.DisplaySearchResults, results: results});
 | 
| +    },
 | 
|      __proto__: PanelWithSidebar.prototype
 | 
|  }
 | 
|  
 | 
| @@ -706,8 +882,17 @@ function InspectedWindow()
 | 
|          this._fire(new Resource(message.arguments[0]), message.arguments[1]);
 | 
|      }
 | 
|  
 | 
| +    /**
 | 
| +     * @this {EventSinkImpl}
 | 
| +     */
 | 
| +    function dispatchResourceEditEvent(message)
 | 
| +    {
 | 
| +        this._fire(new Resource(message.arguments[0]), message.arguments[1], message.arguments[2]);
 | 
| +    }
 | 
| +
 | 
|      this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
 | 
|      this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
 | 
| +    this.onResourceContentEdited = new EventSink(events.ResourceContentEdited, dispatchResourceEditEvent);
 | 
|  }
 | 
|  
 | 
|  InspectedWindow.prototype = {
 | 
| @@ -794,6 +979,10 @@ ResourceImpl.prototype = {
 | 
|      setContent: function(content, commit, callback)
 | 
|      {
 | 
|          extensionServer.sendRequest({ command: commands.SetResourceContent, url: this._url, content: content, commit: commit }, callback);
 | 
| +    },
 | 
| +
 | 
| +    setLineMessages: function(messages, callback) {
 | 
| +        extensionServer.sendRequest({ command: commands.SetResourceLineMessages, url: this._url, messages: messages }, callback);
 | 
|      }
 | 
|  }
 | 
|  
 | 
| @@ -966,6 +1155,7 @@ function platformExtensionAPI(coreAPI)
 | 
|      chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
 | 
|      chrome.devtools.network = coreAPI.network;
 | 
|      chrome.devtools.panels = coreAPI.panels;
 | 
| +    chrome.devtools.language = coreAPI.language;
 | 
|  
 | 
|      // default to expose experimental APIs for now.
 | 
|      if (extensionInfo.exposeExperimentalAPIs !== false) {
 | 
| 
 |