/* 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 "ButtonControlFrame.h" #include "mozilla/BasicEvents.h" #include "mozilla/Casting.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/EventDispatcher.h" #include "mozilla/MappedDeclarationsBuilder.h" #include "mozilla/Maybe.h" #include "mozilla/MouseEvents.h" #include "mozilla/PresShell.h" #include "mozilla/PresState.h" #include "mozilla/StaticPrefs_ui.h" #include "mozilla/TextEvents.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/FormData.h" #include "mozilla/dom/HTMLOptGroupElement.h" #include "mozilla/dom/HTMLOptionElement.h" #include "mozilla/dom/HTMLSelectElementBinding.h" #include "mozilla/dom/MouseEventBinding.h" #include "mozilla/dom/UnionTypes.h" #include "mozilla/dom/WindowGlobalChild.h" #include "nsComboboxControlFrame.h" #include "nsComputedDOMStyle.h" #include "nsContentCreatorFunctions.h" #include "nsContentList.h" #include "nsContentUtils.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsIFrame.h" #include "nsLayoutUtils.h" #include "nsListControlFrame.h" #ifdef ACCESSIBILITY # include "nsAccessibilityService.h" #endif NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select) static bool IsOptionInteractivelySelectable( const mozilla::dom::HTMLSelectElement& aSelect, mozilla::dom::HTMLOptionElement& aOption) { if (aSelect.IsOptionDisabled(&aOption)) { return false; } if (!aSelect.IsCombobox()) { return aOption.GetPrimaryFrame(); } // TODO(emilio): This is a bit silly and doesn't match the options that we // show / don't show in the dropdown, but matches the frame construction we // do for multiple selects. For backwards compat also don't allow selecting // options in a display: contents subtree interactively. // test_select_key_navigation_bug1498769.html tests for this and should // probably be changed (and this loop removed) or alternatively // SelectChild.sys.mjs should be changed to match it. for (mozilla::dom::Element* el = &aOption; el && el != &aSelect; el = el->GetParentElement()) { RefPtr style = nsComputedDOMStyle::GetComputedStyleNoFlush(el); if (!style) { return false; } auto display = style->StyleDisplay()->mDisplay; if (display == mozilla::StyleDisplay::None || display == mozilla::StyleDisplay::Contents) { return false; } } return true; } static mozilla::StaticAutoPtr sIncrementalString; static mozilla::TimeStamp gLastKeyTime; static uintptr_t sLastSelectKeyHandler = 0; static nsString& GetIncrementalString() { MOZ_ASSERT(sLastSelectKeyHandler != 0); if (!sIncrementalString) { sIncrementalString = new nsString(); mozilla::ClearOnShutdown(&sIncrementalString); } return *sIncrementalString; } namespace mozilla::dom { //---------------------------------------------------------------------- // // SafeOptionListMutation // SafeOptionListMutation::SafeOptionListMutation(nsIContent* aSelect, nsIContent* aParent, nsIContent* aKid, uint32_t aIndex, bool aNotify) : mSelect(HTMLSelectElement::FromNodeOrNull(aSelect)), mTopLevelMutation(false), mNeedsRebuild(false), mNotify(aNotify) { if (mSelect) { mInitialSelectedOption = mSelect->Item(mSelect->SelectedIndex()); 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(mNotify); } nsresult rv; if (aKid) { rv = mSelect->WillAddOptions(aKid, aParent, aIndex, mNotify); } else { rv = mSelect->WillRemoveOptions(aParent, aIndex, mNotify); } mNeedsRebuild = NS_FAILED(rv); } } SafeOptionListMutation::~SafeOptionListMutation() { if (mSelect) { if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) { mSelect->RebuildOptionsArray(true); } if (mTopLevelMutation) { mSelect->mMutating = false; } if (mSelect->Item(mSelect->SelectedIndex()) != mInitialSelectedOption) { // We must have triggered the SelectSomething() codepath, which can cause // our validity to change. Unfortunately, our attempt to update validity // in that case may not have worked correctly, because we actually call it // before we have inserted the new