mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: keep a plugin off when it is turned off during its first start
This commit is contained in:
@@ -194,6 +194,85 @@ describe('loadSandboxedPlugin when a frame is slow to boot', () => {
|
||||
expect(mocks.createBackgroundInstance).toHaveBeenCalledTimes(1);
|
||||
expect(lastStatus()).toMatchObject({ status: 'error', error: 'bundle threw' });
|
||||
});
|
||||
|
||||
it('shows no error and leaves no frame when a plugin turned off while starting times out', async () => {
|
||||
mocks.getCode.mockResolvedValue(CODE);
|
||||
const frame = hung();
|
||||
mocks.createBackgroundInstance.mockReturnValueOnce(frame);
|
||||
|
||||
const run = loadSandboxedPlugin(plugin());
|
||||
await framesStarted(1);
|
||||
unloadSandboxedPlugin('managed-plugin');
|
||||
await vi.advanceTimersByTimeAsync(30_000);
|
||||
await run;
|
||||
|
||||
expect(frame.destroy).toHaveBeenCalled();
|
||||
expect(statuses.some((s) => s.status === 'error')).toBe(false);
|
||||
expect(lastStatus()).toMatchObject({ id: 'managed-plugin', status: 'disabled' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('a plugin turned off during its first start, with no remembered slots', () => {
|
||||
function startingFrame() {
|
||||
let finish!: (info: unknown) => void;
|
||||
let fail!: (err: Error) => void;
|
||||
const bg = fakeBackground();
|
||||
bg.initPromise = new Promise((res, rej) => { finish = res; fail = rej; });
|
||||
mocks.createBackgroundInstance.mockImplementation(() => bg);
|
||||
return { bg, finish, fail };
|
||||
}
|
||||
const INFO = { hooks: [], slots: [{ name: 'sidebar-section', hasShouldShow: false, order: 100 }], shortcuts: [] };
|
||||
const started = () => vi.waitFor(() => expect(mocks.createBackgroundInstance).toHaveBeenCalled());
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
mocks.getCode.mockResolvedValue(CODE);
|
||||
});
|
||||
|
||||
it('does not come up running when its init then succeeds', async () => {
|
||||
const { bg, finish } = startingFrame();
|
||||
const load = loadSandboxedPlugin(plugin());
|
||||
await started();
|
||||
|
||||
unloadSandboxedPlugin('managed-plugin');
|
||||
finish(INFO);
|
||||
await load;
|
||||
|
||||
expect(bg.destroy).toHaveBeenCalled();
|
||||
expect(getActive('managed-plugin')).toBeUndefined();
|
||||
expect(statuses.some((s) => s.status === 'running')).toBe(false);
|
||||
expect(lastStatus()).toMatchObject({ id: 'managed-plugin', status: 'disabled' });
|
||||
});
|
||||
|
||||
it('shows no error when its init then fails', async () => {
|
||||
const { bg, fail } = startingFrame();
|
||||
const load = loadSandboxedPlugin(plugin());
|
||||
await started();
|
||||
|
||||
unloadSandboxedPlugin('managed-plugin');
|
||||
fail(new Error('bundle threw'));
|
||||
await load;
|
||||
|
||||
expect(bg.destroy).toHaveBeenCalled();
|
||||
expect(statuses.some((s) => s.status === 'error')).toBe(false);
|
||||
expect(lastStatus()).toMatchObject({ id: 'managed-plugin', status: 'disabled' });
|
||||
});
|
||||
|
||||
it('runs after all when it is turned back on while still starting', async () => {
|
||||
const { bg, finish } = startingFrame();
|
||||
const load = loadSandboxedPlugin(plugin());
|
||||
await started();
|
||||
|
||||
unloadSandboxedPlugin('managed-plugin');
|
||||
const again = loadSandboxedPlugin(plugin());
|
||||
finish(INFO);
|
||||
await Promise.all([load, again]);
|
||||
|
||||
expect(bg.destroy).not.toHaveBeenCalled();
|
||||
expect(mocks.createBackgroundInstance).toHaveBeenCalledTimes(1);
|
||||
expect(getActive('managed-plugin')).toBeDefined();
|
||||
expect(lastStatus()).toMatchObject({ id: 'managed-plugin', status: 'running' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadSandboxedPlugin bundle sourcing', () => {
|
||||
|
||||
@@ -238,6 +238,15 @@ export function loadSandboxedPlugin(plugin: InstalledPlugin): Promise<void> {
|
||||
return run;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turned off during a start with no remembered slots: unload found nothing
|
||||
* registered, so the frame is still up and the status still unset.
|
||||
*/
|
||||
function stopCancelledStart(pluginId: string, background: { destroy(): void } | null): void {
|
||||
try { background?.destroy(); } catch { /* ignore */ }
|
||||
storeAccessor?.setPluginStatus(pluginId, 'disabled');
|
||||
}
|
||||
|
||||
async function startSandboxedPlugin(plugin: InstalledPlugin, retried = false): Promise<void> {
|
||||
// A plugin that declares network access doesn't start while third-party
|
||||
// traffic is off; the rest run (#903).
|
||||
@@ -280,6 +289,10 @@ async function startSandboxedPlugin(plugin: InstalledPlugin, retried = false): P
|
||||
INIT_TIMEOUT_MS,
|
||||
`[plugin-sandbox] "${plugin.id}" init`,
|
||||
);
|
||||
if (startsCancelled.has(plugin.id)) {
|
||||
stopCancelledStart(plugin.id, bg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wire hook proxies: every hookName the plugin registered gets a HookBus
|
||||
// entry whose handler dispatches into the sandbox. `shortcut:<id>` hooks
|
||||
@@ -360,6 +373,10 @@ async function startSandboxedPlugin(plugin: InstalledPlugin, retried = false): P
|
||||
} catch (err) {
|
||||
// Turned off while it was starting: unload has set the status.
|
||||
if (background?.closed && getActive(plugin.id)?.background !== background) return;
|
||||
if (startsCancelled.has(plugin.id)) {
|
||||
stopCancelledStart(plugin.id, background);
|
||||
return;
|
||||
}
|
||||
// Offered from the last start: take the slots back down.
|
||||
if (background && getActive(plugin.id)?.background === background) deregisterActive(plugin.id);
|
||||
// A busy machine can take longer than the limit to boot a frame, and the
|
||||
|
||||
Reference in New Issue
Block a user