From d4cc13fbcfbf68dfab1a4a6831c6dc623cd8cbb8 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 3 Sep 2026 03:23:59 +0800 Subject: [PATCH] fix(inert): blur focus after dynamic activation --- .../src/document_runtime/mutation_commands.rs | 15 ++++ .../src/script_vm/tests/dom_xhr/shadow_dom.rs | 72 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/moli-renderer-v8/src/document_runtime/mutation_commands.rs b/moli-renderer-v8/src/document_runtime/mutation_commands.rs index eb03c18604..35378488e7 100644 --- a/moli-renderer-v8/src/document_runtime/mutation_commands.rs +++ b/moli-renderer-v8/src/document_runtime/mutation_commands.rs @@ -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( diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs index f37d798483..2326ad37f0 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/shadow_dom.rs @@ -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/");