fix(inert): blur focus after dynamic activation

This commit is contained in:
ldm0
2026-09-09 06:52:21 +08:00
parent 1413e95e06
commit d4cc13fbcf
2 changed files with 87 additions and 0 deletions
@@ -240,6 +240,21 @@ impl DocumentRuntime {
crate::native_bridge::element::handle_popover_attribute_change(
scope, host_ptr, handle, namespace, local_name, old_value, new_value,
);
if namespace.is_none()
&& local_name.eq_ignore_ascii_case("inert")
&& old_value.is_none()
&& new_value.is_some()
&& self
.dom_host
.node(handle)
.and_then(Node::as_element)
.is_some_and(|element| {
element.namespace() == crate::native_bridge::document::XHTML_NS
})
&& let Some(active) = self.active_element_handle()
{
crate::native_bridge::element::schedule_focus_blur_if_needed(scope, host_ptr, active);
}
}
pub(crate) fn set_text_content(
@@ -2535,6 +2535,78 @@ async fn focused_display_none_blur_listener_sees_updated_focus_state() {
assert_eq!(sync_state, "none|true|true");
assert_eq!(blur_state, "block,true,true|block");
}
#[tokio::test]
async fn focused_element_blurs_after_it_becomes_inert() {
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
let mut vm =
new_storage_test_vm_with_loader("https://dynamic-inert-focus-fixup.test/", &loader);
let direct_sync_state = vm
.eval(
r#"
(() => {
const html = document.documentElement || document.appendChild(document.createElement('html'));
const body = document.body || html.appendChild(document.createElement('body'));
const wrapper = document.createElement('div');
const input = document.createElement('input');
wrapper.appendChild(input);
body.appendChild(wrapper);
globalThis.__dynamicInertFocus = { wrapper, input, blurCount: 0 };
input.addEventListener('blur', () => globalThis.__dynamicInertFocus.blurCount++);
input.focus();
input.inert = true;
return [document.activeElement === input, globalThis.__dynamicInertFocus.blurCount].join('|');
})()
"#,
)
.expect("direct inert focus setup should evaluate");
assert!(
vm.run_next_due_timer_callback_for_test(&loader)
.await
.expect("direct inert focus-fixup timer should run")
);
let direct_async_state = vm
.eval(
r#"
[document.activeElement === document.body, globalThis.__dynamicInertFocus.blurCount].join('|')
"#,
)
.expect("direct inert focus-fixup result should evaluate");
let ancestor_sync_state = vm
.eval(
r#"
(() => {
const { wrapper, input } = globalThis.__dynamicInertFocus;
input.inert = false;
input.focus();
wrapper.setAttribute('inert', '');
return [document.activeElement === input, globalThis.__dynamicInertFocus.blurCount].join('|');
})()
"#,
)
.expect("ancestor inert focus setup should evaluate");
assert!(
vm.run_next_due_timer_callback_for_test(&loader)
.await
.expect("ancestor inert focus-fixup timer should run")
);
let ancestor_async_state = vm
.eval(
r#"
[document.activeElement === document.body, globalThis.__dynamicInertFocus.blurCount].join('|')
"#,
)
.expect("ancestor inert focus-fixup result should evaluate");
assert_eq!(direct_sync_state, "true|0");
assert_eq!(direct_async_state, "true|1");
assert_eq!(ancestor_sync_state, "true|1");
assert_eq!(ancestor_async_state, "true|2");
}
#[test]
fn scroll_container_focusability_tracks_current_layout() {
let mut vm = new_storage_test_vm("https://scroll-focusability.test/");