mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Update document focus when element focusability changes.
This commit is contained in:
parent
757371f4f0
commit
d55424e88f
8 changed files with 89 additions and 72 deletions
|
@ -1025,6 +1025,17 @@ impl Document {
|
|||
*self.focus_transaction.borrow_mut() = FocusTransaction::InTransaction(Default::default());
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#focus-fixup-rule>
|
||||
pub(crate) fn perform_focus_fixup_rule(&self, not_focusable: &Element) {
|
||||
if Some(not_focusable) != self.focused.get().as_ref().map(|e| &**e) {
|
||||
return;
|
||||
}
|
||||
self.request_focus(
|
||||
self.GetBody().as_ref().map(|e| &*e.upcast()),
|
||||
FocusType::Element,
|
||||
)
|
||||
}
|
||||
|
||||
/// Request that the given element receive focus once the current transaction is complete.
|
||||
/// If None is passed, then whatever element is currently focused will no longer be focused
|
||||
/// once the transaction is complete.
|
||||
|
|
|
@ -1300,12 +1300,12 @@ impl Element {
|
|||
if self.is_actually_disabled() {
|
||||
return false;
|
||||
}
|
||||
// TODO: Check whether the element is being rendered (i.e. not hidden).
|
||||
let node = self.upcast::<Node>();
|
||||
if node.get_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE) {
|
||||
return true;
|
||||
}
|
||||
// https://html.spec.whatwg.org/multipage/#specially-focusable
|
||||
|
||||
// <a>, <input>, <select>, and <textrea> are inherently focusable.
|
||||
match node.type_id() {
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLAnchorElement,
|
||||
|
@ -1832,6 +1832,67 @@ impl Element {
|
|||
pub fn get_name(&self) -> Option<Atom> {
|
||||
self.rare_data().as_ref()?.name_attribute.clone()
|
||||
}
|
||||
|
||||
fn is_sequentially_focusable(&self) -> bool {
|
||||
let element = self.upcast::<Element>();
|
||||
let node = self.upcast::<Node>();
|
||||
if !node.is_connected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if element.has_attribute(&local_name!("hidden")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.disabled_state() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if element.has_attribute(&local_name!("tabindex")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
match node.type_id() {
|
||||
// <button>, <select>, <iframe>, and <textarea> are implicitly focusable.
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLButtonElement,
|
||||
)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLSelectElement,
|
||||
)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLIFrameElement,
|
||||
)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLTextAreaElement,
|
||||
)) => true,
|
||||
|
||||
// Links that generate actual links are focusable.
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLAnchorElement,
|
||||
)) => element.has_attribute(&local_name!("href")),
|
||||
|
||||
//TODO focusable if editing host
|
||||
//TODO focusable if "sorting interface th elements"
|
||||
_ => {
|
||||
// Draggable elements are focusable.
|
||||
element.get_string_attribute(&local_name!("draggable")) == "true"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_sequentially_focusable_status(&self) {
|
||||
let node = self.upcast::<Node>();
|
||||
let is_sequentially_focusable = self.is_sequentially_focusable();
|
||||
node.set_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE, is_sequentially_focusable);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#focus-fixup-rule
|
||||
if !is_sequentially_focusable {
|
||||
let document = document_from_node(self);
|
||||
document.perform_focus_fixup_rule(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementMethods for Element {
|
||||
|
@ -2751,6 +2812,9 @@ impl VirtualMethods for Element {
|
|||
let node = self.upcast::<Node>();
|
||||
let doc = node.owner_doc();
|
||||
match attr.local_name() {
|
||||
&local_name!("tabindex") | &local_name!("draggable") | &local_name!("hidden") => {
|
||||
self.update_sequentially_focusable_status()
|
||||
},
|
||||
&local_name!("style") => {
|
||||
// Modifying the `style` attribute might change style.
|
||||
*self.style_attribute.borrow_mut() = match mutation {
|
||||
|
@ -2917,6 +2981,8 @@ impl VirtualMethods for Element {
|
|||
return;
|
||||
}
|
||||
|
||||
self.update_sequentially_focusable_status();
|
||||
|
||||
if let Some(ref value) = *self.id_attribute.borrow() {
|
||||
if let Some(shadow_root) = self.upcast::<Node>().containing_shadow_root() {
|
||||
shadow_root.register_element_id(self, value.clone());
|
||||
|
@ -2945,6 +3011,8 @@ impl VirtualMethods for Element {
|
|||
return;
|
||||
}
|
||||
|
||||
self.update_sequentially_focusable_status();
|
||||
|
||||
let doc = document_from_node(self);
|
||||
|
||||
if let Some(ref shadow_root) = self.shadow_root() {
|
||||
|
|
|
@ -233,6 +233,7 @@ impl VirtualMethods for HTMLButtonElement {
|
|||
el.check_ancestors_disabled_state_for_form_control();
|
||||
},
|
||||
}
|
||||
el.update_sequentially_focusable_status();
|
||||
},
|
||||
&local_name!("type") => match mutation {
|
||||
AttributeMutation::Set(_) => {
|
||||
|
|
|
@ -28,7 +28,7 @@ use crate::dom::htmlinputelement::{HTMLInputElement, InputType};
|
|||
use crate::dom::htmllabelelement::HTMLLabelElement;
|
||||
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
|
||||
use crate::dom::node::{document_from_node, window_from_node};
|
||||
use crate::dom::node::{BindContext, Node, NodeFlags, ShadowIncluding};
|
||||
use crate::dom::node::{Node, ShadowIncluding};
|
||||
use crate::dom::text::Text;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
|
@ -91,53 +91,6 @@ impl HTMLElement {
|
|||
let eventtarget = self.upcast::<EventTarget>();
|
||||
eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>()
|
||||
}
|
||||
|
||||
fn update_sequentially_focusable_status(&self) {
|
||||
let element = self.upcast::<Element>();
|
||||
let node = self.upcast::<Node>();
|
||||
if element.has_attribute(&local_name!("tabindex")) {
|
||||
node.set_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE, true);
|
||||
} else {
|
||||
match node.type_id() {
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLButtonElement,
|
||||
)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLSelectElement,
|
||||
)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLIFrameElement,
|
||||
)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLTextAreaElement,
|
||||
)) => node.set_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE, true),
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLLinkElement,
|
||||
)) |
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLAnchorElement,
|
||||
)) => {
|
||||
if element.has_attribute(&local_name!("href")) {
|
||||
node.set_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE, true);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if let Some(attr) = element.get_attribute(&ns!(), &local_name!("draggable")) {
|
||||
let value = attr.value();
|
||||
let is_true = match *value {
|
||||
AttrValue::String(ref string) => string == "true",
|
||||
_ => false,
|
||||
};
|
||||
node.set_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE, is_true);
|
||||
} else {
|
||||
node.set_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE, false);
|
||||
}
|
||||
//TODO set SEQUENTIALLY_FOCUSABLE flag if editing host
|
||||
//TODO set SEQUENTIALLY_FOCUSABLE flag if "sorting interface th elements"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HTMLElementMethods for HTMLElement {
|
||||
|
@ -855,13 +808,6 @@ impl VirtualMethods for HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn bind_to_tree(&self, context: &BindContext) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.bind_to_tree(context);
|
||||
}
|
||||
self.update_sequentially_focusable_status();
|
||||
}
|
||||
|
||||
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
|
||||
match name {
|
||||
&local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
|
||||
|
|
|
@ -182,14 +182,17 @@ impl VirtualMethods for HTMLFieldSetElement {
|
|||
let el = field.downcast::<Element>().unwrap();
|
||||
el.set_disabled_state(true);
|
||||
el.set_enabled_state(false);
|
||||
el.update_sequentially_focusable_status();
|
||||
}
|
||||
} else {
|
||||
for field in fields {
|
||||
let el = field.downcast::<Element>().unwrap();
|
||||
el.check_disabled_attribute();
|
||||
el.check_ancestors_disabled_state_for_form_control();
|
||||
el.update_sequentially_focusable_status();
|
||||
}
|
||||
}
|
||||
el.update_sequentially_focusable_status();
|
||||
},
|
||||
&local_name!("form") => {
|
||||
self.form_attribute_mutated(mutation);
|
||||
|
|
|
@ -2291,6 +2291,8 @@ impl VirtualMethods for HTMLInputElement {
|
|||
let read_write = !(self.ReadOnly() || el.disabled_state());
|
||||
el.set_read_write_state(read_write);
|
||||
}
|
||||
|
||||
el.update_sequentially_focusable_status();
|
||||
},
|
||||
&local_name!("checked") if !self.checked_changed.get() => {
|
||||
let checked_state = match mutation {
|
||||
|
|
|
@ -478,6 +478,7 @@ impl VirtualMethods for HTMLTextAreaElement {
|
|||
}
|
||||
},
|
||||
}
|
||||
el.update_sequentially_focusable_status();
|
||||
},
|
||||
local_name!("maxlength") => match *attr.value() {
|
||||
AttrValue::Int(_, value) => {
|
||||
|
|
|
@ -2,21 +2,6 @@
|
|||
[Disabling contenteditable]
|
||||
expected: FAIL
|
||||
|
||||
[Hiding the active element]
|
||||
expected: FAIL
|
||||
|
||||
[Disabling the active element (making it expressly inert)]
|
||||
expected: FAIL
|
||||
|
||||
[Changing the first legend element in disabled <fieldset>]
|
||||
expected: FAIL
|
||||
|
||||
[Disabling <fieldset> affects its descendants]
|
||||
expected: FAIL
|
||||
|
||||
[Removing the tabindex attribute from a div]
|
||||
expected: FAIL
|
||||
|
||||
[Removing the active element from the DOM]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue