Fix mobile overlay inert ownership (#1352)
All checks were successful
CI / lint (pull_request) Successful in 3m19s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 5m45s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy 2026-08-24 13:49:48 +00:00
parent c73fbf8379
commit 084c0b5732
2 changed files with 46 additions and 1 deletions

View File

@ -8,12 +8,25 @@
let active = false; let active = false;
let previous = '#/my-work'; let previous = '#/my-work';
let retries = 0; let retries = 0;
let backgroundInert = null;
function attr(element, name, value) { function attr(element, name, value) {
if (value === null) element.removeAttribute(name); if (value === null) element.removeAttribute(name);
else element.setAttribute(name, value); else element.setAttribute(name, value);
} }
function containBackground() {
if (backgroundInert) return;
backgroundInert = new Map(o.backgrounds.map(element => [element, element.inert]));
backgroundInert.forEach((_wasInert, element) => { element.inert = true; });
}
function releaseBackground() {
if (!backgroundInert) return;
backgroundInert.forEach((wasInert, element) => { element.inert = wasInert; });
backgroundInert = null;
}
function render(open, focus = false) { function render(open, focus = false) {
const wasActive = active; const wasActive = active;
active = Boolean(open && o.mediaQuery.matches); active = Boolean(open && o.mediaQuery.matches);
@ -28,7 +41,8 @@
attr(o.root, 'aria-modal', active ? 'true' : null); attr(o.root, 'aria-modal', active ? 'true' : null);
attr(o.root, 'aria-hidden', active || !o.mediaQuery.matches ? null : 'true'); attr(o.root, 'aria-hidden', active || !o.mediaQuery.matches ? null : 'true');
o.root.inert = !active && o.mediaQuery.matches; o.root.inert = !active && o.mediaQuery.matches;
o.backgrounds.forEach(element => { element.inert = active; }); if (active) containBackground();
else releaseBackground();
o.dock.hidden = active; o.dock.hidden = active;
attr(o.hud, 'data-overlay-hidden', active ? 'true' : null); attr(o.hud, 'data-overlay-hidden', active ? 'true' : null);
if (active) o.closeButton.focus(); if (active) o.closeButton.focus();

View File

@ -133,6 +133,37 @@ process.stdout.write(JSON.stringify({{current:controller.current(),hash:location
} }
def test_late_inactive_route_event_does_not_release_background_owned_by_next_overlay():
script = f"""
const createInsights = require({json.dumps(str(CONTROLLER))});
const listeners = {{}};
const element = () => ({{
hidden:false, inert:false, attributes:{{}}, focuses:0, listeners:{{}},
setAttribute(name,value) {{ this.attributes[name] = String(value); }},
removeAttribute(name) {{ delete this.attributes[name]; }},
addEventListener(name,fn) {{ this.listeners[name] = fn; }},
focus() {{ this.focuses += 1; }},
}});
const root=element(), launcher=element(), closeButton=element(), dock=element(), hud=element(), header=element();
const location={{pathname:'/dashboard/', search:'', hash:'#/insights'}};
const history={{state:null, pushState() {{}}, back() {{}}, replaceState(state,unused,url) {{
this.state=state; location.hash=url.slice(url.indexOf('#'));
}}}};
const controller=createInsights({{
root,launcher,closeButton,dock,hud,backgrounds:[header],history,location,
eventTarget:{{addEventListener(name,fn) {{ listeners[name]=fn; }}}},
mediaQuery:{{matches:true,addEventListener() {{}}}},
}});
controller.start();
closeButton.listeners.click();
header.inert = true;
listeners.hashchange();
process.stdout.write(JSON.stringify({{current:controller.current(), headerInert:header.inert}}));
"""
assert run_node(script) == {"current": False, "headerInert": True}
def test_escape_requests_back_navigation_only_while_insights_is_open(): def test_escape_requests_back_navigation_only_while_insights_is_open():
script = f""" script = f"""
const createInsights=require({json.dumps(str(CONTROLLER))}); const createInsights=require({json.dumps(str(CONTROLLER))});