Repository: microsoft/vscode-edge-devtools
Pull request #72 (2019-07-12 18:22:27)

Adding placeholder code for openInEditor function
This PR is the first step in the feature to open links from the Elements tool in the VS Code editor (see #55). It adds patch code so that the Elements tool will call into the new openInEditor() function when a user clicks a revealer link. The openInEditor() function sends a message to the extension which is where we will actually open the file. The actual open will be implemented in a later PR.

* Added patch for calling openInEditor from the Elements tool
* Added plumbing for passing request to the extension
* Added placeholder for actual opening functionality
* Updated tests


Patch:
diff --git a/src/common/webviewEvents.ts b/src/common/webviewEvents.ts
index edc82dc..31b662d 100644
--- a/src/common/webviewEvents.ts
+++ b/src/common/webviewEvents.ts
@@ -1,10 +1,11 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
-export type WebviewEvent = "getState" | "getUrl" | "ready" | "setState" | "telemetry" | "websocket";
+export type WebviewEvent = "getState" | "getUrl" | "openInEditor" | "ready" | "setState" | "telemetry" | "websocket";
 export const webviewEventNames: WebviewEvent[] = [
     "getState",
     "getUrl",
+    "openInEditor",
     "ready",
     "setState",
     "telemetry",
diff --git a/src/devtoolsPanel.ts b/src/devtoolsPanel.ts
index 24002c6..f38f956 100644
--- a/src/devtoolsPanel.ts
+++ b/src/devtoolsPanel.ts
@@ -49,6 +49,7 @@ export class DevToolsPanel {
         this.panelSocket.on("getState", (msg) => this.onSocketGetState(msg));
         this.panelSocket.on("setState", (msg) => this.onSocketSetState(msg));
         this.panelSocket.on("getUrl", (msg) => this.onSocketGetUrl(msg));
+        this.panelSocket.on("openInEditor", (msg) => this.onSocketOpenInEditor(msg));
 
         // Handle closing
         this.panel.onDidDispose(() => {
@@ -168,6 +169,10 @@ export class DevToolsPanel {
         encodeMessageForChannel((msg) => this.panel.webview.postMessage(msg), "getUrl", { id: request.id, content });
     }
 
+    private async onSocketOpenInEditor(message: string) {
+        // TODO: Parse message and open the requested file
+    }
+
     private update() {
         this.panel.webview.html = this.getHtmlForWebview();
     }
diff --git a/src/host/polyfills/simpleView.ts b/src/host/polyfills/simpleView.ts
index a626a16..c306b63 100644
--- a/src/host/polyfills/simpleView.ts
+++ b/src/host/polyfills/simpleView.ts
@@ -1,10 +1,34 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+import ToolsHost from "../toolsHost";
+
+declare var InspectorFrontendHost: ToolsHost;
+
+interface IRevealable {
+    lineNumber: number;
+    columnNumber: number;
+    uiSourceCode: {
+        _url: string;
+    };
+}
+
+export function revealInVSCode(revealable: IRevealable | undefined, omitFocus: boolean) {
+    if (revealable && revealable.uiSourceCode && revealable.uiSourceCode._url) {
+        InspectorFrontendHost.openInEditor(
+            revealable.uiSourceCode._url,
+            revealable.lineNumber,
+            revealable.columnNumber,
+        );
+    }
+
+    return Promise.resolve();
+}
+
 export function applyCommonRevealerPatch(content: string) {
     return content.replace(
         /Common\.Revealer\.reveal\s*=\s*function\(revealable,\s*omitFocus\)\s*{/g,
-        "Common.Revealer.reveal = function() { Promise.resolve(); return;");
+        `Common.Revealer.reveal = ${revealInVSCode.toString().slice(0, -1)}`);
 }
 
 export function applyInspectorViewPatch(content: string) {
diff --git a/src/host/toolsHost.ts b/src/host/toolsHost.ts
index fc9fe43..d6ea745 100644
--- a/src/host/toolsHost.ts
+++ b/src/host/toolsHost.ts
@@ -68,6 +68,12 @@ export default class ToolsHost {
         });
     }
 
+    public openInEditor(url: string, line: number, column: number) {
+        // Forward the data to the extension
+        const request = { column, line, url };
+        encodeMessageForChannel((msg) => window.parent.postMessage(msg, "*"), "openInEditor", request);
+    }
+
     public onMessageFromChannel(e: WebviewEvent, args: string): boolean {
         switch (e) {
             case "getState": {


Test patch:
diff --git a/src/host/polyfills/simpleView.test.ts b/src/host/polyfills/simpleView.test.ts
index 1a12407..f6085fa 100644
--- a/src/host/polyfills/simpleView.test.ts
+++ b/src/host/polyfills/simpleView.test.ts
@@ -2,11 +2,31 @@
 // Licensed under the MIT License.
 
 describe("simpleView", () => {
+    it("revealInVSCode calls openInEditor", async () => {
+        const apply = await import("./simpleView");
+        const expected = {
+            columnNumber: 0,
+            lineNumber: 0,
+            uiSourceCode: {
+                _url: "http://bing.com",
+            },
+        };
+        const mockOpen = jest.fn();
+        (global as any).InspectorFrontendHost = {
+            openInEditor: mockOpen,
+        };
+
+        await apply.revealInVSCode(expected, false);
+
+        expect(mockOpen).toHaveBeenCalled();
+    });
+
     it("applyCommonRevealerPatch correctly changes text", async () => {
         const apply = await import("./simpleView");
         const result = apply.applyCommonRevealerPatch(
             "Common.Revealer.reveal = function(revealable, omitFocus) { // code");
-        expect(result).toEqual("Common.Revealer.reveal = function() { Promise.resolve(); return; // code");
+        expect(result).toEqual(
+            expect.stringContaining("Common.Revealer.reveal = function revealInVSCode(revealable, omitFocus) {"));
     });
 
     it("applyInspectorViewPatch correctly changes text", async () => {
diff --git a/src/host/toolsHost.test.ts b/src/host/toolsHost.test.ts
index 37dc1e4..821d3ee 100644
--- a/src/host/toolsHost.test.ts
+++ b/src/host/toolsHost.test.ts
@@ -191,6 +191,32 @@ describe("toolsHost", () => {
         });
     });
 
+    describe("openInEditor", () => {
+        it("calls across to extension", async () => {
+            const { default: toolsHost } = await import("./toolsHost");
+            const host = new toolsHost();
+
+            const expectedRequest = {
+                column: 500,
+                line: 23,
+                url: "webpack://file-to-open.css",
+            };
+            host.openInEditor(expectedRequest.url, expectedRequest.line, expectedRequest.column);
+
+            expect(mockWebviewEvents.encodeMessageForChannel).toHaveBeenCalledWith(
+                expect.any(Function),
+                "openInEditor",
+                expect.objectContaining(expectedRequest),
+            );
+
+            // Ensure that the encoded message is actually passed over to the extension
+            const expectedPostedMessage = "encodedMessage";
+            const postMessage = getFirstCallback(mockWebviewEvents.encodeMessageForChannel);
+            postMessage.callback.call(postMessage.thisObj, expectedPostedMessage);
+            expect(window.parent.postMessage).toHaveBeenCalledWith(expectedPostedMessage, "*");
+        });
+    });
+
     describe("onMessageFromChannel", () => {
         it("calls onResolvedUrlFromChannel on getUrl message", async () => {
             const { default: toolsHost } = await import("./toolsHost");