Skip to content

Commit 377c1db

Browse files
JustMell0simonrey1
authored andcommitted
fix(extensions): skip registering commands and menus for disabled extensions
Signed-off-by: Dias Tursynbayev <original.justmello1337@gmail.com>
1 parent aeca85a commit 377c1db

2 files changed

Lines changed: 153 additions & 38 deletions

File tree

packages/main/src/plugin/extension/extension-loader.spec.ts

Lines changed: 121 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,13 @@ class TestExtensionLoader extends ExtensionLoader {
141141

142142
let extensionLoader: TestExtensionLoader;
143143

144-
const commandRegistry: CommandRegistry = {} as unknown as CommandRegistry;
144+
const commandRegistry: CommandRegistry = {
145+
registerCommandsFromExtension: vi.fn(),
146+
} as unknown as CommandRegistry;
145147

146-
const menuRegistry: MenuRegistry = {} as unknown as MenuRegistry;
148+
const menuRegistry: MenuRegistry = {
149+
registerMenus: vi.fn(),
150+
} as unknown as MenuRegistry;
147151

148152
const kubernetesGeneratorRegistry: KubeGeneratorRegistry = {} as unknown as KubeGeneratorRegistry;
149153

@@ -213,16 +217,21 @@ const authenticationProviderRegistry: AuthenticationImpl = {
213217
registerAuthenticationProvider: vi.fn(),
214218
} as unknown as AuthenticationImpl;
215219

216-
const iconRegistry: IconRegistry = {} as unknown as IconRegistry;
220+
const iconRegistry: IconRegistry = {
221+
registerIconContribution: vi.fn(),
222+
} as unknown as IconRegistry;
217223

218224
const onboardingRegistry: OnboardingRegistry = {
219225
getOnboarding: vi.fn(),
226+
registerOnboarding: vi.fn(),
220227
} as unknown as OnboardingRegistry;
221228

222229
const telemetryTrackMock = vi.fn();
223230
const telemetry: Telemetry = { aggregateTrack: vi.fn(), track: telemetryTrackMock } as unknown as Telemetry;
224231

225-
const viewRegistry: ViewRegistry = {} as unknown as ViewRegistry;
232+
const viewRegistry: ViewRegistry = {
233+
registerViews: vi.fn(),
234+
} as unknown as ViewRegistry;
226235

227236
const context: Context = new Context(apiSender);
228237

@@ -400,6 +409,14 @@ beforeEach(() => {
400409
telemetryTrackMock.mockResolvedValue(undefined);
401410
vi.clearAllMocks();
402411

412+
configurationRegistryGetConfigurationMock.mockReturnValue({
413+
get: vi.fn().mockImplementation((key: string, defaultValue?: unknown) => {
414+
if (key === 'disabled') {
415+
return [];
416+
}
417+
return defaultValue;
418+
}),
419+
});
403420
vi.mocked(extensionDevelopmentFolder).getDevelopmentFolders.mockReturnValue([]);
404421
});
405422

@@ -733,18 +750,113 @@ test('Verify extension load', async () => {
733750
dispose: vi.fn(),
734751
});
735752

736-
expect(telemetry.track).toBeCalledWith(
737-
'loadExtension.error',
738-
expect.objectContaining({ extensionId: id, extensionVersion: '1.1' }),
739-
);
740-
741753
expect(extensionDevelopmentFolder.addExternalExtensionId).toBeCalledWith(id);
742754

743755
// remove extension
744756
await extensionLoader.removeExtension(id);
745757
expect(extensionDevelopmentFolder.removeExternalExtensionId).toBeCalledWith(id);
746758
});
747759

760+
test('Verify disabled extension skips registering contributions and runtime activation', async () => {
761+
const id = 'extension.disabled';
762+
763+
configurationRegistryGetConfigurationMock.mockReturnValue({
764+
get: vi.fn().mockImplementation((key: string, defaultValue?: unknown) => {
765+
if (key === 'disabled') {
766+
return [id];
767+
}
768+
return defaultValue;
769+
}),
770+
});
771+
772+
const loadRuntimeSpy = vi.spyOn(extensionLoader, 'loadRuntime' as keyof TestExtensionLoader);
773+
774+
const subscriptions: IDisposable[] = [];
775+
776+
await extensionLoader.loadExtension({
777+
id: id,
778+
name: 'disabled-ext',
779+
path: 'dummy',
780+
api: {} as typeof containerDesktopAPI,
781+
mainPath: 'main.js',
782+
removable: true,
783+
devMode: false,
784+
manifest: {
785+
version: '1.0',
786+
contributes: {
787+
commands: [{ command: 'ext.cmd', title: 'Test Command' }],
788+
menus: { 'dashboard/image': [{ command: 'ext.cmd' }] },
789+
icons: { 'ext-icon': { default: 'icon.png' } },
790+
themes: [{ id: 'ext-theme', name: 'Theme', parent: 'dark', colors: {} }],
791+
views: { icons: [{ id: 'ext-view', name: 'View', when: '' }] },
792+
onboarding: { title: 'Onboarding', steps: [] },
793+
},
794+
} as unknown as ExtensionManifest,
795+
subscriptions: subscriptions,
796+
readme: '',
797+
dispose: vi.fn(),
798+
});
799+
800+
expect(commandRegistry.registerCommandsFromExtension).not.toHaveBeenCalled();
801+
expect(menuRegistry.registerMenus).not.toHaveBeenCalled();
802+
expect(iconRegistry.registerIconContribution).not.toHaveBeenCalled();
803+
expect(colorRegistry.registerExtensionThemes).not.toHaveBeenCalled();
804+
expect(viewRegistry.registerViews).not.toHaveBeenCalled();
805+
expect(onboardingRegistry.registerOnboarding).not.toHaveBeenCalled();
806+
expect(notificationRegistry.registerExtension).not.toHaveBeenCalled();
807+
808+
expect(loadRuntimeSpy).not.toHaveBeenCalled();
809+
});
810+
811+
test('Verify enabled extension registers contributions and activates runtime', async () => {
812+
const id = 'extension.enabled';
813+
814+
vi.mocked(commandRegistry.registerCommandsFromExtension).mockReturnValue(Disposable.create(() => {}));
815+
vi.mocked(menuRegistry.registerMenus).mockReturnValue(Disposable.create(() => {}));
816+
vi.mocked(colorRegistry.registerExtensionThemes).mockReturnValue(Disposable.create(() => {}));
817+
vi.mocked(viewRegistry.registerViews).mockReturnValue(Disposable.create(() => {}));
818+
vi.mocked(onboardingRegistry.registerOnboarding).mockReturnValue(Disposable.create(() => {}));
819+
vi.mocked(notificationRegistry.registerExtension).mockReturnValue(Disposable.create(() => {}));
820+
821+
const subscriptions: IDisposable[] = [];
822+
823+
await extensionLoader.loadExtension({
824+
id: id,
825+
name: 'enabled-ext',
826+
path: 'dummy',
827+
api: {} as typeof containerDesktopAPI,
828+
mainPath: '',
829+
removable: true,
830+
devMode: false,
831+
manifest: {
832+
version: '1.0',
833+
contributes: {
834+
commands: [{ command: 'ext.cmd', title: 'Test Command' }],
835+
menus: { 'dashboard/image': [{ command: 'ext.cmd' }] },
836+
icons: { 'ext-icon': { default: 'icon.png' } },
837+
themes: [{ id: 'ext-theme', name: 'Theme', parent: 'dark', colors: {} }],
838+
views: { icons: [{ id: 'ext-view', name: 'View', when: '' }] },
839+
onboarding: { title: 'Onboarding', steps: [] },
840+
},
841+
} as unknown as ExtensionManifest,
842+
subscriptions: subscriptions,
843+
readme: '',
844+
dispose: vi.fn(),
845+
});
846+
847+
expect(commandRegistry.registerCommandsFromExtension).toHaveBeenCalledWith(id, [
848+
{ command: 'ext.cmd', title: 'Test Command' },
849+
]);
850+
expect(menuRegistry.registerMenus).toHaveBeenCalledWith({ 'dashboard/image': [{ command: 'ext.cmd' }] });
851+
expect(iconRegistry.registerIconContribution).toHaveBeenCalled();
852+
expect(colorRegistry.registerExtensionThemes).toHaveBeenCalled();
853+
expect(viewRegistry.registerViews).toHaveBeenCalledWith(id, {
854+
icons: [{ id: 'ext-view', name: 'View', when: '' }],
855+
});
856+
expect(onboardingRegistry.registerOnboarding).toHaveBeenCalled();
857+
expect(notificationRegistry.registerExtension).toHaveBeenCalledWith(id);
858+
});
859+
748860
test('Verify extension do not add configuration to subscriptions', async () => {
749861
const id = 'extension.foo';
750862

packages/main/src/plugin/extension/extension-loader.ts

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -811,41 +811,44 @@ export class ExtensionLoader implements IAsyncDisposable {
811811
},
812812
]);
813813
}
814+
const isDisabled = this.getDisabledExtensionIds().includes(extension.id);
814815

815-
const extensionCommands = extension.manifest?.contributes?.commands;
816-
if (extensionCommands) {
817-
const disposable = this.commandRegistry.registerCommandsFromExtension(extension.id, extensionCommands);
818-
extension.subscriptions.push(disposable);
819-
}
816+
if (!isDisabled) {
817+
const extensionCommands = extension.manifest?.contributes?.commands;
818+
if (extensionCommands) {
819+
const disposable = this.commandRegistry.registerCommandsFromExtension(extension.id, extensionCommands);
820+
extension.subscriptions.push(disposable);
821+
}
820822

821-
// register menus after the contributed commands so we can see if contributed commands have icons
822-
const menus = extension.manifest?.contributes?.menus;
823-
if (menus) {
824-
extension.subscriptions.push(this.menuRegistry.registerMenus(menus));
825-
}
823+
// register menus after the contributed commands so we can see if contributed commands have icons
824+
const menus = extension.manifest?.contributes?.menus;
825+
if (menus) {
826+
extension.subscriptions.push(this.menuRegistry.registerMenus(menus));
827+
}
826828

827-
const icons = extension.manifest?.contributes?.icons;
828-
if (icons) {
829-
this.iconRegistry.registerIconContribution(extension, icons);
830-
}
829+
const icons = extension.manifest?.contributes?.icons;
830+
if (icons) {
831+
this.iconRegistry.registerIconContribution(extension, icons);
832+
}
831833

832-
const themes = extension.manifest?.contributes?.themes;
833-
if (themes) {
834-
const disposable = this.colorRegistry.registerExtensionThemes(extension, themes);
835-
extension.subscriptions.push(disposable);
836-
}
834+
const themes = extension.manifest?.contributes?.themes;
835+
if (themes) {
836+
const disposable = this.colorRegistry.registerExtensionThemes(extension, themes);
837+
extension.subscriptions.push(disposable);
838+
}
837839

838-
const views = extension.manifest?.contributes?.views;
839-
if (views) {
840-
extension.subscriptions.push(this.viewRegistry.registerViews(extension.id, views));
841-
}
840+
const views = extension.manifest?.contributes?.views;
841+
if (views) {
842+
extension.subscriptions.push(this.viewRegistry.registerViews(extension.id, views));
843+
}
842844

843-
const onboarding = extension.manifest?.contributes?.onboarding;
844-
if (onboarding) {
845-
extension.subscriptions.push(this.onboardingRegistry.registerOnboarding(extension, onboarding));
846-
}
845+
const onboarding = extension.manifest?.contributes?.onboarding;
846+
if (onboarding) {
847+
extension.subscriptions.push(this.onboardingRegistry.registerOnboarding(extension, onboarding));
848+
}
847849

848-
extension.subscriptions.push(this.notificationRegistry.registerExtension(extension.id));
850+
extension.subscriptions.push(this.notificationRegistry.registerExtension(extension.id));
851+
}
849852

850853
extension.api ??= this.createApi(extension);
851854
const extensionWithApi = extension as AnalyzedExtensionWithApi;
@@ -877,7 +880,7 @@ export class ExtensionLoader implements IAsyncDisposable {
877880
await this.extensionWatcher.monitor(extension);
878881
}
879882

880-
if (!this.getDisabledExtensionIds().includes(extension.id)) {
883+
if (!isDisabled) {
881884
const beforeLoadingRuntime = performance.now();
882885
const runtime = this.loadRuntime(extension);
883886
const afterLoadingRuntime = performance.now();

0 commit comments

Comments
 (0)