Skip to content

fix(desktop): resync renderer after Windows maximize - #3472

Merged
Astro-Han merged 7 commits into
apache:mainfrom
1625567290:fix/windows-maximize-renderer-resize
Aug 23, 2026
Merged

fix(desktop): resync renderer after Windows maximize#3472
Astro-Han merged 7 commits into
apache:mainfrom
1625567290:fix/windows-maximize-renderer-resize

Conversation

@1625567290

Copy link
Copy Markdown
Contributor

Summary

  • Re-run Electron's root view layout after a native Windows maximize so the primary renderer fills the new client area.
  • Coalesce maximize and resize notifications, and skip work after restore or teardown.
  • Add focused coverage for platform gating, event coalescing, layout/repaint order, and destroyed-window guards.

Fixes #3416

Verification

  • npm --workspace @maka/desktop run typecheck
  • Windows maximize renderer sync tests: 4/4 passed.
  • Desktop suite on the refreshed base: 1095/1096 passed. The only failure was the existing shell environment timeout test reading its temporary PID file before creation under full-suite concurrency.
  • Re-ran apps/desktop/dist/main/__tests__/shell-env.test.js three times in isolation: 12/12 passed on every run.

Validation limit

The current host is macOS, so the packaged Windows 11 maximize transition still needs Windows CI or a Windows machine for final visual confirmation.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the workaround behind a small Windows-only scheduler. The lifecycle guards and coalescing look sound, and I did not find a production correctness issue in the implementation. The remaining gap is that the test proves calls against a mock, while the bug and the proposed fix both live in Electron's real Windows layout behavior; I left that boundary inline.

AI-assisted review disclosure: OpenAI Codex performed an independent exact-head review. I verified the implementation, lifecycle guards, test scope, and live PR state, and I made the final review decision.

@Astro-Han

Copy link
Copy Markdown
Contributor

Heads up on the failing Release Windows checkit isn't caused by anything in this PR.

It fails at scripts/verify-windows-sandbox-e2e.mjs:97 with:

FilesystemWorkerClientError: The target was created while this call waited for the lock; re-read before writing.

That's a regression from #3001, which merged into main earlier today: FilesystemWorkerClient now rejects a write to an existing file when the caller doesn't supply expectedIdentity, and the Windows sandbox verifier calls the client directly rather than through FilesystemExecutor. Tracked in #3484 — nothing for you to fix here.

The CI job on this head is green, and the review of the actual change is clean: the native GetClientRect/IsZoomed probes are a stronger check than the CDP path they replace, and adding rendererViewportMatchesNativeClient catches DPI-scaling mismatches the previous viewport comparison couldn't. We also confirmed the 108 removed lines are the superseded CDP helpers rather than unrelated checks, and that the packaged-Git verification strengthened in #3473 is untouched.

We'll approve once #3484 is resolved and the Windows job can go green on this head.


AI-assisted review.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — the diagnosis here is the part that matters, and it is right: maximize is where this reproduces, but the actual gap is that Electron 43's root View layout/repaint does not follow the native maximize. Naming the trigger and the cause separately is what keeps this from becoming a CSS patch.

Reviewed at exact head 83d3d12174620640ea8475cde7673534c8c3142a against base f83b469b557a30036d11572d3c14d69bc0766eaa. No P0–P3.

What we checked, since 495 lines for a window-resize fix invites the question:

  • The production change is 48 lines and sits inside the existing main-window lifecycle. Re-calling setContentView(window.contentView) is not a workaround — NativeWindowViews::SetContentView detaches and re-attaches the content view and then runs FlushPendingRootLayout, which is exactly the layout flush that did not happen. Reusing the same contentView means no new size state is introduced; pending only coalesces events. There is no second size authority here.
  • The remaining ~270 lines are the verifier, and they buy something real: the previous coverage asserted that a mock was called. This reads the native client area through PowerShell GetClientRect/IsZoomed and the renderer viewport through CDP, then walks normal → maximized → normal checking DPR and html/body/#root/.appFrame alignment against the native client. For a bug that only exists at the native/renderer boundary on Windows, that is the only evidence that means anything from a macOS checkout.
  • Guards cover the failure paths: window destroyed, WebContents destroyed, restore, non-Windows. We looked for a stale-pending path that could fire against a destroyed window and did not find one.

Verified locally: helper tests 4/4, node --test scripts/verify-packaged-app.test.mjs scripts/verify-windows-harness.test.mjs 51/51, strict TS compile clean, git diff --check clean.

Not approving yet, and it is not about the code. The red test and package runs on this head are stale merge refs — they were created at 11:23Z against a base that predates the midTurn fix that landed on main at 11:49Z, and the failure in the log is packages/cli/src/pi-tui-runner.ts missing midTurn, which has nothing to do with this PR. A rerun will not help; pull_request runs test the merge commit computed at event time, so only a new push recomputes it. Please rebase, and we will take the result on the new head.


This review was AI-assisted. Findings were verified against the exact head listed above; any mistakes are ours to correct — please push back where we got it wrong.

@Astro-Han

Copy link
Copy Markdown
Contributor

Heads up — this has drifted into conflict with main and can't be merged or reviewed as-is.

Worth knowing before you rebase: #3397 landed on 2026-08-22 and added ASF license headers across ~2685 files, so a rebase will touch more than you'd expect, and any file you add now needs a header (npm run write:asf-headers). A green check from before that date no longer proves anything about the current tree.

I'd like to review this — just ping me once it's rebased and CI is green.

@1625567290
1625567290 force-pushed the fix/windows-maximize-renderer-resize branch from 83d3d12 to 9fcd182 Compare August 23, 2026 02:20
@1625567290

Copy link
Copy Markdown
Contributor Author

Rebased onto current main at f19eede03 and force-updated the branch to 9fcd1826d. The workflow trigger conflict is resolved, both new TypeScript files have ASF headers, and GitHub now reports the PR as mergeable.

Local validation completed:

  • npm run build
  • Desktop typecheck
  • lint and format checks
  • ASF header check
  • Windows helper/harness tests: 65/65
  • maximize renderer sync tests: 4/4

The new CI and Release Windows check runs are currently marked action_required, so they need maintainer approval before they can execute. I also replied to and resolved the packaged Windows smoke review thread. Please approve the two workflow runs; I will follow up once they finish.

@Astro-Han

Copy link
Copy Markdown
Contributor

Reviewed at 9fcd1826. No blocking findings from me. Recording what I checked and one small note.

The problem statement holds — a native Windows maximize changes the HWND client area, and Electron's root view layout does not reliably follow the native transition, so the renderer keeps the pre-maximize height. That is an ordinary user path, not a constructed one.

What I liked about the shape of the fix: createWindowsMaximizeRendererSync does not introduce a second source of truth for window size. It re-applies the same contentView to force a root-view layout, and never touches native bounds or restored bounds. The guards are complete in both directions — it returns early on non-win32, on a destroyed window, on a non-maximized window, and re-checks all of those inside the deferred callback, plus webContents.isDestroyed(). The pending flag coalesces bursts. I walked the deferred callback's exits for restore, window teardown, WebContents teardown, repeated events, and non-win32, and did not find one that escapes a guard.

The verification is the strongest part. Rather than asserting against mocks, the new packaged smoke drives normal → maximized → normal through the real HWND (GetClientRect / IsZoomed) and checks DPR, document and visual viewport, and html / body / #root / .appFrame against the native client rect over CDP. That also answers the question I had while reading — whether restore is broken the same way as maximize, given the mechanism is symmetric — because the smoke exercises the restore leg too.

[P3] handleResize and handleMaximize are byte-identical.

const handleResize = (): void => { scheduleSave(); scheduleMaximizedRendererSync(); };
const handleMaximize = (): void => { scheduleSave(); scheduleMaximizedRendererSync(); };

Two names for one function, bound to two events. One shared handler would read the same and remove the chance of the two drifting apart later under the impression that they are meant to differ. Purely cosmetic — no behavioural consequence, since pending already coalesces the pair of events Windows emits on maximize.

On CI: both workflow runs on this head are action_required with 0s elapsed, so nothing has actually executed. That is a fork-PR approval gate rather than anything wrong with the branch, but it does mean there is no CI evidence for this head yet — worth a maintainer kicking the runs off, particularly since the Windows smoke this PR adds is the part that would prove the fix. I did not treat the author's local verification or the reviews on earlier heads as a substitute.

One thing I could not check: the fix depends on setContentView(sameView) causing Electron to invalidate and re-lay-out the root view. That is a side effect of the current implementation rather than a documented contract, so a future Electron could stop honouring it. The packaged Windows smoke is the right mitigation — it would fail loudly rather than silently regressing — so this is a note, not a finding.

@1625567290

1625567290 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han Thanks for the review. Addressed the P3 in c733e773a: resize and maximize now share the same handleWindowGeometryChange callback, removing the duplicate handlers without changing behavior.

Local validation is green:

  • desktop main-process build
  • Windows maximize renderer-sync tests (4/4)
  • desktop typecheck
  • repository lint and format checks
  • ASF header check
  • git diff --check

The updated-head CI and Release Windows check workflows were created and are currently waiting for maintainer approval to run.

Comment thread apps/desktop/src/main/windows-maximize-renderer-sync.ts
Comment thread scripts/verify-packaged-app.mjs Outdated

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving c733e773a363c34944b61c1880f3e2d1694b4907. Both required checks are terminal green and bound to that exact SHA (package 32614918972, test 32614918937). Three P3s, all inline, none blocking.

  • #3472 (comment) — the deferred body has no try/catch, so a native throw becomes a main-process uncaughtException.
  • #3472 (comment) — the DPR clause in rendererViewportMatchesNativeClient may be specific to 96-DPI runners.
  • Child WebContentsView coverage — discussed below, since it is about what the tests reach rather than about a line.

Thanks for folding in the earlier note about the two byte-identical geometry handlers; handleWindowGeometryChange reads better than the duplicate did.

What was verified on a real machine

Windows x64, 2560×1440 at 150%, Electron 43.2.0. Three maximize/restore rounds plus six 80 ms rapid toggles on both the base and the patched build, with a full-viewport diagnostic pattern injected into the renderer so "did it paint correctly" became pixel-measurable, cross-checked three ways per step (screenshot, CDP layout probe, native client probe).

  • Layout agreed with the native client area within 1 px at every step of all 14, including immediately after maximize, on both builds.
  • The full-viewport border filled the entire client area in every maximized screenshot, consistent at t0 / 150 ms / 1.5 s.
  • The rAF frame counter advanced continuously throughout, including after the rapid toggles — the paint pipeline never froze.

The honest part, which I would rather state than bury: the base build did not reproduce the original bug on that machine. The unpatched version painted correctly too. So that run cannot demonstrate "broken before, fixed after" — it can only demonstrate that the patch introduces no observable regression there. The "the bug exists and this fixes it" link rests on the packaged maximize/restore check this PR adds, which is green on this head.

Two caveats from that run, also stated rather than smoothed over: the window was backgrounded with three anti-throttling flags set (a fidelity concession, since a person was using the machine), and maximize/restore was driven through ShowWindowAsync(SW_MAXIMIZE/SW_RESTORE) — the native path the maximize button and Win+↑ both funnel into, but not the literal gestures.

Child WebContentsView — the gap that was open, now filled

Re-applying the same contentView had no coverage for the embedded-browser child views: the unit test mocks an empty contentView, and the verify probe only inspects the main renderer. That mattered because setContentView is the one operation in this change that could plausibly disturb a child view's attachment.

It was closed two ways. A minimal reproduction on the same Electron version (contentView + child WebContentsView, reapply on and off, three rounds plus rapid toggles) showed the child survives, stays in contentView.children, keeps its bounds, keeps rAF running, and captures correct pixels — with both groups identical. Then the patched dev app with a real session and the browser panel open showed the child's frame counter advancing continuously through every maximize phase (+18–19 frames per 300 ms), no destruction, no detachment, and the DOM-side strip at the right position and full height.

Not verified, and I am not going to claim it: the child's visual position after maximize while visible — the run was interrupted twice by the machine's actual user and stopped rather than pressed. Multi-monitor and mixed-DPI were not reachable with one display.

Reviewed at 2026-08-23 12:42 UTC. No P0–P2.

@1625567290
1625567290 force-pushed the fix/windows-maximize-renderer-resize branch from c733e77 to dbd401e Compare August 23, 2026 06:07
@1625567290

Copy link
Copy Markdown
Contributor Author

@Astro-Han Both new P3s are addressed in dbd401e15 and replied to inline. The branch is also rebased onto current main (6ada6b546).

Post-rebase local validation is green: workspace dependency build, Desktop main-process build, maximize sync and Windows harness tests (43/43), Desktop typecheck, lint, format, ASF headers, and diff checks.

The new-head CI and Release Windows check runs are both action_required; please approve them so the exact-head checks can run.

@Astro-Han

Copy link
Copy Markdown
Contributor

Hi — thanks for the dbd401e15 fix addressing the two P3s.

Heads-up that this branch now conflicts with current main and can't be merged as-is. I tested a rebase locally in a throwaway worktree (your branch was not touched); it stops on:

  • scripts/verify-windows-harness.test.mjs

That's a single real source conflict, so it needs your judgement rather than a mechanical resolution:

git fetch upstream && git rebase upstream/main
# resolve, then
git push --force-with-lease

Once the branch is conflict-free and CI is green on the new head, I'll pick the review back up — the P3 fixes themselves aren't in question here, this is purely the branch having drifted behind main.


AI-assisted maintenance note, not a review. It does not count as the required human review under CONTRIBUTING.md §Review.

@1625567290
1625567290 force-pushed the fix/windows-maximize-renderer-resize branch from dbd401e to d95e89c Compare August 23, 2026 12:00
@1625567290

1625567290 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han Rebased once more onto current main (a2f2a1afa) after the branch drifted into conflict. The new head is d95e89c5f, and GitHub reports it mergeable.

The verifier conflict was resolved by retaining both the current rollback-registration coverage from main and this PR's maximize/renderer coverage.

Exact-head local validation:

  • Desktop workspace dependencies and main-process build
  • maximize renderer-sync tests: 5/5
  • Windows harness tests: 39/39
  • Desktop typecheck
  • ASF header check
  • git diff --check

Fresh exact-head checks should be created for this push.

The fresh workflows are waiting for maintainer approval: CI 32638152502 and Release Windows check 32638152482. Please approve both exact-head runs.

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acting on behalf of repository owner WAWQAQ under the standing Kabi authorization for PR review and APPROVE; exact head d95e89c5fd5d4480b998ee0d2f4745dd8feb0a05.

GO: no P0–P2; one non-blocking P3 inline. Incremental coverage included the full old-to-new range-diff, the added teardown-race error boundary, the packaged Windows geometry verifier, and separate reconciliation of reviews, inline comments, and issue comments. Focused validation: maximize sync 5/5 and Windows harness 39/39. Not independently re-run on a physical Windows host in this pass.

Fresh machine gates: OPEN, non-draft, MERGEABLE/CLEAN; exact-head test and package are completed/success. This agent-executed approval does not claim to satisfy the repository's separate required-human-review wording. No merge performed.

// Electron can report CSS or physical viewport pixels depending on the
// packaged app's DPI-awareness mode. Proportional agreement with the native
// client is the stable contract; equating that scale to DPR is not.
return dimensionsMatch(widthScale, heightScale, 0.01);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] This now checks only that the native/client width and height have the same scale, so any uniformly scaled stale renderer is accepted. Calling this production function with a renderer/layout of 800×600, a maximized native client of 1600×1200, and DPR 1 returns true, even though the renderer is still at the restored size. The existing guards do not catch it: the document can fill its own stale viewport and both scales are still exactly 2. The negative test uses 1920×900, whose aspect-ratio mismatch exercises a different case. Please add a uniformly scaled stale case and require the common scale to be approximately 1 (native reports CSS pixels) or approximately devicePixelRatio (native reports physical pixels). This is non-blocking because the reported #3416 path (1240×820 → 1920×1080) changes aspect ratio and remains covered.

@Astro-Han
Astro-Han merged commit f2b3c89 into apache:main Aug 23, 2026
2 checks passed
mikemikimike pushed a commit to mikemikimike/maka that referenced this pull request Aug 23, 2026
* 修复 Windows 最大化后渲染区未同步

* 补充 Windows 最大化打包烟测

* 改用原生窗口句柄验证最大化

* 强化 Windows 最大化尺寸烟测

* 补充 Windows 最大化文件许可头

* 合并窗口尺寸同步事件处理器

* 收紧 Windows 最大化同步容错
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(desktop): maximizing from a restored window on Windows leaves the renderer at the old size — unpainted area shows as a white edge

3 participants