fix: close and recreate macOS windows safely

This commit is contained in:
鲨鱼辣椒
2026-09-29 19:50:25 +08:00
parent f443f3bc66
commit b5e6c663ef
19 changed files with 1079 additions and 172 deletions

View File

@@ -106,7 +106,7 @@ describe('ElectronAgentBrowserAdapter', () => {
});
it.each([false, true])('captures a page without presenting a hidden native view (visible=%s)', async (visible) => {
const adapter = new ElectronAgentBrowserAdapter({} as never);
const adapter = new ElectronAgentBrowserAdapter(() => null);
const view = adapter.createView('browser-screenshot');
const native = electronMocks.nativeViews[0];
native.getVisible.mockReturnValue(visible);
@@ -184,6 +184,46 @@ describe('ElectronAgentBrowserAdapter', () => {
).toHaveBeenCalledOnce();
});
it('binds new views to the reopened window and never detaches them from an old owner', () => {
const makeWindow = () => ({
isDestroyed: vi.fn().mockReturnValue(false),
contentView: { addChildView: vi.fn(), removeChildView: vi.fn() },
webContents: { getZoomFactor: vi.fn().mockReturnValue(1) },
getContentBounds: vi.fn().mockReturnValue({ width: 900, height: 650 }),
});
const first = makeWindow();
const second = makeWindow();
let current: ReturnType<typeof makeWindow> | null = first;
const adapter = new ElectronAgentBrowserAdapter(() => current as never);
const oldView = adapter.createView('first');
adapter.mount(oldView);
first.isDestroyed.mockReturnValue(true);
current = null;
adapter.unmount(oldView);
adapter.destroy(oldView);
current = second;
const newView = adapter.createView('second');
adapter.mount(newView);
newView.setBounds({ x: 10, y: 10, width: 800, height: 600 });
// A late old-view cleanup cannot remove a view from the replacement window.
adapter.unmount(oldView);
expect(second.contentView.addChildView).toHaveBeenCalledWith(electronMocks.nativeViews[1]);
expect(second.contentView.removeChildView).not.toHaveBeenCalled();
expect(first.contentView.addChildView).toHaveBeenCalledTimes(1);
expect(electronMocks.nativeViews[1].setBounds).toHaveBeenCalledWith({ x: 10, y: 10, width: 800, height: 600 });
});
it('allows detached preflight views but refuses visible mounting without a window', () => {
const adapter = new ElectronAgentBrowserAdapter(() => null);
const view = adapter.createView('detached');
view.setBounds({ x: 0, y: 0, width: 390, height: 844 });
expect(electronMocks.nativeViews[0].setBounds).toHaveBeenCalledWith({ x: 0, y: 0, width: 390, height: 844 });
expect(() => adapter.mount(view)).toThrow('请先打开麦洛窗口');
expect(() => adapter.unmount(view)).not.toThrow();
adapter.destroy(view);
expect((electronMocks.nativeViews[0].webContents as { close: unknown }).close).toHaveBeenCalledOnce();
});
it('denies downloads and clears only the requested project partition', async () => {
const adapter = new ElectronAgentBrowserAdapter({
isDestroyed: vi.fn().mockReturnValue(false),