mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
script: Fully implement DocumentOrShadowRoot#activeElement (#43861)
`DocumentOrShadowRoot#activeElement` should return retargeted results. What that means is that if the DOM anchor of the `Document`'s focused focusable area is within a shadow root, `Document#activeElement` should return the shadow host. This change implements that behavior, properly returning the `activeElement` from both `Document` and `ShadowRoot`. Testing: This causes a decent number of WPT tests and subtests to start passing. One subtest starts to fail, because it uses the `autofocus` attribute which we do not yet support. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
@@ -371,7 +371,7 @@ pub(crate) struct Document {
|
||||
/// Whether the DOMContentLoaded event has already been dispatched.
|
||||
domcontentloaded_dispatched: Cell<bool>,
|
||||
/// The element that currently has the document focus context.
|
||||
focused: MutNullableDom<Element>,
|
||||
focused_element: MutNullableDom<Element>,
|
||||
/// The last sequence number sent to the constellation.
|
||||
#[no_trace]
|
||||
focus_sequence: Cell<FocusSequenceNumber>,
|
||||
@@ -1336,8 +1336,8 @@ impl Document {
|
||||
|
||||
/// Return the element that currently has focus.
|
||||
// https://w3c.github.io/uievents/#events-focusevent-doc-focus
|
||||
pub(crate) fn get_focused_element(&self) -> Option<DomRoot<Element>> {
|
||||
self.focused.get()
|
||||
pub(crate) fn focused_element(&self) -> Option<DomRoot<Element>> {
|
||||
self.focused_element.get()
|
||||
}
|
||||
|
||||
/// Get the last sequence number sent to the constellation.
|
||||
@@ -1368,7 +1368,7 @@ impl Document {
|
||||
/// TODO: Handle the "focus changed during ongoing navigation" flag.
|
||||
pub(crate) fn perform_focus_fixup_rule(&self, can_gc: CanGc) {
|
||||
if self
|
||||
.focused
|
||||
.focused_element
|
||||
.get()
|
||||
.as_deref()
|
||||
.is_none_or(|focused| focused.is_focusable_area())
|
||||
@@ -1409,9 +1409,10 @@ impl Document {
|
||||
},
|
||||
true,
|
||||
),
|
||||
FocusOperation::Unfocus => {
|
||||
(self.focused.get().as_deref().map(DomRoot::from_ref), false)
|
||||
},
|
||||
FocusOperation::Unfocus => (
|
||||
self.focused_element.get().as_deref().map(DomRoot::from_ref),
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
if !new_focus_state {
|
||||
@@ -1426,7 +1427,7 @@ impl Document {
|
||||
}
|
||||
}
|
||||
|
||||
let old_focused = self.focused.get();
|
||||
let old_focused = self.focused_element.get();
|
||||
let old_focus_state = self.has_focus.get();
|
||||
|
||||
debug!(
|
||||
@@ -1470,7 +1471,7 @@ impl Document {
|
||||
self.fire_focus_event(FocusEventType::Blur, self.global().upcast(), None, can_gc);
|
||||
}
|
||||
|
||||
self.focused.set(new_focused.as_deref());
|
||||
self.focused_element.set(new_focused.as_deref());
|
||||
self.has_focus.set(new_focus_state);
|
||||
|
||||
if old_focus_state != new_focus_state && new_focus_state {
|
||||
@@ -3855,7 +3856,7 @@ impl Document {
|
||||
ready_state: Cell::new(ready_state),
|
||||
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
|
||||
|
||||
focused: Default::default(),
|
||||
focused_element: Default::default(),
|
||||
focus_sequence: Cell::new(FocusSequenceNumber::default()),
|
||||
has_focus: Cell::new(has_focus),
|
||||
current_script: Default::default(),
|
||||
@@ -5051,11 +5052,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-document-activeelement>
|
||||
fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
|
||||
self.document_or_shadow_root.get_active_element(
|
||||
self.get_focused_element(),
|
||||
self.GetBody(),
|
||||
self.GetDocumentElement(),
|
||||
)
|
||||
self.document_or_shadow_root.active_element(self.upcast())
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-document-hasfocus>
|
||||
|
||||
@@ -1357,7 +1357,7 @@ impl DocumentEventHandler {
|
||||
can_gc: CanGc,
|
||||
) -> InputEventResult {
|
||||
let document = self.window.Document();
|
||||
let focused = document.get_focused_element();
|
||||
let focused = document.focused_element();
|
||||
let body = document.GetBody();
|
||||
|
||||
let target = match (&focused, &body) {
|
||||
@@ -1434,7 +1434,7 @@ impl DocumentEventHandler {
|
||||
// spec: https://w3c.github.io/uievents/#compositionupdate
|
||||
// spec: https://w3c.github.io/uievents/#compositionend
|
||||
// > Event.target : focused element processing the composition
|
||||
let focused = document.get_focused_element();
|
||||
let focused = document.focused_element();
|
||||
let target = if let Some(elem) = &focused {
|
||||
elem.upcast()
|
||||
} else {
|
||||
@@ -1752,7 +1752,7 @@ impl DocumentEventHandler {
|
||||
|
||||
// Step 6 if the context is editable:
|
||||
let document = self.window.Document();
|
||||
let target = target.or(document.get_focused_element());
|
||||
let target = target.or(document.focused_element());
|
||||
let target = target
|
||||
.map(|target| DomRoot::from_ref(target.upcast()))
|
||||
.or_else(|| {
|
||||
@@ -2018,7 +2018,7 @@ impl DocumentEventHandler {
|
||||
let mut starting_point = self
|
||||
.window
|
||||
.Document()
|
||||
.get_focused_element()
|
||||
.focused_element()
|
||||
.map(DomRoot::upcast::<Node>);
|
||||
|
||||
// > 2. If there is a sequential focus navigation starting point defined and it is inside
|
||||
@@ -2242,7 +2242,7 @@ impl DocumentEventHandler {
|
||||
|
||||
let document = self.window.Document();
|
||||
let mut scrolling_box = document
|
||||
.get_focused_element()
|
||||
.focused_element()
|
||||
.or(self.most_recently_clicked_element.get())
|
||||
.and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
|
||||
.unwrap_or_else(|| {
|
||||
|
||||
@@ -10,6 +10,8 @@ use embedder_traits::UntrustedNodeAddress;
|
||||
use js::rust::HandleValue;
|
||||
use layout_api::ElementsFromPointFlags;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
|
||||
use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
|
||||
use script_bindings::error::{Error, ErrorResult};
|
||||
use script_bindings::script_runtime::{CanGc, JSContext};
|
||||
use servo_arc::Arc;
|
||||
@@ -21,7 +23,9 @@ use style::stylesheets::{Stylesheet, StylesheetContents};
|
||||
use stylo_atoms::Atom;
|
||||
use webrender_api::units::LayoutPoint;
|
||||
|
||||
use crate::dom::Document;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
|
||||
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
|
||||
use crate::dom::bindings::conversions::{ConversionResult, SafeFromJSValConvertible};
|
||||
@@ -31,10 +35,9 @@ use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::trace::HashMapTracedValues;
|
||||
use crate::dom::css::stylesheetlist::StyleSheetListOwner;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::html::htmlelement::HTMLElement;
|
||||
use crate::dom::node::{self, Node, VecPreOrderInsertionHelper};
|
||||
use crate::dom::shadowroot::ShadowRoot;
|
||||
use crate::dom::types::CSSStyleSheet;
|
||||
use crate::dom::types::{CSSStyleSheet, EventTarget};
|
||||
use crate::dom::window::Window;
|
||||
use crate::stylesheet_set::StylesheetSetRef;
|
||||
|
||||
@@ -221,23 +224,48 @@ impl DocumentOrShadowRoot {
|
||||
elements
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-document-activeelement
|
||||
pub(crate) fn get_active_element(
|
||||
&self,
|
||||
focused_element: Option<DomRoot<Element>>,
|
||||
body: Option<DomRoot<HTMLElement>>,
|
||||
document_element: Option<DomRoot<Element>>,
|
||||
) -> Option<DomRoot<Element>> {
|
||||
// TODO: Step 2.
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-documentorshadowroot-activeelement-dev>
|
||||
pub(crate) fn active_element(&self, this: &Node) -> Option<DomRoot<Element>> {
|
||||
// Step 1. Let candidate be this's node document's focused area's DOM anchor.
|
||||
//
|
||||
// Note: When `Document::focused_element` returns `None`, that means that the
|
||||
// `Document` / viewport itself is focused.
|
||||
let document = self.window.Document();
|
||||
let candidate = match document.focused_element() {
|
||||
Some(candidate) => DomRoot::upcast::<Node>(candidate),
|
||||
None => DomRoot::upcast::<Node>(document.clone()),
|
||||
};
|
||||
|
||||
match focused_element {
|
||||
Some(element) => Some(element), // Step 3. and 4.
|
||||
None => match body {
|
||||
// Step 5.
|
||||
Some(body) => Some(DomRoot::upcast(body)),
|
||||
None => document_element,
|
||||
},
|
||||
// Step 2. Set candidate to the result of retargeting candidate against this.
|
||||
//
|
||||
// Note: `retarget()` operates on `EventTarget`, but we can be assured that we are
|
||||
// only dealing with various kinds of `Node`s here.
|
||||
let candidate =
|
||||
DomRoot::downcast::<Node>(candidate.upcast::<EventTarget>().retarget(this.upcast()))?;
|
||||
|
||||
// Step 3. If candidate's root is not this, then return null.
|
||||
if this != &*candidate.GetRootNode(&GetRootNodeOptions::empty()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 4. If candidate is not a Document object, then return candidate.
|
||||
if let Some(candidate) = DomRoot::downcast::<Element>(candidate.clone()) {
|
||||
return Some(candidate);
|
||||
}
|
||||
assert!(candidate.is::<Document>());
|
||||
|
||||
// Step 5. If candidate has a body element, then return that body element.
|
||||
if let Some(body) = document.GetBody() {
|
||||
return Some(DomRoot::upcast(body));
|
||||
}
|
||||
|
||||
// Step 6. If candidate's document element is non-null, then return that document element.
|
||||
if let Some(document_element) = document.GetDocumentElement() {
|
||||
return Some(document_element);
|
||||
}
|
||||
|
||||
// Step 7. Return null.
|
||||
None
|
||||
}
|
||||
|
||||
/// Remove a stylesheet owned by `owner` from the list of document sheets.
|
||||
|
||||
@@ -1339,7 +1339,7 @@ impl VirtualMethods for HTMLElement {
|
||||
// TODO: Should this also happen for non-HTML elements such as SVG elements?
|
||||
let element = self.as_element();
|
||||
if document
|
||||
.get_focused_element()
|
||||
.focused_element()
|
||||
.is_some_and(|focused_element| &*focused_element == element)
|
||||
{
|
||||
document.focus(
|
||||
|
||||
@@ -95,7 +95,7 @@ impl Node {
|
||||
.and_then(Element::shadow_root)
|
||||
.is_some_and(|shadow_root| shadow_root.DelegatesFocus())
|
||||
{
|
||||
if let Some(focused_element) = self.owner_document().get_focused_element() {
|
||||
if let Some(focused_element) = self.owner_document().focused_element() {
|
||||
// > Step 2. If focus target is a shadow-including inclusive ancestor of
|
||||
// > focusedElement, then return focusedElement.
|
||||
if self
|
||||
|
||||
@@ -179,11 +179,6 @@ impl ShadowRoot {
|
||||
&self.document
|
||||
}
|
||||
|
||||
pub(crate) fn get_focused_element(&self) -> Option<DomRoot<Element>> {
|
||||
// XXX get retargeted focused element
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn stylesheet_count(&self) -> usize {
|
||||
self.author_styles.borrow().stylesheets.len()
|
||||
}
|
||||
@@ -387,8 +382,7 @@ impl ShadowRoot {
|
||||
impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-document-activeelement>
|
||||
fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
|
||||
self.document_or_shadow_root
|
||||
.get_active_element(self.get_focused_element(), None, None)
|
||||
self.document_or_shadow_root.active_element(self.upcast())
|
||||
}
|
||||
|
||||
/// <https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint>
|
||||
|
||||
@@ -5,8 +5,5 @@
|
||||
[Shift+Tab escape from video element inside focusgroup goes to adjacent item]
|
||||
expected: FAIL
|
||||
|
||||
[Tab through video controls outside focusgroup follows normal tab order]
|
||||
expected: FAIL
|
||||
|
||||
[Tab escape from inside video shadow DOM controls reaches adjacent focusgroup item]
|
||||
expected: FAIL
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[autofocus-in-not-fully-active-document.html]
|
||||
[Autofocus element in not-fully-active document should not be queued.]
|
||||
expected: FAIL
|
||||
@@ -1,6 +0,0 @@
|
||||
[slot-element-focusable.tentative.html]
|
||||
[slot element with display: block should be focusable]
|
||||
expected: FAIL
|
||||
|
||||
[slot element with default style should be focusable]
|
||||
expected: FAIL
|
||||
@@ -1,6 +0,0 @@
|
||||
[slot-element-tabbable.tentative.html]
|
||||
[slot element with display: block should be focusable]
|
||||
expected: FAIL
|
||||
|
||||
[slot element with default style should be focusable]
|
||||
expected: FAIL
|
||||
@@ -1,7 +1,4 @@
|
||||
[dialog-focus-previous-outside.html]
|
||||
[Focus restore should not occur when the focused element is in a shadowroot outside of the dialog.]
|
||||
expected: FAIL
|
||||
|
||||
[Focus restore should occur when the focused element is in a shadowroot inside the dialog.]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
@@ -7,6 +7,3 @@
|
||||
|
||||
[Focus should be moved to the previously focused element even if it has moved to shadow DOM root in between show/close]
|
||||
expected: FAIL
|
||||
|
||||
[Focus should be moved to the shadow DOM host if the previouly focused element is a shadow DOM node]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
[ShadowRoot-interface.html]
|
||||
[ShadowRoot.activeElement must return the focused element of the context object when shadow root is open.]
|
||||
expected: FAIL
|
||||
|
||||
[ShadowRoot.activeElement must return the focused element of the context object when shadow root is closed.]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[focus-navigation-web-component-radio.html]
|
||||
[Focus for web component input type elements should be bound by <form> inside shadow DOM]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[focus-within-shadow.html]
|
||||
[Don't clear focus within shadow root if light DOM children are cleared]
|
||||
expected: FAIL
|
||||
@@ -1,12 +0,0 @@
|
||||
[DocumentOrShadowRoot-activeElement.html]
|
||||
[activeElement on document & shadow root when focused element is in the shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[activeElement on a neighboring host when focused element is in another shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[activeElement when focused element is in a nested shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[activeElement when focused element is in a parent shadow tree]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[delegatesFocus-tabindex-change.html]
|
||||
[Setting tabindex on the shadow host of a focused element with delegatesFocus should not change focus.]
|
||||
expected: FAIL
|
||||
@@ -5,8 +5,5 @@
|
||||
[Focus should be delegated to the autofocus element when the inner host has delegates focus]
|
||||
expected: FAIL
|
||||
|
||||
[Focus should not be delegated to the slotted elements]
|
||||
expected: FAIL
|
||||
|
||||
[Focus should be delegated to the nested div which has autofocus based on the tree order]
|
||||
expected: FAIL
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
[focus-method-delegatesFocus.html]
|
||||
[focus() on host with delegatesFocus, all tabindex=0]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus & tabindex =-1, all other tabindex=0]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus & no tabindex, all other tabindex=0]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus & tabindex = 0, all other tabindex=-1]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus, all tabindex=-1]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus & tabindex=0, #belowSlots with tabindex=0]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus & tabindex=0, #aboveSlots and #belowSlots with tabindex=0]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus & tabindex=0, #aboveSlots with tabindex=0 and #belowSlots with tabindex=1]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus & tabindex=0, #slottedToFirstSlot, #slottedToSecondSlot, #belowSlots with tabindex=0]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus and already-focused non-first shadow descendant]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus with another host with no delegatesFocus and a focusable child]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus with another host with delegatesFocus and a focusable child]
|
||||
expected: FAIL
|
||||
|
||||
[focus() on host with delegatesFocus and slotted focusable children]
|
||||
expected: FAIL
|
||||
@@ -1,9 +0,0 @@
|
||||
[focus-method-with-delegatesFocus.html]
|
||||
[on focus(), focusable xshadow2 with delegatesFocus=true delegates focus into its inner element.]
|
||||
expected: FAIL
|
||||
|
||||
[if an element within shadow is focused, focusing on shadow host should not slide focus to its inner element.]
|
||||
expected: FAIL
|
||||
|
||||
[xshadow2.focus() shouldn't move focus to #one when its inner element is already focused.]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[focus-slot-box-generated-tabindex-0.html]
|
||||
[slot with tabindex=0 that generates a box should be focusable]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[focus-tabindex-order-shadow-varying-tabindex-2.html]
|
||||
[Order with different tabindex on host]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[focus-tabindex-order-shadow-varying-tabindex-3.html]
|
||||
[Order with different tabindex on host]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[test-007.html]
|
||||
[A_10_01_01_03_01_T01]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[test-001.html]
|
||||
[A_07_03_01_T01]
|
||||
expected: FAIL
|
||||
@@ -1,3 +0,0 @@
|
||||
[test-002.html]
|
||||
[A_07_03_02_T01]
|
||||
expected: FAIL
|
||||
@@ -1,2 +0,0 @@
|
||||
[viewport-apply-initial-scale-after-navigation.html]
|
||||
expected: TIMEOUT
|
||||
Reference in New Issue
Block a user