script: Add an initial implementation of the "focus update steps" (#44360)

This moves Servo closer to the focus parts of the HTML specification.
The behavior should be the same as before, but now the code in `script`
matches the structure of the specification.

The main goal is to set us up for:
 - Firing focus events in the right order on nested documents
 - A proper implementation of the unfocusing steps.

Testing: This should not change behavior so is covered by existing
tests.

Signed-off-by: Martin Robinson <mrobinson@fastmail.fm>
Co-authored-by: Martin Robinson <mrobinson@fastmail.fm>
This commit is contained in:
Martin Robinson
2026-04-22 17:55:36 +02:00
committed by GitHub
parent c2b88ff7f5
commit 1528f31269
7 changed files with 337 additions and 204 deletions

View File

@@ -128,7 +128,7 @@ use crate::dom::csp::{CspReporting, GlobalCspReporting, Violation};
use crate::dom::customelementregistry::{
CallbackReaction, CustomElementDefinition, CustomElementReactionStack,
};
use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
use crate::dom::document::focus::FocusableArea;
use crate::dom::document::{
Document, DocumentSource, HasBrowsingContext, IsHTMLDocument, RenderingUpdateReason,
};
@@ -2867,21 +2867,28 @@ impl ScriptThread {
return;
}
let focusable_area = browsing_context_id
.and_then(|browsing_context_id| {
document
.iframes()
.get(browsing_context_id)
.map(|iframe| FocusableArea::Node {
node: DomRoot::from_ref(iframe.element.upcast()),
kind: Default::default(),
})
// This is separate from the next few lines in order to drop the borrow
// on `document.iframes()`.
let iframe_element = browsing_context_id.and_then(|browsing_context_id| {
document
.iframes()
.get(browsing_context_id)
.map(|iframe| iframe.element.as_rooted())
});
let focusable_area = iframe_element
.map(|iframe_element| {
let kind = iframe_element.upcast::<Element>().focusable_area_kind();
FocusableArea::IFrameViewport {
iframe_element,
kind,
}
})
.unwrap_or(FocusableArea::Viewport);
focus_handler.focus(
FocusOperation::Focus(focusable_area),
FocusInitiator::Remote,
focus_handler.focus_update_steps(
focusable_area.focus_chain(),
focus_handler.current_focus_chain(),
&focusable_area,
CanGc::from_cx(cx),
);
}
@@ -2907,7 +2914,8 @@ impl ScriptThread {
// We ignore unfocus requests for top-level `Document`s as they *always* have focus.
// Note that this does not take into account system focus.
if document.window().is_top_level() {
let window = document.window();
if window.is_top_level() {
return;
}
@@ -2921,9 +2929,11 @@ impl ScriptThread {
);
return;
}
focus_handler.focus(
FocusOperation::Unfocus,
FocusInitiator::Remote,
focus_handler.focus_update_steps(
vec![],
focus_handler.current_focus_chain(),
&FocusableArea::Viewport,
CanGc::from_cx(cx),
);
}