Skip to content

Commit

Permalink
Labels are a live list in tree order
Browse files Browse the repository at this point in the history
  • Loading branch information
pshaughn committed Jan 6, 2020
1 parent 0d142be commit 036e8da
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 102 deletions.
6 changes: 3 additions & 3 deletions components/script/dom/htmlbuttonelement.rs
Expand Up @@ -41,6 +41,7 @@ pub struct HTMLButtonElement {
htmlelement: HTMLElement,
button_type: Cell<ButtonType>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
}

impl HTMLButtonElement {
Expand All @@ -58,6 +59,7 @@ impl HTMLButtonElement {
),
button_type: Cell::new(ButtonType::Submit),
form_owner: Default::default(),
labels_node_list: Default::default(),
}
}

Expand Down Expand Up @@ -149,9 +151,7 @@ impl HTMLButtonElementMethods for HTMLButtonElement {
make_setter!(SetValue, "value");

// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
fn Labels(&self) -> DomRoot<NodeList> {
self.upcast::<HTMLElement>().labels()
}
make_labels_getter!(Labels, labels_node_list);
}

impl HTMLButtonElement {
Expand Down
70 changes: 37 additions & 33 deletions components/script/dom/htmlelement.rs
Expand Up @@ -4,10 +4,10 @@

use crate::dom::activation::{synthetic_click_activation, ActivationSource};
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding;
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::error::{Error, ErrorResult};
Expand All @@ -29,7 +29,6 @@ use crate::dom::htmlinputelement::{HTMLInputElement, InputType};
use crate::dom::htmllabelelement::HTMLLabelElement;
use crate::dom::node::{document_from_node, window_from_node};
use crate::dom::node::{BindContext, Node, NodeFlags, ShadowIncluding};
use crate::dom::nodelist::NodeList;
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
Expand Down Expand Up @@ -677,43 +676,48 @@ impl HTMLElement {
}

// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
pub fn labels(&self) -> DomRoot<NodeList> {
debug_assert!(self.is_labelable_element());

// This gets the nth label in tree order.
pub fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
let element = self.upcast::<Element>();
let window = window_from_node(element);

// Traverse ancestors for implicitly associated <label> elements
// https://html.spec.whatwg.org/multipage/#the-label-element:attr-label-for-4
let ancestors = self
.upcast::<Node>()
.ancestors()
.filter_map(DomRoot::downcast::<HTMLElement>)
// If we reach a labelable element, we have a guarantee no ancestors above it
// will be a label for this HTMLElement
.take_while(|elem| !elem.is_labelable_element())
.filter_map(DomRoot::downcast::<HTMLLabelElement>)
.filter(|elem| !elem.upcast::<Element>().has_attribute(&local_name!("for")))
.filter(|elem| elem.first_labelable_descendant().as_deref() == Some(self))
.map(DomRoot::upcast::<Node>);

let id = element.Id();
let id = match &id as &str {
"" => return NodeList::new_simple_list(&window, ancestors),
id => id,
};

// Traverse entire tree for <label> elements with `for` attribute matching `id`
// Traverse entire tree for <label> elements that have
// this as their control.
// There is room for performance optimization, as we don't need
// the actual result of GetControl, only whether the result
// would match self.
// (Even more room for performance optimization: do what
// nodelist ChildrenList does and keep a mutation-aware cursor
// around; this may be hard since labels need to keep working
// even as they get detached into a subtree and reattached to
// a document.)
let root_element = element.root_element();
let root_node = root_element.upcast::<Node>();
let children = root_node
root_node
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.filter(|elem| elem.is::<HTMLLabelElement>())
.filter(|elem| elem.get_string_attribute(&local_name!("for")) == id)
.map(DomRoot::upcast::<Node>);
.filter_map(DomRoot::downcast::<HTMLLabelElement>)
.filter(|elem| match elem.GetControl() {
Some(control) => &*control == self,
_ => false,
})
.nth(index as usize)
.map(|n| DomRoot::from_ref(n.upcast::<Node>()))
}

NodeList::new_simple_list(&window, children.chain(ancestors))
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
// This counts the labels of the element, to support NodeList::Length
pub fn labels_count(&self) -> u32 {
// see label_at comments about performance
let element = self.upcast::<Element>();
let root_element = element.root_element();
let root_node = root_element.upcast::<Node>();
root_node
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<HTMLLabelElement>)
.filter(|elem| match elem.GetControl() {
Some(control) => &*control == self,
_ => false,
})
.count() as u32
}
}

Expand Down
16 changes: 12 additions & 4 deletions components/script/dom/htmlinputelement.rs
Expand Up @@ -234,6 +234,7 @@ pub struct HTMLInputElement {

filelist: MutNullableDom<FileList>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
}

#[derive(JSTraceable)]
Expand Down Expand Up @@ -303,6 +304,7 @@ impl HTMLInputElement {
value_dirty: Cell::new(false),
filelist: MutNullableDom::new(None),
form_owner: Default::default(),
labels_node_list: MutNullableDom::new(None),
}
}

Expand Down Expand Up @@ -791,12 +793,18 @@ impl HTMLInputElementMethods for HTMLInputElement {
}

// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
fn Labels(&self) -> DomRoot<NodeList> {
// Different from make_labels_getter because this one
// conditionally returns null.
fn GetLabels(&self) -> Option<DomRoot<NodeList>> {
if self.input_type() == InputType::Hidden {
let window = window_from_node(self);
NodeList::empty(&window)
None
} else {
self.upcast::<HTMLElement>().labels()
Some(self.labels_node_list.or_init(|| {
NodeList::new_labels_list(
self.upcast::<Node>().owner_doc().window(),
self.upcast::<HTMLElement>(),
)
}))
}
}

Expand Down
50 changes: 38 additions & 12 deletions components/script/dom/htmllabelelement.rs
Expand Up @@ -4,8 +4,11 @@

use crate::dom::activation::{synthetic_click_activation, Activatable, ActivationSource};
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding;
use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
Expand All @@ -15,7 +18,7 @@ use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
use crate::dom::node::{document_from_node, Node, ShadowIncluding};
use crate::dom::node::{Node, ShadowIncluding};
use crate::dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
Expand Down Expand Up @@ -99,10 +102,6 @@ impl HTMLLabelElementMethods for HTMLLabelElement {

// https://html.spec.whatwg.org/multipage/#dom-label-control
fn GetControl(&self) -> Option<DomRoot<HTMLElement>> {
if !self.upcast::<Node>().is_in_doc() {
return None;
}

let for_attr = match self
.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("for"))
Expand All @@ -111,13 +110,40 @@ impl HTMLLabelElementMethods for HTMLLabelElement {
None => return self.first_labelable_descendant(),
};

let for_value = for_attr.value();
document_from_node(self)
.get_element_by_id(for_value.as_atom())
.and_then(DomRoot::downcast::<HTMLElement>)
.into_iter()
.filter(|e| e.is_labelable_element())
.next()
let for_value = for_attr.Value();

// "If the attribute is specified and there is an element in the tree
// whose ID is equal to the value of the for attribute, and the first
// such element in tree order is a labelable element, then that
// element is the label element's labeled control."
// Two subtle points here: we need to search the _tree_, which is
// not necessarily the document if we're detached from the document,
// and we only consider one element even if a later element with
// the same ID is labelable.

let maybe_found = self
.upcast::<Node>()
.GetRootNode(&GetRootNodeOptions::empty())
.traverse_preorder(ShadowIncluding::No)
.find_map(|e| {
if let Some(htmle) = e.downcast::<HTMLElement>() {
if htmle.upcast::<Element>().Id() == for_value {
Some(DomRoot::from_ref(htmle))
} else {
None
}
} else {
None
}
});
// We now have the element that we would return, but only return it
// if it's labelable.
if let Some(ref maybe_labelable) = maybe_found {
if maybe_labelable.is_labelable_element() {
return maybe_found;
}
}
None
}
}

Expand Down
8 changes: 4 additions & 4 deletions components/script/dom/htmlmeterelement.rs
Expand Up @@ -6,7 +6,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLMeterElementBinding::{
self, HTMLMeterElementMethods,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::document::Document;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::node::Node;
Expand All @@ -17,6 +17,7 @@ use html5ever::{LocalName, Prefix};
#[dom_struct]
pub struct HTMLMeterElement {
htmlelement: HTMLElement,
labels_node_list: MutNullableDom<NodeList>,
}

impl HTMLMeterElement {
Expand All @@ -27,6 +28,7 @@ impl HTMLMeterElement {
) -> HTMLMeterElement {
HTMLMeterElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
labels_node_list: MutNullableDom::new(None),
}
}

Expand All @@ -48,7 +50,5 @@ impl HTMLMeterElement {

impl HTMLMeterElementMethods for HTMLMeterElement {
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
fn Labels(&self) -> DomRoot<NodeList> {
self.upcast::<HTMLElement>().labels()
}
make_labels_getter!(Labels, labels_node_list);
}
6 changes: 3 additions & 3 deletions components/script/dom/htmloutputelement.rs
Expand Up @@ -22,6 +22,7 @@ use html5ever::{LocalName, Prefix};
pub struct HTMLOutputElement {
htmlelement: HTMLElement,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
}

impl HTMLOutputElement {
Expand All @@ -33,6 +34,7 @@ impl HTMLOutputElement {
HTMLOutputElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
form_owner: Default::default(),
labels_node_list: Default::default(),
}
}

Expand Down Expand Up @@ -65,9 +67,7 @@ impl HTMLOutputElementMethods for HTMLOutputElement {
}

// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
fn Labels(&self) -> DomRoot<NodeList> {
self.upcast::<HTMLElement>().labels()
}
make_labels_getter!(Labels, labels_node_list);
}

impl VirtualMethods for HTMLOutputElement {
Expand Down
8 changes: 4 additions & 4 deletions components/script/dom/htmlprogresselement.rs
Expand Up @@ -6,7 +6,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLProgressElementBinding::{
self, HTMLProgressElementMethods,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::document::Document;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::node::Node;
Expand All @@ -17,6 +17,7 @@ use html5ever::{LocalName, Prefix};
#[dom_struct]
pub struct HTMLProgressElement {
htmlelement: HTMLElement,
labels_node_list: MutNullableDom<NodeList>,
}

impl HTMLProgressElement {
Expand All @@ -27,6 +28,7 @@ impl HTMLProgressElement {
) -> HTMLProgressElement {
HTMLProgressElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
labels_node_list: MutNullableDom::new(None),
}
}

Expand All @@ -48,7 +50,5 @@ impl HTMLProgressElement {

impl HTMLProgressElementMethods for HTMLProgressElement {
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
fn Labels(&self) -> DomRoot<NodeList> {
self.upcast::<HTMLElement>().labels()
}
make_labels_getter!(Labels, labels_node_list);
}
6 changes: 3 additions & 3 deletions components/script/dom/htmlselectelement.rs
Expand Up @@ -61,6 +61,7 @@ pub struct HTMLSelectElement {
htmlelement: HTMLElement,
options: MutNullableDom<HTMLOptionsCollection>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
}

static DEFAULT_SELECT_SIZE: u32 = 0;
Expand All @@ -80,6 +81,7 @@ impl HTMLSelectElement {
),
options: Default::default(),
form_owner: Default::default(),
labels_node_list: Default::default(),
}
}

Expand Down Expand Up @@ -249,9 +251,7 @@ impl HTMLSelectElementMethods for HTMLSelectElement {
}

// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
fn Labels(&self) -> DomRoot<NodeList> {
self.upcast::<HTMLElement>().labels()
}
make_labels_getter!(Labels, labels_node_list);

// https://html.spec.whatwg.org/multipage/#dom-select-options
fn Options(&self) -> DomRoot<HTMLOptionsCollection> {
Expand Down
6 changes: 3 additions & 3 deletions components/script/dom/htmltextareaelement.rs
Expand Up @@ -52,6 +52,7 @@ pub struct HTMLTextAreaElement {
// https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
value_dirty: Cell<bool>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
}

pub trait LayoutHTMLTextAreaElementHelpers {
Expand Down Expand Up @@ -153,6 +154,7 @@ impl HTMLTextAreaElement {
)),
value_dirty: Cell::new(false),
form_owner: Default::default(),
labels_node_list: Default::default(),
}
}

Expand Down Expand Up @@ -316,9 +318,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement {
}

// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
fn Labels(&self) -> DomRoot<NodeList> {
self.upcast::<HTMLElement>().labels()
}
make_labels_getter!(Labels, labels_node_list);

// https://html.spec.whatwg.org/multipage/#dom-textarea/input-select
fn Select(&self) {
Expand Down

0 comments on commit 036e8da

Please sign in to comment.