Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/preload/windows-titlebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,12 @@ function installLayout(document: Document): void {
}
#${DRAG_REGION_ID} {
position: fixed;
z-index: 10;
/* An app-region marker, not a target: keep it behind the app content, or
it wins the hit test for the very points its own "no-drag" opt-outs
exempt and the clicks never reach the controls underneath. Window
dragging is unaffected -- that comes from the app-region rectangle,
not from hit testing. */
z-index: -1;
top: 0;
left: 0;
right: calc(var(${CAPTION_WIDTH_PROPERTY}, 140px) + 44px);
Expand All @@ -144,7 +149,21 @@ function installDragRegion(document: Document): void {
const dragRegion = document.createElement('div')
dragRegion.id = DRAG_REGION_ID
dragRegion.setAttribute('aria-hidden', 'true')
document.body.appendChild(dragRegion)
// Mount the strip AHEAD of the app root, never appended after it.
//
// Electron resolves overlapping app-region rectangles in tree order: every
// `drag` rectangle is added and every `no-drag` rectangle is subtracted as
// the tree is walked. A strip appended after the app root is therefore
// applied after every `no-drag` opt-out that came before it -- including this
// shell's own rule for `button / a / input / [role="button"] / [role="tab"] /
// [role="menuitem"] / [data-dsh-no-drag]` -- and swallows them. A point
// inside the strip then resolves as a caption point, the renderer never sees
// a mouse event, and a control that lives there (the conversation header's
// action row: the agent-preset chip plus every plugin entry registered next
// to it) looks right and never receives a click. Ahead of the app root the
// opt-outs that follow do subtract, which is what the `no-drag` rule above
// exists for.
document.body.insertBefore(dragRegion, document.body.firstChild)

// When any modal or dialog is open, hide the drag region completely
// so all buttons (especially near the top 36px) are 100% clickable.
Expand Down
21 changes: 21 additions & 0 deletions test/windows-titlebar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,25 @@ describe('Windows titlebar menu', () => {
expect(preload).toContain("attributeFilter: ['data-ds-dark-theme', 'class', 'style']")
expect(preload).toContain("ipcRenderer.invoke('desktop-titlebar:set-theme', isDark)")
})

it('mounts the drag strip ahead of the app root and behind the content it covers', async () => {
const preload = await readFile('src/preload/windows-titlebar.ts', 'utf8')
const ruleStart = preload.indexOf('#${DRAG_REGION_ID} {')
const rule = preload.slice(ruleStart, preload.indexOf('\n }', ruleStart))

// Electron resolves app-region rectangles in tree order, so the drag
// rectangle has to be mounted before the `no-drag` opt-outs it must yield
// to: appended after the app root it wins over every one of them and the
// controls drawn inside the strip never receive a click.
expect(preload).toContain('document.body.insertBefore(dragRegion, document.body.firstChild)')
expect(preload).not.toContain('document.body.appendChild(dragRegion)')

// The marker also stays out of the hit-test path it covers, so an exempted
// point reaches the control underneath, while dragging keeps coming from
// the app-region rectangle itself.
expect(ruleStart).toBeGreaterThan(-1)
expect(rule).toContain('position: fixed;')
expect(rule).toContain('z-index: -1;')
expect(rule).toContain('-webkit-app-region: drag;')
})
})