script/constellation: Rename and consolidate cross-Document focus messaging (#44020)

There are two times that Servo needs to ask other `Document`s to either
focus or blur.

- During processing of the "focusing steps". When a new element gains
  focus this may cause focus to be lost or gained in parent `<iframe>`s.
- When calling `focus()` on a DOM Window from another origin.

In both of these cases we need to request that a `Document` gain or lose
focus via the Constellation, but in the second case we may have a
`BrowsingContextId` of the `<iframe>` gaining focus and a
`FocusSequence`. This change splits those cases into two kinds of
messages.

Finally, run the entire focusing steps when calling `window.focus()`
instead of going to the constellation immediately. This will be
important in a followup changes where messaging order is made more
consistent.

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

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson
2026-04-09 10:59:47 +02:00
committed by GitHub
parent 553f125773
commit 718c8913af
11 changed files with 206 additions and 208 deletions

View File

@@ -1825,18 +1825,21 @@ impl ScriptThread {
ScriptThreadMessage::RemoveHistoryStates(pipeline_id, history_states) => {
self.handle_remove_history_states(pipeline_id, history_states)
},
ScriptThreadMessage::FocusIFrame(parent_pipeline_id, frame_id, sequence) => self
.handle_focus_iframe_msg(
parent_pipeline_id,
frame_id,
sequence,
CanGc::from_cx(cx),
),
ScriptThreadMessage::FocusDocument(pipeline_id, sequence) => {
self.handle_focus_document_msg(pipeline_id, sequence, CanGc::from_cx(cx))
ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
pipeline_id,
sequence,
iframe_browsing_context_id,
) => self.handle_focus_document_as_part_of_focusing_steps(
cx,
pipeline_id,
sequence,
iframe_browsing_context_id,
),
ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(pipeline_id, sequence) => {
self.handle_unfocus_document_as_part_of_focusing_steps(cx, pipeline_id, sequence);
},
ScriptThreadMessage::Unfocus(pipeline_id, sequence) => {
self.handle_unfocus_msg(pipeline_id, sequence, CanGc::from_cx(cx))
ScriptThreadMessage::FocusDocument(pipeline_id) => {
self.handle_focus_document(cx, pipeline_id);
},
ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, msg) => {
self.handle_webdriver_msg(pipeline_id, msg, cx)
@@ -2845,101 +2848,87 @@ impl ScriptThread {
warn!("change of activity sent to nonexistent pipeline");
}
fn handle_focus_iframe_msg(
fn handle_focus_document_as_part_of_focusing_steps(
&self,
parent_pipeline_id: PipelineId,
browsing_context_id: BrowsingContextId,
cx: &mut js::context::JSContext,
pipeline_id: PipelineId,
sequence: FocusSequenceNumber,
can_gc: CanGc,
browsing_context_id: Option<BrowsingContextId>,
) {
let document = self
.documents
.borrow()
.find_document(parent_pipeline_id)
.unwrap();
let Some(iframe_element) = ({
// Enclose `iframes()` call and create a new root to avoid retaining
// borrow.
let iframes = document.iframes();
iframes
.get(browsing_context_id)
.map(|iframe| DomRoot::from_ref(iframe.element.upcast::<Node>()))
}) else {
let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
warn!("Unknown {pipeline_id:?} for FocusDocumentAsPartOfFocusingSteps message.");
return;
};
if document.focus_handler().focus_sequence() > sequence {
let focus_handler = document.focus_handler();
if focus_handler.focus_sequence() > sequence {
debug!(
"Disregarding the FocusIFrame message because the contained sequence number is \
too old ({:?} < {:?})",
sequence,
document.focus_handler().focus_sequence()
"Disregarding the FocusDocumentAsPartOfFocusingSteps message because \
the contained sequence number is too old ({sequence:?} < {:?})",
focus_handler.focus_sequence()
);
return;
}
if let Some(focusable_area) = iframe_element.get_the_focusable_area() {
iframe_element.owner_document().focus_handler().focus(
FocusOperation::Focus(focusable_area),
FocusInitiator::Remote,
can_gc,
);
}
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(),
})
})
.unwrap_or(FocusableArea::Viewport);
focus_handler.focus(
FocusOperation::Focus(focusable_area),
FocusInitiator::Remote,
CanGc::from_cx(cx),
);
}
fn handle_focus_document_msg(
&self,
pipeline_id: PipelineId,
sequence: FocusSequenceNumber,
can_gc: CanGc,
) {
if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
let focus_handler = document.focus_handler();
if focus_handler.focus_sequence() > sequence {
debug!(
"Disregarding the FocusDocument message because the contained sequence number is \
too old ({:?} < {:?})",
sequence,
focus_handler.focus_sequence()
);
return;
}
focus_handler.focus(
FocusOperation::Focus(FocusableArea::Viewport),
FocusInitiator::Remote,
can_gc,
);
} else {
warn!(
"Couldn't find document by pipleline_id:{pipeline_id:?} when handle_focus_document_msg."
);
}
fn handle_focus_document(&self, cx: &mut js::context::JSContext, pipeline_id: PipelineId) {
let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
warn!("Unknown {pipeline_id:?} for FocusDocument message.");
return;
};
document.window().Focus(cx);
}
fn handle_unfocus_msg(
fn handle_unfocus_document_as_part_of_focusing_steps(
&self,
cx: &mut js::context::JSContext,
pipeline_id: PipelineId,
sequence: FocusSequenceNumber,
can_gc: CanGc,
) {
if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
let focus_handler = document.focus_handler();
if focus_handler.focus_sequence() > sequence {
debug!(
"Disregarding the Unfocus message because the contained sequence number is \
too old ({:?} < {:?})",
sequence,
focus_handler.focus_sequence()
);
return;
}
focus_handler.handle_container_unfocus(can_gc);
} else {
warn!(
"Couldn't find document by pipleline_id:{pipeline_id:?} when handle_unfocus_msg."
);
let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
warn!("Unknown {pipeline_id:?} for UnfocusDocumentAsPartOfFocusingSteps");
return;
};
// 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() {
return;
}
let focus_handler = document.focus_handler();
if focus_handler.focus_sequence() > sequence {
debug!(
"Disregarding the Unfocus message because the contained sequence number is \
too old ({:?} < {:?})",
sequence,
focus_handler.focus_sequence()
);
return;
}
focus_handler.focus(
FocusOperation::Unfocus,
FocusInitiator::Remote,
CanGc::from_cx(cx),
);
}
#[expect(clippy::too_many_arguments)]