use std::cell::{Cell, Ref};
use std::cmp::Ordering;
use bitflags::bitflags;
use embedder_traits::FocusSequenceNumber;
use js::context::JSContext;
use keyboard_types::Modifiers;
use script_bindings::cell::DomRefCell;
use script_bindings::codegen::GenericBindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
use script_bindings::inheritance::Castable;
use script_bindings::root::{Dom, DomRoot};
use servo_base::id::BrowsingContextId;
use servo_constellation_traits::{
RemoteFocusOperation, ScriptToConstellationMessage, SequentialFocusDirection,
};
use crate::dom::bindings::root::MutNullableDom;
use crate::dom::focusevent::FocusEventType;
use crate::dom::node::focus::FocusNavigationScopeOwner;
use crate::dom::types::{
Element, EventTarget, FocusEvent, HTMLElement, HTMLIFrameElement, KeyboardEvent, Window,
};
use crate::dom::{Document, Event, EventBubbles, EventCancelable, Node, NodeTraits};
use crate::realms::enter_auto_realm;
#[derive(Clone, Copy, Debug, Default, JSTraceable, MallocSizeOf, PartialEq)]
pub(crate) struct FocusableAreaKind(u8);
bitflags! {
impl FocusableAreaKind: u8 {
const Click = 1 << 0;
const Sequential = 1 << 1;
}
}
#[derive(Clone, Default, JSTraceable, MallocSizeOf, PartialEq)]
pub(crate) enum FocusableArea {
Node {
node: DomRoot<Node>,
kind: FocusableAreaKind,
},
IFrameViewport {
iframe_element: DomRoot<HTMLIFrameElement>,
kind: FocusableAreaKind,
},
#[default]
Viewport,
}
impl std::fmt::Debug for FocusableArea {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Node { node, kind } => f
.debug_struct("Node")
.field("node", node)
.field("kind", kind)
.finish(),
Self::IFrameViewport {
iframe_element,
kind,
} => f
.debug_struct("IFrameViewport")
.field("pipeline", &iframe_element.pipeline_id())
.field("kind", kind)
.finish(),
Self::Viewport => write!(f, "Viewport"),
}
}
}
impl FocusableArea {
pub(crate) fn kind(&self) -> FocusableAreaKind {
match self {
Self::Node { kind, .. } | Self::IFrameViewport { kind, .. } => *kind,
Self::Viewport => FocusableAreaKind::Click | FocusableAreaKind::Sequential,
}
}
pub(crate) fn element(&self) -> Option<&Element> {
match self {
Self::Node { node, .. } => node.downcast(),
Self::IFrameViewport { iframe_element, .. } => Some(iframe_element.upcast()),
Self::Viewport => None,
}
}
pub(crate) fn dom_anchor(&self, document: &Document) -> DomRoot<Node> {
match self {
Self::Node { node, .. } => node.clone(),
Self::IFrameViewport { iframe_element, .. } => {
DomRoot::from_ref(iframe_element.upcast())
},
Self::Viewport => DomRoot::from_ref(document.upcast()),
}
}
pub(crate) fn focus_chain(&self) -> Vec<FocusableArea> {
match self {
FocusableArea::Node { .. } | FocusableArea::IFrameViewport { .. } => {
vec![self.clone(), FocusableArea::Viewport]
},
FocusableArea::Viewport => vec![self.clone()],
}
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct DocumentFocusHandler {
window: Dom<Window>,
focused_area: DomRefCell<FocusableArea>,
#[no_trace]
focus_sequence: Cell<FocusSequenceNumber>,
has_focus: Cell<bool>,
sequential_focus_navigation_starting_point: MutNullableDom<Node>,
}
impl DocumentFocusHandler {
pub(crate) fn new(window: &Window, has_focus: bool) -> Self {
Self {
window: Dom::from_ref(window),
focused_area: Default::default(),
focus_sequence: Cell::new(FocusSequenceNumber::default()),
has_focus: Cell::new(has_focus),
sequential_focus_navigation_starting_point: Default::default(),
}
}
pub(crate) fn has_focus(&self) -> bool {
self.has_focus.get()
}
pub(crate) fn set_has_focus(&self, has_focus: bool) {
self.has_focus.set(has_focus);
}
pub(crate) fn focused_area(&self) -> Ref<'_, FocusableArea> {
self.focused_area.borrow()
}
pub(crate) fn set_focused_area(&self, new_focusable_area: FocusableArea) {
if new_focusable_area == *self.focused_area.borrow() {
return;
}
fn recursively_set_focus_status(element: &Element, new_state: bool) {
element.set_focus_state(new_state);
let Some(shadow_root) = element.containing_shadow_root() else {
return;
};
recursively_set_focus_status(&shadow_root.Host(), new_state);
}
if let Some(previously_focused_element) = self.focused_area.borrow().element() {
recursively_set_focus_status(previously_focused_element, false);
}
if let Some(newly_focused_element) = new_focusable_area.element() {
recursively_set_focus_status(newly_focused_element, true);
}
*self.focused_area.borrow_mut() = new_focusable_area;
}
pub fn focus_sequence(&self) -> FocusSequenceNumber {
self.focus_sequence.get()
}
fn increment_fetch_focus_sequence(&self) -> FocusSequenceNumber {
self.focus_sequence.set(FocusSequenceNumber(
self.focus_sequence
.get()
.0
.checked_add(1)
.expect("too many focus messages have been sent"),
));
self.focus_sequence.get()
}
pub(crate) fn current_focus_chain(&self) -> Vec<FocusableArea> {
if !self.has_focus() {
return vec![];
}
self.focused_area().focus_chain()
}
pub(crate) fn focus(&self, cx: &mut JSContext, new_focus_target: FocusableArea) {
let old_focus_chain = self.current_focus_chain();
let new_focus_chain = new_focus_target.focus_chain();
self.focus_update_steps(cx, new_focus_chain, old_focus_chain, &new_focus_target);
let child_browsing_context_id = match new_focus_target {
FocusableArea::IFrameViewport { iframe_element, .. } => {
iframe_element.browsing_context_id()
},
_ => None,
};
let sequence = self.increment_fetch_focus_sequence();
debug!(
"Advertising the focus request to the constellation \
with sequence number {sequence:?} and child \
{child_browsing_context_id:?}",
);
self.window.send_to_constellation(
ScriptToConstellationMessage::FocusAncestorBrowsingContextsForFocusingSteps(
child_browsing_context_id,
sequence,
),
);
}
pub(crate) fn focus_update_steps(
&self,
cx: &mut JSContext,
mut new_focus_chain: Vec<FocusableArea>,
mut old_focus_chain: Vec<FocusableArea>,
new_focus_target: &FocusableArea,
) {
let new_focus_chain_was_empty = new_focus_chain.is_empty();
while let (Some(last_new), Some(last_old)) =
(new_focus_chain.last(), old_focus_chain.last())
{
if last_new == last_old {
new_focus_chain.pop();
old_focus_chain.pop();
} else {
break;
}
}
if old_focus_chain.is_empty() && new_focus_chain.is_empty() {
return;
}
self.set_focused_area(FocusableArea::Viewport);
let last_old_focus_chain_entry = old_focus_chain.len().saturating_sub(1);
for (index, entry) in old_focus_chain.iter().enumerate() {
let blur_event_target = match entry {
FocusableArea::Node { node, .. } => Some(node.upcast::<EventTarget>()),
FocusableArea::IFrameViewport { iframe_element, .. } => {
Some(iframe_element.upcast())
},
FocusableArea::Viewport => Some(self.window.upcast::<EventTarget>()),
};
let related_blur_target = match new_focus_chain.last() {
Some(FocusableArea::Node { node, .. })
if index == last_old_focus_chain_entry &&
matches!(entry, FocusableArea::Node { .. }) =>
{
Some(node.upcast())
},
_ => None,
};
if let Some(blur_event_target) = blur_event_target {
self.fire_focus_event(
cx,
FocusEventType::Blur,
blur_event_target,
related_blur_target,
);
}
}
if &*self.focused_area() != new_focus_target &&
let Some(html_element) = new_focus_target
.element()
.and_then(|element| element.downcast::<HTMLElement>())
{
html_element.handle_focus_state_for_contenteditable(cx);
}
self.set_has_focus(!new_focus_chain_was_empty);
let last_new_focus_chain_entry = new_focus_chain.len().saturating_sub(1); for (index, entry) in new_focus_chain.iter().enumerate().rev() {
if index == 0 {
self.set_focused_area(entry.clone());
}
let focus_event_target = match entry {
FocusableArea::Node { node, .. } => Some(node.upcast::<EventTarget>()),
FocusableArea::IFrameViewport { iframe_element, .. } => {
Some(iframe_element.upcast())
},
FocusableArea::Viewport => Some(self.window.upcast::<EventTarget>()),
};
let related_focus_target = match old_focus_chain.last() {
Some(FocusableArea::Node { node, .. })
if index == last_new_focus_chain_entry &&
matches!(entry, FocusableArea::Node { .. }) =>
{
Some(node.upcast())
},
_ => None,
};
if let Some(focus_event_target) = focus_event_target {
self.fire_focus_event(
cx,
FocusEventType::Focus,
focus_event_target,
related_focus_target,
);
}
}
}
pub(crate) fn fire_focus_event(
&self,
cx: &mut JSContext,
focus_event_type: FocusEventType,
event_target: &EventTarget,
related_target: Option<&EventTarget>,
) {
let event_name = match focus_event_type {
FocusEventType::Focus => "focus".into(),
FocusEventType::Blur => "blur".into(),
};
let event = FocusEvent::new(
cx,
&self.window,
event_name,
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
Some(&self.window),
0i32,
related_target,
);
let event = event.upcast::<Event>();
event.set_trusted(true);
event.fire(cx, event_target);
}
pub(crate) fn perform_focus_fixup_rule(&self, cx: &mut JSContext) {
if self
.focused_area
.borrow()
.element()
.is_none_or(|focused| focused.is_focusable_area())
{
return;
}
self.focus(cx, FocusableArea::Viewport);
}
pub(crate) fn set_sequential_focus_navigation_starting_point(&self, node: &Node) {
self.sequential_focus_navigation_starting_point
.set(Some(node));
}
fn sequential_focus_navigation_starting_point(&self) -> Option<DomRoot<Node>> {
self.sequential_focus_navigation_starting_point
.get()
.filter(|node| node.is_connected())
}
pub(crate) fn sequential_focus_navigation_via_keyboard_event(
&self,
cx: &mut JSContext,
event: &KeyboardEvent,
) {
let direction = if event.modifiers().contains(Modifiers::SHIFT) {
SequentialFocusDirection::Backward
} else {
SequentialFocusDirection::Forward
};
self.sequential_focus_navigation(cx, direction);
}
fn sequential_focus_navigation(&self, cx: &mut JSContext, direction: SequentialFocusDirection) {
let mut starting_point = self
.focused_area()
.element()
.map(|element| DomRoot::from_ref(element.upcast::<Node>()));
if let Some(sequential_focus_navigation_starting_point) =
self.sequential_focus_navigation_starting_point() &&
starting_point.as_ref().is_none_or(|starting_point| {
starting_point.is_ancestor_of(&sequential_focus_navigation_starting_point)
})
{
starting_point = Some(sequential_focus_navigation_starting_point);
}
self.sequential_focus_navigation_loop(
cx,
starting_point,
direction,
false,
);
}
fn sequential_focus_navigation_loop(
&self,
cx: &mut JSContext,
starting_point: Option<DomRoot<Node>>,
direction: SequentialFocusDirection,
allow_focusing_viewport: bool,
) {
let starting_point_is_navigable = starting_point
.as_ref()
.is_none_or(|starting_point| starting_point.is::<HTMLIFrameElement>());
let selection_mechanism = starting_point
.as_ref()
.and_then(|node| node.downcast::<Element>())
.filter(|element| element.is_sequentially_focusable())
.map(|element| {
SequentialFocusNavigationMechanism::Sequential(
element.explicitly_set_tab_index().unwrap_or_default(),
)
})
.unwrap_or_else(|| {
if starting_point_is_navigable {
SequentialFocusNavigationMechanism::FirstOrLast
} else {
SequentialFocusNavigationMechanism::Dom
}
});
let candidate = SequentialFocusNavigationSearch::new(
starting_point
.as_ref()
.and_then(|node| node.containing_focus_navigation_scope_owner())
.unwrap_or_else(|| FocusNavigationScopeOwner::Document(self.window.Document())),
direction,
selection_mechanism,
starting_point,
)
.search();
if let Some(candidate) = candidate {
let document = self.window.Document();
let event_handler = document.event_handler();
event_handler.focus_and_scroll_to_element_for_key_event(cx, &candidate);
match candidate.downcast::<HTMLIFrameElement>() {
Some(iframe_element) => self.sequentially_focus_child_iframe_local_or_remote(
cx,
iframe_element,
direction,
),
None => event_handler.focus_and_scroll_to_element_for_key_event(cx, &candidate),
}
return;
}
self.sequential_focus_navigation_starting_point.clear();
if allow_focusing_viewport {
self.focus(cx, FocusableArea::Viewport);
return;
}
if self.window.is_top_level() {
return;
}
self.sequentially_focus_parent_local_or_remote(cx, direction);
}
fn sequentially_focus_child_iframe_local_or_remote(
&self,
cx: &mut JSContext,
iframe_element: &HTMLIFrameElement,
direction: SequentialFocusDirection,
) {
if let Some(content_document) = iframe_element.GetContentDocument() {
content_document
.focus_handler()
.sequential_focus_from_another_document(cx, None, direction);
} else if let Some(browsing_context_id) = iframe_element.browsing_context_id() {
self.window.send_to_constellation(
ScriptToConstellationMessage::FocusRemoteBrowsingContext(
browsing_context_id,
RemoteFocusOperation::Sequential(direction, None),
),
);
} else {
iframe_element
.upcast::<Node>()
.run_the_focusing_steps(cx, None);
}
}
fn sequentially_focus_parent_local_or_remote(
&self,
cx: &mut JSContext,
direction: SequentialFocusDirection,
) {
let window_proxy = self.window.window_proxy();
if let Some(iframe) = window_proxy.frame_element() {
let browsing_context_id = iframe
.downcast::<HTMLIFrameElement>()
.and_then(|iframe_element| iframe_element.browsing_context_id());
iframe
.owner_document()
.focus_handler()
.sequential_focus_from_another_document(cx, browsing_context_id, direction);
} else if let Some(browsing_context_id) = window_proxy
.parent()
.map(|parent| parent.browsing_context_id())
{
self.window.send_to_constellation(
ScriptToConstellationMessage::FocusRemoteBrowsingContext(
browsing_context_id,
RemoteFocusOperation::Sequential(
direction,
Some(window_proxy.browsing_context_id()),
),
),
);
}
}
pub(crate) fn sequential_focus_from_another_document(
&self,
cx: &mut JSContext,
browsing_context_id: Option<BrowsingContextId>,
direction: SequentialFocusDirection,
) {
let mut realm = enter_auto_realm(cx, &*self.window);
let cx = &mut realm.current_realm();
let starting_point = browsing_context_id.and_then(|browsing_context_id| {
self.window
.Document()
.iframes()
.get(browsing_context_id)
.map(|iframe| DomRoot::from_ref(iframe.element.upcast::<Node>()))
});
self.sequential_focus_navigation_loop(
cx,
starting_point,
direction,
true,
);
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum SequentialFocusNavigationMechanism {
Dom,
Sequential(i32 ),
FirstOrLast,
}
#[derive(PartialEq)]
enum Continue {
Yes,
No,
}
#[derive(PartialEq)]
pub(crate) enum SequentialFocusNavigationSearchContext {
Original,
Nested,
Containing,
}
pub(crate) struct SequentialFocusNavigationSearch {
focus_navigation_scope_owner: FocusNavigationScopeOwner,
direction: SequentialFocusDirection,
mechanism: SequentialFocusNavigationMechanism,
starting_point: Option<DomRoot<Node>>,
current_winner: Option<(DomRoot<Element>, i32)>,
passed_starting_point: bool,
search_context: SequentialFocusNavigationSearchContext,
}
impl SequentialFocusNavigationSearch {
pub(crate) fn new(
focus_navigation_scope_owner: FocusNavigationScopeOwner,
direction: SequentialFocusDirection,
mechanism: SequentialFocusNavigationMechanism,
starting_point: Option<DomRoot<Node>>,
) -> Self {
let passed_starting_point = starting_point.is_none();
Self {
focus_navigation_scope_owner,
direction,
mechanism,
starting_point,
current_winner: Default::default(),
passed_starting_point,
search_context: SequentialFocusNavigationSearchContext::Original,
}
}
pub(crate) fn search(mut self) -> Option<DomRoot<Element>> {
for node in self.focus_navigation_scope_owner.iterator() {
if self.process_node(&node) == Continue::No {
break;
}
}
if let Some(winner) = self.current_winner.take() {
return Some(winner.0);
}
if self.search_context != SequentialFocusNavigationSearchContext::Nested {
return self.maybe_search_in_containing_focus_navigation_scope();
}
None
}
fn maybe_search_in_containing_focus_navigation_scope(&self) -> Option<DomRoot<Element>> {
let containing_node = self.focus_navigation_scope_owner.node();
let containing_focus_navigation_scope_owner =
containing_node.containing_focus_navigation_scope_owner()?;
let tab_index = containing_node
.downcast::<Element>()?
.explicitly_set_tab_index()
.unwrap_or_default();
let mechanism = match &self.mechanism {
SequentialFocusNavigationMechanism::Sequential(..) if tab_index == -1 => {
SequentialFocusNavigationMechanism::Dom
},
SequentialFocusNavigationMechanism::Sequential(..) => {
SequentialFocusNavigationMechanism::Sequential(tab_index)
},
mechanism => *mechanism,
};
if self.direction == SequentialFocusDirection::Backward &&
let Some(containing_element) = containing_node.downcast::<Element>() &&
containing_element.is_sequentially_focusable()
{
return Some(DomRoot::from_ref(containing_element));
}
Self {
focus_navigation_scope_owner: containing_focus_navigation_scope_owner,
direction: self.direction,
mechanism,
starting_point: Some(DomRoot::from_ref(containing_node)),
current_winner: Default::default(),
passed_starting_point: false,
search_context: SequentialFocusNavigationSearchContext::Containing,
}
.search()
}
fn process_node(&mut self, node: &Node) -> Continue {
if Some(node) == self.starting_point.as_deref() {
self.passed_starting_point = true;
} else if self.process_node_as_sequentially_focusable_node(node) == Continue::No {
return Continue::No;
}
self.process_node_as_focus_scope_owner(node)
}
fn process_node_as_sequentially_focusable_node(&mut self, node: &Node) -> Continue {
let Some(element) = node.downcast::<Element>() else {
return Continue::Yes;
};
if !element.is_sequentially_focusable() {
return Continue::Yes;
}
let tab_index = element.explicitly_set_tab_index().unwrap_or_default();
let (is_new_winner, should_continue) = self.process_candidate_with_tab_index(tab_index);
if is_new_winner {
self.current_winner = Some((DomRoot::from_ref(element), tab_index));
}
should_continue
}
fn process_node_as_focus_scope_owner(&mut self, node: &Node) -> Continue {
if self.focus_navigation_scope_owner.node() == node {
return Continue::Yes;
}
if Some(node) == self.starting_point.as_deref() &&
self.search_context == SequentialFocusNavigationSearchContext::Containing
{
return Continue::Yes;
}
let Some(focus_navigation_scope_owner) = node.as_focus_navigation_scope_owner() else {
return Continue::Yes;
};
let tab_index = focus_navigation_scope_owner
.node()
.downcast::<Element>()
.and_then(Element::explicitly_set_tab_index)
.unwrap_or_default();
let (is_new_winner, should_continue) = self.process_candidate_with_tab_index(tab_index);
if !is_new_winner {
return should_continue;
}
let mechanism = match self.mechanism {
SequentialFocusNavigationMechanism::Dom => SequentialFocusNavigationMechanism::Dom,
_ => SequentialFocusNavigationMechanism::FirstOrLast,
};
let element = Self {
focus_navigation_scope_owner,
direction: self.direction,
mechanism,
starting_point: None,
current_winner: Default::default(),
passed_starting_point: self.passed_starting_point,
search_context: SequentialFocusNavigationSearchContext::Nested,
}
.search();
let Some(element) = element else {
return Continue::Yes;
};
self.current_winner = Some((element, tab_index));
should_continue
}
fn process_candidate_with_tab_index(&mut self, candidate_tab_index: i32) -> (bool, Continue) {
match self.mechanism {
SequentialFocusNavigationMechanism::Dom => self.process_element_for_dom_traversal(),
SequentialFocusNavigationMechanism::Sequential(focused_element_tab_index) => self
.process_element_for_sequential_traversal(
candidate_tab_index,
focused_element_tab_index,
),
SequentialFocusNavigationMechanism::FirstOrLast => (
self.process_element_for_first_or_last_traversal(candidate_tab_index),
Continue::Yes,
),
}
}
fn process_element_for_dom_traversal(&self) -> (bool, Continue) {
match self.direction {
SequentialFocusDirection::Forward if self.passed_starting_point => (true, Continue::No),
SequentialFocusDirection::Forward => (false, Continue::Yes),
SequentialFocusDirection::Backward if !self.passed_starting_point => {
(true, Continue::Yes)
},
SequentialFocusDirection::Backward => (false, Continue::No),
}
}
fn process_element_for_first_or_last_traversal(
&self,
candidate_element_tab_index: i32,
) -> bool {
let Some((_, winning_tab_index)) = self.current_winner else {
return true;
};
let candidate_and_current_winner_ordering =
compare_tab_indices(candidate_element_tab_index, winning_tab_index);
match self.direction {
SequentialFocusDirection::Forward
if candidate_and_current_winner_ordering == Ordering::Less =>
{
true
},
SequentialFocusDirection::Backward
if candidate_and_current_winner_ordering != Ordering::Less =>
{
true
},
_ => false,
}
}
fn process_element_for_sequential_traversal(
&self,
candidate_element_tab_index: i32,
focused_element_tab_index: i32,
) -> (bool, Continue) {
let candidate_and_focused_ordering =
compare_tab_indices(candidate_element_tab_index, focused_element_tab_index);
match self.direction {
SequentialFocusDirection::Forward => {
if self.passed_starting_point && candidate_and_focused_ordering == Ordering::Equal {
return (true, Continue::No);
}
if candidate_and_focused_ordering != Ordering::Greater {
return (false, Continue::Yes);
}
let Some((_, winning_tab_index)) = self.current_winner else {
if candidate_element_tab_index == focused_element_tab_index + 1 {
return (true, Continue::No);
}
return (true, Continue::Yes);
};
let should_select =
compare_tab_indices(candidate_element_tab_index, winning_tab_index) ==
Ordering::Less;
(should_select, Continue::Yes)
},
SequentialFocusDirection::Backward => {
if !self.passed_starting_point && candidate_and_focused_ordering == Ordering::Equal
{
return (true, Continue::Yes);
}
if candidate_and_focused_ordering != Ordering::Less {
return (false, Continue::Yes);
}
let Some((_, winning_tab_index)) = self.current_winner else {
return (true, Continue::Yes);
};
let should_select =
compare_tab_indices(candidate_element_tab_index, winning_tab_index) !=
Ordering::Less;
(should_select, Continue::Yes)
},
}
}
}
fn compare_tab_indices(a: i32, b: i32) -> Ordering {
if a == b {
Ordering::Equal
} else if a == 0 {
Ordering::Greater
} else if b == 0 {
Ordering::Less
} else {
a.cmp(&b)
}
}