Skip to content

Commit

Permalink
Feature: Sets different fallback chains. (#2431)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vidorteg authored Sep 18, 2024
1 parent c992af6 commit 99ca580
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 37 deletions.
95 changes: 59 additions & 36 deletions src/devtoolsPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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);
Expand All @@ -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<string>('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;
Expand Down Expand Up @@ -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<string>('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<string>('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();
}
}
}

Expand Down Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface IRuntimeConfig {
useLocalEdgeWatch: boolean;
devtoolsBaseUri?: string;
defaultEntrypoint?: string;
browserFlavor: BrowserFlavor;
}
export interface IStringDictionary<T> {
[name: string]: T;
Expand Down Expand Up @@ -454,6 +455,7 @@ export function removeTrailingSlash(uri: string): string {
export function getRuntimeConfig(config: Partial<IUserConfig> = {}): 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;
Expand Down Expand Up @@ -499,6 +501,7 @@ export function getRuntimeConfig(config: Partial<IUserConfig> = {}): IRuntimeCon
return {
pathMapping: resolvedMappingOverrides,
sourceMapPathOverrides: resolvedOverrides,
browserFlavor,
sourceMaps,
webRoot: resolvedWebRoot,
isJsDebugProxiedCDPConnection: false,
Expand Down
2 changes: 1 addition & 1 deletion src/versionSocketConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions test/devtoolsPanel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe("devtoolsPanel", () => {
webRoot: "",
isJsDebugProxiedCDPConnection: false,
useLocalEdgeWatch: false,
browserFlavor: "Default",
};

mockPanel = {
Expand Down Expand Up @@ -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();

Expand Down
5 changes: 5 additions & 0 deletions test/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export function createFakeVSCode() {
showTextDocument: jest.fn(),
showInformationMessage: jest.fn(),
showWarningMessage: jest.fn().mockResolvedValue({}),
activeColorTheme: {
kind: 1
}
},
workspace: {
createFileSystemWatcher: jest.fn(),
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit 99ca580

Please sign in to comment.