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;