author | Reuben Morais <reuben.morais@gmail.com> |
Mon, 02 Sep 2013 14:23:27 -0300 | |
changeset 145240 | 499f3c4fbd98f5440ed73288ca8c0153480370a0 |
parent 145239 | 24ffdfbf55d8d8abf8257a4ad9acdb6f0232f5e9 |
child 145241 | 1604454f8d214b06225e7118a6864064131bfedb |
push id | 33208 |
push user | reuben.morais@gmail.com |
push date | Mon, 02 Sep 2013 17:23:46 +0000 |
treeherder | mozilla-inbound@499f3c4fbd98 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | smaug |
bugs | 596681 |
milestone | 26.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/content/base/src/nsContentList.h +++ b/content/base/src/nsContentList.h @@ -294,16 +294,26 @@ public: NS_PRECONDITION(mXMLMatchAtom, "How did we get here with a null match atom on our list?"); return mXMLMatchAtom->Equals(aKey.mTagname) && mRootNode == aKey.mRootNode && mMatchNameSpaceId == aKey.mMatchNameSpaceId; } + /** + * Sets the state to LIST_DIRTY and clears mElements array. + * @note This is the only acceptable way to set state to LIST_DIRTY. + */ + void SetDirty() + { + mState = LIST_DIRTY; + Reset(); + } + protected: /** * Returns whether the element matches our criterion * * @param aElement the element to attempt to match * @return whether we match */ bool Match(mozilla::dom::Element *aElement); @@ -346,26 +356,16 @@ protected: void RemoveFromHashtable(); /** * If state is not LIST_UP_TO_DATE, fully populate ourselves with * all the nodes we can find. */ inline void BringSelfUpToDate(bool aDoFlush); /** - * Sets the state to LIST_DIRTY and clears mElements array. - * @note This is the only acceptable way to set state to LIST_DIRTY. - */ - void SetDirty() - { - mState = LIST_DIRTY; - Reset(); - } - - /** * To be called from non-destructor locations that want to remove from caches. * Needed because if subclasses want to have cache behavior they can't just * override RemoveFromHashtable(), since we call that in our destructor. */ virtual void RemoveFromCaches() { RemoveFromHashtable(); }
--- a/content/html/content/src/HTMLSelectElement.cpp +++ b/content/html/content/src/HTMLSelectElement.cpp @@ -8,16 +8,17 @@ #include "mozAutoDocUpdate.h" #include "mozilla/Attributes.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLOptGroupElement.h" #include "mozilla/dom/HTMLOptionElement.h" #include "mozilla/dom/HTMLSelectElementBinding.h" #include "mozilla/Util.h" #include "nsContentCreatorFunctions.h" +#include "nsContentList.h" #include "nsError.h" #include "nsEventDispatcher.h" #include "nsEventStates.h" #include "nsFormSubmission.h" #include "nsGkAtoms.h" #include "nsGUIEvent.h" #include "nsIComboboxControlFrame.h" #include "nsIDocument.h" @@ -134,20 +135,22 @@ HTMLSelectElement::~HTMLSelectElement() // ISupports NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSelectElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLSelectElement, nsGenericHTMLFormElementWithState) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOptions) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedOptions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLSelectElement, nsGenericHTMLFormElementWithState) NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedOptions) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_ADDREF_INHERITED(HTMLSelectElement, Element) NS_IMPL_RELEASE_INHERITED(HTMLSelectElement, Element) // QueryInterface implementation for HTMLSelectElement NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLSelectElement) NS_INTERFACE_TABLE_INHERITED2(HTMLSelectElement, @@ -764,16 +767,44 @@ HTMLSelectElement::SetLength(uint32_t aL return; } MOZ_ASSERT(node); } } } } +/* static */ +bool +HTMLSelectElement::MatchSelectedOptions(nsIContent* aContent, + int32_t /* unused */, + nsIAtom* /* unused */, + void* /* unused*/) +{ + HTMLOptionElement* option = HTMLOptionElement::FromContent(aContent); + return option && option->Selected(); +} + +nsIHTMLCollection* +HTMLSelectElement::SelectedOptions() +{ + if (!mSelectedOptions) { + mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr, + nullptr, /* deep */ true); + } + return mSelectedOptions; +} + +NS_IMETHODIMP +HTMLSelectElement::GetSelectedOptions(nsIDOMHTMLCollection** aSelectedOptions) +{ + NS_ADDREF(*aSelectedOptions = SelectedOptions()); + return NS_OK; +} + //NS_IMPL_INT_ATTR(HTMLSelectElement, SelectedIndex, selectedindex) NS_IMETHODIMP HTMLSelectElement::GetSelectedIndex(int32_t* aValue) { *aValue = SelectedIndex(); return NS_OK; @@ -842,16 +873,17 @@ HTMLSelectElement::OnOptionSelected(nsIS } } // Let the frame know too if (aSelectFrame) { aSelectFrame->OnOptionSelected(aIndex, aSelected); } + UpdateSelectedOptions(); UpdateValueMissingValidityState(); UpdateState(aNotify); } void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) { mSelectedIndex = -1; @@ -1843,24 +1875,34 @@ HTMLSelectElement::FieldSetDisabledChang void HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify) { if (!mDefaultSelectionSet) { return; } + UpdateSelectedOptions(); + bool previousSelectionChangedValue = mSelectionHasChanged; mSelectionHasChanged = aValue; if (mSelectionHasChanged != previousSelectionChangedValue) { UpdateState(aNotify); } } +void +HTMLSelectElement::UpdateSelectedOptions() +{ + if (mSelectedOptions) { + mSelectedOptions->SetDirty(); + } +} + JSObject* HTMLSelectElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aScope) { return HTMLSelectElementBinding::Wrap(aCx, aScope, this); } } // namespace dom } // namespace mozilla
--- a/content/html/content/src/HTMLSelectElement.h +++ b/content/html/content/src/HTMLSelectElement.h @@ -13,17 +13,19 @@ #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/HTMLOptionsCollection.h" #include "mozilla/ErrorResult.h" #include "nsCheapSets.h" #include "nsCOMPtr.h" #include "nsError.h" #include "mozilla/dom/HTMLFormElement.h" +class nsContentList; class nsIDOMHTMLOptionElement; +class nsIHTMLCollection; class nsISelectControlFrame; class nsPresState; namespace mozilla { namespace dom { class HTMLSelectElement; @@ -203,16 +205,21 @@ public: ErrorResult& aRv); // Uses XPCOM Remove. void IndexedSetter(uint32_t aIndex, HTMLOptionElement* aOption, ErrorResult& aRv) { mOptions->IndexedSetter(aIndex, aOption, aRv); } + static bool MatchSelectedOptions(nsIContent* aContent, int32_t, nsIAtom*, + void*); + + nsIHTMLCollection* SelectedOptions(); + int32_t SelectedIndex() const { return mSelectedIndex; } void SetSelectedIndex(int32_t aIdx, ErrorResult& aRv) { aRv = SetSelectedIndexInternal(aIdx, true); } @@ -549,16 +556,22 @@ protected: void VerifyOptionsArray(); #endif nsresult SetSelectedIndexInternal(int32_t aIndex, bool aNotify); void SetSelectionChanged(bool aValue, bool aNotify); /** + * Marks the selectedOptions list as dirty, so that it'll populate itself + * again. + */ + void UpdateSelectedOptions(); + + /** * Return whether an element should have a validity UI. * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes). * * @return Whether the element should have a validity UI. */ bool ShouldShowValidityUI() const { /** * Always show the validity UI if the form has already tried to be submitted @@ -613,14 +626,19 @@ protected: * index if multiple are selected) */ int32_t mSelectedIndex; /** * The temporary restore state in case we try to restore before parser is * done adding options */ nsCOMPtr<SelectState> mRestoreState; + + /** + * The live list of selected options. + */ + nsRefPtr<nsContentList> mSelectedOptions; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_HTMLSelectElement_h
--- a/content/html/content/test/forms/Makefile.in +++ b/content/html/content/test/forms/Makefile.in @@ -62,12 +62,13 @@ MOCHITEST_FILES = \ test_input_event.html \ test_input_number_rounding.html \ test_valueAsDate_pref.html \ test_input_color_picker_initial.html \ test_input_color_picker_update.html \ test_input_color_picker_popup.html \ test_input_color_input_change_events.html \ test_restore_form_elements.html \ + test_select_selectedOptions.html \ $(NULL) include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/content/html/content/test/forms/test_select_selectedOptions.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=596681 +--> +<head> + <title>Test for HTMLSelectElement.selectedOptions</title> + <script type="application/javascript" src="/MochiKit/packed.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=596681">Mozilla Bug 596681</a> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for HTMLSelectElement's selectedOptions attribute. + * + * selectedOptions is a live list of the options that have selectedness of true + * (not the selected content attribute). + * + * See http://www.whatwg.org/html/#dom-select-selectedoptions + **/ + +function checkSelectedOptions(size, elements) +{ + is(selectedOptions.length, size, + "select should have " + size + " selected options"); + for (let i = 0; i < size; ++i) { + ok(selectedOptions[i], "selected option is valid"); + if (selectedOptions[i]) { + is(selectedOptions[i].value, elements[i].value, "selected options are correct"); + } + } +} + +let select = document.createElement("select"); +document.body.appendChild(select); +let selectedOptions = select.selectedOptions; + +ok("selectedOptions" in select, + "select element should have a selectedOptions IDL attribute"); + +ok(select.selectedOptions instanceof HTMLCollection, + "selectedOptions should be an HTMLCollection instance"); + +let option1 = document.createElement("option"); +let option2 = document.createElement("option"); +let option3 = document.createElement("option"); +option1.id = "option1"; +option1.value = "option1"; +option2.value = "option2"; +option3.value = "option3"; + +checkSelectedOptions(0, null); + +select.add(option1, null); +is(selectedOptions.namedItem("option1").value, "option1", "named getter works"); +checkSelectedOptions(1, [option1]); + +select.add(option2, null); +checkSelectedOptions(1, [option1]); + +select.options[1].selected = true; +checkSelectedOptions(1, [option2]); + +select.multiple = true; +checkSelectedOptions(1, [option2]); + +select.options[0].selected = true; +checkSelectedOptions(2, [option1, option2]); + +option1.selected = false; +// Usinig selected directly on the option should work. +checkSelectedOptions(1, [option2]); + +select.remove(1); +select.add(option2, 0); +select.options[0].selected = true; +select.options[1].selected = true; +// Should be in tree order. +checkSelectedOptions(2, [option2, option1]); + +select.add(option3, null); +checkSelectedOptions(2, [option2, option1]); + +select.options[2].selected = true; +checkSelectedOptions(3, [option2, option1, option3]); + +select.length = 0; +option1.selected = false; +option2.selected = false; +option3.selected = false; +var optgroup1 = document.createElement("optgroup"); +optgroup1.appendChild(option1); +optgroup1.appendChild(option2); +select.add(optgroup1) +var optgroup2 = document.createElement("optgroup"); +optgroup2.appendChild(option3); +select.add(optgroup2); + +checkSelectedOptions(0, null); + +option2.selected = true; +checkSelectedOptions(1, [option2]); + +option3.selected = true; +checkSelectedOptions(2, [option2, option3]); + +optgroup1.removeChild(option2); +checkSelectedOptions(1, [option3]); + +document.body.removeChild(select); +option1.selected = true; +checkSelectedOptions(2, [option1, option3]); +</script> +</pre> +</body> +</html>
--- a/dom/interfaces/html/nsIDOMHTMLSelectElement.idl +++ b/dom/interfaces/html/nsIDOMHTMLSelectElement.idl @@ -14,17 +14,17 @@ * http://www.w3.org/TR/DOM-Level-2-HTML/ * * with changes from the work-in-progress WHATWG HTML specification: * http://www.whatwg.org/specs/web-apps/current-work/ */ interface nsIDOMValidityState; -[scriptable, uuid(846578b2-6d4f-4399-86cc-2c05f19469d0)] +[scriptable, uuid(d8914a2d-3556-4b66-911c-a84c4394e7fa)] interface nsIDOMHTMLSelectElement : nsISupports { attribute boolean autofocus; attribute boolean disabled; readonly attribute nsIDOMHTMLFormElement form; attribute boolean multiple; attribute DOMString name; attribute unsigned long size; @@ -39,16 +39,17 @@ interface nsIDOMHTMLSelectElement : nsIS // since IDL doesn't support overfload. // void add(in nsIDOMHTMLElement, [optional] in nsIDOMHTMLElement) // void add(in nsIDOMHTMLElement, in long) void add(in nsIDOMHTMLElement element, [optional] in nsIVariant before) raises(DOMException); void remove(in long index); + readonly attribute nsIDOMHTMLCollection selectedOptions; attribute long selectedIndex; attribute DOMString value; // The following lines are part of the constraint validation API, see: // http://www.whatwg.org/specs/web-apps/current-work/#the-constraint-validation-api readonly attribute boolean willValidate; readonly attribute nsIDOMValidityState validity; readonly attribute DOMString validationMessage;
--- a/dom/webidl/HTMLSelectElement.webidl +++ b/dom/webidl/HTMLSelectElement.webidl @@ -33,17 +33,17 @@ interface HTMLSelectElement : HTMLElemen getter Element? item(unsigned long index); HTMLOptionElement? namedItem(DOMString name); [Throws] void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); void remove(long index); [Throws] setter creator void (unsigned long index, HTMLOptionElement? option); -// NYI: readonly attribute HTMLCollection selectedOptions; + readonly attribute HTMLCollection selectedOptions; [SetterThrows, Pure] attribute long selectedIndex; [Pure] attribute DOMString value; readonly attribute boolean willValidate; readonly attribute ValidityState validity; readonly attribute DOMString validationMessage;