Repository: microsoft/vscode-edge-devtools
Pull request #1804 (2023-10-24 21:24:22)

User/elormcoch/wsl extension fix
Adapts the launch and debug commands based on whether the extension is run in a remote environment.

ElormCoch: @microsoft-github-policy-service agree company="Microsoft"
vidorteg: /azp run
vidorteg: @microsoft-github-policy-service rerun

Patch:
diff --git a/src/extension.ts b/src/extension.ts
index 8e7257f..4c41e68 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -239,20 +239,15 @@ export function activate(context: vscode.ExtensionContext): void {
         void setCSSMirrorContentEnabled(context, !cssMirrorContent);
     }));
 
-    context.subscriptions.push(vscode.commands.registerCommand(`${SETTINGS_VIEW_NAME}.launchHtml`, (fileUri: vscode.Uri): void => {
+    context.subscriptions.push(vscode.commands.registerCommand(`${SETTINGS_VIEW_NAME}.launchHtml`, async (fileUri: vscode.Uri): Promise<void> => {
         telemetryReporter.sendTelemetryEvent('contextMenu/launchHtml');
-        const edgeDebugConfig = providedHeadlessDebugConfig;
-        const devToolsAttachConfig = providedLaunchDevToolsConfig;
-        edgeDebugConfig.url = `file://${fileUri.fsPath}`;
-        devToolsAttachConfig.url = `file://${fileUri.fsPath}`;
-        void vscode.debug.startDebugging(undefined, edgeDebugConfig).then(() => vscode.debug.startDebugging(undefined, devToolsAttachConfig));
+        await launchHtml(fileUri);
     }));
 
-    context.subscriptions.push(vscode.commands.registerCommand(`${SETTINGS_VIEW_NAME}.launchScreencast`, (fileUri: vscode.Uri): void => {
+
+    context.subscriptions.push(vscode.commands.registerCommand(`${SETTINGS_VIEW_NAME}.launchScreencast`, async (fileUri: vscode.Uri): Promise<void> => {
         telemetryReporter.sendTelemetryEvent('contextMenu/launchScreencast');
-        const edgeDebugConfig = providedHeadlessDebugConfig;
-        edgeDebugConfig.url = `file://${fileUri.fsPath}`;
-        void vscode.debug.startDebugging(undefined, edgeDebugConfig).then(() => attach(context, fileUri.fsPath, undefined, true, true));
+        await launchScreencast(context, fileUri);
     }));
 
     void vscode.commands.executeCommand('setContext', 'titleCommandsRegistered', true);
@@ -275,6 +270,39 @@ export function activate(context: vscode.ExtensionContext): void {
     });
 }
 
+export async function launchHtml(fileUri: vscode.Uri): Promise<void> {
+    const edgeDebugConfig = providedHeadlessDebugConfig;
+    const devToolsAttachConfig = providedLaunchDevToolsConfig;
+    if (!vscode.env.remoteName) {
+        edgeDebugConfig.url = `file://${fileUri.fsPath}`;
+        devToolsAttachConfig.url = `file://${fileUri.fsPath}`;
+        void vscode.debug.startDebugging(undefined, edgeDebugConfig).then(() => vscode.debug.startDebugging(undefined, devToolsAttachConfig));
+    } else {
+        // Parse the filename from the remoteName, file authority and path e.g. file://wsl.localhost/ubuntu-20.04/test/index.html
+        const url = `file://${vscode.env.remoteName}.localhost/${fileUri.authority.split('+')[1]}/${fileUri.fsPath.replace(/\\/g, '/')}`;
+        edgeDebugConfig.url = url;
+        devToolsAttachConfig.url = url;
+        const { port, userDataDir } = getRemoteEndpointSettings();
+        const browserPath = await getBrowserPath();
+        await launchBrowser(browserPath, port, url, userDataDir, /** headless */ true).then(() => vscode.debug.startDebugging(undefined, devToolsAttachConfig));
+    }
+}
+
+export async function launchScreencast(context: vscode.ExtensionContext, fileUri: vscode.Uri): Promise<void> {
+    const edgeDebugConfig = providedHeadlessDebugConfig;
+    if (!vscode.env.remoteName) {
+        edgeDebugConfig.url = `file://${fileUri.fsPath}`;
+        void vscode.debug.startDebugging(undefined, edgeDebugConfig).then(() => attach(context, fileUri.fsPath, undefined, true, true));
+    } else {
+        // Parse the filename from the remoteName, file authority and path e.g. file://wsl.localhost/ubuntu-20.04/test/index.html
+        const url = `file://${vscode.env.remoteName}.localhost/${fileUri.authority.split('+')[1]}/${fileUri.fsPath.replace(/\\/g, '/')}`;
+        edgeDebugConfig.url = url;
+        const { port, userDataDir } = getRemoteEndpointSettings();
+        const browserPath = await getBrowserPath();
+        await launchBrowser(browserPath, port,  url, userDataDir, /** headless */ true).then(() => attach(context, url, undefined, true, true));
+    }
+}
+
 function startWebhint(context: vscode.ExtensionContext): void {
     const args = [context.globalStoragePath, languageServerName];
     const module = context.asAbsolutePath('node_modules/vscode-webhint/dist/src/server.js');
diff --git a/src/utils.ts b/src/utils.ts
index ae539db..d43035a 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -390,8 +390,9 @@ export async function getBrowserPath(config: Partial<IUserConfig> = {}): Promise
  * @param port The port on which to enable remote debugging
  * @param targetUrl The url of the page to open
  * @param userDataDir The user data directory for the launched instance
+ * @param forceHeadless This force overrides the --headless arg for browser launch
  */
-export async function launchBrowser(browserPath: string, port: number, targetUrl: string, userDataDir?: string): Promise<puppeteer.Browser> {
+export async function launchBrowser(browserPath: string, port: number, targetUrl: string, userDataDir?: string, forceHeadless?: boolean): Promise<puppeteer.Browser> {
     const args = [
         '--no-first-run',
         '--no-default-browser-check',
@@ -399,7 +400,7 @@ export async function launchBrowser(browserPath: string, port: number, targetUrl
         targetUrl,
     ];
 
-    const headless: boolean = isHeadlessEnabled();
+    const headless: boolean = forceHeadless ?? isHeadlessEnabled();
 
     let browserArgs: string[] = getBrowserArgs();
     browserArgs = browserArgs.filter(arg => !arg.startsWith('--remote-debugging-port') && arg !== targetUrl);


Test patch:
diff --git a/test/extension.test.ts b/test/extension.test.ts
index 3d1e6a7..9ea50e2 100644
--- a/test/extension.test.ts
+++ b/test/extension.test.ts
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
-import { ExtensionContext } from "vscode";
+import { ExtensionContext, Uri} from "vscode";
 import TelemetryReporter from "vscode-extension-telemetry";
 import { createFakeExtensionContext, createFakeTelemetryReporter, createFakeVSCode, Mocked } from "./helpers/helpers";
 import {
@@ -396,6 +396,8 @@ describe("extension", () => {
         let mockReporter: Mocked<Readonly<TelemetryReporter>>;
         let mockUtils: Partial<Mocked<typeof import("../src/utils")>>;
         let mockPanel: Partial<Mocked<typeof import("../src/devtoolsPanel")>>;
+        let startDebuggingMock: jest.Mock;
+        let mockVSCode: any;
 
         beforeEach(() => {
             mockReporter = createFakeTelemetryReporter();
@@ -409,6 +411,7 @@ describe("extension", () => {
                     port: "port",
                     timeout: 10000,
                     useHttps: false,
+                    userDataDir: "profile"
                 }),
                 getSupportedStaticAnalysisFileTypes: jest.fn(),
                 getRuntimeConfig: jest.fn().mockReturnValue(fakeRuntimeConfig),
@@ -428,13 +431,68 @@ describe("extension", () => {
                     createOrShow: jest.fn(),
                 } as any,
             };
+            mockVSCode = createFakeVSCode();
+            startDebuggingMock = mockVSCode.debug.startDebugging;
 
-            jest.doMock("vscode", () => createFakeVSCode(), { virtual: true });
+            jest.doMock("vscode", () => mockVSCode, { virtual: true });
             jest.doMock("../src/utils", () => mockUtils);
             jest.doMock("../src/devtoolsPanel", () => mockPanel);
             jest.resetModules();
         });
 
+
+        it("can launch html files in remote (wsl) context", async () => {
+            const expectedRemoteName = 'wsl';
+            const testFileUri = {
+                scheme: 'vscode-remote',
+                authority: 'wsl+ubuntu-20.04',
+                fsPath: 'test/path.html',
+                query: '',
+                fragment: ''
+            } as Uri;
+
+            mockVSCode.env.remoteName = expectedRemoteName;
+
+            const expectedUrl = `file://${expectedRemoteName}.localhost/ubuntu-20.04/test/path.html`;
+
+            const newExtension = await import("../src/extension");
+            await newExtension.launchHtml(testFileUri);
+
+            expect(mockUtils.getRemoteEndpointSettings).toHaveBeenCalled()
+            expect(mockUtils.getBrowserPath).toHaveBeenCalled();
+            expect(mockUtils.launchBrowser).toHaveBeenCalledWith(
+                expect.any(String) /** browserPath */,
+                expect.any(String) /** port */,
+                expectedUrl /** targetUrl */,
+                expect.any(String) /** userDataDir */,
+                expect.any(Boolean) /** headlessOverride */
+            );
+            expect(startDebuggingMock).toHaveBeenCalledWith(undefined, expect.objectContaining({
+                url: expectedUrl
+            }));
+        });
+
+        it("can launch html files in non-remote contexts", async () => {
+            mockVSCode.env.remoteName = undefined;
+            const testFileUri = {
+                scheme: 'file',
+                authority: '',
+                fsPath: 'test/path.html',
+                query: '',
+                fragment: ''
+            } as Uri;
+            const expectedUrl = `file://test/path.html`;
+
+            const newExtension = await import("../src/extension");
+            await newExtension.launchHtml(testFileUri);
+            expect(startDebuggingMock).toHaveBeenNthCalledWith(1, undefined, expect.objectContaining({
+                url: expectedUrl
+            }));
+            expect(startDebuggingMock).toHaveBeenNthCalledWith(2, undefined, expect.objectContaining({
+                url: expectedUrl
+            }));
+        });
+
         it("calls launch on launch command", async () => {
             const vscode = jest.requireMock("vscode");
             const context = createFakeExtensionContext();
diff --git a/test/helpers/helpers.ts b/test/helpers/helpers.ts
index d3824a4..17b5746 100644
--- a/test/helpers/helpers.ts
+++ b/test/helpers/helpers.ts
@@ -61,7 +61,8 @@ export function createFakeVSCode() {
             activeDebugSession: {
                 id: 'vscode-session-debug-id',
                 name: 'someName',
-            }
+            },
+            startDebugging: jest.fn().mockResolvedValue(true),
         },
         extensions: {
             getExtension: jest.fn().mockReturnValue({ 
@@ -73,6 +74,7 @@ export function createFakeVSCode() {
         env: {
             clipboard: { writeText: jest.fn() },
             machineId: "someValue.machineId",
+            remoteName: '',
         },
         languages: {
             createDiagnosticCollection: jest.fn(),