1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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/. */
- #include "mozilla/dom/HTMLSelectElement.h"
- #include "mozAutoDocUpdate.h"
- #include "mozilla/Attributes.h"
- #include "mozilla/BasicEvents.h"
- #include "mozilla/EventDispatcher.h"
- #include "mozilla/EventStates.h"
- #include "mozilla/dom/Element.h"
- #include "mozilla/dom/HTMLFormSubmission.h"
- #include "mozilla/dom/HTMLOptGroupElement.h"
- #include "mozilla/dom/HTMLOptionElement.h"
- #include "mozilla/dom/HTMLSelectElementBinding.h"
- #include "mozilla/dom/UnionTypes.h"
- #include "nsContentCreatorFunctions.h"
- #include "nsContentList.h"
- #include "nsError.h"
- #include "nsGkAtoms.h"
- #include "nsIComboboxControlFrame.h"
- #include "nsIDocument.h"
- #include "nsIFormControlFrame.h"
- #include "nsIForm.h"
- #include "nsIFormProcessor.h"
- #include "nsIFrame.h"
- #include "nsIListControlFrame.h"
- #include "nsISelectControlFrame.h"
- #include "nsLayoutUtils.h"
- #include "nsMappedAttributes.h"
- #include "nsPresState.h"
- #include "nsRuleData.h"
- #include "nsServiceManagerUtils.h"
- #include "nsStyleConsts.h"
- #include "nsTextNode.h"
- NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select)
- namespace mozilla {
- namespace dom {
- NS_IMPL_ISUPPORTS(SelectState, SelectState)
- //----------------------------------------------------------------------
- //
- // SafeOptionListMutation
- //
- SafeOptionListMutation::SafeOptionListMutation(nsIContent* aSelect,
- nsIContent* aParent,
- nsIContent* aKid,
- uint32_t aIndex,
- bool aNotify)
- : mSelect(HTMLSelectElement::FromContentOrNull(aSelect))
- , mTopLevelMutation(false)
- , mNeedsRebuild(false)
- {
- if (mSelect) {
- mTopLevelMutation = !mSelect->mMutating;
- if (mTopLevelMutation) {
- mSelect->mMutating = true;
- } else {
- // This is very unfortunate, but to handle mutation events properly,
- // option list must be up-to-date before inserting or removing options.
- // Fortunately this is called only if mutation event listener
- // adds or removes options.
- mSelect->RebuildOptionsArray(aNotify);
- }
- nsresult rv;
- if (aKid) {
- rv = mSelect->WillAddOptions(aKid, aParent, aIndex, aNotify);
- } else {
- rv = mSelect->WillRemoveOptions(aParent, aIndex, aNotify);
- }
- mNeedsRebuild = NS_FAILED(rv);
- }
- }
- SafeOptionListMutation::~SafeOptionListMutation()
- {
- if (mSelect) {
- if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) {
- mSelect->RebuildOptionsArray(true);
- }
- if (mTopLevelMutation) {
- mSelect->mMutating = false;
- }
- #ifdef DEBUG
- mSelect->VerifyOptionsArray();
- #endif
- }
- }
- //----------------------------------------------------------------------
- //
- // HTMLSelectElement
- //
- // construction, destruction
- HTMLSelectElement::HTMLSelectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
- FromParser aFromParser)
- : nsGenericHTMLFormElementWithState(aNodeInfo),
- mOptions(new HTMLOptionsCollection(this)),
- mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
- mIsDoneAddingChildren(!aFromParser),
- mDisabledChanged(false),
- mMutating(false),
- mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
- mSelectionHasChanged(false),
- mDefaultSelectionSet(false),
- mCanShowInvalidUI(true),
- mCanShowValidUI(true),
- mNonOptionChildren(0),
- mOptGroupCount(0),
- mSelectedIndex(-1)
- {
- SetHasWeirdParserInsertionMode();
- // DoneAddingChildren() will be called later if it's from the parser,
- // otherwise it is
- // Set up our default state: enabled, optional, and valid.
- AddStatesSilently(NS_EVENT_STATE_ENABLED |
- NS_EVENT_STATE_OPTIONAL |
- NS_EVENT_STATE_VALID);
- }
- // 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_INHERITED(HTMLSelectElement,
- nsIDOMHTMLSelectElement,
- nsIConstraintValidation)
- NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
- // nsIDOMHTMLSelectElement
- NS_IMPL_ELEMENT_CLONE(HTMLSelectElement)
- // nsIConstraintValidation
- NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLSelectElement)
- NS_IMETHODIMP
- HTMLSelectElement::SetCustomValidity(const nsAString& aError)
- {
- nsIConstraintValidation::SetCustomValidity(aError);
- UpdateState(true);
- return NS_OK;
- }
- void
- HTMLSelectElement::GetAutocomplete(DOMString& aValue)
- {
- const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
- mAutocompleteAttrState =
- nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
- mAutocompleteAttrState);
- }
- NS_IMETHODIMP
- HTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
- {
- return nsGenericHTMLFormElementWithState::GetForm(aForm);
- }
- nsresult
- HTMLSelectElement::InsertChildAt(nsIContent* aKid,
- uint32_t aIndex,
- bool aNotify)
- {
- SafeOptionListMutation safeMutation(this, this, aKid, aIndex, aNotify);
- nsresult rv = nsGenericHTMLFormElementWithState::InsertChildAt(aKid, aIndex,
- aNotify);
- if (NS_FAILED(rv)) {
- safeMutation.MutationFailed();
- }
- return rv;
- }
- void
- HTMLSelectElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
- {
- SafeOptionListMutation safeMutation(this, this, nullptr, aIndex, aNotify);
- nsGenericHTMLFormElementWithState::RemoveChildAt(aIndex, aNotify);
- }
- void
- HTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions,
- int32_t aListIndex,
- int32_t aDepth,
- bool aNotify)
- {
- MOZ_ASSERT(aDepth == 0 || aDepth == 1);
- int32_t insertIndex = aListIndex;
- HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
- if (optElement) {
- mOptions->InsertOptionAt(optElement, insertIndex);
- insertIndex++;
- } else if (aDepth == 0) {
- // If it's at the top level, then we just found out there are non-options
- // at the top level, which will throw off the insert count
- mNonOptionChildren++;
- // Deal with optgroups
- if (aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
- mOptGroupCount++;
- for (nsIContent* child = aOptions->GetFirstChild();
- child;
- child = child->GetNextSibling()) {
- optElement = HTMLOptionElement::FromContent(child);
- if (optElement) {
- mOptions->InsertOptionAt(optElement, insertIndex);
- insertIndex++;
- }
- }
- }
- } // else ignore even if optgroup; we want to ignore nested optgroups.
- // Deal with the selected list
- if (insertIndex - aListIndex) {
- // Fix the currently selected index
- if (aListIndex <= mSelectedIndex) {
- mSelectedIndex += (insertIndex - aListIndex);
- SetSelectionChanged(true, aNotify);
- }
- // Get the frame stuff for notification. No need to flush here
- // since if there's no frame for the select yet the select will
- // get into the right state once it's created.
- nsISelectControlFrame* selectFrame = nullptr;
- nsWeakFrame weakSelectFrame;
- bool didGetFrame = false;
- // Actually select the options if the added options warrant it
- for (int32_t i = aListIndex; i < insertIndex; i++) {
- // Notify the frame that the option is added
- if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
- selectFrame = GetSelectFrame();
- weakSelectFrame = do_QueryFrame(selectFrame);
- didGetFrame = true;
- }
- if (selectFrame) {
- selectFrame->AddOption(i);
- }
- RefPtr<HTMLOptionElement> option = Item(i);
- if (option && option->Selected()) {
- // Clear all other options
- if (!HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
- uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED | NOTIFY;
- SetOptionsSelectedByIndex(i, i, mask);
- }
- // This is sort of a hack ... we need to notify that the option was
- // set and change selectedIndex even though we didn't really change
- // its value.
- OnOptionSelected(selectFrame, i, true, false, false);
- }
- }
- CheckSelectSomething(aNotify);
- }
- }
- nsresult
- HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
- int32_t aListIndex,
- int32_t aDepth,
- bool aNotify)
- {
- MOZ_ASSERT(aDepth == 0 || aDepth == 1);
- int32_t numRemoved = 0;
- HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
- if (optElement) {
- if (mOptions->ItemAsOption(aListIndex) != optElement) {
- NS_ERROR("wrong option at index");
- return NS_ERROR_UNEXPECTED;
- }
- mOptions->RemoveOptionAt(aListIndex);
- numRemoved++;
- } else if (aDepth == 0) {
- // Yay, one less artifact at the top level.
- mNonOptionChildren--;
- // Recurse down deeper for options
- if (mOptGroupCount && aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
- mOptGroupCount--;
- for (nsIContent* child = aOptions->GetFirstChild();
- child;
- child = child->GetNextSibling()) {
- optElement = HTMLOptionElement::FromContent(child);
- if (optElement) {
- if (mOptions->ItemAsOption(aListIndex) != optElement) {
- NS_ERROR("wrong option at index");
- return NS_ERROR_UNEXPECTED;
- }
- mOptions->RemoveOptionAt(aListIndex);
- numRemoved++;
- }
- }
- }
- } // else don't check for an optgroup; we want to ignore nested optgroups
- if (numRemoved) {
- // Tell the widget we removed the options
- nsISelectControlFrame* selectFrame = GetSelectFrame();
- if (selectFrame) {
- nsAutoScriptBlocker scriptBlocker;
- for (int32_t i = aListIndex; i < aListIndex + numRemoved; ++i) {
- selectFrame->RemoveOption(i);
- }
- }
- // Fix the selected index
- if (aListIndex <= mSelectedIndex) {
- if (mSelectedIndex < (aListIndex+numRemoved)) {
- // aListIndex <= mSelectedIndex < aListIndex+numRemoved
- // Find a new selected index if it was one of the ones removed.
- FindSelectedIndex(aListIndex, aNotify);
- } else {
- // Shift the selected index if something in front of it was removed
- // aListIndex+numRemoved <= mSelectedIndex
- mSelectedIndex -= numRemoved;
- SetSelectionChanged(true, aNotify);
- }
- }
- // Select something in case we removed the selected option on a
- // single select
- if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) {
- // Update the validity state in case of we've just removed the last
- // option.
- UpdateValueMissingValidityState();
- UpdateState(aNotify);
- }
- }
- return NS_OK;
- }
- // XXXldb Doing the processing before the content nodes have been added
- // to the document (as the name of this function seems to require, and
- // as the callers do), is highly unusual. Passing around unparented
- // content to other parts of the app can make those things think the
- // options are the root content node.
- NS_IMETHODIMP
- HTMLSelectElement::WillAddOptions(nsIContent* aOptions,
- nsIContent* aParent,
- int32_t aContentIndex,
- bool aNotify)
- {
- if (this != aParent && this != aParent->GetParent()) {
- return NS_OK;
- }
- int32_t level = aParent == this ? 0 : 1;
- // Get the index where the options will be inserted
- int32_t ind = -1;
- if (!mNonOptionChildren) {
- // If there are no artifacts, aContentIndex == ind
- ind = aContentIndex;
- } else {
- // If there are artifacts, we have to get the index of the option the
- // hard way
- int32_t children = aParent->GetChildCount();
- if (aContentIndex >= children) {
- // If the content insert is after the end of the parent, then we want to get
- // the next index *after* the parent and insert there.
- ind = GetOptionIndexAfter(aParent);
- } else {
- // If the content insert is somewhere in the middle of the container, then
- // we want to get the option currently at the index and insert in front of
- // that.
- nsIContent* currentKid = aParent->GetChildAt(aContentIndex);
- NS_ASSERTION(currentKid, "Child not found!");
- if (currentKid) {
- ind = GetOptionIndexAt(currentKid);
- } else {
- ind = -1;
- }
- }
- }
- InsertOptionsIntoList(aOptions, ind, level, aNotify);
- return NS_OK;
- }
- NS_IMETHODIMP
- HTMLSelectElement::WillRemoveOptions(nsIContent* aParent,
- int32_t aContentIndex,
- bool aNotify)
- {
- if (this != aParent && this != aParent->GetParent()) {
- return NS_OK;
- }
- int32_t level = this == aParent ? 0 : 1;
- // Get the index where the options will be removed
- nsIContent* currentKid = aParent->GetChildAt(aContentIndex);
- if (currentKid) {
- int32_t ind;
- if (!mNonOptionChildren) {
- // If there are no artifacts, aContentIndex == ind
- ind = aContentIndex;
- } else {
- // If there are artifacts, we have to get the index of the option the
- // hard way
- ind = GetFirstOptionIndex(currentKid);
- }
- if (ind != -1) {
- nsresult rv = RemoveOptionsFromList(currentKid, ind, level, aNotify);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- return NS_OK;
- }
- int32_t
- HTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions)
- {
- // Search this node and below.
- // If not found, find the first one *after* this node.
- int32_t retval = GetFirstOptionIndex(aOptions);
- if (retval == -1) {
- retval = GetOptionIndexAfter(aOptions);
- }
- return retval;
- }
- int32_t
- HTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions)
- {
- // - If this is the select, the next option is the last.
- // - If not, search all the options after aOptions and up to the last option
- // in the parent.
- // - If it's not there, search for the first option after the parent.
- if (aOptions == this) {
- return Length();
- }
- int32_t retval = -1;
- nsCOMPtr<nsIContent> parent = aOptions->GetParent();
- if (parent) {
- int32_t index = parent->IndexOf(aOptions);
- int32_t count = parent->GetChildCount();
- retval = GetFirstChildOptionIndex(parent, index+1, count);
- if (retval == -1) {
- retval = GetOptionIndexAfter(parent);
- }
- }
- return retval;
- }
- int32_t
- HTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions)
- {
- int32_t listIndex = -1;
- HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
- if (optElement) {
- GetOptionIndex(optElement, 0, true, &listIndex);
- return listIndex;
- }
- listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount());
- return listIndex;
- }
- int32_t
- HTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions,
- int32_t aStartIndex,
- int32_t aEndIndex)
- {
- int32_t retval = -1;
- for (int32_t i = aStartIndex; i < aEndIndex; ++i) {
- retval = GetFirstOptionIndex(aOptions->GetChildAt(i));
- if (retval != -1) {
- break;
- }
- }
- return retval;
- }
- nsISelectControlFrame*
- HTMLSelectElement::GetSelectFrame()
- {
- nsIFormControlFrame* form_control_frame = GetFormControlFrame(false);
- nsISelectControlFrame* select_frame = nullptr;
- if (form_control_frame) {
- select_frame = do_QueryFrame(form_control_frame);
- }
- return select_frame;
- }
- void
- HTMLSelectElement::Add(const HTMLOptionElementOrHTMLOptGroupElement& aElement,
- const Nullable<HTMLElementOrLong>& aBefore,
- ErrorResult& aRv)
- {
- nsGenericHTMLElement& element =
- aElement.IsHTMLOptionElement() ?
- static_cast<nsGenericHTMLElement&>(aElement.GetAsHTMLOptionElement()) :
- static_cast<nsGenericHTMLElement&>(aElement.GetAsHTMLOptGroupElement());
- if (aBefore.IsNull()) {
- Add(element, static_cast<nsGenericHTMLElement*>(nullptr), aRv);
- } else if (aBefore.Value().IsHTMLElement()) {
- Add(element, &aBefore.Value().GetAsHTMLElement(), aRv);
- } else {
- Add(element, aBefore.Value().GetAsLong(), aRv);
- }
- }
- void
- HTMLSelectElement::Add(nsGenericHTMLElement& aElement,
- nsGenericHTMLElement* aBefore,
- ErrorResult& aError)
- {
- if (!aBefore) {
- Element::AppendChild(aElement, aError);
- return;
- }
- // Just in case we're not the parent, get the parent of the reference
- // element
- nsCOMPtr<nsINode> parent = aBefore->Element::GetParentNode();
- if (!parent || !nsContentUtils::ContentIsDescendantOf(parent, this)) {
- // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
- // element.
- aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
- return;
- }
- // If the before parameter is not null, we are equivalent to the
- // insertBefore method on the parent of before.
- nsCOMPtr<nsINode> refNode = aBefore;
- parent->InsertBefore(aElement, refNode, aError);
- }
- NS_IMETHODIMP
- HTMLSelectElement::Add(nsIDOMHTMLElement* aElement,
- nsIVariant* aBefore)
- {
- uint16_t dataType;
- nsresult rv = aBefore->GetDataType(&dataType);
- NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIContent> element = do_QueryInterface(aElement);
- nsGenericHTMLElement* htmlElement =
- nsGenericHTMLElement::FromContentOrNull(element);
- if (!htmlElement) {
- return NS_ERROR_NULL_POINTER;
- }
- // aBefore is omitted, undefined or null
- if (dataType == nsIDataType::VTYPE_EMPTY ||
- dataType == nsIDataType::VTYPE_VOID) {
- ErrorResult error;
- Add(*htmlElement, (nsGenericHTMLElement*)nullptr, error);
- return error.StealNSResult();
- }
- nsCOMPtr<nsISupports> supports;
- // whether aBefore is nsIDOMHTMLElement...
- if (NS_SUCCEEDED(aBefore->GetAsISupports(getter_AddRefs(supports)))) {
- nsCOMPtr<nsIContent> beforeElement = do_QueryInterface(supports);
- nsGenericHTMLElement* beforeHTMLElement =
- nsGenericHTMLElement::FromContentOrNull(beforeElement);
- NS_ENSURE_TRUE(beforeHTMLElement, NS_ERROR_DOM_SYNTAX_ERR);
- ErrorResult error;
- Add(*htmlElement, beforeHTMLElement, error);
- return error.StealNSResult();
- }
- // otherwise, whether aBefore is long
- int32_t index;
- NS_ENSURE_SUCCESS(aBefore->GetAsInt32(&index), NS_ERROR_DOM_SYNTAX_ERR);
- ErrorResult error;
- Add(*htmlElement, index, error);
- return error.StealNSResult();
- }
- NS_IMETHODIMP
- HTMLSelectElement::Remove(int32_t aIndex)
- {
- nsCOMPtr<nsINode> option = Item(static_cast<uint32_t>(aIndex));
- if (!option) {
- return NS_OK;
- }
- option->Remove();
- return NS_OK;
- }
- NS_IMETHODIMP
- HTMLSelectElement::GetOptions(nsIDOMHTMLOptionsCollection** aValue)
- {
- NS_IF_ADDREF(*aValue = GetOptions());
- return NS_OK;
- }
- NS_IMETHODIMP
- HTMLSelectElement::GetType(nsAString& aType)
- {
- if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
- aType.AssignLiteral("select-multiple");
- }
- else {
- aType.AssignLiteral("select-one");
- }
- return NS_OK;
- }
- NS_IMETHODIMP
- HTMLSelectElement::GetLength(uint32_t* aLength)
- {
- return mOptions->GetLength(aLength);
- }
- #define MAX_DYNAMIC_SELECT_LENGTH 10000
- NS_IMETHODIMP
- HTMLSelectElement::SetLength(uint32_t aLength)
- {
- ErrorResult rv;
- SetLength(aLength, rv);
- return rv.StealNSResult();
- }
- void
- HTMLSelectElement::SetLength(uint32_t aLength, ErrorResult& aRv)
- {
- uint32_t curlen = Length();
- if (curlen > aLength) { // Remove extra options
- for (uint32_t i = curlen; i > aLength; --i) {
- MOZ_ALWAYS_SUCCEEDS(Remove(i - 1));
- }
- } else if (aLength > curlen) {
- if (aLength > MAX_DYNAMIC_SELECT_LENGTH) {
- aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
- return;
- }
- RefPtr<mozilla::dom::NodeInfo> nodeInfo;
- nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::option,
- getter_AddRefs(nodeInfo));
- nsCOMPtr<nsINode> node = NS_NewHTMLOptionElement(nodeInfo.forget());
- RefPtr<nsTextNode> text = new nsTextNode(mNodeInfo->NodeInfoManager());
- aRv = node->AppendChildTo(text, false);
- if (aRv.Failed()) {
- return;
- }
- for (uint32_t i = curlen; i < aLength; i++) {
- nsINode::AppendChild(*node, aRv);
- if (aRv.Failed()) {
- return;
- }
- if (i + 1 < aLength) {
- node = node->CloneNode(true, aRv);
- if (aRv.Failed()) {
- return;
- }
- MOZ_ASSERT(node);
- }
- }
- }
- }
- /* static */
- bool
- HTMLSelectElement::MatchSelectedOptions(Element* aElement,
- int32_t /* unused */,
- nsIAtom* /* unused */,
- void* /* unused*/)
- {
- HTMLOptionElement* option = HTMLOptionElement::FromContent(aElement);
- 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;
- }
- nsresult
- HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify)
- {
- int32_t oldSelectedIndex = mSelectedIndex;
- uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED;
- if (aNotify) {
- mask |= NOTIFY;
- }
- SetOptionsSelectedByIndex(aIndex, aIndex, mask);
- nsresult rv = NS_OK;
- nsISelectControlFrame* selectFrame = GetSelectFrame();
- if (selectFrame) {
- rv = selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
- }
- SetSelectionChanged(true, aNotify);
- return rv;
- }
- NS_IMETHODIMP
- HTMLSelectElement::SetSelectedIndex(int32_t aIndex)
- {
- return SetSelectedIndexInternal(aIndex, true);
- }
- NS_IMETHODIMP
- HTMLSelectElement::GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
- int32_t aStartIndex, bool aForward,
- int32_t* aIndex)
- {
- nsCOMPtr<nsINode> option = do_QueryInterface(aOption);
- return mOptions->GetOptionIndex(option->AsElement(), aStartIndex, aForward, aIndex);
- }
- bool
- HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex)
- {
- HTMLOptionElement* option = Item(static_cast<uint32_t>(aIndex));
- return option && option->Selected();
- }
- void
- HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
- int32_t aIndex,
- bool aSelected,
- bool aChangeOptionState,
- bool aNotify)
- {
- // Set the selected index
- if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) {
- mSelectedIndex = aIndex;
- SetSelectionChanged(true, aNotify);
- } else if (!aSelected && aIndex == mSelectedIndex) {
- FindSelectedIndex(aIndex + 1, aNotify);
- }
- if (aChangeOptionState) {
- // Tell the option to get its bad self selected
- RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(aIndex));
- if (option) {
- option->SetSelectedInternal(aSelected, aNotify);
- }
- }
- // 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;
- SetSelectionChanged(true, aNotify);
- uint32_t len = Length();
- for (int32_t i = aStartIndex; i < int32_t(len); i++) {
- if (IsOptionSelectedByIndex(i)) {
- mSelectedIndex = i;
- SetSelectionChanged(true, aNotify);
- break;
- }
- }
- }
- // XXX Consider splitting this into two functions for ease of reading:
- // SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled)
- // startIndex, endIndex - the range of options to turn on
- // (-1, -1) will clear all indices no matter what.
- // clearAll - will clear all other options unless checkDisabled is on
- // and all the options attempted to be set are disabled
- // (note that if it is not multiple, and an option is selected,
- // everything else will be cleared regardless).
- // checkDisabled - if this is TRUE, and an option is disabled, it will not be
- // changed regardless of whether it is selected or not.
- // Generally the UI passes TRUE and JS passes FALSE.
- // (setDisabled currently is the opposite)
- // DeselectOptionsByIndex(startIndex, endIndex, checkDisabled)
- // startIndex, endIndex - the range of options to turn on
- // (-1, -1) will clear all indices no matter what.
- // checkDisabled - if this is TRUE, and an option is disabled, it will not be
- // changed regardless of whether it is selected or not.
- // Generally the UI passes TRUE and JS passes FALSE.
- // (setDisabled currently is the opposite)
- //
- // XXXbz the above comment is pretty confusing. Maybe we should actually
- // document the args to this function too, in addition to documenting what
- // things might end up looking like? In particular, pay attention to the
- // setDisabled vs checkDisabled business.
- bool
- HTMLSelectElement::SetOptionsSelectedByIndex(int32_t aStartIndex,
- int32_t aEndIndex,
- uint32_t aOptionsMask)
- {
- #if 0
- printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex,
- (aOptionsMask & IS_SELECTED ? 'Y' : 'N'),
- (aOptionsMask & CLEAR_ALL ? 'Y' : 'N'));
- #endif
- // Don't bother if the select is disabled
- if (!(aOptionsMask & SET_DISABLED) && IsDisabled()) {
- return false;
- }
- // Don't bother if there are no options
- uint32_t numItems = Length();
- if (numItems == 0) {
- return false;
- }
- // First, find out whether multiple items can be selected
- bool isMultiple = Multiple();
- // These variables tell us whether any options were selected
- // or deselected.
- bool optionsSelected = false;
- bool optionsDeselected = false;
- nsISelectControlFrame* selectFrame = nullptr;
- bool didGetFrame = false;
- nsWeakFrame weakSelectFrame;
- if (aOptionsMask & IS_SELECTED) {
- // Setting selectedIndex to an out-of-bounds index means -1. (HTML5)
- if (aStartIndex < 0 || AssertedCast<uint32_t>(aStartIndex) >= numItems ||
- aEndIndex < 0 || AssertedCast<uint32_t>(aEndIndex) >= numItems) {
- aStartIndex = -1;
- aEndIndex = -1;
- }
- // Only select the first value if it's not multiple
- if (!isMultiple) {
- aEndIndex = aStartIndex;
- }
- // This variable tells whether or not all of the options we attempted to
- // select are disabled. If ClearAll is passed in as true, and we do not
- // select anything because the options are disabled, we will not clear the
- // other options. (This is to make the UI work the way one might expect.)
- bool allDisabled = !(aOptionsMask & SET_DISABLED);
- //
- // Save a little time when clearing other options
- //
- int32_t previousSelectedIndex = mSelectedIndex;
- //
- // Select the requested indices
- //
- // If index is -1, everything will be deselected (bug 28143)
- if (aStartIndex != -1) {
- MOZ_ASSERT(aStartIndex >= 0);
- MOZ_ASSERT(aEndIndex >= 0);
- // Loop through the options and select them (if they are not disabled and
- // if they are not already selected).
- for (uint32_t optIndex = AssertedCast<uint32_t>(aStartIndex);
- optIndex <= AssertedCast<uint32_t>(aEndIndex);
- optIndex++) {
- RefPtr<HTMLOptionElement> option = Item(optIndex);
- // Ignore disabled options.
- if (!(aOptionsMask & SET_DISABLED)) {
- if (option && IsOptionDisabled(option)) {
- continue;
- }
- allDisabled = false;
- }
- // If the index is already selected, ignore it.
- if (option && !option->Selected()) {
- // To notify the frame if anything gets changed. No need
- // to flush here, if there's no frame yet we don't need to
- // force it to be created just to notify it about a change
- // in the select.
- selectFrame = GetSelectFrame();
- weakSelectFrame = do_QueryFrame(selectFrame);
- didGetFrame = true;
- OnOptionSelected(selectFrame, optIndex, true, true,
- aOptionsMask & NOTIFY);
- optionsSelected = true;
- }
- }
- }
- // Next remove all other options if single select or all is clear
- // If index is -1, everything will be deselected (bug 28143)
- if (((!isMultiple && optionsSelected)
- || ((aOptionsMask & CLEAR_ALL) && !allDisabled)
- || aStartIndex == -1)
- && previousSelectedIndex != -1) {
- for (uint32_t optIndex = AssertedCast<uint32_t>(previousSelectedIndex);
- optIndex < numItems;
- optIndex++) {
- if (static_cast<int32_t>(optIndex) < aStartIndex ||
- static_cast<int32_t>(optIndex) > aEndIndex) {
- HTMLOptionElement* option = Item(optIndex);
- // If the index is already selected, ignore it.
- if (option && option->Selected()) {
- if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
- // To notify the frame if anything gets changed, don't
- // flush, if the frame doesn't exist we don't need to
- // create it just to tell it about this change.
- selectFrame = GetSelectFrame();
- weakSelectFrame = do_QueryFrame(selectFrame);
- didGetFrame = true;
- }
- OnOptionSelected(selectFrame, optIndex, false, true,
- aOptionsMask & NOTIFY);
- optionsDeselected = true;
- // Only need to deselect one option if not multiple
- if (!isMultiple) {
- break;
- }
- }
- }
- }
- }
- } else {
- // If we're deselecting, loop through all selected items and deselect
- // any that are in the specified range.
- for (int32_t optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
- HTMLOptionElement* option = Item(optIndex);
- if (!(aOptionsMask & SET_DISABLED) && IsOptionDisabled(option)) {
- continue;
- }
- // If the index is already selected, ignore it.
- if (option && option->Selected()) {
- if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
- // To notify the frame if anything gets changed, don't
- // flush, if the frame doesn't exist we don't need to
- // create it just to tell it about this change.
- selectFrame = GetSelectFrame();
- weakSelectFrame = do_QueryFrame(selectFrame);
- didGetFrame = true;
- }
- OnOptionSelected(selectFrame, optIndex, false, true,
- aOptionsMask & NOTIFY);
- optionsDeselected = true;
- }
- }
- }
- // Make sure something is selected unless we were set to -1 (none)
- if (optionsDeselected && aStartIndex != -1) {
- optionsSelected =
- CheckSelectSomething(aOptionsMask & NOTIFY) || optionsSelected;
- }
- // Let the caller know whether anything was changed
- return optionsSelected || optionsDeselected;
- }
- NS_IMETHODIMP
- HTMLSelectElement::IsOptionDisabled(int32_t aIndex, bool* aIsDisabled)
- {
- *aIsDisabled = false;
- RefPtr<HTMLOptionElement> option = Item(aIndex);
- NS_ENSURE_TRUE(option, NS_ERROR_FAILURE);
- *aIsDisabled = IsOptionDisabled(option);
- return NS_OK;
- }
- bool
- HTMLSelectElement::IsOptionDisabled(HTMLOptionElement* aOption)
- {
- MOZ_ASSERT(aOption);
- if (aOption->Disabled()) {
- return true;
- }
- // Check for disabled optgroups
- // If there are no artifacts, there are no optgroups
- if (mNonOptionChildren) {
- for (nsCOMPtr<Element> node = static_cast<nsINode*>(aOption)->GetParentElement();
- node;
- node = node->GetParentElement()) {
- // If we reached the select element, we're done
- if (node->IsHTMLElement(nsGkAtoms::select)) {
- return false;
- }
- RefPtr<HTMLOptGroupElement> optGroupElement =
- HTMLOptGroupElement::FromContent(node);
- if (!optGroupElement) {
- // If you put something else between you and the optgroup, you're a
- // moron and you deserve not to have optgroup disabling work.
- return false;
- }
- if (optGroupElement->Disabled()) {
- return true;
- }
- }
- }
- return false;
- }
- NS_IMETHODIMP
- HTMLSelectElement::GetValue(nsAString& aValue)
- {
- DOMString value;
- GetValue(value);
- value.ToString(aValue);
- return NS_OK;
- }
- void
- HTMLSelectElement::GetValue(DOMString& aValue)
- {
- int32_t selectedIndex = SelectedIndex();
- if (selectedIndex < 0) {
- return;
- }
- RefPtr<HTMLOptionElement> option =
- Item(static_cast<uint32_t>(selectedIndex));
- if (!option) {
- return;
- }
- DebugOnly<nsresult> rv = option->GetValue(aValue);
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- }
- NS_IMETHODIMP
- HTMLSelectElement::SetValue(const nsAString& aValue)
- {
- uint32_t length = Length();
- for (uint32_t i = 0; i < length; i++) {
- RefPtr<HTMLOptionElement> option = Item(i);
- if (!option) {
- continue;
- }
- nsAutoString optionVal;
- option->GetValue(optionVal);
- if (optionVal.Equals(aValue)) {
- SetSelectedIndexInternal(int32_t(i), true);
- return NS_OK;
- }
- }
- // No matching option was found.
- SetSelectedIndexInternal(-1, true);
- return NS_OK;
- }
- NS_IMPL_BOOL_ATTR(HTMLSelectElement, Autofocus, autofocus)
- NS_IMPL_BOOL_ATTR(HTMLSelectElement, Disabled, disabled)
- NS_IMPL_BOOL_ATTR(HTMLSelectElement, Multiple, multiple)
- NS_IMPL_STRING_ATTR(HTMLSelectElement, Name, name)
- NS_IMPL_BOOL_ATTR(HTMLSelectElement, Required, required)
- NS_IMPL_UINT_ATTR(HTMLSelectElement, Size, size)
- int32_t
- HTMLSelectElement::TabIndexDefault()
- {
- return 0;
- }
- bool
- HTMLSelectElement::IsHTMLFocusable(bool aWithMouse,
- bool* aIsFocusable, int32_t* aTabIndex)
- {
- if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable,
- aTabIndex))
- {
- return true;
- }
- *aIsFocusable = !IsDisabled();
- return false;
- }
- NS_IMETHODIMP
- HTMLSelectElement::Item(uint32_t aIndex, nsIDOMNode** aReturn)
- {
- return mOptions->Item(aIndex, aReturn);
- }
- NS_IMETHODIMP
- HTMLSelectElement::NamedItem(const nsAString& aName, nsIDOMNode** aReturn)
- {
- return mOptions->NamedItem(aName, aReturn);
- }
- bool
- HTMLSelectElement::CheckSelectSomething(bool aNotify)
- {
- if (mIsDoneAddingChildren) {
- if (mSelectedIndex < 0 && IsCombobox()) {
- return SelectSomething(aNotify);
- }
- }
- return false;
- }
- bool
- HTMLSelectElement::SelectSomething(bool aNotify)
- {
- // If we're not done building the select, don't play with this yet.
- if (!mIsDoneAddingChildren) {
- return false;
- }
- uint32_t count;
- GetLength(&count);
- for (uint32_t i = 0; i < count; i++) {
- bool disabled;
- nsresult rv = IsOptionDisabled(i, &disabled);
- if (NS_FAILED(rv) || !disabled) {
- rv = SetSelectedIndexInternal(i, aNotify);
- NS_ENSURE_SUCCESS(rv, false);
- UpdateValueMissingValidityState();
- UpdateState(aNotify);
- return true;
- }
- }
- return false;
- }
- nsresult
- HTMLSelectElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
- nsIContent* aBindingParent,
- bool aCompileEventHandlers)
- {
- nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent,
- aBindingParent,
- aCompileEventHandlers);
- NS_ENSURE_SUCCESS(rv, rv);
- // If there is a disabled fieldset in the parent chain, the element is now
- // barred from constraint validation.
- // XXXbz is this still needed now that fieldset changes always call
- // FieldSetDisabledChanged?
- UpdateBarredFromConstraintValidation();
- // And now make sure our state is up to date
- UpdateState(false);
- return rv;
- }
- void
- HTMLSelectElement::UnbindFromTree(bool aDeep, bool aNullParent)
- {
- nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
- // We might be no longer disabled because our parent chain changed.
- // XXXbz is this still needed now that fieldset changes always call
- // FieldSetDisabledChanged?
- UpdateBarredFromConstraintValidation();
- // And now make sure our state is up to date
- UpdateState(false);
- }
- nsresult
- HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
- const nsAttrValueOrString* aValue,
- bool aNotify)
- {
- if (aNameSpaceID == kNameSpaceID_None) {
- if (aName == nsGkAtoms::disabled) {
- if (aNotify) {
- mDisabledChanged = true;
- }
- } else if (aName == nsGkAtoms::multiple) {
- if (!aValue && aNotify && mSelectedIndex >= 0) {
- // We're changing from being a multi-select to a single-select.
- // Make sure we only have one option selected before we do that.
- // Note that this needs to come before we really unset the attr,
- // since SetOptionsSelectedByIndex does some bail-out type
- // optimization for cases when the select is not multiple that
- // would lead to only a single option getting deselected.
- SetSelectedIndexInternal(mSelectedIndex, aNotify);
- }
- }
- }
- return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
- aValue, aNotify);
- }
- nsresult
- HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
- const nsAttrValue* aValue,
- const nsAttrValue* aOldValue, bool aNotify)
- {
- if (aNameSpaceID == kNameSpaceID_None) {
- if (aName == nsGkAtoms::disabled) {
- UpdateBarredFromConstraintValidation();
- } else if (aName == nsGkAtoms::required) {
- UpdateValueMissingValidityState();
- } else if (aName == nsGkAtoms::autocomplete) {
- // Clear the cached @autocomplete attribute state
- mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
- } else if (aName == nsGkAtoms::multiple) {
- if (!aValue && aNotify) {
- // We might have become a combobox; make sure _something_ gets
- // selected in that case
- CheckSelectSomething(aNotify);
- }
- }
- }
- return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
- aValue, aOldValue,
- aNotify);
- }
- void
- HTMLSelectElement::DoneAddingChildren(bool aHaveNotified)
- {
- mIsDoneAddingChildren = true;
- nsISelectControlFrame* selectFrame = GetSelectFrame();
- // If we foolishly tried to restore before we were done adding
- // content, restore the rest of the options proper-like
- if (mRestoreState) {
- RestoreStateTo(mRestoreState);
- mRestoreState = nullptr;
- }
- // Notify the frame
- if (selectFrame) {
- selectFrame->DoneAddingChildren(true);
- }
- if (!mInhibitStateRestoration) {
- nsresult rv = GenerateStateKey();
- if (NS_SUCCEEDED(rv)) {
- RestoreFormControlState();
- }
- }
- // Now that we're done, select something (if it's a single select something
- // must be selected)
- if (!CheckSelectSomething(false)) {
- // If an option has @selected set, it will be selected during parsing but
- // with an empty value. We have to make sure the select element updates it's
- // validity state to take this into account.
- UpdateValueMissingValidityState();
- // And now make sure we update our content state too
- UpdateState(aHaveNotified);
- }
- mDefaultSelectionSet = true;
- }
- bool
- HTMLSelectElement::ParseAttribute(int32_t aNamespaceID,
- nsIAtom* aAttribute,
- const nsAString& aValue,
- nsAttrValue& aResult)
- {
- if (kNameSpaceID_None == aNamespaceID) {
- if (aAttribute == nsGkAtoms::size) {
- return aResult.ParsePositiveIntValue(aValue);
- } else if (aAttribute == nsGkAtoms::autocomplete) {
- aResult.ParseAtomArray(aValue);
- return true;
- }
- }
- return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
- aResult);
- }
- void
- HTMLSelectElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
- nsRuleData* aData)
- {
- nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes, aData);
- nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
- }
- nsChangeHint
- HTMLSelectElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
- int32_t aModType) const
- {
- nsChangeHint retval =
- nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
- if (aAttribute == nsGkAtoms::multiple ||
- aAttribute == nsGkAtoms::size) {
- retval |= nsChangeHint_ReconstructFrame;
- }
- return retval;
- }
- NS_IMETHODIMP_(bool)
- HTMLSelectElement::IsAttributeMapped(const nsIAtom* aAttribute) const
- {
- static const MappedAttributeEntry* const map[] = {
- sCommonAttributeMap,
- sImageAlignAttributeMap
- };
- return FindAttributeDependence(aAttribute, map);
- }
- nsMapRuleToAttributesFunc
- HTMLSelectElement::GetAttributeMappingFunction() const
- {
- return &MapAttributesIntoRule;
- }
- bool
- HTMLSelectElement::IsDisabledForEvents(WidgetEvent* aEvent)
- {
- nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
- nsIFrame* formFrame = nullptr;
- if (formControlFrame) {
- formFrame = do_QueryFrame(formControlFrame);
- }
- return IsElementDisabledForEvents(aEvent, formFrame);
- }
- nsresult
- HTMLSelectElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
- {
- aVisitor.mCanHandle = false;
- if (IsDisabledForEvents(aVisitor.mEvent)) {
- return NS_OK;
- }
- return nsGenericHTMLFormElementWithState::GetEventTargetParent(aVisitor);
- }
- nsresult
- HTMLSelectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
- {
- if (aVisitor.mEvent->mMessage == eFocus) {
- // If the invalid UI is shown, we should show it while focused and
- // update the invalid/valid UI.
- mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
- // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
- // UI while focused.
- mCanShowValidUI = ShouldShowValidityUI();
- // We don't have to update NS_EVENT_STATE_MOZ_UI_INVALID nor
- // NS_EVENT_STATE_MOZ_UI_VALID given that the states should not change.
- } else if (aVisitor.mEvent->mMessage == eBlur) {
- mCanShowInvalidUI = true;
- mCanShowValidUI = true;
- UpdateState(true);
- }
- return nsGenericHTMLFormElementWithState::PostHandleEvent(aVisitor);
- }
- EventStates
- HTMLSelectElement::IntrinsicState() const
- {
- EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
- if (IsCandidateForConstraintValidation()) {
- if (IsValid()) {
- state |= NS_EVENT_STATE_VALID;
- } else {
- state |= NS_EVENT_STATE_INVALID;
- if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
- (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
- (mCanShowInvalidUI && ShouldShowValidityUI()))) {
- state |= NS_EVENT_STATE_MOZ_UI_INVALID;
- }
- }
- // :-moz-ui-valid applies if all the following are true:
- // 1. The element is not focused, or had either :-moz-ui-valid or
- // :-moz-ui-invalid applying before it was focused ;
- // 2. The element is either valid or isn't allowed to have
- // :-moz-ui-invalid applying ;
- // 3. The element has no form owner or its form owner doesn't have the
- // novalidate attribute set ;
- // 4. The element has already been modified or the user tried to submit the
- // form owner while invalid.
- if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
- (mCanShowValidUI && ShouldShowValidityUI() &&
- (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
- !mCanShowInvalidUI)))) {
- state |= NS_EVENT_STATE_MOZ_UI_VALID;
- }
- }
- if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
- state |= NS_EVENT_STATE_REQUIRED;
- } else {
- state |= NS_EVENT_STATE_OPTIONAL;
- }
- return state;
- }
- // nsIFormControl
- NS_IMETHODIMP
- HTMLSelectElement::SaveState()
- {
- RefPtr<SelectState> state = new SelectState();
- uint32_t len = Length();
- for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
- HTMLOptionElement* option = Item(optIndex);
- if (option && option->Selected()) {
- nsAutoString value;
- option->GetValue(value);
- state->PutOption(optIndex, value);
- }
- }
- nsPresState* presState = GetPrimaryPresState();
- if (presState) {
- presState->SetStateProperty(state);
- if (mDisabledChanged) {
- // We do not want to save the real disabled state but the disabled
- // attribute.
- presState->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
- }
- }
- return NS_OK;
- }
- bool
- HTMLSelectElement::RestoreState(nsPresState* aState)
- {
- // Get the presentation state object to retrieve our stuff out of.
- nsCOMPtr<SelectState> state(
- do_QueryInterface(aState->GetStateProperty()));
- if (state) {
- RestoreStateTo(state);
- // Don't flush, if the frame doesn't exist yet it doesn't care if
- // we're reset or not.
- DispatchContentReset();
- }
- if (aState->IsDisabledSet()) {
- SetDisabled(aState->GetDisabled());
- }
- return false;
- }
- void
- HTMLSelectElement::RestoreStateTo(SelectState* aNewSelected)
- {
- if (!mIsDoneAddingChildren) {
- mRestoreState = aNewSelected;
- return;
- }
- uint32_t len = Length();
- uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED | NOTIFY;
- // First clear all
- SetOptionsSelectedByIndex(-1, -1, mask);
- // Next set the proper ones
- for (uint32_t i = 0; i < len; i++) {
- HTMLOptionElement* option = Item(i);
- if (option) {
- nsAutoString value;
- nsresult rv = option->GetValue(value);
- if (NS_SUCCEEDED(rv) && aNewSelected->ContainsOption(i, value)) {
- SetOptionsSelectedByIndex(i, i, IS_SELECTED | SET_DISABLED | NOTIFY);
- }
- }
- }
- }
- NS_IMETHODIMP
- HTMLSelectElement::Reset()
- {
- uint32_t numSelected = 0;
- //
- // Cycle through the options array and reset the options
- //
- uint32_t numOptions = Length();
- for (uint32_t i = 0; i < numOptions; i++) {
- RefPtr<HTMLOptionElement> option = Item(i);
- if (option) {
- //
- // Reset the option to its default value
- //
- uint32_t mask = SET_DISABLED | NOTIFY;
- if (option->DefaultSelected()) {
- mask |= IS_SELECTED;
- numSelected++;
- }
- SetOptionsSelectedByIndex(i, i, mask);
- }
- }
- //
- // If nothing was selected and it's not multiple, select something
- //
- if (numSelected == 0 && IsCombobox()) {
- SelectSomething(true);
- }
- SetSelectionChanged(false, true);
- //
- // Let the frame know we were reset
- //
- // Don't flush, if there's no frame yet it won't care about us being
- // reset even if we forced it to be created now.
- //
- DispatchContentReset();
- return NS_OK;
- }
- static NS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID);
- NS_IMETHODIMP
- HTMLSelectElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
- {
- // Disabled elements don't submit
- if (IsDisabled()) {
- return NS_OK;
- }
- //
- // Get the name (if no name, no submit)
- //
- nsAutoString name;
- GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
- if (name.IsEmpty()) {
- return NS_OK;
- }
- //
- // Submit
- //
- uint32_t len = Length();
- nsAutoString mozType;
- nsCOMPtr<nsIFormProcessor> keyGenProcessor;
- if (GetAttr(kNameSpaceID_None, nsGkAtoms::moztype, mozType) &&
- mozType.EqualsLiteral("-mozilla-keygen")) {
- keyGenProcessor = do_GetService(kFormProcessorCID);
- }
- for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
- HTMLOptionElement* option = Item(optIndex);
- // Don't send disabled options
- if (!option || IsOptionDisabled(option)) {
- continue;
- }
- if (!option->Selected()) {
- continue;
- }
- nsString value;
- MOZ_ALWAYS_SUCCEEDS(option->GetValue(value));
- if (keyGenProcessor) {
- nsString tmp(value);
- if (NS_SUCCEEDED(keyGenProcessor->ProcessValue(this, name, tmp))) {
- value = tmp;
- }
- }
- aFormSubmission->AddNameValuePair(name, value);
- }
- return NS_OK;
- }
- void
- HTMLSelectElement::DispatchContentReset()
- {
- nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
- if (formControlFrame) {
- // Only dispatch content reset notification if this is a list control
- // frame or combo box control frame.
- if (IsCombobox()) {
- nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
- if (comboFrame) {
- comboFrame->OnContentReset();
- }
- } else {
- nsIListControlFrame* listFrame = do_QueryFrame(formControlFrame);
- if (listFrame) {
- listFrame->OnContentReset();
- }
- }
- }
- }
- static void
- AddOptions(nsIContent* aRoot, HTMLOptionsCollection* aArray)
- {
- for (nsIContent* child = aRoot->GetFirstChild();
- child;
- child = child->GetNextSibling()) {
- HTMLOptionElement* opt = HTMLOptionElement::FromContent(child);
- if (opt) {
- aArray->AppendOption(opt);
- } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
- for (nsIContent* grandchild = child->GetFirstChild();
- grandchild;
- grandchild = grandchild->GetNextSibling()) {
- opt = HTMLOptionElement::FromContent(grandchild);
- if (opt) {
- aArray->AppendOption(opt);
- }
- }
- }
- }
- }
- void
- HTMLSelectElement::RebuildOptionsArray(bool aNotify)
- {
- mOptions->Clear();
- AddOptions(this, mOptions);
- FindSelectedIndex(0, aNotify);
- }
- bool
- HTMLSelectElement::IsValueMissing()
- {
- if (!Required()) {
- return false;
- }
- uint32_t length = Length();
- for (uint32_t i = 0; i < length; ++i) {
- RefPtr<HTMLOptionElement> option = Item(i);
- if (!option->Selected()) {
- continue;
- }
- if (IsOptionDisabled(option)) {
- continue;
- }
- nsAutoString value;
- MOZ_ALWAYS_SUCCEEDS(option->GetValue(value));
- if (!value.IsEmpty()) {
- return false;
- }
- }
- return true;
- }
- void
- HTMLSelectElement::UpdateValueMissingValidityState()
- {
- SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
- }
- nsresult
- HTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage,
- ValidityStateType aType)
- {
- switch (aType) {
- case VALIDITY_STATE_VALUE_MISSING: {
- nsXPIDLString message;
- nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
- "FormValidationSelectMissing",
- message);
- aValidationMessage = message;
- return rv;
- }
- default: {
- return nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
- }
- }
- }
- #ifdef DEBUG
- void
- HTMLSelectElement::VerifyOptionsArray()
- {
- int32_t index = 0;
- for (nsIContent* child = nsINode::GetFirstChild();
- child;
- child = child->GetNextSibling()) {
- HTMLOptionElement* opt = HTMLOptionElement::FromContent(child);
- if (opt) {
- NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
- "Options collection broken");
- } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
- for (nsIContent* grandchild = child->GetFirstChild();
- grandchild;
- grandchild = grandchild->GetNextSibling()) {
- opt = HTMLOptionElement::FromContent(grandchild);
- if (opt) {
- NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
- "Options collection broken");
- }
- }
- }
- }
- }
- #endif
- void
- HTMLSelectElement::UpdateBarredFromConstraintValidation()
- {
- SetBarredFromConstraintValidation(IsDisabled());
- }
- void
- HTMLSelectElement::FieldSetDisabledChanged(bool aNotify)
- {
- UpdateBarredFromConstraintValidation();
- nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
- }
- 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();
- }
- }
- bool
- HTMLSelectElement::OpenInParentProcess()
- {
- nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
- nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
- if (comboFrame) {
- return comboFrame->IsOpenInParentProcess();
- }
- return false;
- }
- void
- HTMLSelectElement::SetOpenInParentProcess(bool aVal)
- {
- nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
- nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
- if (comboFrame) {
- comboFrame->SetOpenInParentProcess(aVal);
- }
- }
- JSObject*
- HTMLSelectElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
- {
- return HTMLSelectElementBinding::Wrap(aCx, this, aGivenProto);
- }
- } // namespace dom
- } // namespace mozilla
|