fix(desktop): preserve owner close and title behavior
This commit is contained in:
1 parent
291dd1f448
commit
081b562487
7 files changed
+60
-32
No files matched your search
@@ -21,8 +21,8 @@
|
||||
en: 5f2d25a1d8472647
|
||||
zh: 2acb5c5d7d1917e2
|
||||
/deepseek-harness-desktop/closing-the-window-and-quitting:
|
||||
en: 24387a07ae4eb8e7
|
||||
zh: 0119352685cf273c
|
||||
en: bb35a022c37955c9
|
||||
zh: ab2ef22d398d83a0
|
||||
/deepseek-harness-desktop/key-technical-decisions:
|
||||
en: 3724c53da4f7647b
|
||||
zh: 76ae549d43c99bb4
|
||||
@@ -63,8 +63,8 @@
|
||||
en: 2e57457c44c85b5f
|
||||
zh: 5b8510a3dc4c6c9d
|
||||
/deepseek-harness-desktop/updates/mandatory-update-policy:
|
||||
en: 4d32791befb86d50
|
||||
zh: e7610cb426243a20
|
||||
en: 405319004e0b243e
|
||||
zh: b34cd362d2d2e602
|
||||
/deepseek-harness-desktop/updates/local-updater-qualification:
|
||||
en: 68ec2f8bd5763ea6
|
||||
zh: 23b932dfe8503602
|
||||
|
||||
@@ -70,7 +70,7 @@ Closing the main window (macOS close button and ⌘W; Windows ×, Alt+F4, and th
|
||||
|
||||
Windows keeps a tray icon for the whole run. Its tooltip is the product name, a single click shows and focuses the window, and the context menu offers Open DeepSeek Harness and Quit DeepSeek Harness in the shell locale. Before the first hide, the shared update-dialog overlay explains that running tasks continue and the window can be reopened from the system tray. Confirm hides the window and writes `background-close-confirmed` under Electron userData; Escape, dismissal, or dialog failure keeps the window visible and does not record acknowledgement. Repeated close requests focus the existing shell dialog. In-place updates retain the marker and uninstall removes it. The old `background-notice-shown` marker does not suppress this confirmation. Closing the window sends no system notification. The tray bitmaps are `resources/tray-windows.ico`, rendered from `resources/icon-windows.svg` at 16, 20, 24, 32, 40, 48, and 64 pixels by `pnpm run render:tray-icon`, and packaged as `resources/tray.ico`. macOS ships no menu bar icon.
|
||||
|
||||
Every ordinary quit entry — ⌘Q, the application menu, the Dock menu, the Windows tray and caption Application menu, and the quits caused by closing the mandatory-update or welcome window — asks the Host what it would interrupt. The Host answers over the private IPC channel with two facts: active tasks under the same rule as the update restart check (running agents including subagents and approval waits, queued messages, running or stopping jobs), and armed scheduled reminders reported by the `schedule` family of `workspace/session-activity` for the sessions loaded during this run. Neither fact, the quit proceeds silently. Otherwise a native message box without an owner window — hidden windows stay hidden — shows **Quit DeepSeek Harness?** with one of three localized explanations: running tasks will be interrupted, scheduled tasks will not run while the application is closed, or both. Quit is the default button and Cancel answers Esc; macOS places Cancel left of Quit, Windows places Quit left of Cancel, and the Windows task dialog shows the application icon and stays light regardless of the application theme. A Host that has not reached ready or has failed cannot run tasks, so the quit proceeds without asking. An inspection failure or a Host that misses the two-second deadline is treated as running tasks. While the box is open, another quit request joins it instead of stacking a second one (macOS also raises it; Electron exposes no handle to the Windows task dialog), the explanation does not change when work starts or ends, Quit stops the application without re-inspecting, and Cancel changes nothing. Cancelling a quit that closing the welcome window started shows the welcome window again.
|
||||
Every ordinary quit entry — ⌘Q, the application menu, the Dock menu, the Windows tray and caption Application menu, and the quit caused by closing the welcome window — asks the Host what it would interrupt. The Host answers over the private IPC channel with two facts: active tasks under the same rule as the update restart check (running agents including subagents and approval waits, queued messages, running or stopping jobs), and armed scheduled reminders reported by the `schedule` family of `workspace/session-activity` for the sessions loaded during this run. Neither fact, the quit proceeds silently. Otherwise a native message box without an owner window — hidden windows stay hidden — shows **Quit DeepSeek Harness?** with one of three localized explanations: running tasks will be interrupted, scheduled tasks will not run while the application is closed, or both. Quit is the default button and Cancel answers Esc; macOS places Cancel left of Quit, Windows places Quit left of Cancel, and the Windows task dialog shows the application icon and stays light regardless of the application theme. A Host that has not reached ready or has failed cannot run tasks, so the quit proceeds without asking. An inspection failure or a Host that misses the two-second deadline is treated as running tasks. While the box is open, another quit request joins it instead of stacking a second one (macOS also raises it; Electron exposes no handle to the Windows task dialog), the explanation does not change when work starts or ends, Quit stops the application without re-inspecting, and Cancel changes nothing. Cancelling a quit that closing the welcome window started shows the welcome window again.
|
||||
|
||||
The mandatory update confirmation includes the installation wait notice on Windows; macOS uses the shorter restart explanation.
|
||||
|
||||
@@ -332,7 +332,7 @@ When permitted, login is offered during user-initiated checks and the packaged a
|
||||
|
||||
Login and policy requests share an in-memory Session, separate from product windows and the updater; restarting requires a new login. Closing cancels login, and navigation failure provides localized retry guidance. Returning to the service triggers a fresh policy query; a redirect, cookie, or HTTP 422 is not a valid policy decision. Cancellation, expiry, and invalid responses retain any known mandatory block. Fixed login outcomes appear in process diagnostics and the optional update journal; cookies, OAuth parameters, and remote error text are not recorded by the login controller. Live Harness gateway/API integration and macOS login qualification remain unverified.
|
||||
|
||||
A flattened `40005` opens a shell-owned modal and refuses subsequent plugin mutations without stopping existing Host tasks. Server title and detail are optional plain text and fall back to client copy; an absent or unapproved download URL hides the external-page actions without clearing the block. On macOS the mandatory overlay uses an isolated shell-origin WebContentsView inside its native owner. It fades in and out in place, retains its backdrop across update-state changes, and blocks product keyboard input. Loading or updating the view never shows or activates a separate native window; returning to an already focused owner focuses the view. Closing its native owner requests application quit through the normal task confirmation flow. On Windows the isolated application preload mounts a shell-origin frame inside the main window, covering the content below the 40 DIP caption with a backdrop and dialog. It blocks background page input without creating another native window; moving and maximizing affect only the main window. Caption menus retain mouse and keyboard operation; Web controls in the caption remain reachable. The shared DOM is a presentation constraint, not a security boundary: product scripts can hide the overlay but cannot clear main-process policy or authorize installation through it. Update actions and state use a private MessageChannel between the isolated preload and shell frame. The parent remains enabled for native move, resize, minimize, maximize, and close controls. Closing the application performs cleanup without clearing the update requirement; Esc does not dismiss the overlay. Once installation is approved, installer-owned quit releases the modal before Electron closes windows. Download, file verification including preparation, task inspection, and installation confirmation share this same modal. Only the second user approval permits task teardown and installation; deferral retains the block and package. Restart feedback mentions task stopping only when affected tasks exist. Policy is not persisted across application restarts, and policy responses never revoke or replace an updater artifact.
|
||||
A flattened `40005` opens a shell-owned modal and refuses subsequent plugin mutations without stopping existing Host tasks. Server title and detail are optional plain text and fall back to client copy; an absent or unapproved download URL hides the external-page actions without clearing the block. On macOS the mandatory overlay uses an isolated shell-origin WebContentsView inside its native owner. It fades in and out in place, retains its backdrop across update-state changes, and blocks product keyboard input. Loading or updating the view never shows or activates a separate native window; returning to an already focused owner focuses the view. Closing its native owner follows the [normal window-close behavior](#closing-the-window-and-quitting). View loading failures are logged without changing the owner title or clearing the policy. On Windows the isolated application preload mounts a shell-origin frame inside the main window, covering the content below the 40 DIP caption with a backdrop and dialog. It blocks background page input without creating another native window; moving and maximizing affect only the main window. Caption menus retain mouse and keyboard operation; Web controls in the caption remain reachable. The shared DOM is a presentation constraint, not a security boundary: product scripts can hide the overlay but cannot clear main-process policy or authorize installation through it. Update actions and state use a private MessageChannel between the isolated preload and shell frame. The parent remains enabled for native move, resize, minimize, maximize, and close controls. Closing the application performs cleanup without clearing the update requirement; Esc does not dismiss the overlay. Once installation is approved, installer-owned quit releases the modal before Electron closes windows. Download, file verification including preparation, task inspection, and installation confirmation share this same modal. Only the second user approval permits task teardown and installation; deferral retains the block and package. Restart feedback mentions task stopping only when affected tasks exist. Policy is not persisted across application restarts, and policy responses never revoke or replace an updater artifact.
|
||||
|
||||
Failures retain blocking, localized retry guidance, and folded diagnostics inside the modal. The allowed download-page action appears in recovery states, not beside normal download or installation. Requesting the browser immediately exposes a copy alternative even while the OS request is pending; a resolved request does not prove the page opened. Copy failure reveals the complete, read-only address for manual copying. Browser and clipboard outcomes do not overwrite updater errors. Only a fresh valid no-force response clears the block; the top-menu check remains available while blocked.
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ macOS 安装会创建 `/usr/local/bin/dsh`;目录权限需要时,系统会
|
||||
|
||||
Windows 在整个运行期间常驻托盘图标。悬停提示为产品名,单击显示并聚焦窗口,右键菜单提供壳语言下的"打开 DeepSeek Harness"和"退出 DeepSeek Harness"。首次隐藏前复用更新弹窗,显示“正在运行的任务不会中断,可在系统托盘中重新打开窗口”和“确认”按钮。确认后隐藏窗口,并在 Electron userData 下写入 `background-close-confirmed`;Esc、关闭弹窗或加载失败均保持主窗口可见,不记录确认。重复关闭请求会聚焦已有壳弹窗。覆盖更新保留标记,卸载删除标记。旧的 `background-notice-shown` 标记不会跳过此确认。关闭窗口不发送系统通知。托盘位图是 `resources/tray-windows.ico`,由 `pnpm run render:tray-icon` 从 `resources/icon-windows.svg` 按 16、20、24、32、40、48、64 像素分别渲染,打包为 `resources/tray.ico`。macOS 不提供菜单栏图标。
|
||||
|
||||
所有普通退出入口——⌘Q、应用菜单、Dock 菜单、Windows 托盘和标题栏"应用程序"菜单,以及关闭强制更新窗口或欢迎窗口引起的退出——都先向 Host 查询退出会中断什么。Host 通过私有 IPC 通道回答两项事实:与更新重启检查同一口径的运行中任务(运行中的 agent,包括子代理和等待审批的回合、排队消息、运行中或停止中的后台任务),以及本次运行中已加载会话里由 `workspace/session-activity` 的 `schedule` family 报告的已挂定时器的提醒。两项都没有时直接退出,不弹框。否则弹出一个没有父窗口的原生消息框——隐藏的窗口保持隐藏——标题为**退出 DeepSeek Harness?**,正文为三种本地化说明之一:正在运行的任务将会中断、应用关闭期间定时任务不会运行,或两者兼有。"退出"是默认按钮,Esc 等同"取消";macOS 上"取消"在"退出"左侧,Windows 上"退出"在"取消"左侧,Windows 任务对话框显示应用图标且不跟随应用主题、始终为浅色。Host 尚未就绪或已失败时不可能有任务在跑,直接退出。查询失败或 Host 超过两秒截止时间未答复,按运行中任务处理。弹框打开期间,再次请求退出只会并入同一弹框而不叠加新弹框(macOS 上还会把它提到前面;Electron 不暴露 Windows 任务对话框的句柄);任务开始或结束不会改变文案;点"退出"不再重新查询即停止应用;点"取消"不发生任何变化。取消由关闭欢迎窗口引起的退出时,欢迎窗口会重新显示。
|
||||
所有普通退出入口——⌘Q、应用菜单、Dock 菜单、Windows 托盘和标题栏"应用程序"菜单,以及关闭欢迎窗口引起的退出——都先向 Host 查询退出会中断什么。Host 通过私有 IPC 通道回答两项事实:与更新重启检查同一口径的运行中任务(运行中的 agent,包括子代理和等待审批的回合、排队消息、运行中或停止中的后台任务),以及本次运行中已加载会话里由 `workspace/session-activity` 的 `schedule` family 报告的已挂定时器的提醒。两项都没有时直接退出,不弹框。否则弹出一个没有父窗口的原生消息框——隐藏的窗口保持隐藏——标题为**退出 DeepSeek Harness?**,正文为三种本地化说明之一:正在运行的任务将会中断、应用关闭期间定时任务不会运行,或两者兼有。"退出"是默认按钮,Esc 等同"取消";macOS 上"取消"在"退出"左侧,Windows 上"退出"在"取消"左侧,Windows 任务对话框显示应用图标且不跟随应用主题、始终为浅色。Host 尚未就绪或已失败时不可能有任务在跑,直接退出。查询失败或 Host 超过两秒截止时间未答复,按运行中任务处理。弹框打开期间,再次请求退出只会并入同一弹框而不叠加新弹框(macOS 上还会把它提到前面;Electron 不暴露 Windows 任务对话框的句柄);任务开始或结束不会改变文案;点"退出"不再重新查询即停止应用;点"取消"不发生任何变化。取消由关闭欢迎窗口引起的退出时,欢迎窗口会重新显示。
|
||||
|
||||
强制更新的安装确认在 Windows 上额外显示等待提示,macOS 只显示简短的重启说明。
|
||||
|
||||
@@ -332,7 +332,7 @@ Electron 主进程在启动时读取一次 `app.getPath('userData')/desktop/sett
|
||||
|
||||
登录和策略请求共用内存 Session,与产品窗口及 updater 隔离;应用重启后需要重新登录。关闭窗口取消登录,导航失败提供本地化重试提示。返回服务后重新查询策略;重定向、Cookie 或 HTTP 422 都不是有效策略决定。取消、登录过期及无效响应均保留已知强更阻塞。固定登录结果写入进程诊断及可选更新日志;登录控制器不记录 Cookie、OAuth 参数或远程错误原文。真实 Harness 网关/API 联调及 macOS 登录验收仍未完成。
|
||||
|
||||
扁平化的 `40005` 打开壳拥有的模态窗口,并拒绝后续插件修改,不停止现有 Host 任务。服务端标题与详情是可选纯文本,缺失时使用客户端兜底文案;缺少下载地址或地址未获批准时隐藏外部页面操作,不解除阻塞。macOS 强更蒙层使用原生窗口内、隔离的壳静态源 WebContentsView,原位渐入渐出,在更新状态切换时保留蒙层并拦截产品键盘输入。加载或更新视图不会显示或激活独立原生窗口;回到已获焦点的父窗口时聚焦视图。关闭原生父窗口会通过常规任务确认流程请求退出应用。Windows 由隔离的应用 preload 在主窗口内挂载 shell 来源的 frame,以蒙层和弹窗覆盖 40 DIP 顶栏下方的内容区域。它阻止背景页面输入,不创建额外的原生窗口;移动和最大化只作用于主窗口。顶栏菜单保留鼠标和键盘操作,顶栏中的 Web 控件仍可触达。共享 DOM 只约束展示,不构成安全边界:产品脚本可以隐藏蒙层,但不能借此清除主进程策略或授权安装。更新操作与状态通过隔离 preload 和壳 frame 之间的私有 MessageChannel 传递。父窗口保持启用,原生移动、缩放、最小化、最大化和关闭控件仍可操作。退出应用会完成清理,不会解除更新要求;Esc 不会关闭覆盖层。批准安装后,安装器接管的退出流程会在 Electron 关闭窗口前释放模态窗口。下载、含准备步骤的文件校验、任务检查和安装确认共用同一弹窗。只有第二次用户批准才允许任务收尾和安装;稍后更新保留阻塞与安装包。仅存在受影响任务时,重启文案才提示正在停止任务。策略不跨应用重启持久化,策略响应也不作废或替换 updater 产物。
|
||||
扁平化的 `40005` 打开壳拥有的模态窗口,并拒绝后续插件修改,不停止现有 Host 任务。服务端标题与详情是可选纯文本,缺失时使用客户端兜底文案;缺少下载地址或地址未获批准时隐藏外部页面操作,不解除阻塞。macOS 强更蒙层使用原生窗口内、隔离的壳静态源 WebContentsView,原位渐入渐出,在更新状态切换时保留蒙层并拦截产品键盘输入。加载或更新视图不会显示或激活独立原生窗口;回到已获焦点的父窗口时聚焦视图。关闭原生父窗口遵循[常规关窗行为](#closing-the-window-and-quitting)。视图加载失败会记录错误,不改写父窗口标题,也不解除策略阻断。Windows 由隔离的应用 preload 在主窗口内挂载 shell 来源的 frame,以蒙层和弹窗覆盖 40 DIP 顶栏下方的内容区域。它阻止背景页面输入,不创建额外的原生窗口;移动和最大化只作用于主窗口。顶栏菜单保留鼠标和键盘操作,顶栏中的 Web 控件仍可触达。共享 DOM 只约束展示,不构成安全边界:产品脚本可以隐藏蒙层,但不能借此清除主进程策略或授权安装。更新操作与状态通过隔离 preload 和壳 frame 之间的私有 MessageChannel 传递。父窗口保持启用,原生移动、缩放、最小化、最大化和关闭控件仍可操作。退出应用会完成清理,不会解除更新要求;Esc 不会关闭覆盖层。批准安装后,安装器接管的退出流程会在 Electron 关闭窗口前释放模态窗口。下载、含准备步骤的文件校验、任务检查和安装确认共用同一弹窗。只有第二次用户批准才允许任务收尾和安装;稍后更新保留阻塞与安装包。仅存在受影响任务时,重启文案才提示正在停止任务。策略不跨应用重启持久化,策略响应也不作废或替换 updater 产物。
|
||||
|
||||
失败时在同一弹窗内保留阻塞、本地化重试提示和折叠诊断。白名单下载页面操作只在恢复状态出现,不与正常下载或安装并列。请求打开浏览器后立即提供复制替代入口,即使系统请求尚未返回;请求成功不证明网页已打开。复制失败时展示完整、只读的地址供手动复制。浏览器与剪贴板结果不覆盖 updater 错误。只有新的有效无需强更响应才解除阻塞;阻塞期间仍可使用顶部菜单检查。
|
||||
|
||||
|
||||
@@ -1085,7 +1085,6 @@ async function main(): Promise<void> {
|
||||
window.on('close', (event) => {
|
||||
if (quitting || shellInstallerOwnsQuit || sessionEnding) return
|
||||
event.preventDefault()
|
||||
if (process.platform !== 'win32' && isMandatory()) { app.quit(); return }
|
||||
if (updateDialog.isOpen) { updateDialog.focus(); return }
|
||||
const hide = (): void => {
|
||||
if (!quitting && !shellInstallerOwnsQuit && !sessionEnding && !window.isDestroyed()) hideMainWindow(window)
|
||||
|
||||
@@ -212,16 +212,13 @@ export class DesktopMandatoryUpdateWindow {
|
||||
window.webContents.setWindowOpenHandler(() => ({ action: 'deny' }))
|
||||
window.webContents.on('will-navigate', (event, url) => { if (url !== page) event.preventDefault() })
|
||||
window.webContents.on('will-redirect', (event) => { event.preventDefault() })
|
||||
const reportLoadFailure = (error: unknown): void => {
|
||||
if (!this.disposed && !window.isDestroyed()) console.error('desktop policy: mandatory update view failed to load', error)
|
||||
}
|
||||
window.webContents.on('render-process-gone', () => {
|
||||
if (!this.disposed) void window.loadURL(page).catch(() => {
|
||||
// A failed recovery keeps the parent blocked and leaves application exit available.
|
||||
if (!window.isDestroyed()) parent.setTitle(this.options.locale.messages.mandatoryActionFailed)
|
||||
})
|
||||
})
|
||||
void window.loadURL(page).catch(() => {
|
||||
// The parent stays modal-blocked if its dedicated recovery document cannot load.
|
||||
if (!window.isDestroyed()) parent.setTitle(this.options.locale.messages.mandatoryActionFailed)
|
||||
if (!this.disposed) void window.loadURL(page).catch(reportLoadFailure)
|
||||
})
|
||||
void window.loadURL(page).catch(reportLoadFailure)
|
||||
}
|
||||
this.window.webContents.send(MANDATORY_IPC.state, this.view())
|
||||
}
|
||||
|
||||
@@ -1299,7 +1299,7 @@ describe('desktop main startup', () => {
|
||||
expect(harness.trays[0]!.setContextMenu.mock.calls.length).toBe(relabels + 1)
|
||||
})
|
||||
|
||||
it('routes closing a macOS owner with a mandatory view through application quit', async () => {
|
||||
it('hides a macOS owner with a mandatory view without quitting or stopping the Host', async () => {
|
||||
vi.stubGlobal('process', { ...process, platform: 'darwin' })
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', allowedPageOrigins: ['https://downloads.example.com'] }
|
||||
vi.stubGlobal('fetch', vi.fn(async () => Response.json({ code: 40005, data: {
|
||||
@@ -1307,13 +1307,17 @@ describe('desktop main startup', () => {
|
||||
} })))
|
||||
const host = await readyWorkspace()
|
||||
await harness.policyBlocked.promise
|
||||
harness.windows[0]!.close()
|
||||
expect(harness.app.quit).toHaveBeenCalled()
|
||||
const window = harness.windows[0]!
|
||||
const hides = window.hide.mock.calls.length
|
||||
window.close()
|
||||
expect(window.hide).toHaveBeenCalledTimes(hides + 1)
|
||||
expect(window.isDestroyed()).toBe(false)
|
||||
expect(harness.app.quit).not.toHaveBeenCalled()
|
||||
expect(harness.backgroundNotice.close).not.toHaveBeenCalled()
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(host.inspectQuit).toHaveBeenCalledOnce()
|
||||
host.exited.resolve()
|
||||
await harness.quitCompleted.promise
|
||||
expect(host.inspectQuit).not.toHaveBeenCalled()
|
||||
expect(host.stop).not.toHaveBeenCalled()
|
||||
harness.app.emit('activate')
|
||||
expect(window.show).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps the workspace visible while acknowledgement is pending and ignores a destroyed window', async () => {
|
||||
|
||||
@@ -29,24 +29,28 @@ function fakeWindow() {
|
||||
isFocused: () => true, isMinimized: () => false, focus: vi.fn(), show: vi.fn(), restore: vi.fn() })
|
||||
}
|
||||
afterEach(() => { ui?.dispose(); vi.restoreAllMocks(); vi.clearAllMocks(); native.handlers.clear(); vi.useRealTimers() })
|
||||
function setup(platform: NodeJS.Platform = 'darwin') {
|
||||
function setup(platform: NodeJS.Platform = 'darwin', beforeSync?: (surface: ReturnType<typeof fakeWindow>) => void) {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue(platform)
|
||||
window = fakeWindow()
|
||||
if (platform === 'win32') window.webContents.mainFrame.url = 'dsh-app://app/'
|
||||
const owner = platform === 'win32' ? window : fakeWindow()
|
||||
owner.webContents.mainFrame.url = 'dsh-app://app/'
|
||||
let policy: DesktopPolicyState = { blocking: true, checking: false, page: 'https://downloads.example.com/desktop' }
|
||||
let update: DesktopUpdateState = { phase: 'ready', version: '1.0.1-nightly.1' }
|
||||
const install = vi.fn(async () => update)
|
||||
const createOverlay = (): WindowFixture => window
|
||||
const parent = createOverlay as () => BrowserWindow
|
||||
ui = new DesktopMandatoryUpdateWindow({ overlays: { create: parent }, preload: 'owned', locale: resolveDesktopLocale('zh'),
|
||||
const create = createOverlay as () => BrowserWindow
|
||||
const currentParent = (): WindowFixture => platform === 'win32' ? window : owner
|
||||
const parent = currentParent as () => BrowserWindow
|
||||
ui = new DesktopMandatoryUpdateWindow({ overlays: { create }, preload: 'owned', locale: resolveDesktopLocale('zh'),
|
||||
allowedPageOrigins: ['https://downloads.example.com'], parent,
|
||||
policy: () => policy, update: () => update, refresh: async () => {}, download: async () => update, install })
|
||||
beforeSync?.(window)
|
||||
ui.sync()
|
||||
const event = { sender: window.webContents, senderFrame: window.webContents.mainFrame }
|
||||
const view = () => native.handlers.get(MANDATORY_IPC.status)!(event) as MandatoryUpdateView
|
||||
const action = (name: string, version: string = '1.0.1-nightly.1', revision = view().confirmation?.revision) =>
|
||||
native.handlers.get(MANDATORY_IPC.action)!(event, name, version, revision)
|
||||
return { view, action, install, policy(next: DesktopPolicyState) { policy = next; ui!.sync() },
|
||||
return { view, action, install, owner, policy(next: DesktopPolicyState) { policy = next; ui!.sync() },
|
||||
update(next: DesktopUpdateState) { update = next; ui!.sync() } }
|
||||
}
|
||||
|
||||
@@ -209,10 +213,11 @@ it('updates the same macOS view in the background without showing or focusing th
|
||||
const f = setup()
|
||||
f.update({ phase: 'downloading', version: '1.0.1-nightly.1', percent: 35 })
|
||||
f.update({ phase: 'error', version: '1.0.1-nightly.1', failedOperation: 'download' })
|
||||
expect(window.show).not.toHaveBeenCalled()
|
||||
expect(window.focus).not.toHaveBeenCalled()
|
||||
expect(f.owner).not.toBe(window)
|
||||
expect(f.owner.show).not.toHaveBeenCalled()
|
||||
expect(f.owner.focus).not.toHaveBeenCalled()
|
||||
expect(window.loadURL).toHaveBeenCalledOnce()
|
||||
expect(ui!.confirmationWindow).toBe(window)
|
||||
expect(ui!.confirmationWindow).toBe(f.owner)
|
||||
expect(f.view().policy.blocking).toBe(true)
|
||||
})
|
||||
|
||||
@@ -223,3 +228,26 @@ it('cancels outstanding approval when the mandatory view is destroyed without cl
|
||||
expect(await approval).toBe(false)
|
||||
expect(f.view().policy.blocking).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['initial', 'recovery'] as const)('keeps the native owner title unchanged after %s view loading fails', async (stage) => {
|
||||
const failure = new Error('view load failed')
|
||||
const report = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const f = setup('darwin', (surface) => {
|
||||
if (stage === 'initial') surface.loadURL.mockRejectedValueOnce(failure)
|
||||
})
|
||||
expect(f.owner).not.toBe(window)
|
||||
if (stage === 'recovery') {
|
||||
window.loadURL.mockRejectedValueOnce(failure)
|
||||
window.webContents.emit('render-process-gone')
|
||||
}
|
||||
await expect(window.loadURL.mock.results.at(-1)!.value).rejects.toBe(failure)
|
||||
expect(f.owner.setTitle).not.toHaveBeenCalled()
|
||||
expect(f.owner.show).not.toHaveBeenCalled()
|
||||
expect(f.owner.focus).not.toHaveBeenCalled()
|
||||
expect(native.quit).not.toHaveBeenCalled()
|
||||
expect(f.view().policy.blocking).toBe(true)
|
||||
expect(report).toHaveBeenCalledWith(expect.stringContaining('mandatory update view'), failure)
|
||||
f.policy({ blocking: false, checking: false })
|
||||
ui!.dispose()
|
||||
expect(f.owner.setTitle).not.toHaveBeenCalled()
|
||||
})
|
||||
Reference in new issue
Block a user