From c992af69e91a5350c3c2ab0d05ca516a8a511b4a Mon Sep 17 00:00:00 2001 From: Vidal Ortega <43078681+vidorteg@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:52:49 -0700 Subject: [PATCH 1/2] Fixing typo --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index b1b1cdbf..98960ee0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -110,7 +110,7 @@ const WIN_APP_DATA = process.env.LOCALAPPDATA || '/'; const msEdgeBrowserMapping: Map = new Map(); // Current Revision: 127.0.2594.0 -export const CDN_FALLBACK_REVISION = '@xxf163ae219c3b08cda5aafa6b262442715a8a9893'; +export const CDN_FALLBACK_REVISION = '@f163ae219c3b08cda5aafa6b262442715a8a9893'; /** Build-specified flags. */ declare const DEBUG: boolean; From 99ca58016abec1d76cc1e12bd9632aaecb948a45 Mon Sep 17 00:00:00 2001 From: Vidal Ortega <43078681+vidorteg@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:49:18 -0700 Subject: [PATCH 2/2] Feature: Sets different fallback chains. (#2431) User has the ability to select the fallback chain by selecting the browser flavor. If the user wants a specific version (browserFlavor) the extension will behave in the same way as before, otherwise the first thing we will try to get is a working version of the tools from the CDN fallback address. This process takes less than 1s, making it faster for most of the users --- src/devtoolsPanel.ts | 95 +++++++++++++++++++++------------- src/utils.ts | 3 ++ src/versionSocketConnection.ts | 2 +- test/devtoolsPanel.test.ts | 2 + test/helpers/helpers.ts | 5 ++ 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/src/devtoolsPanel.ts b/src/devtoolsPanel.ts index 6b82504b..4d11f9fd 100644 --- a/src/devtoolsPanel.ts +++ b/src/devtoolsPanel.ts @@ -57,6 +57,8 @@ export class DevToolsPanel { private collectConsoleMessages = true; private currentRevision: string | undefined; private cssWarningActive: boolean; + private fallbackChain: (() => void)[] = []; + private getFallbackRevisionFunction: (() => void) = () => {}; private constructor( panel: vscode.WebviewPanel, @@ -72,7 +74,7 @@ export class DevToolsPanel { this.config = config; this.timeStart = null; this.devtoolsBaseUri = this.config.devtoolsBaseUri || null; - this.isHeadless = false; + this.isHeadless = SettingsProvider.instance.getHeadlessSettings(); this.cssWarningActive = false; // Hook up the socket events @@ -103,7 +105,12 @@ export class DevToolsPanel { // This Websocket is only used on initial connection to determine the browser version. // The browser version is used to select the correct hashed version of the devtools this.versionDetectionSocket = new BrowserVersionDetectionSocket(this.targetUrl); - this.versionDetectionSocket.on('setCdnParameters', (msg: {revision: string; isHeadless: boolean}) => this.setCdnParameters(msg)); + + // Gets an array of functions that will be tried to get the right Devtools revision. + this.fallbackChain = this.determineVersionFallback(); + if (this.fallbackChain.length > 0) { + this.getFallbackRevisionFunction = this.fallbackChain.pop() || this.getFallbackRevisionFunction; + } // Handle closing this.panel.onDidDispose(() => { @@ -117,8 +124,7 @@ export class DevToolsPanel { // Connection type determined already this.update(); } else { - // Use version socket to determine which Webview/Tools to use - this.versionDetectionSocket.detectVersion(); + this.getFallbackRevisionFunction(); } } }, this, this.disposables); @@ -137,12 +143,52 @@ export class DevToolsPanel { }); } + /** + * Allows multiple fallbacks, allowing the user to select between stability + * or latest features. + * @returns A function array that has the fallback chain. + */ + determineVersionFallback() { + const browserFlavor = this.config.browserFlavor; + const storedRevision = this.context.globalState.get('fallbackRevision') || ''; + const callWrapper = (revision: string) => { + this.setCdnParameters({revision, isHeadless: this.isHeadless}); + }; + + // Use version socket to determine which Webview/Tools to use + const detectedVersion = () => { + this.versionDetectionSocket.on('setCdnParameters', (msg: {revision: string; isHeadless: boolean}) => { + this.setCdnParameters(msg); + }); + + this.versionDetectionSocket.detectVersion.bind(this.versionDetectionSocket)(); + }; + + // we reverse the array so that it behaves like a stack. + switch (browserFlavor) { + case 'Beta': + case 'Canary': + case 'Dev': + case 'Stable': { + return [ detectedVersion, + () => callWrapper(CDN_FALLBACK_REVISION), + () => callWrapper(storedRevision)].reverse(); + } + + case 'Default': + default: { + return [() => callWrapper(CDN_FALLBACK_REVISION), + detectedVersion, + () => callWrapper(storedRevision)].reverse(); + } + } + } + dispose(): void { DevToolsPanel.instance = undefined; this.panel.dispose(); this.panelSocket.dispose(); - this.versionDetectionSocket.dispose(); if (this.timeStart !== null) { const timeEnd = performance.now(); const sessionTime = timeEnd - this.timeStart; @@ -406,41 +452,18 @@ export class DevToolsPanel { private onSocketDevToolsConnection(success: string) { if (success === 'true') { void this.context.globalState.update('fallbackRevision', this.currentRevision); - this.context.globalState.update('retryAttemptToLoadCDN', '1'); + this.fallbackChain = this.determineVersionFallback(); } else { - let retryNumber: number; - try { - retryNumber = parseInt(this.context.globalState.get('retryAttemptToLoadCDN') || '1', 10); - } catch { - retryNumber = 1; - } - - let fallbackRevision; - switch (retryNumber) { - case 1: { - // Always try the latest specified revision first, this will keep it updated. - fallbackRevision = CDN_FALLBACK_REVISION; - this.context.globalState.update('retryAttemptToLoadCDN', ++retryNumber); - break; - } - case 2: { - // Retry connection with latest well known fallback that this environment knows. - fallbackRevision = this.context.globalState.get('fallbackRevision') ?? ''; - this.context.globalState.update('retryAttemptToLoadCDN', ++retryNumber); - break; - } - default: { - // Could not find suitable version. - this.context.globalState.update('retryAttemptToLoadCDN', '1'); - return; - } - } - if (this.currentRevision) { this.telemetryReporter.sendTelemetryEvent('websocket/failedConnection', {revision: this.currentRevision}); } - this.setCdnParameters({revision: fallbackRevision, isHeadless: this.isHeadless}); + // We failed trying to retrieve the specified revision + // we fallback to the next option if available. + if (this.fallbackChain.length > 0) { + this.getFallbackRevisionFunction = this.fallbackChain.pop() || (() => {}); + this.getFallbackRevisionFunction(); + } } } @@ -574,7 +597,7 @@ export class DevToolsPanel { } private setCdnParameters(msg: {revision: string, isHeadless: boolean}) { - this.currentRevision = msg.revision || CDN_FALLBACK_REVISION; + this.currentRevision = msg.revision; this.devtoolsBaseUri = `https://devtools.azureedge.net/serve_file/${this.currentRevision}/vscode_app.html`; this.isHeadless = msg.isHeadless; this.update(); diff --git a/src/utils.ts b/src/utils.ts index 98960ee0..6e4bafc9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -63,6 +63,7 @@ export interface IRuntimeConfig { useLocalEdgeWatch: boolean; devtoolsBaseUri?: string; defaultEntrypoint?: string; + browserFlavor: BrowserFlavor; } export interface IStringDictionary { [name: string]: T; @@ -454,6 +455,7 @@ export function removeTrailingSlash(uri: string): string { export function getRuntimeConfig(config: Partial = {}): IRuntimeConfig { const settings = vscode.workspace.getConfiguration(SETTINGS_STORE_NAME); const pathMapping = config.pathMapping || settings.get('pathMapping') || SETTINGS_DEFAULT_PATH_MAPPING; + const browserFlavor = config.browserFlavor || settings.get('browserFlavor') || 'Default'; const sourceMapPathOverrides = config.sourceMapPathOverrides || settings.get('sourceMapPathOverrides') || SETTINGS_DEFAULT_PATH_OVERRIDES; const webRoot = config.webRoot || settings.get('webRoot') || SETTINGS_DEFAULT_WEB_ROOT; @@ -499,6 +501,7 @@ export function getRuntimeConfig(config: Partial = {}): IRuntimeCon return { pathMapping: resolvedMappingOverrides, sourceMapPathOverrides: resolvedOverrides, + browserFlavor, sourceMaps, webRoot: resolvedWebRoot, isJsDebugProxiedCDPConnection: false, diff --git a/src/versionSocketConnection.ts b/src/versionSocketConnection.ts index 02301bf5..6b67a088 100644 --- a/src/versionSocketConnection.ts +++ b/src/versionSocketConnection.ts @@ -17,7 +17,7 @@ export interface BrowserVersionCdpResponse { } // Minimum supported version of Edge -export const MIN_SUPPORTED_VERSION = '120.0.2210.181'; +export const MIN_SUPPORTED_VERSION = '127.0.2592.0'; export const MIN_SUPPORTED_REVISION = CDN_FALLBACK_REVISION; export class BrowserVersionDetectionSocket extends EventEmitter { diff --git a/test/devtoolsPanel.test.ts b/test/devtoolsPanel.test.ts index 0d62465e..74097f38 100644 --- a/test/devtoolsPanel.test.ts +++ b/test/devtoolsPanel.test.ts @@ -48,6 +48,7 @@ describe("devtoolsPanel", () => { webRoot: "", isJsDebugProxiedCDPConnection: false, useLocalEdgeWatch: false, + browserFlavor: "Default", }; mockPanel = { @@ -173,6 +174,7 @@ describe("devtoolsPanel", () => { describe("update", () => { it("adds attempts to detect browser version only when visible", async () => { const dtp = await import("../src/devtoolsPanel"); + mockRuntimeConfig.browserFlavor = 'Stable'; dtp.DevToolsPanel.createOrShow(context, mockTelemetry, "", mockRuntimeConfig); expect(mockPanel.onDidChangeViewState).toHaveBeenCalled(); diff --git a/test/helpers/helpers.ts b/test/helpers/helpers.ts index e81da00a..19781d76 100644 --- a/test/helpers/helpers.ts +++ b/test/helpers/helpers.ts @@ -87,6 +87,9 @@ export function createFakeVSCode() { showTextDocument: jest.fn(), showInformationMessage: jest.fn(), showWarningMessage: jest.fn().mockResolvedValue({}), + activeColorTheme: { + kind: 1 + } }, workspace: { createFileSystemWatcher: jest.fn(), @@ -140,9 +143,11 @@ export function createFakeVSCode() { * Create a fake VS Code extension context that can be used in tests */ export function createFakeExtensionContext() { + const mockedGlobalState = new Map(); return { extensionPath: "", subscriptions: [], + globalState: mockedGlobalState, workspaceState: { get: jest.fn(), update: jest.fn(),