diff --git a/components/script/dom/htmlcollection.rs b/components/script/dom/htmlcollection.rs index 2f2265907193..66a810eff9f7 100644 --- a/components/script/dom/htmlcollection.rs +++ b/components/script/dom/htmlcollection.rs @@ -229,6 +229,9 @@ impl HTMLCollection { } } + pub fn root_node(&self) -> Root { + Root::from_ref(&self.root) + } } // TODO: Make this generic, and avoid code duplication diff --git a/components/script/dom/htmloptionscollection.rs b/components/script/dom/htmloptionscollection.rs new file mode 100644 index 000000000000..dd6d45319746 --- /dev/null +++ b/components/script/dom/htmloptionscollection.rs @@ -0,0 +1,186 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; +use dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding; +use dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods; +use dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use dom::bindings::codegen::UnionTypes::{HTMLOptionElementOrHTMLOptGroupElement, HTMLElementOrLong}; +use dom::bindings::error::{Error, ErrorResult}; +use dom::bindings::global::GlobalRef; +use dom::bindings::inheritance::Castable; +use dom::bindings::js::{Root, RootedReference}; +use dom::bindings::reflector::reflect_dom_object; +use dom::bindings::str::DOMString; +use dom::element::Element; +use dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use dom::htmloptionelement::HTMLOptionElement; +use dom::node::{document_from_node, Node}; +use dom::window::Window; + +#[dom_struct] +pub struct HTMLOptionsCollection { + collection: HTMLCollection, +} + +impl HTMLOptionsCollection { + fn new_inherited(root: &Node, filter: Box) -> HTMLOptionsCollection { + HTMLOptionsCollection { + collection: HTMLCollection::new_inherited(root, filter), + } + } + + pub fn new(window: &Window, root: &Node, filter: Box) + -> Root + { + reflect_dom_object(box HTMLOptionsCollection::new_inherited(root, filter), + GlobalRef::Window(window), + HTMLOptionsCollectionBinding::Wrap) + } + + fn add_new_elements(&self, count: u32) -> ErrorResult { + let root = self.upcast().root_node(); + let document = document_from_node(root.r()); + + for _ in 0..count { + let element = HTMLOptionElement::new(atom!("option"), None, document.r()); + let node = element.upcast::(); + try!(root.AppendChild(node)); + }; + Ok(()) + } +} + +impl HTMLOptionsCollectionMethods for HTMLOptionsCollection { + // FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of + // HTMLOptionsCollection) implements NamedGetter. + // https://github.com/servo/servo/issues/5875 + // + // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem + fn NamedGetter(&self, name: DOMString) -> Option> { + self.upcast().NamedItem(name) + } + + // https://heycam.github.io/webidl/#dfn-supported-property-names + fn SupportedPropertyNames(&self) -> Vec { + self.upcast().SupportedPropertyNames() + } + + // FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of + // HTMLOptionsCollection) implements IndexedGetter. + // https://github.com/servo/servo/issues/5875 + // + // https://dom.spec.whatwg.org/#dom-htmlcollection-item + fn IndexedGetter(&self, index: u32) -> Option> { + self.upcast().IndexedGetter(index) + } + + // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-setter + fn IndexedSetter(&self, index: u32, value: Option<&HTMLOptionElement>) -> ErrorResult { + if let Some(value) = value { + // Step 2 + let length = self.upcast().Length(); + + // Step 3 + let n = index as i32 - length as i32; + + // Step 4 + if n > 0 { + try!(self.add_new_elements(n as u32)); + } + + // Step 5 + let node = value.upcast::(); + let root = self.upcast().root_node(); + if n >= 0 { + Node::pre_insert(node, root.r(), None).map(|_| ()) + } else { + let child = self.upcast().IndexedGetter(index).unwrap(); + let child_node = child.r().upcast::(); + + root.r().ReplaceChild(node, child_node).map(|_| ()) + } + } else { + // Step 1 + self.Remove(index as i32); + Ok(()) + } + } + + // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length + fn Length(&self) -> u32 { + self.upcast().Length() + } + + // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length + fn SetLength(&self, length: u32) { + let current_length = self.upcast().Length(); + let delta = length as i32 - current_length as i32; + if delta < 0 { + // new length is lower - deleting last option elements + for index in (length..current_length).rev() { + self.Remove(index as i32) + } + } else if delta > 0 { + // new length is higher - adding new option elements + self.add_new_elements(delta as u32).unwrap(); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-add + fn Add(&self, element: HTMLOptionElementOrHTMLOptGroupElement, before: Option) -> ErrorResult { + let root = self.upcast().root_node(); + + let node: &Node = match element { + HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElement(ref element) => element.upcast(), + HTMLOptionElementOrHTMLOptGroupElement::HTMLOptGroupElement(ref element) => element.upcast(), + }; + + // Step 1 + if node.is_ancestor_of(root.r()) { + return Err(Error::HierarchyRequest); + } + + if let Some(HTMLElementOrLong::HTMLElement(ref before_element)) = before { + // Step 2 + let before_node = before_element.upcast::(); + if !root.r().is_ancestor_of(before_node) { + return Err(Error::NotFound); + } + + // Step 3 + if node == before_node { + return Ok(()); + } + } + + // Step 4 + let reference_node = before.and_then(|before| { + match before { + HTMLElementOrLong::HTMLElement(element) => Some(Root::upcast::(element)), + HTMLElementOrLong::Long(index) => { + self.upcast().IndexedGetter(index as u32).map(Root::upcast::) + } + } + }); + + // Step 5 + let parent = if let Some(reference_node) = reference_node.r() { + reference_node.GetParentNode().unwrap() + } else { + root + }; + + // Step 6 + Node::pre_insert(node, parent.r(), reference_node.r()).map(|_| ()) + } + + // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-remove + fn Remove(&self, index: i32) { + if let Some(element) = self.upcast().IndexedGetter(index as u32) { + element.r().Remove(); + } + } +} diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 6cf63c7e6301..7dc89cc610ee 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -3,22 +3,29 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::attr::Attr; +use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; +use dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods; use dom::bindings::codegen::Bindings::HTMLSelectElementBinding; use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::UnionTypes::HTMLElementOrLong; use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement; +//use dom::bindings::error::ErrorResult; use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; +use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::bindings::str::DOMString; use dom::document::Document; use dom::element::{AttributeMutation, Element}; +use dom::htmlcollection::CollectionFilter; use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformelement::{FormDatumValue, FormControl, FormDatum, HTMLFormElement}; +use dom::htmloptgroupelement::HTMLOptGroupElement; use dom::htmloptionelement::HTMLOptionElement; -use dom::node::{document_from_node, Node, UnbindContext, window_from_node}; +use dom::htmloptionscollection::HTMLOptionsCollection; +use dom::node::{Node, UnbindContext, window_from_node}; use dom::nodelist::NodeList; use dom::validation::Validatable; use dom::validitystate::ValidityState; @@ -27,9 +34,31 @@ use string_cache::Atom; use style::attr::AttrValue; use style::element_state::*; +#[derive(JSTraceable, HeapSizeOf)] +struct OptionsFilter; +impl CollectionFilter for OptionsFilter { + fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool { + if !elem.is::() { + return false; + } + + let node = elem.upcast::(); + if root.is_parent_of(node) { + return true; + } + + match node.GetParentNode() { + Some(optgroup) => + optgroup.is::() && root.is_parent_of(optgroup.r()), + None => false, + } + } +} + #[dom_struct] pub struct HTMLSelectElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + options: MutNullableHeap>, } static DEFAULT_SELECT_SIZE: u32 = 0; @@ -41,7 +70,8 @@ impl HTMLSelectElement { HTMLSelectElement { htmlelement: HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, - local_name, prefix, document) + local_name, prefix, document), + options: Default::default() } } @@ -185,48 +215,48 @@ impl HTMLSelectElementMethods for HTMLSelectElement { self.upcast::().labels() } - // https://html.spec.whatwg.org/multipage/#dom-select-length - fn SetLength(&self, value: u32) { - let length = self.Length(); - let node = self.upcast::(); - if value < length { // truncate the number of option elements - let mut iter = node.rev_children().take((length - value) as usize); - while let Some(child) = iter.next() { - if let Err(e) = node.RemoveChild(&child) { - warn!("Error removing child of HTMLSelectElement: {:?}", e); - } - } - } else if value > length { // add new blank option elements - let document = document_from_node(self); - for _ in 0..(value - length) { - let element = HTMLOptionElement::new(atom!("option"), None, &document.upcast()); - if let Err(e) = node.AppendChild(element.upcast()) { - warn!("error appending child of HTMLSelectElement: {:?}", e); - } - } - } + // https://html.spec.whatwg.org/multipage/#dom-select-options + fn Options(&self) -> Root { + self.options.or_init(|| { + let window = window_from_node(self); + HTMLOptionsCollection::new(window.r(), + self.upcast(), box OptionsFilter) + }) } // https://html.spec.whatwg.org/multipage/#dom-select-length fn Length(&self) -> u32 { - self.upcast::() - .traverse_preorder() - .filter_map(Root::downcast::) - .count() as u32 + self.Options().Length() + } + + // https://html.spec.whatwg.org/multipage/#dom-select-length + fn SetLength(&self, length: u32) { + self.Options().SetLength(length) } // https://html.spec.whatwg.org/multipage/#dom-select-item fn Item(&self, index: u32) -> Option> { - self.upcast::() - .traverse_preorder() - .filter_map(Root::downcast::) - .nth(index as usize) - .map(|item| Root::from_ref(item.upcast())) + self.Options().upcast().Item(index) } // https://html.spec.whatwg.org/multipage/#dom-select-item fn IndexedGetter(&self, index: u32) -> Option> { - self.Item(index) + self.Options().IndexedGetter(index) + } + + // https://html.spec.whatwg.org/multipage/#dom-select-nameditem + fn NamedItem(&self, name: DOMString) -> Option> { + self.Options().NamedGetter(name).map_or(None, |e| Root::downcast::(e)) + } + + // https://html.spec.whatwg.org/multipage/#dom-select-remove + fn Remove_(&self, index: i32) { + self.Options().Remove(index) + } + + // https://html.spec.whatwg.org/multipage/#dom-select-remove + fn Remove(&self) { + self.upcast::().Remove() } } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 24951c0c075f..0081efba21c0 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -324,6 +324,7 @@ pub mod htmlobjectelement; pub mod htmlolistelement; pub mod htmloptgroupelement; pub mod htmloptionelement; +pub mod htmloptionscollection; pub mod htmloutputelement; pub mod htmlparagraphelement; pub mod htmlparamelement; diff --git a/components/script/dom/webidls/HTMLOptionsCollection.webidl b/components/script/dom/webidls/HTMLOptionsCollection.webidl new file mode 100644 index 000000000000..0451647b9812 --- /dev/null +++ b/components/script/dom/webidls/HTMLOptionsCollection.webidl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// https://html.spec.whatwg.org/multipage/#htmloptionscollection +[Exposed=(Window,Worker)] +interface HTMLOptionsCollection : HTMLCollection { + // inherits item(), namedItem() + attribute unsigned long length; // shadows inherited length + //[CEReactions] + [Throws] + setter void (unsigned long index, HTMLOptionElement? option); + //[CEReactions] + [Throws] + void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); + //[CEReactions] + void remove(long index); + //attribute long selectedIndex; +}; diff --git a/components/script/dom/webidls/HTMLSelectElement.webidl b/components/script/dom/webidls/HTMLSelectElement.webidl index b513806f9659..027eea6362c0 100644 --- a/components/script/dom/webidls/HTMLSelectElement.webidl +++ b/components/script/dom/webidls/HTMLSelectElement.webidl @@ -14,14 +14,14 @@ interface HTMLSelectElement : HTMLElement { readonly attribute DOMString type; - //readonly attribute HTMLOptionsCollection options; + readonly attribute HTMLOptionsCollection options; attribute unsigned long length; getter Element? item(unsigned long index); - //HTMLOptionElement? namedItem(DOMString name); + HTMLOptionElement? namedItem(DOMString name); // Note: this function currently only exists for union.html. void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); - //void remove(); // ChildNode overload - //void remove(long index); + void remove(); // ChildNode overload + void remove(long index); //setter void (unsigned long index, HTMLOptionElement? option); //readonly attribute HTMLCollection selectedOptions;