Skip to main content

script/dom/document/
focus.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, Ref};
6use std::cmp::Ordering;
7
8use bitflags::bitflags;
9use embedder_traits::FocusSequenceNumber;
10use js::context::{JSContext, NoGC};
11use js::gc::RootedGuard;
12use keyboard_types::Modifiers;
13use script_bindings::cell::DomRefCell;
14use script_bindings::codegen::GenericBindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
15use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
16use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
17use script_bindings::inheritance::Castable;
18use script_bindings::root::{Dom, DomRoot};
19use servo_base::id::BrowsingContextId;
20use servo_constellation_traits::{
21    RemoteFocusOperation, ScriptToConstellationMessage, SequentialFocusDirection,
22};
23
24use crate::dom::bindings::root::MutNullableDom;
25use crate::dom::focusevent::FocusEventType;
26use crate::dom::node::focus::{FocusNavigationScopeOwner, FocusTrigger};
27use crate::dom::types::{
28    Element, EventTarget, FocusEvent, HTMLElement, HTMLIFrameElement, KeyboardEvent, Window,
29};
30use crate::dom::{Document, Event, EventBubbles, EventCancelable, Node, NodeTraits};
31use crate::realms::enter_auto_realm;
32
33/// The kind of focusable area a [`FocusableArea`] is. A [`FocusableArea`] may be click focusable,
34/// sequentially focusable, or both.
35#[derive(Clone, Copy, Debug, Default, JSTraceable, MallocSizeOf, PartialEq)]
36pub(crate) struct FocusableAreaKind(u8);
37
38bitflags! {
39    impl FocusableAreaKind: u8 {
40        /// <https://html.spec.whatwg.org/multipage/#click-focusable>
41        ///
42        /// > A focusable area is said to be click focusable if the user agent determines that it is
43        /// > click focusable. User agents should consider focusable areas with non-null tabindex values
44        /// > to be click focusable.
45        const Click = 1 << 0;
46        /// <https://html.spec.whatwg.org/multipage/#sequentially-focusable>.
47        ///
48        /// > A focusable area is said to be sequentially focusable if it is included in its
49        /// > Document's sequential focus navigation order and the user agent determines that it is
50        /// > sequentially focusable.
51        const Sequential = 1 << 1;
52    }
53}
54
55/// <https://html.spec.whatwg.org/multipage/#focusable-area>
56#[derive(Clone, Default, JSTraceable, MallocSizeOf, PartialEq)]
57#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
58pub(crate) enum FocusableArea {
59    Node {
60        node: Dom<Node>,
61        kind: FocusableAreaKind,
62    },
63    /// The viewport of an `<iframe>` element in its containing `Document`. `<iframe>`s
64    /// are focusable areas, but have special behavior when focusing.
65    IFrameViewport {
66        iframe_element: Dom<HTMLIFrameElement>,
67        kind: FocusableAreaKind,
68    },
69    #[default]
70    Viewport,
71}
72
73impl js::gc::Rootable for FocusableArea {}
74
75impl std::fmt::Debug for FocusableArea {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Self::Node { node, kind } => f
79                .debug_struct("Node")
80                .field("node", node)
81                .field("kind", kind)
82                .finish(),
83            Self::IFrameViewport {
84                iframe_element,
85                kind,
86            } => f
87                .debug_struct("IFrameViewport")
88                .field("pipeline", &iframe_element.pipeline_id())
89                .field("kind", kind)
90                .finish(),
91            Self::Viewport => write!(f, "Viewport"),
92        }
93    }
94}
95
96impl FocusableArea {
97    pub(crate) fn kind(&self) -> FocusableAreaKind {
98        match self {
99            Self::Node { kind, .. } | Self::IFrameViewport { kind, .. } => *kind,
100            Self::Viewport => FocusableAreaKind::Click | FocusableAreaKind::Sequential,
101        }
102    }
103
104    /// If this focusable area is a node, return it as an [`Element`] if it is possible, otherwise
105    /// return `None`. This is the [`Element`] to use for applying `:focus` state and for firing
106    /// `blur` and `focus` events if any.
107    ///
108    /// Note: This is currently in a transitional state while the code moves more toward the
109    /// specification.
110    pub(crate) fn element(&self) -> Option<&Element> {
111        match self {
112            Self::Node { node, .. } => node.downcast(),
113            Self::IFrameViewport { iframe_element, .. } => Some(iframe_element.upcast()),
114            Self::Viewport => None,
115        }
116    }
117
118    /// <https://html.spec.whatwg.org/multipage/#dom-anchor>
119    pub(crate) fn dom_anchor(&self, document: &Document) -> DomRoot<Node> {
120        match self {
121            Self::Node { node, .. } => node.as_rooted(),
122            Self::IFrameViewport { iframe_element, .. } => {
123                DomRoot::from_ref(iframe_element.upcast())
124            },
125            Self::Viewport => DomRoot::from_ref(document.upcast()),
126        }
127    }
128
129    pub(crate) fn focus_chain(&self) -> Vec<FocusableArea> {
130        match self {
131            FocusableArea::Node { .. } | FocusableArea::IFrameViewport { .. } => {
132                vec![self.clone(), FocusableArea::Viewport]
133            },
134            FocusableArea::Viewport => vec![self.clone()],
135        }
136    }
137}
138
139/// The [`DocumentFocusHandler`] is a structure responsible for handling and storing data related to
140/// focus for the `Document`. It exists to decrease the size of the `Document`.
141/// structure.
142#[derive(JSTraceable, MallocSizeOf)]
143#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
144pub(crate) struct DocumentFocusHandler {
145    /// The [`Window`] element for this [`DocumentFocusHandler`].
146    window: Dom<Window>,
147    /// The focused area of the [`Document`].
148    ///
149    /// <https://html.spec.whatwg.org/multipage/#focused-area-of-the-document>
150    focused_area: DomRefCell<FocusableArea>,
151    /// The last sequence number sent to the constellation.
152    #[no_trace]
153    focus_sequence: Cell<FocusSequenceNumber>,
154    /// Indicates whether the container is included in the top-level browsing
155    /// context's focus chain (not considering system focus). Permanently `true`
156    /// for a top-level document.
157    has_focus: Cell<bool>,
158    /// <https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-starting-point>
159    sequential_focus_navigation_starting_point: MutNullableDom<Node>,
160}
161
162impl DocumentFocusHandler {
163    pub(crate) fn new(window: &Window, has_focus: bool) -> Self {
164        Self {
165            window: Dom::from_ref(window),
166            focused_area: Default::default(),
167            focus_sequence: Cell::new(FocusSequenceNumber::default()),
168            has_focus: Cell::new(has_focus),
169            sequential_focus_navigation_starting_point: Default::default(),
170        }
171    }
172
173    pub(crate) fn has_focus(&self) -> bool {
174        self.has_focus.get()
175    }
176
177    pub(crate) fn set_has_focus(&self, has_focus: bool) {
178        self.has_focus.set(has_focus);
179    }
180
181    /// Return the element that currently has focus. If `None` is returned the viewport itself has focus.
182    pub(crate) fn focused_area(&self) -> Ref<'_, FocusableArea> {
183        self.focused_area.borrow()
184    }
185
186    /// Set the element that currently has focus and update the focus state for both the previously
187    /// set element (if any) and the new one, as well as the new one. This will not do anything if
188    /// the new element is the same as the previous one. Note that this *will not* fire any focus
189    /// events. If that is necessary the [`DocumentFocusHandler::focus`] should be used.
190    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
191    pub(crate) fn set_focused_area(&self, new_focusable_area: FocusableArea) {
192        if new_focusable_area == *self.focused_area.borrow() {
193            return;
194        }
195
196        // From <https://html.spec.whatwg.org/multipage/#selector-focus>
197        // > For the purposes of the CSS :focus pseudo-class, an element has the focus when:
198        // >  - it is not itself a navigable container; and
199        // >  - any of the following are true:
200        // >    - it is one of the elements listed in the current focus chain of the top-level
201        // >      traversable; or
202        // >    - its shadow root shadowRoot is not null and shadowRoot is the root of at least one
203        // >      element that has the focus.
204        //
205        // We are trying to accomplish the last requirement here, by walking up the tree and
206        // marking each shadow host as focused.
207        fn recursively_set_focus_status(element: &Element, new_state: bool) {
208            element.set_focus_state(new_state);
209
210            let Some(shadow_root) = element.containing_shadow_root() else {
211                return;
212            };
213            recursively_set_focus_status(&shadow_root.Host(), new_state);
214        }
215
216        if let Some(previously_focused_element) = self.focused_area.borrow().element() {
217            recursively_set_focus_status(previously_focused_element, false);
218        }
219        if let Some(newly_focused_element) = new_focusable_area.element() {
220            recursively_set_focus_status(newly_focused_element, true);
221        }
222
223        *self.focused_area.borrow_mut() = new_focusable_area;
224    }
225
226    /// Get the last sequence number sent to the constellation.
227    ///
228    /// Received focus-related messages with sequence numbers less than the one
229    /// returned by this method must be discarded.
230    pub fn focus_sequence(&self) -> FocusSequenceNumber {
231        self.focus_sequence.get()
232    }
233
234    /// Generate the next sequence number for focus-related messages.
235    fn increment_fetch_focus_sequence(&self) -> FocusSequenceNumber {
236        self.focus_sequence.set(FocusSequenceNumber(
237            self.focus_sequence
238                .get()
239                .0
240                .checked_add(1)
241                .expect("too many focus messages have been sent"),
242        ));
243        self.focus_sequence.get()
244    }
245
246    /// <https://html.spec.whatwg.org/multipage/#current-focus-chain-of-a-top-level-traversable>
247    pub(crate) fn current_focus_chain(&self) -> Vec<FocusableArea> {
248        // > The current focus chain of a top-level traversable is the focus chain of the
249        // > currently focused area of traversable, if traversable is non-null, or an empty list
250        // > otherwise.
251
252        // We cannot easily get the full focus chain of the top-level traversable, so we just
253        // get the bits that intersect with this `Document`. The rest will be handled
254        // internally in [`Self::focus_update_steps`].
255        if !self.has_focus() {
256            return vec![];
257        }
258        self.focused_area().focus_chain()
259    }
260
261    /// Reassign the focus context to the element that last requested focus during this
262    /// transaction, or the document if no elements requested it.
263    pub(crate) fn focus(&self, cx: &mut JSContext, new_focus_target: &FocusableArea) {
264        rooted!(&in(cx) let new_focus_chain = new_focus_target.focus_chain());
265        rooted!(&in(cx) let old_focus_chain = self.current_focus_chain());
266
267        self.focus_update_steps(cx, new_focus_chain, old_focus_chain, new_focus_target);
268
269        // Advertise the change in the focus chain.
270        // <https://html.spec.whatwg.org/multipage/#focus-chain>
271        // <https://html.spec.whatwg.org/multipage/#focusing-steps>
272        //
273        // TODO: Integrate this into the "focus update steps."
274        //
275        // If the top-level BC doesn't have system focus, this won't
276        // have an immediate effect, but it will when we gain system
277        // focus again. Therefore we still have to send `ScriptMsg::
278        // Focus`.
279        //
280        // When a container with a non-null nested browsing context is
281        // focused, its active document becomes the focused area of the
282        // top-level browsing context instead. Therefore we need to let
283        // the constellation know if such a container is focused.
284        //
285        // > The focusing steps for an object `new focus target` [...]
286        // >
287        // >  3. If `new focus target` is a browsing context container
288        // >     with non-null nested browsing context, then set
289        // >     `new focus target` to the nested browsing context's
290        // >     active document.
291        let child_browsing_context_id = match new_focus_target {
292            FocusableArea::IFrameViewport { iframe_element, .. } => {
293                iframe_element.browsing_context_id()
294            },
295            _ => None,
296        };
297        let sequence = self.increment_fetch_focus_sequence();
298
299        debug!(
300            "Advertising the focus request to the constellation \
301                        with sequence number {sequence:?} and child \
302                        {child_browsing_context_id:?}",
303        );
304        self.window.send_to_constellation(
305            ScriptToConstellationMessage::FocusAncestorBrowsingContextsForFocusingSteps(
306                child_browsing_context_id,
307                sequence,
308            ),
309        );
310    }
311
312    /// <https://html.spec.whatwg.org/multipage/#focus-update-steps>
313    pub(crate) fn focus_update_steps(
314        &self,
315        cx: &mut JSContext,
316        mut new_focus_chain: RootedGuard<'_, Vec<FocusableArea>>,
317        mut old_focus_chain: RootedGuard<'_, Vec<FocusableArea>>,
318        new_focus_target: &FocusableArea,
319    ) {
320        let new_focus_chain_was_empty = new_focus_chain.is_empty();
321
322        // Step 1: If the last entry in old chain and the last entry in new chain are the same,
323        // pop the last entry from old chain and the last entry from new chain and redo this
324        // step.
325        //
326        // We avoid recursion here.
327        while let (Some(last_new), Some(last_old)) =
328            (new_focus_chain.last(), old_focus_chain.last())
329        {
330            if last_new == last_old {
331                new_focus_chain.as_mut_ref(cx.no_gc()).pop();
332                old_focus_chain.as_mut_ref(cx.no_gc()).pop();
333            } else {
334                break;
335            }
336        }
337
338        // If the two focus chains are both empty, focus hasn't changed. This isn't in the
339        // specification, but we must do it because we set the focused area to the viewport
340        // before blurring. If no focus changes, that would mean the currently focused element
341        // loses focus.
342        if old_focus_chain.is_empty() && new_focus_chain.is_empty() {
343            return;
344        }
345        // Although the "focusing steps" in the HTML specification say to wait until after firing
346        // the "blur" event to change the currently focused area of the Document, browsers tend
347        // to set it to the viewport before firing the "blur" event.
348        //
349        // See https://github.com/whatwg/html/issues/1569
350        self.set_focused_area(FocusableArea::Viewport);
351
352        // Step 2: For each entry entry in old chain, in order, run these substeps:
353        // Note: `old_focus_chain` might be empty!
354        let last_old_focus_chain_entry = old_focus_chain.len().saturating_sub(1);
355        for (index, entry) in old_focus_chain.iter().enumerate() {
356            // Step 2.1: If entry is an input element, and the change event applies to the element,
357            // and the element does not have a defined activation behavior, and the user has
358            // changed the element's value or its list of selected files while the control was
359            // focused without committing that change (such that it is different to what it was
360            // when the control was first focused), then:
361            // Step 2.1.1: Set entry's user validity to true.
362            // Step 2.1.2: Fire an event named change at the element, with the bubbles attribute initialized to true.
363            // TODO: Implement this.
364
365            // Step 2.2:
366            // - If entry is an element, let blur event target be entry.
367            // - If entry is a Document object, let blur event target be that Document object's
368            //    relevant global object.
369            // - Otherwise, let blur event target be null.
370            //
371            // Note: We always send focus and blur events for `<iframe>` elements, but other
372            // browsers only seem to do that conditionally. This needs a bit more research.
373            let blur_event_target = match entry {
374                FocusableArea::Node { node, .. } => Some(node.upcast::<EventTarget>()),
375                FocusableArea::IFrameViewport { iframe_element, .. } => {
376                    Some(iframe_element.upcast())
377                },
378                FocusableArea::Viewport => Some(self.window.upcast::<EventTarget>()),
379            };
380
381            // Step 2.3: If entry is the last entry in old chain, and entry is an Element, and
382            // the last entry in new chain is also an Element, then let related blur target be
383            // the last entry in new chain. Otherwise, let related blur target be null.
384            //
385            // Note: This can only happen when the focused `Document` doesn't change and we are
386            // moving focus from one element to another. These elements are the last in the chain
387            // because of the popping we do at the start of these steps.
388            let related_blur_target = match new_focus_chain.last() {
389                Some(FocusableArea::Node { node, .. })
390                    if index == last_old_focus_chain_entry &&
391                        matches!(entry, FocusableArea::Node { .. }) =>
392                {
393                    Some(node.upcast())
394                },
395                _ => None,
396            };
397
398            // Step 2.4: If blur event target is not null, fire a focus event named blur at
399            // blur event target, with related blur target as the related target.
400            if let Some(blur_event_target) = blur_event_target {
401                // <https://w3c.github.io/uievents/#focusout>
402                // "blur" must be fired before "focusout".
403                self.fire_focus_event(
404                    cx,
405                    FocusEventType::Blur,
406                    blur_event_target,
407                    related_blur_target,
408                );
409
410                self.fire_focus_event(
411                    cx,
412                    FocusEventType::FocusOut,
413                    blur_event_target,
414                    related_blur_target,
415                );
416            }
417        }
418
419        // Step 3: Apply any relevant platform-specific conventions for focusing new focus
420        // target. (For example, some platforms select the contents of a text control when that
421        // control is focused.)
422        if &*self.focused_area() != new_focus_target &&
423            let Some(html_element) = new_focus_target
424                .element()
425                .and_then(|element| element.downcast::<HTMLElement>())
426        {
427            html_element.handle_focus_state_for_contenteditable(cx);
428        }
429
430        self.set_has_focus(!new_focus_chain_was_empty);
431
432        // Step 4: For each entry entry in new chain, in reverse order, run these substeps:
433        // Note: `new_focus_chain` might be empty!
434        let last_new_focus_chain_entry = new_focus_chain.len().saturating_sub(1); // Might be empty, so calculated here.
435        for (index, entry) in new_focus_chain.iter().enumerate().rev() {
436            // Step 4.1: If entry is a focusable area, and the focused area of the document is
437            // not entry:
438            //
439            // Here we deviate from the specification a bit, as all focus chain elements are
440            // focusable areas currently. We just assume that it means the first entry of the
441            // chain, which is the new focus target
442            if index == 0 {
443                // Step 4.1.1: Set document's relevant global object's navigation API's focus
444                // changed during ongoing navigation to true.
445                // TODO: Implement this.
446
447                // Step 4.1.2: Designate entry as the focused area of the document.
448                self.set_focused_area(entry.clone());
449            }
450
451            // Step 4.2:
452            // - If entry is an element, let focus event target be entry.
453            // - If entry is a Document object, let focus event target be that Document
454            //   object's relevant global object.
455            // - Otherwise, let focus event target be null.
456            //
457            // Note: We always send focus and blur events for `<iframe>` elements, but other
458            // browsers only seem to do that conditionally. This needs a bit more research.
459            let focus_event_target = match entry {
460                FocusableArea::Node { node, .. } => Some(node.upcast::<EventTarget>()),
461                FocusableArea::IFrameViewport { iframe_element, .. } => {
462                    Some(iframe_element.upcast())
463                },
464                FocusableArea::Viewport => Some(self.window.upcast::<EventTarget>()),
465            };
466
467            // Step 4.3: If entry is the last entry in new chain, and entry is an Element, and
468            // the last entry in old chain is also an Element, then let related focus target be
469            // the last entry in old chain. Otherwise, let related focus target be null.
470            //
471            // Note: This can only happen when the focused `Document` doesn't change and we are
472            // moving focus from one element to another. These elements are the last in the chain
473            // because of the popping we do at the start of these steps.
474            let related_focus_target = match old_focus_chain.last() {
475                Some(FocusableArea::Node { node, .. })
476                    if index == last_new_focus_chain_entry &&
477                        matches!(entry, FocusableArea::Node { .. }) =>
478                {
479                    Some(node.upcast())
480                },
481                _ => None,
482            };
483
484            // Step 4.4: If focus event target is not null, fire a focus event named focus at
485            // focus event target, with related focus target as the related target.
486            if let Some(focus_event_target) = focus_event_target {
487                // <https://w3c.github.io/uievents/#focusin>
488                // "focus" must be fired before "focusIn".
489                self.fire_focus_event(
490                    cx,
491                    FocusEventType::Focus,
492                    focus_event_target,
493                    related_focus_target,
494                );
495
496                self.fire_focus_event(
497                    cx,
498                    FocusEventType::FocusIn,
499                    focus_event_target,
500                    related_focus_target,
501                );
502            }
503        }
504    }
505
506    /// <https://html.spec.whatwg.org/multipage/#fire-a-focus-event>
507    pub(crate) fn fire_focus_event(
508        &self,
509        cx: &mut JSContext,
510        focus_event_type: FocusEventType,
511        event_target: &EventTarget,
512        related_target: Option<&EventTarget>,
513    ) {
514        let event_name = match focus_event_type {
515            FocusEventType::Focus => "focus".into(),
516            FocusEventType::Blur => "blur".into(),
517            FocusEventType::FocusIn => "focusin".into(),
518            FocusEventType::FocusOut => "focusout".into(),
519        };
520
521        let event_bubbles = match focus_event_type {
522            FocusEventType::Focus | FocusEventType::Blur => EventBubbles::DoesNotBubble,
523            FocusEventType::FocusIn | FocusEventType::FocusOut => EventBubbles::Bubbles,
524        };
525
526        let event = FocusEvent::new(
527            cx,
528            &self.window,
529            event_name,
530            event_bubbles,
531            EventCancelable::NotCancelable,
532            Some(&self.window),
533            0i32,
534            related_target,
535        );
536        let event = event.upcast::<Event>();
537        event.set_trusted(true);
538        event.set_composed(true);
539        event.fire(cx, event_target);
540    }
541
542    /// <https://html.spec.whatwg.org/multipage/#focus-fixup-rule>
543    /// > For each doc of docs, if the focused area of doc is not a focusable area, then run the
544    /// > focusing steps for doc's viewport, and set doc's relevant global object's navigation API's
545    /// > focus changed during ongoing navigation to false.
546    ///
547    /// TODO: Handle the "focus changed during ongoing navigation" flag.
548    pub(crate) fn perform_focus_fixup_rule(&self, cx: &mut JSContext) {
549        if self
550            .focused_area
551            .borrow()
552            .element()
553            .is_none_or(|focused| focused.is_focusable_area(cx.no_gc()))
554        {
555            return;
556        }
557        self.focus(cx, &FocusableArea::Viewport);
558    }
559
560    pub(crate) fn set_sequential_focus_navigation_starting_point(&self, node: &Node) {
561        self.sequential_focus_navigation_starting_point
562            .set(Some(node));
563    }
564
565    fn sequential_focus_navigation_starting_point(&self) -> Option<DomRoot<Node>> {
566        self.sequential_focus_navigation_starting_point
567            .get()
568            .filter(|node| node.is_connected())
569    }
570
571    pub(crate) fn sequential_focus_navigation_via_keyboard_event(
572        &self,
573        cx: &mut JSContext,
574        event: &KeyboardEvent,
575    ) {
576        let direction = if event.modifiers().contains(Modifiers::SHIFT) {
577            SequentialFocusDirection::Backward
578        } else {
579            SequentialFocusDirection::Forward
580        };
581
582        self.sequential_focus_navigation(cx, direction);
583    }
584
585    /// <https://html.spec.whatwg.org/multipage/#sequential-focus-navigation:currently-focused-area-of-a-top-level-traversable>
586    fn sequential_focus_navigation(&self, cx: &mut JSContext, direction: SequentialFocusDirection) {
587        // > When the user requests that focus move from the currently focused area of a top-level
588        // > traversable to the next or previous focusable area (e.g., as the default action of
589        // > pressing the tab key), or when the user requests that focus sequentially move to a
590        // > top-level traversable in the first place (e.g., from the browser's location bar), the
591        // > user agent must use the following algorithm:
592
593        // > 1. Let starting point be the currently focused area of a top-level traversable, if the
594        // > user requested to move focus sequentially from there, or else the top-level traversable
595        // > itself, if the user instead requested to move focus from outside the top-level
596        // > traversable.
597        //
598        // Note: Here `None` represents the current traversible.
599        let mut starting_point = self
600            .focused_area()
601            .element()
602            .map(|element| DomRoot::from_ref(element.upcast::<Node>()));
603
604        // > 2. If there is a sequential focus navigation starting point defined and it is inside
605        // > starting point, then let starting point be the sequential focus navigation starting point
606        // > instead.
607        if let Some(sequential_focus_navigation_starting_point) =
608            self.sequential_focus_navigation_starting_point() &&
609            starting_point.as_ref().is_none_or(|starting_point| {
610                starting_point.is_ancestor_of(&sequential_focus_navigation_starting_point)
611            })
612        {
613            starting_point = Some(sequential_focus_navigation_starting_point);
614        }
615
616        // > 3. Let direction be "forward" if the user requested the next control, and "backward" if
617        // > the user requested the previous control.
618        //
619        // Note: This is handled by the `direction` argument to this method.
620        self.sequential_focus_navigation_loop(
621            cx,
622            starting_point,
623            direction,
624            false, /* allow_focusing_viewport */
625        );
626    }
627
628    /// The inner loop ("Loop") of:
629    /// <https://html.spec.whatwg.org/multipage/#sequential-focus-navigation:currently-focused-area-of-a-top-level-traversable>
630    fn sequential_focus_navigation_loop(
631        &self,
632        cx: &mut JSContext,
633        starting_point: Option<DomRoot<Node>>,
634        direction: SequentialFocusDirection,
635        allow_focusing_viewport: bool,
636    ) {
637        // > 4. Loop: Let selection mechanism be "sequential" if starting point is a navigable or if
638        // > starting point is in its Document's sequential focus navigation order.
639        // > Otherwise, starting point is not in its Document's sequential focus navigation order;
640        // > let selection mechanism be "DOM".
641        let starting_point_is_navigable = starting_point
642            .as_ref()
643            .is_none_or(|starting_point| starting_point.is::<HTMLIFrameElement>());
644        let selection_mechanism = starting_point
645            .as_ref()
646            .and_then(|node| node.downcast::<Element>())
647            .filter(|element| element.is_sequentially_focusable(cx.no_gc()))
648            .map(|element| {
649                SequentialFocusNavigationMechanism::Sequential(
650                    element.explicitly_set_tab_index().unwrap_or_default(),
651                )
652            })
653            .unwrap_or_else(|| {
654                if starting_point_is_navigable {
655                    SequentialFocusNavigationMechanism::FirstOrLast
656                } else {
657                    SequentialFocusNavigationMechanism::Dom
658                }
659            });
660
661        // > 5. Let candidate be the result of running the sequential navigation search algorithm
662        // > with starting point, direction, and selection mechanism.
663        let candidate = SequentialFocusNavigationSearch::new(
664            starting_point
665                .as_ref()
666                .and_then(|node| node.containing_focus_navigation_scope_owner())
667                .unwrap_or_else(|| FocusNavigationScopeOwner::Document(self.window.Document())),
668            direction,
669            selection_mechanism,
670            starting_point,
671        )
672        .search(cx.no_gc());
673
674        // > 6. If candidate is not null, then run the focusing steps for candidate and return.
675        if let Some(candidate) = candidate {
676            let document = self.window.Document();
677            let event_handler = document.event_handler();
678            event_handler.focus_and_scroll_to_element_for_key_event(cx, &candidate);
679            // We can't simply run the focusing steps, because:
680            //  1. The focusing steps do not scroll to the element.
681            //  2. When focus shifts to a child navigable (iframe) we have special behavior to reach
682            //     across document boundaries to focus the first focusable element in the iframe.
683            match candidate.downcast::<HTMLIFrameElement>() {
684                Some(iframe_element) => self.sequentially_focus_child_iframe_local_or_remote(
685                    cx,
686                    iframe_element,
687                    direction,
688                ),
689                None => event_handler.focus_and_scroll_to_element_for_key_event(cx, &candidate),
690            }
691            return;
692        }
693
694        // > 7. Otherwise, unset the sequential focus navigation starting point.
695        self.sequential_focus_navigation_starting_point.clear();
696
697        // This is not in the specification, but there's a difference between moving focus into
698        // a child `<iframe>` and within a Document. If no suitable focusable area can be found
699        // when moving into an `<iframe>`, we want to focus the `<iframe>`'s viewport itself.
700        if allow_focusing_viewport {
701            self.focus(cx, &FocusableArea::Viewport);
702            return;
703        }
704
705        // > 8. If starting point is a top-level traversable, or a focusable area in the top-level
706        // > traversable, the user agent should transfer focus to its own controls appropriately (if
707        // > any), honouring direction, and then return.
708        // TODO: Implement this.
709        if self.window.is_top_level() {
710            return;
711        }
712
713        // > 9. Otherwise, starting point is a focusable area in a child navigable. Set starting
714        // > point to that child navigable's parent and return to the step labeled loop.
715        self.sequentially_focus_parent_local_or_remote(cx, direction);
716    }
717
718    fn sequentially_focus_child_iframe_local_or_remote(
719        &self,
720        cx: &mut JSContext,
721        iframe_element: &HTMLIFrameElement,
722        direction: SequentialFocusDirection,
723    ) {
724        if let Some(content_document) = iframe_element.GetContentDocument() {
725            // The <iframe> is in the same `ScriptThread` and we have direct access to it. We can
726            // move the focus directly.
727            content_document
728                .focus_handler()
729                .sequential_focus_from_another_document(cx, None, direction);
730        } else if let Some(browsing_context_id) = iframe_element.browsing_context_id() {
731            self.window.send_to_constellation(
732                ScriptToConstellationMessage::FocusRemoteBrowsingContext(
733                    browsing_context_id,
734                    RemoteFocusOperation::Sequential(direction, None),
735                ),
736            );
737        } else {
738            iframe_element
739                .upcast::<Node>()
740                .run_the_focusing_steps(cx, None, FocusTrigger::Other);
741        }
742    }
743
744    fn sequentially_focus_parent_local_or_remote(
745        &self,
746        cx: &mut JSContext,
747        direction: SequentialFocusDirection,
748    ) {
749        let window_proxy = self.window.window_proxy();
750        if let Some(iframe) = window_proxy.frame_element() {
751            // The parent browsing context is in the same `ScriptThread` and we have direct access
752            // to it. We can move the focus directly.
753            let browsing_context_id = iframe
754                .downcast::<HTMLIFrameElement>()
755                .and_then(|iframe_element| iframe_element.browsing_context_id());
756            iframe
757                .owner_document()
758                .focus_handler()
759                .sequential_focus_from_another_document(cx, browsing_context_id, direction);
760        } else if let Some(browsing_context_id) = window_proxy
761            .parent()
762            .map(|parent| parent.browsing_context_id())
763        {
764            self.window.send_to_constellation(
765                ScriptToConstellationMessage::FocusRemoteBrowsingContext(
766                    browsing_context_id,
767                    RemoteFocusOperation::Sequential(
768                        direction,
769                        Some(window_proxy.browsing_context_id()),
770                    ),
771                ),
772            );
773        }
774    }
775
776    pub(crate) fn sequential_focus_from_another_document(
777        &self,
778        cx: &mut JSContext,
779        browsing_context_id: Option<BrowsingContextId>,
780        direction: SequentialFocusDirection,
781    ) {
782        let mut realm = enter_auto_realm(cx, &*self.window);
783        let cx = &mut realm.current_realm();
784        let starting_point = browsing_context_id.and_then(|browsing_context_id| {
785            self.window
786                .Document()
787                .iframes()
788                .get(browsing_context_id)
789                .map(|iframe| DomRoot::from_ref(iframe.element.upcast::<Node>()))
790        });
791        self.sequential_focus_navigation_loop(
792            cx,
793            starting_point,
794            direction,
795            true, /* allow focusing viewport */
796        );
797    }
798}
799
800/// <https://html.spec.whatwg.org/multipage/#selection-mechanism>
801///
802/// This also incorporates the case where the starting point is a navigable
803/// as that is a distinct set of behaviors from the two kinds of mechanisms
804/// listed in the specification.
805#[derive(Clone, Copy, Debug)]
806pub(crate) enum SequentialFocusNavigationMechanism {
807    Dom,
808    Sequential(i32 /* focused_element_tab_index */),
809    /// This case isn't mentioned explicitly in the specification, but it's implied. It works
810    /// like `Sequential`, but without a starting point. This kind of search will return the
811    /// first or last (depending on direction) sequentially focusable element in sequential
812    /// focus order. This is used in two situations:
813    ///
814    ///  - When the starting point is a navigable
815    ///  - When descending into a nested focus scope
816    FirstOrLast,
817}
818
819#[derive(PartialEq)]
820enum Continue {
821    Yes,
822    No,
823}
824
825#[derive(PartialEq)]
826pub(crate) enum SequentialFocusNavigationSearchContext {
827    /// The focus scope that initiated this search. Containing contexts and nested contexts can
828    /// both be searched.
829    Original,
830    /// The search has descended into a nested search scope. Containing contexts should never
831    /// be searched.
832    Nested,
833    /// The search has ascended to search a containing search scope. The starting point's focus
834    /// scope should never be searched to avoid cycles.
835    Containing,
836}
837
838/// This structure is used to do a traversal search of the DOM in order to find an
839/// appropriate target when doing sequential focus navigation, such as when handling
840/// tab key presses.
841///
842/// The specification talks about the [flattened tabindex-ordered focus navigation scope],
843/// which represents all of the [tabindex-ordered focus navigation scope]s of a particular
844/// page, flattened into a single list of all the sequentially focusable areas of the
845/// page. Then, the specification describes how to search this list during sequential focus
846/// navigation.
847///
848/// The choice that Servo and other browsers make is to trade updating this flattened list
849/// during every DOM mutation (frequent) with a DOM traversal of, potentially, the entire
850/// document during sequential focus navigation (infrequent).
851///
852/// The search done via [`SequentialFocusNavigationSearch`] matches the semantics of the
853/// flattened tabindex-ordered focus navigation scope without having to maintain the
854/// flattened list. It uses a series of nested traversals (one per focus scope) that
855/// only considers each focusable area of a page at most once.
856///
857/// The search performs a linear DOM traversal starting at the containing focus scope of
858/// the search start point. When encountering a nested focus scope, if that scope could
859/// contain the final target for the search, the search recurses into the nested scope. If
860/// the search reaches the end of a focus scope without finding a candidate, the search
861/// continues in the focus scope's containing scope (though never re-ascending back into a
862/// scope it recursed from).
863///
864/// [flattened tabindex-ordered focus navigation scope]: https://html.spec.whatwg.org/multipage/#flattened-tabindex-ordered-focus-navigation-scope
865/// [tabindex-ordered focus navigation scope]: https://html.spec.whatwg.org/multipage/#tabindex-ordered-focus-navigation-scope
866pub(crate) struct SequentialFocusNavigationSearch {
867    focus_navigation_scope_owner: FocusNavigationScopeOwner,
868    direction: SequentialFocusDirection,
869    mechanism: SequentialFocusNavigationMechanism,
870    starting_point: Option<DomRoot<Node>>,
871    current_winner: Option<(DomRoot<Element>, i32)>,
872    passed_starting_point: bool,
873    search_context: SequentialFocusNavigationSearchContext,
874}
875
876impl SequentialFocusNavigationSearch {
877    pub(crate) fn new(
878        focus_navigation_scope_owner: FocusNavigationScopeOwner,
879        direction: SequentialFocusDirection,
880        mechanism: SequentialFocusNavigationMechanism,
881        starting_point: Option<DomRoot<Node>>,
882    ) -> Self {
883        // If there's no starting point, the starting point is actually the root element, which
884        // we always have passed.
885        let passed_starting_point = starting_point.is_none();
886        Self {
887            focus_navigation_scope_owner,
888            direction,
889            mechanism,
890            starting_point,
891            current_winner: Default::default(),
892            passed_starting_point,
893            search_context: SequentialFocusNavigationSearchContext::Original,
894        }
895    }
896
897    pub(crate) fn search(mut self, no_gc: &NoGC) -> Option<DomRoot<Element>> {
898        for node in self.focus_navigation_scope_owner.iterator() {
899            if self.process_node(no_gc, &node) == Continue::No {
900                break;
901            }
902        }
903
904        if let Some(winner) = self.current_winner.take() {
905            return Some(winner.0);
906        }
907
908        // If searching a nested focus navigation scope, never try to search the containing
909        // scope, as that will lead to an endless cycle.
910        if self.search_context != SequentialFocusNavigationSearchContext::Nested {
911            return self.maybe_search_in_containing_focus_navigation_scope(no_gc);
912        }
913
914        None
915    }
916
917    fn maybe_search_in_containing_focus_navigation_scope(
918        &self,
919        no_gc: &NoGC,
920    ) -> Option<DomRoot<Element>> {
921        let containing_node = self.focus_navigation_scope_owner.node();
922        let containing_focus_navigation_scope_owner =
923            containing_node.containing_focus_navigation_scope_owner()?;
924
925        let tab_index = containing_node
926            .downcast::<Element>()?
927            .explicitly_set_tab_index()
928            .unwrap_or_default();
929        let mechanism = match &self.mechanism {
930            // If the traversal was sequential, but the containing focus navigation scope owner was
931            // explicitly marked as not sequentially focusable, the search in the containing scope
932            // needs to work like a DOM traversal i.e. take the first sequentially focusable target
933            // after this one in the parent traversal.
934            SequentialFocusNavigationMechanism::Sequential(..) if tab_index == -1 => {
935                SequentialFocusNavigationMechanism::Dom
936            },
937            SequentialFocusNavigationMechanism::Sequential(..) => {
938                SequentialFocusNavigationMechanism::Sequential(tab_index)
939            },
940            mechanism => *mechanism,
941        };
942
943        if self.direction == SequentialFocusDirection::Backward &&
944            let Some(containing_element) = containing_node.downcast::<Element>() &&
945            containing_element.is_sequentially_focusable(no_gc)
946        {
947            return Some(DomRoot::from_ref(containing_element));
948        }
949
950        Self {
951            focus_navigation_scope_owner: containing_focus_navigation_scope_owner,
952            direction: self.direction,
953            mechanism,
954            starting_point: Some(DomRoot::from_ref(containing_node)),
955            current_winner: Default::default(),
956            passed_starting_point: false,
957            search_context: SequentialFocusNavigationSearchContext::Containing,
958        }
959        .search(no_gc)
960    }
961
962    fn process_node(&mut self, no_gc: &NoGC, node: &Node) -> Continue {
963        if Some(node) == self.starting_point.as_deref() {
964            self.passed_starting_point = true;
965        } else if self.process_node_as_sequentially_focusable_node(no_gc, node) == Continue::No {
966            return Continue::No;
967        }
968
969        self.process_node_as_focus_scope_owner(no_gc, node)
970    }
971
972    /// If this node is sequentially focusable, consider whether or not to accept it
973    /// as the new winner.
974    fn process_node_as_sequentially_focusable_node(
975        &mut self,
976        no_gc: &NoGC,
977        node: &Node,
978    ) -> Continue {
979        let Some(element) = node.downcast::<Element>() else {
980            return Continue::Yes;
981        };
982        if !element.is_sequentially_focusable(no_gc) {
983            return Continue::Yes;
984        }
985
986        let tab_index = element.explicitly_set_tab_index().unwrap_or_default();
987        let (is_new_winner, should_continue) = self.process_candidate_with_tab_index(tab_index);
988        if is_new_winner {
989            self.current_winner = Some((DomRoot::from_ref(element), tab_index));
990        }
991        should_continue
992    }
993
994    /// If this node itself forms a nested sequential focus scope, decide whether or
995    /// not to descend and consider its contained focusable areas as candidates.
996    fn process_node_as_focus_scope_owner(&mut self, no_gc: &NoGC, node: &Node) -> Continue {
997        // Never try to recurse into the same focus scope that we are in. This path
998        // might be reached if we are in the root focus scope where the document is
999        // one of the nodes processed.
1000        if self.focus_navigation_scope_owner.node() == node {
1001            return Continue::Yes;
1002        }
1003
1004        // If the search has ascended into a containing scope, never try to search back down
1005        // into the scope that originated this part of the search. Otherwise the search would
1006        // cycle endlessly.
1007        if Some(node) == self.starting_point.as_deref() &&
1008            self.search_context == SequentialFocusNavigationSearchContext::Containing
1009        {
1010            return Continue::Yes;
1011        }
1012
1013        let Some(focus_navigation_scope_owner) = node.as_focus_navigation_scope_owner() else {
1014            return Continue::Yes;
1015        };
1016
1017        // The candidate inherits the tab index of the node that establishes its containing
1018        // sequential focus navigation scope.
1019        let tab_index = focus_navigation_scope_owner
1020            .node()
1021            .downcast::<Element>()
1022            .and_then(Element::explicitly_set_tab_index)
1023            .unwrap_or_default();
1024        let (is_new_winner, should_continue) = self.process_candidate_with_tab_index(tab_index);
1025        if !is_new_winner {
1026            return should_continue;
1027        }
1028
1029        let mechanism = match self.mechanism {
1030            // If we were searching without regard to sequential focus order, keep doing that.
1031            SequentialFocusNavigationMechanism::Dom => SequentialFocusNavigationMechanism::Dom,
1032            // If we were searching taking into account sequential focus order, keep doing that, but
1033            // take the first candidate in sequential focus order without regard to the outer scope's
1034            // starting point or tab index.
1035            _ => SequentialFocusNavigationMechanism::FirstOrLast,
1036        };
1037
1038        let element = Self {
1039            focus_navigation_scope_owner,
1040            direction: self.direction,
1041            mechanism,
1042            starting_point: None,
1043            current_winner: Default::default(),
1044            passed_starting_point: self.passed_starting_point,
1045            search_context: SequentialFocusNavigationSearchContext::Nested,
1046        }
1047        .search(no_gc);
1048
1049        let Some(element) = element else {
1050            return Continue::Yes;
1051        };
1052
1053        self.current_winner = Some((element, tab_index));
1054        should_continue
1055    }
1056
1057    /// Process the node or focus scope owner with the provided tab index according to this
1058    /// search's search mechanism. Returns a boolean that is true if this candidate is the new
1059    /// winner and a [`Continue`] which says whether to keep searching or stop.
1060    fn process_candidate_with_tab_index(&mut self, candidate_tab_index: i32) -> (bool, Continue) {
1061        match self.mechanism {
1062            SequentialFocusNavigationMechanism::Dom => self.process_element_for_dom_traversal(),
1063            SequentialFocusNavigationMechanism::Sequential(focused_element_tab_index) => self
1064                .process_element_for_sequential_traversal(
1065                    candidate_tab_index,
1066                    focused_element_tab_index,
1067                ),
1068            SequentialFocusNavigationMechanism::FirstOrLast => (
1069                self.process_element_for_first_or_last_traversal(candidate_tab_index),
1070                Continue::Yes,
1071            ),
1072        }
1073    }
1074
1075    /// Process the node or focus scope owner, given the state of [`Self::passed_starting_point`]
1076    /// with for searches with the [`SequentialFocusNavigationMechanism::Dom`] search mechanism.
1077    /// Returns a boolean that is true if this candidate is the new winner and a [`Continue`] which
1078    /// says whether to keep searching or stop.
1079    fn process_element_for_dom_traversal(&self) -> (bool, Continue) {
1080        match self.direction {
1081            // direction is "forward"
1082            // > Let candidate be the first suitable sequentially focusable area after starting point,
1083            // > in starting point's Document's sequential focus navigation order, if any; or else
1084            // > null
1085            SequentialFocusDirection::Forward if self.passed_starting_point => (true, Continue::No),
1086            // If searching forward, do not consider anything until passing the starting point.
1087            SequentialFocusDirection::Forward => (false, Continue::Yes),
1088            // direction is "backward"
1089            // > Let candidate be the last suitable sequentially focusable area before starting
1090            // > point, in starting point's Document's sequential focus navigation order, if any; or
1091            // > else null
1092            SequentialFocusDirection::Backward if !self.passed_starting_point => {
1093                (true, Continue::Yes)
1094            },
1095            // There is no possible winner after the starting point when searching backward.
1096            SequentialFocusDirection::Backward => (false, Continue::No),
1097        }
1098    }
1099
1100    /// Process the node or focus scope owner with the provided tab index for searches with the
1101    /// [`SequentialFocusNavigationMechanism::FirstOrLast`] search mechanism. Returns a boolean
1102    /// that is true if this candidate is the new winner.
1103    fn process_element_for_first_or_last_traversal(
1104        &self,
1105        candidate_element_tab_index: i32,
1106    ) -> bool {
1107        let Some((_, winning_tab_index)) = self.current_winner else {
1108            return true;
1109        };
1110
1111        let candidate_and_current_winner_ordering =
1112            compare_tab_indices(candidate_element_tab_index, winning_tab_index);
1113        match self.direction {
1114            // direction is "forward"
1115            // > Let candidate be the first suitable sequentially focusable area in starting point's
1116            // > active document, if any; or else null
1117            //
1118            // There's an ambiguity in the specification here. In this case it says to choose
1119            // the "first suitable sequentially focusable area." It's possible to interpret this
1120            // as the first in DOM order, but browsers seem to agree to follow tab index
1121            // order instead.
1122            //
1123            // Pick the lowest, prioritizing the earlier node when equal.
1124            SequentialFocusDirection::Forward
1125                if candidate_and_current_winner_ordering == Ordering::Less =>
1126            {
1127                true
1128            },
1129            // direction is "backward"
1130            // > Let candidate be the last suitable sequentially focusable area in starting point's
1131            // > active document, if any; or else null
1132            //
1133            // There's an ambiguity in the specification here. In this case it says to choose
1134            // the "last suitable sequentially focusable area" It's possible to interpret this
1135            // as the first in DOM order, but browsers seem to agree to following tab index
1136            // order instead.
1137            //
1138            // Pick the highest, prioritizing the later node when equal.
1139            SequentialFocusDirection::Backward
1140                if candidate_and_current_winner_ordering != Ordering::Less =>
1141            {
1142                true
1143            },
1144            _ => false,
1145        }
1146    }
1147
1148    /// Process the node or focus scope owner with the provided tab index for searches with the
1149    /// [`SequentialFocusNavigationMechanism::Sequential`] search mechanism. Returns a boolean
1150    /// that is true if this candidate is the new winner and a [`Continue`] which says whether to
1151    /// keep searching or stop.
1152    fn process_element_for_sequential_traversal(
1153        &self,
1154        candidate_element_tab_index: i32,
1155        focused_element_tab_index: i32,
1156    ) -> (bool, Continue) {
1157        let candidate_and_focused_ordering =
1158            compare_tab_indices(candidate_element_tab_index, focused_element_tab_index);
1159        match self.direction {
1160            SequentialFocusDirection::Forward => {
1161                // If moving forward the first element with equal tab index after the current
1162                // element is the winner.
1163                if self.passed_starting_point && candidate_and_focused_ordering == Ordering::Equal {
1164                    return (true, Continue::No);
1165                }
1166                // If the candidate element does not have a greater tab index, then discard it.
1167                if candidate_and_focused_ordering != Ordering::Greater {
1168                    return (false, Continue::Yes);
1169                }
1170                let Some((_, winning_tab_index)) = self.current_winner else {
1171                    // If this candidate has a tab index which is one greater than the current
1172                    // tab index, then we know it is the winner, because we give precedence to
1173                    // elements earlier in the DOM.
1174                    if candidate_element_tab_index == focused_element_tab_index + 1 {
1175                        return (true, Continue::No);
1176                    }
1177
1178                    return (true, Continue::Yes);
1179                };
1180
1181                // If the candidate element has a lesser tab index than the current winner,
1182                // then it becomes the winner.
1183                let should_select =
1184                    compare_tab_indices(candidate_element_tab_index, winning_tab_index) ==
1185                        Ordering::Less;
1186
1187                (should_select, Continue::Yes)
1188            },
1189            SequentialFocusDirection::Backward => {
1190                // If moving backward the last element with an equal tab index that precedes
1191                // the focused element in the DOM is the winner.
1192                if !self.passed_starting_point && candidate_and_focused_ordering == Ordering::Equal
1193                {
1194                    return (true, Continue::Yes);
1195                }
1196                // If the candidate does not have a lesser tab index, then discard it.
1197                if candidate_and_focused_ordering != Ordering::Less {
1198                    return (false, Continue::Yes);
1199                }
1200                let Some((_, winning_tab_index)) = self.current_winner else {
1201                    return (true, Continue::Yes);
1202                };
1203                // If the candidate element's tab index is not less than the current winner,
1204                // then it becomes the new winner. This means that when the tab indices are
1205                // equal, we give preference to the last one in DOM order.
1206                let should_select =
1207                    compare_tab_indices(candidate_element_tab_index, winning_tab_index) !=
1208                        Ordering::Less;
1209                (should_select, Continue::Yes)
1210            },
1211        }
1212    }
1213}
1214
1215/// Compare two tab indices according to <https://html.spec.whatwg.org/multipage/#tabindex-value>.
1216///
1217/// `Ordering::Less`: The index should come before the other in sequential focus order.
1218/// `Ordering::Equal`: The two indices should be processed in DOM order, respecting focus direction
1219/// and focus scopes.
1220/// `Ordering::Greater`: The index should come after the other in sequential focus order.
1221///
1222/// Note that a tabindex of 0 should come after all others, which is essentially why we need this
1223/// function.
1224fn compare_tab_indices(a: i32, b: i32) -> Ordering {
1225    if a == b {
1226        Ordering::Equal
1227    } else if a == 0 {
1228        Ordering::Greater
1229    } else if b == 0 {
1230        Ordering::Less
1231    } else {
1232        a.cmp(&b)
1233    }
1234}