Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ask_for_reset for HTMLSelectElement. #7963

Merged
merged 11 commits into from Oct 28, 2015
@@ -14,6 +14,7 @@ use dom::document::Document;
use dom::element::{AttributeMutation, Element, IN_ENABLED_STATE};
use dom::htmlelement::HTMLElement;
use dom::htmlscriptelement::HTMLScriptElement;
use dom::htmlselectelement::HTMLSelectElement;
use dom::node::Node;
use dom::text::Text;
use dom::virtualmethods::VirtualMethods;
@@ -51,6 +52,21 @@ impl HTMLOptionElement {
let element = HTMLOptionElement::new_inherited(localName, prefix, document);
Node::reflect_node(box element, document, HTMLOptionElementBinding::Wrap)
}

pub fn set_selectedness(&self, selected: bool) {
self.selectedness.set(selected);
}

fn pick_if_selected_and_reset(&self) {
if let Some(select) = self.upcast::<Node>().ancestors()
.filter_map(Root::downcast::<HTMLSelectElement>)
.next() {
if self.Selected() {
select.pick_option(self);
}
select.ask_for_reset();
}
}
}

fn collect_text(element: &Element, value: &mut DOMString) {
@@ -134,8 +150,7 @@ impl HTMLOptionElementMethods for HTMLOptionElement {
fn SetSelected(&self, selected: bool) {
self.dirtiness.set(true);
self.selectedness.set(selected);
// FIXME: as per the spec, implement 'ask for a reset'
// https://github.com/servo/servo/issues/7774
self.pick_if_selected_and_reset();
}
}

@@ -187,6 +202,8 @@ impl VirtualMethods for HTMLOptionElement {
}

self.upcast::<Element>().check_parent_disabled_state_for_option();

self.pick_if_selected_and_reset();
}

fn unbind_from_tree(&self, tree_in_doc: bool) {
@@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::attr::{Attr, AttrValue};
use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
use dom::bindings::codegen::Bindings::HTMLSelectElementBinding;
use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
use dom::bindings::codegen::UnionTypes::HTMLElementOrLong;
@@ -14,6 +15,7 @@ use dom::element::{AttributeMutation, Element, IN_ENABLED_STATE};
use dom::htmlelement::HTMLElement;
use dom::htmlfieldsetelement::HTMLFieldSetElement;
use dom::htmlformelement::{FormControl, HTMLFormElement};
use dom::htmloptionelement::HTMLOptionElement;
use dom::node::{Node, window_from_node};
use dom::validitystate::ValidityState;
use dom::virtualmethods::VirtualMethods;
@@ -46,6 +48,64 @@ impl HTMLSelectElement {
let element = HTMLSelectElement::new_inherited(localName, prefix, document);
Node::reflect_node(box element, document, HTMLSelectElementBinding::Wrap)
}

// https://html.spec.whatwg.org/multipage/#ask-for-a-reset
pub fn ask_for_reset(&self) {
if self.Multiple() {
return;
}

let mut first_enabled: Option<Root<HTMLOptionElement>> = None;
let mut last_selected: Option<Root<HTMLOptionElement>> = None;

let node = self.upcast::<Node>();
for opt in node.traverse_preorder().filter_map(Root::downcast::<HTMLOptionElement>) {
if opt.Selected() {
opt.set_selectedness(false);
last_selected = Some(Root::from_ref(opt.r()));
}
let element = opt.upcast::<Element>();
if first_enabled.is_none() && !element.get_disabled_state() {
first_enabled = Some(Root::from_ref(opt.r()));
}
}

if let Some(last_selected) = last_selected {
last_selected.set_selectedness(true);
} else {
if self.display_size() == 1 {
if let Some(first_enabled) = first_enabled {
first_enabled.set_selectedness(true);
}
}
}
}

// https://html.spec.whatwg.org/multipage/#concept-select-pick
pub fn pick_option(&self, picked: &HTMLOptionElement) {
if !self.Multiple() {
let node = self.upcast::<Node>();
let picked = picked.upcast();
for opt in node.traverse_preorder().filter_map(Root::downcast::<HTMLOptionElement>) {
if opt.upcast::<HTMLElement>() != picked {
opt.set_selectedness(false);
}
}
}
}

// https://html.spec.whatwg.org/multipage/#concept-select-size
fn display_size(&self) -> u32 {
if self.Size() == 0 {
if self.Multiple() {
4
} else {
1
}
} else {
self.Size()
}
}
}

impl HTMLSelectElementMethods for HTMLSelectElement {
"path": "html/semantics/forms/the-select-element/select-remove.html",
"url": "/html/semantics/forms/the-select-element/select-remove.html"
},
{
"path": "html/semantics/forms/the-select-element/select-ask-for-reset.html",
"url": "/html/semantics/forms/the-select-element/select-ask-for-reset.html"
},
{
"path": "html/semantics/forms/the-textarea-element/textarea-type.html",
"url": "/html/semantics/forms/the-textarea-element/textarea-type.html"
@@ -0,0 +1,4 @@
[select-ask-for-reset.html]

This comment has been minimized.

Copy link
@frewsxcv

frewsxcv Oct 25, 2015

Member

Is there any reason why this test is marked as failing?

This comment has been minimized.

Copy link
@jdm

jdm Oct 25, 2015

Member

See first paragraph of previous comment.

type: testharness
[ask for reset on node remove, non multiple.]
expected: FAIL
@@ -0,0 +1,97 @@
<!doctype html>
<meta charset=utf-8>
<title>HTMLSelectElement ask for reset</title>
<link rel="author" title="Dongie Agnir" href="dongie.agnir@gmail.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id=log></div>
<script>
test(function() {
var select = makeSelect(5);

select.children[4].selected = true;
unselectedExcept(select, 4);

select.children[4].remove();
unselectedExcept(select, 0); // remove selected node, should default to first

select.children[3].selected = true;

select.children[0].remove();
unselectedExcept(select, 2); // last node still selected

select.size = 2;
select.children[2].remove();

unselectedExcept(select, null);
}, "ask for reset on node remove, non multiple.");

test(function() {
var select = makeSelect(3);
select.children[1].selected = true;

// insert selected option, should remain selected
var opt4 = document.createElement("option");
opt4.selected = true;
select.appendChild(opt4);
unselectedExcept(select, 3);

// insert unselected, 3 should remain selected
var opt5 = document.createElement("option");
select.appendChild(opt5);
unselectedExcept(select, 3);
}, "ask for reset on node insert, non multiple.");

test(function() {
var select = makeSelect(3);

var options = select.children;

// select options from first to last
for (var i = 0; i < options.length; ++i) {
options[i].selected = true;
unselectedExcept(select, i);
}

// select options from last to first
for (var i = options.length - 1; i >= 0; --i) {
options[i].selected = true;
unselectedExcept(select, i);
}

options[2].selected = true;
options[2].selected = false; // none selected
unselectedExcept(select, 0);

// disable first so option at index 1 is first eligible
options[0].disabled = true;
options[2].selected = true;
options[2].selected = false; // none selected
unselectedExcept(select, 1);

select.size = 2;
options[1].selected = false;
unselectedExcept(select, null); // size > 1 so should not default to any
}, "change selectedness of option, non multiple.");


function unselectedExcept(sel, opt) {
for (var i = 0; i < sel.children.length; ++i) {
if (i != opt) {
assert_false(sel.children[i].selected, "option should not be selected.");
}
if (opt != null) {
assert_true(sel.children[opt].selected, "option should be selected.");
}
}
}

function makeSelect(n) {
var sel = document.createElement("select");
for (var i = 0; i < n; ++i) {
opt = document.createElement("option");
sel.appendChild(opt);
}
return sel;
}
</script>
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.