Skip to content

Commit 99ca580

Browse files
authored
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
1 parent c992af6 commit 99ca580

File tree

5 files changed

+70
-37
lines changed

5 files changed

+70
-37
lines changed

src/devtoolsPanel.ts

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export class DevToolsPanel {
5757
private collectConsoleMessages = true;
5858
private currentRevision: string | undefined;
5959
private cssWarningActive: boolean;
60+
private fallbackChain: (() => void)[] = [];
61+
private getFallbackRevisionFunction: (() => void) = () => {};
6062

6163
private constructor(
6264
panel: vscode.WebviewPanel,
@@ -72,7 +74,7 @@ export class DevToolsPanel {
7274
this.config = config;
7375
this.timeStart = null;
7476
this.devtoolsBaseUri = this.config.devtoolsBaseUri || null;
75-
this.isHeadless = false;
77+
this.isHeadless = SettingsProvider.instance.getHeadlessSettings();
7678
this.cssWarningActive = false;
7779

7880
// Hook up the socket events
@@ -103,7 +105,12 @@ export class DevToolsPanel {
103105
// This Websocket is only used on initial connection to determine the browser version.
104106
// The browser version is used to select the correct hashed version of the devtools
105107
this.versionDetectionSocket = new BrowserVersionDetectionSocket(this.targetUrl);
106-
this.versionDetectionSocket.on('setCdnParameters', (msg: {revision: string; isHeadless: boolean}) => this.setCdnParameters(msg));
108+
109+
// Gets an array of functions that will be tried to get the right Devtools revision.
110+
this.fallbackChain = this.determineVersionFallback();
111+
if (this.fallbackChain.length > 0) {
112+
this.getFallbackRevisionFunction = this.fallbackChain.pop() || this.getFallbackRevisionFunction;
113+
}
107114

108115
// Handle closing
109116
this.panel.onDidDispose(() => {
@@ -117,8 +124,7 @@ export class DevToolsPanel {
117124
// Connection type determined already
118125
this.update();
119126
} else {
120-
// Use version socket to determine which Webview/Tools to use
121-
this.versionDetectionSocket.detectVersion();
127+
this.getFallbackRevisionFunction();
122128
}
123129
}
124130
}, this, this.disposables);
@@ -137,12 +143,52 @@ export class DevToolsPanel {
137143
});
138144
}
139145

146+
/**
147+
* Allows multiple fallbacks, allowing the user to select between stability
148+
* or latest features.
149+
* @returns A function array that has the fallback chain.
150+
*/
151+
determineVersionFallback() {
152+
const browserFlavor = this.config.browserFlavor;
153+
const storedRevision = this.context.globalState.get<string>('fallbackRevision') || '';
154+
const callWrapper = (revision: string) => {
155+
this.setCdnParameters({revision, isHeadless: this.isHeadless});
156+
};
157+
158+
// Use version socket to determine which Webview/Tools to use
159+
const detectedVersion = () => {
160+
this.versionDetectionSocket.on('setCdnParameters', (msg: {revision: string; isHeadless: boolean}) => {
161+
this.setCdnParameters(msg);
162+
});
163+
164+
this.versionDetectionSocket.detectVersion.bind(this.versionDetectionSocket)();
165+
};
166+
167+
// we reverse the array so that it behaves like a stack.
168+
switch (browserFlavor) {
169+
case 'Beta':
170+
case 'Canary':
171+
case 'Dev':
172+
case 'Stable': {
173+
return [ detectedVersion,
174+
() => callWrapper(CDN_FALLBACK_REVISION),
175+
() => callWrapper(storedRevision)].reverse();
176+
}
177+
178+
case 'Default':
179+
default: {
180+
return [() => callWrapper(CDN_FALLBACK_REVISION),
181+
detectedVersion,
182+
() => callWrapper(storedRevision)].reverse();
183+
}
184+
}
185+
}
186+
140187
dispose(): void {
141188
DevToolsPanel.instance = undefined;
142189

143190
this.panel.dispose();
144191
this.panelSocket.dispose();
145-
this.versionDetectionSocket.dispose();
146192
if (this.timeStart !== null) {
147193
const timeEnd = performance.now();
148194
const sessionTime = timeEnd - this.timeStart;
@@ -406,41 +452,18 @@ export class DevToolsPanel {
406452
private onSocketDevToolsConnection(success: string) {
407453
if (success === 'true') {
408454
void this.context.globalState.update('fallbackRevision', this.currentRevision);
409-
this.context.globalState.update('retryAttemptToLoadCDN', '1');
455+
this.fallbackChain = this.determineVersionFallback();
410456
} else {
411-
let retryNumber: number;
412-
try {
413-
retryNumber = parseInt(this.context.globalState.get<string>('retryAttemptToLoadCDN') || '1', 10);
414-
} catch {
415-
retryNumber = 1;
416-
}
417-
418-
let fallbackRevision;
419-
switch (retryNumber) {
420-
case 1: {
421-
// Always try the latest specified revision first, this will keep it updated.
422-
fallbackRevision = CDN_FALLBACK_REVISION;
423-
this.context.globalState.update('retryAttemptToLoadCDN', ++retryNumber);
424-
break;
425-
}
426-
case 2: {
427-
// Retry connection with latest well known fallback that this environment knows.
428-
fallbackRevision = this.context.globalState.get<string>('fallbackRevision') ?? '';
429-
this.context.globalState.update('retryAttemptToLoadCDN', ++retryNumber);
430-
break;
431-
}
432-
default: {
433-
// Could not find suitable version.
434-
this.context.globalState.update('retryAttemptToLoadCDN', '1');
435-
return;
436-
}
437-
}
438-
439457
if (this.currentRevision) {
440458
this.telemetryReporter.sendTelemetryEvent('websocket/failedConnection', {revision: this.currentRevision});
441459
}
442460

443-
this.setCdnParameters({revision: fallbackRevision, isHeadless: this.isHeadless});
461+
// We failed trying to retrieve the specified revision
462+
// we fallback to the next option if available.
463+
if (this.fallbackChain.length > 0) {
464+
this.getFallbackRevisionFunction = this.fallbackChain.pop() || (() => {});
465+
this.getFallbackRevisionFunction();
466+
}
444467
}
445468
}
446469

@@ -574,7 +597,7 @@ export class DevToolsPanel {
574597
}
575598

576599
private setCdnParameters(msg: {revision: string, isHeadless: boolean}) {
577-
this.currentRevision = msg.revision || CDN_FALLBACK_REVISION;
600+
this.currentRevision = msg.revision;
578601
this.devtoolsBaseUri = `https://devtools.azureedge.net/serve_file/${this.currentRevision}/vscode_app.html`;
579602
this.isHeadless = msg.isHeadless;
580603
this.update();

src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface IRuntimeConfig {
6363
useLocalEdgeWatch: boolean;
6464
devtoolsBaseUri?: string;
6565
defaultEntrypoint?: string;
66+
browserFlavor: BrowserFlavor;
6667
}
6768
export interface IStringDictionary<T> {
6869
[name: string]: T;
@@ -454,6 +455,7 @@ export function removeTrailingSlash(uri: string): string {
454455
export function getRuntimeConfig(config: Partial<IUserConfig> = {}): IRuntimeConfig {
455456
const settings = vscode.workspace.getConfiguration(SETTINGS_STORE_NAME);
456457
const pathMapping = config.pathMapping || settings.get('pathMapping') || SETTINGS_DEFAULT_PATH_MAPPING;
458+
const browserFlavor = config.browserFlavor || settings.get('browserFlavor') || 'Default';
457459
const sourceMapPathOverrides =
458460
config.sourceMapPathOverrides || settings.get('sourceMapPathOverrides') || SETTINGS_DEFAULT_PATH_OVERRIDES;
459461
const webRoot = config.webRoot || settings.get('webRoot') || SETTINGS_DEFAULT_WEB_ROOT;
@@ -499,6 +501,7 @@ export function getRuntimeConfig(config: Partial<IUserConfig> = {}): IRuntimeCon
499501
return {
500502
pathMapping: resolvedMappingOverrides,
501503
sourceMapPathOverrides: resolvedOverrides,
504+
browserFlavor,
502505
sourceMaps,
503506
webRoot: resolvedWebRoot,
504507
isJsDebugProxiedCDPConnection: false,

src/versionSocketConnection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface BrowserVersionCdpResponse {
1717
}
1818

1919
// Minimum supported version of Edge
20-
export const MIN_SUPPORTED_VERSION = '120.0.2210.181';
20+
export const MIN_SUPPORTED_VERSION = '127.0.2592.0';
2121
export const MIN_SUPPORTED_REVISION = CDN_FALLBACK_REVISION;
2222

2323
export class BrowserVersionDetectionSocket extends EventEmitter {

test/devtoolsPanel.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ describe("devtoolsPanel", () => {
4848
webRoot: "",
4949
isJsDebugProxiedCDPConnection: false,
5050
useLocalEdgeWatch: false,
51+
browserFlavor: "Default",
5152
};
5253

5354
mockPanel = {
@@ -173,6 +174,7 @@ describe("devtoolsPanel", () => {
173174
describe("update", () => {
174175
it("adds attempts to detect browser version only when visible", async () => {
175176
const dtp = await import("../src/devtoolsPanel");
177+
mockRuntimeConfig.browserFlavor = 'Stable';
176178
dtp.DevToolsPanel.createOrShow(context, mockTelemetry, "", mockRuntimeConfig);
177179
expect(mockPanel.onDidChangeViewState).toHaveBeenCalled();
178180

test/helpers/helpers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ export function createFakeVSCode() {
8787
showTextDocument: jest.fn(),
8888
showInformationMessage: jest.fn(),
8989
showWarningMessage: jest.fn().mockResolvedValue({}),
90+
activeColorTheme: {
91+
kind: 1
92+
}
9093
},
9194
workspace: {
9295
createFileSystemWatcher: jest.fn(),
@@ -140,9 +143,11 @@ export function createFakeVSCode() {
140143
* Create a fake VS Code extension context that can be used in tests
141144
*/
142145
export function createFakeExtensionContext() {
146+
const mockedGlobalState = new Map();
143147
return {
144148
extensionPath: "",
145149
subscriptions: [],
150+
globalState: mockedGlobalState,
146151
workspaceState: {
147152
get: jest.fn(),
148153
update: jest.fn(),

0 commit comments

Comments
 (0)