Repository: microsoft/vscode-edge-devtools
Pull request #405 (2021-06-03 01:16:31)

[Telemetry]: Patching in DevTools telemetry events for screencast duration and toggle + change default toggle state based on headless settings
Couple of telemetry / screencast related changes in this PR.

- `applyScreencastHeadlessPatch(...)`
    - This modifies ScreencastApp constructor to show/hide screencast based on whether the extension's headless mode is enabled or disabled.
    - Needed to pipe through the headless extension setting into the `vscodeObject` so we can access it in the DevTools code.
    - This also helps set up a `_startTime` property to properly record session duration for `DevTools.ScreencastDuration`
- `applyScreencastTelemetry(...)`
    - This patch injects two telemetry events that are fired upon toggling the screencast button
        - `DevTools.ScreencastToggle`
            - This fires on each button press and shows whether the view is shown / hidden
                - Show: `devtools/DevTools.ScreencastToggle: {"DevTools.ScreencastToggle.actionCode":"1"}`
                - Hide: `devtools/DevTools.ScreencastToggle: {"DevTools.ScreencastToggle.actionCode":"0"}`
        - `DevTools.ScreencastDuration`
            - This fires when the screencast is toggled off and shows the session time in ms
                - `devtools/DevTools.ScreencastDuration: undefined, {"DevTools.ScreencastDuration.duration":1001.1349999113008}` 


Patch:
diff --git a/postBuildStep.ts b/postBuildStep.ts
index 326ae1e..f2a0970 100644
--- a/postBuildStep.ts
+++ b/postBuildStep.ts
@@ -35,8 +35,10 @@ import {
     applyContextMenuRevealOption,
     applyRemovePreferencePatch,
     applyScreencastAppPatch,
-    applyScreencastRepaintPatch,
     applyScreencastCursorPatch,
+    applyScreencastTelemetry,
+    applyScreencastHeadlessPatch,
+    applyScreencastRepaintPatch,
     applySetTabIconPatch,
     applyShowDrawerTabs,
     applyStylesRevealerPatch,
@@ -134,6 +136,8 @@ async function patchFilesForWebView(toolsOutDir: string) {
     ]);
     await patchFileForWebViewWrapper('screencast/screencast.js', toolsOutDir, [
         applyScreencastCursorPatch,
+        applyScreencastTelemetry,
+        applyScreencastHeadlessPatch,
         applyScreencastRepaintPatch,
     ]);
     await patchFileForWebViewWrapper('ui/ui.js', toolsOutDir, [
diff --git a/src/common/settingsProvider.ts b/src/common/settingsProvider.ts
index e1bf841..bddef34 100644
--- a/src/common/settingsProvider.ts
+++ b/src/common/settingsProvider.ts
@@ -26,6 +26,12 @@ export class SettingsProvider {
     return whatsNewEnabled;
   }
 
+  getHeadlessSettings(): boolean {
+    const settings = vscode.workspace.getConfiguration(SETTINGS_STORE_NAME);
+    const isHeadless: boolean = settings.get('headless') || false;
+    return isHeadless;
+  }
+
   static get instance(): SettingsProvider {
     if (!SettingsProvider.singletonInstance) {
       SettingsProvider.singletonInstance = new SettingsProvider();
diff --git a/src/devtoolsPanel.ts b/src/devtoolsPanel.ts
index ca908b4..72bfaaf 100644
--- a/src/devtoolsPanel.ts
+++ b/src/devtoolsPanel.ts
@@ -219,6 +219,7 @@ export class DevToolsPanel {
             enableNetwork: SettingsProvider.instance.isNetworkEnabled(),
             themeString: SettingsProvider.instance.getThemeSettings(),
             whatsNew: SettingsProvider.instance.getWhatsNewSettings(),
+            isHeadless: SettingsProvider.instance.getHeadlessSettings(),
             id });
     }
 
diff --git a/src/host/polyfills/simpleView.ts b/src/host/polyfills/simpleView.ts
index 7b8bc60..4b15bdb 100644
--- a/src/host/polyfills/simpleView.ts
+++ b/src/host/polyfills/simpleView.ts
@@ -443,3 +443,28 @@ export function applyScreencastCursorPatch(content: string): string | null {
     const replacementText = "this._canvasContainerElement.style.cursor = 'unset';";
     return replaceInSourceCode(content, pattern, replacementText, KeepMatchedText.InFront);
 }
+
+export function applyScreencastHeadlessPatch(content: string): string | null {
+    // This patch will toggle the DevTools screencast on or off based on if the user is using headless or non-headless mode.
+    // This patch also marks a time stamp if the setting is enabled.
+    const pattern = /this\._enabledSetting\s*=\s*Settings\.Settings\.instance\(\)\.createSetting\('screencastEnabled',\s*true\);/g;
+    const replacementText = "const isHeadless = Root.Runtime.vscodeSettings.isHeadless; this._enabledSetting.set(isHeadless); this._startTime = isHeadless ? performance.now() : null;";
+    return replaceInSourceCode(content, pattern, replacementText, KeepMatchedText.InFront);
+}
+
+export function applyScreencastTelemetry(content: string): string | null {
+    // This patch will add a telemetry event inside the DevTools that tracks screencast toggle and duration
+    const pattern = /const enabled\s*=\s*!this\._toggleButton\.toggled\(\);/g;
+    const replacementText = `
+    if (enabled) {
+        InspectorFrontendHost.InspectorFrontendHostInstance.recordEnumeratedHistogram('DevTools.ScreencastToggle', 1, 2);
+        this._startTime = performance.now();
+    } else {
+        InspectorFrontendHost.InspectorFrontendHostInstance.recordEnumeratedHistogram('DevTools.ScreencastToggle', 0, 2);
+        if (this._startTime) {
+            const sessionDuration = performance.now() - this._startTime;
+            InspectorFrontendHost.InspectorFrontendHostInstance.recordPerformanceHistogram('DevTools.ScreencastDuration', sessionDuration);
+        }
+    }`;
+    return replaceInSourceCode(content, pattern, replacementText, KeepMatchedText.InFront);
+}
diff --git a/src/host/toolsHost.ts b/src/host/toolsHost.ts
index 40ab6e4..95f42dc 100644
--- a/src/host/toolsHost.ts
+++ b/src/host/toolsHost.ts
@@ -161,6 +161,7 @@ export class ToolsHost {
             enableNetwork: vscodeObject.enableNetwork,
             theme,
             whatsNew: vscodeObject.whatsNew,
+            isHeadless: vscodeObject.isHeadless,
         });
     }
 


Test patch:
diff --git a/test/devtoolsPanel.test.ts b/test/devtoolsPanel.test.ts
index 06f38e5..92b777d 100644
--- a/test/devtoolsPanel.test.ts
+++ b/test/devtoolsPanel.test.ts
@@ -484,7 +484,7 @@ describe("devtoolsPanel", () => {
 
             it("calls getVscodeSettings", async () => {
                 const expectedId = { id: 0 };
-                const expectedState = { enableNetwork: true, themeString: "System preference", whatsNew: true};
+                const expectedState = { enableNetwork: true, themeString: "System preference", whatsNew: true, isHeadless: false};
                 (context.workspaceState.get as jest.Mock).mockReturnValue(expectedState);
 
                 const dtp = await import("../src/devtoolsPanel");
@@ -498,6 +498,7 @@ describe("devtoolsPanel", () => {
                         themeString: expectedState.themeString,
                         enableNetwork: expectedState.enableNetwork,
                         whatsNew: expectedState.whatsNew,
+                        isHeadless: expectedState.isHeadless,
                         id: expectedId.id,
                     },
                 );
diff --git a/test/helpers/helpers.ts b/test/helpers/helpers.ts
index a2ac960..e27d269 100644
--- a/test/helpers/helpers.ts
+++ b/test/helpers/helpers.ts
@@ -79,6 +79,8 @@ export function createFakeVSCode() {
                             return "System preference";
                         case "whatsNew":
                             return true;
+                        case "isHeadless":
+                            return false;
                         default:
                             return undefined;
                     }
diff --git a/test/host/polyfills/simpleView.test.ts b/test/host/polyfills/simpleView.test.ts
index 338a15e..f5f5517 100644
--- a/test/host/polyfills/simpleView.test.ts
+++ b/test/host/polyfills/simpleView.test.ts
@@ -295,11 +295,27 @@ describe("simpleView", () => {
         testPatch(filePath, patch, expectedStrings);
     });
 
-    it("applyScreencastCursorPatch correctly changes inspector.js text to remove touch cursor", async () => {
+    it("applyScreencastCursorPatch correctly changes screencast.js text to remove touch cursor", async () => {
         const filePath = "screencast/screencast.js";
         const patch = SimpleView.applyScreencastCursorPatch;
         const expectedStrings = ["this._canvasContainerElement.style.cursor = 'unset';"];
 
         testPatch(filePath, patch, expectedStrings);
     });
+
+    it("applyScreencastHeadlessPatch correctly replaces screencast.js text to toggle screencast based on headless settings", async () => {
+        const filePath = "screencast/screencast.js";
+        const patch = SimpleView.applyScreencastHeadlessPatch;
+        const expectedStrings = ["const isHeadless = Root.Runtime.vscodeSettings.isHeadless;"];
+
+        testPatch(filePath, patch, expectedStrings);
+    });
+
+    it("applyScreencastTelemetry correctly changes screencast.js text to implement screencast telemetry", async () => {
+        const filePath = "screencast/screencast.js";
+        const patch = SimpleView.applyScreencastTelemetry;
+        const expectedStrings = ["DevTools.ScreencastToggle", "DevTools.ScreencastDuration"];
+
+        testPatch(filePath, patch, expectedStrings);
+    });
 });