Make nsIHTMLCollection the first base for nsContentList and co, since it's needed for WebIDL bindings to work properly and shouldn't change behavior in practice. Move the handling of selected option and validity to HTMLSelectElement's mutation observer. Differential Revision: https://phabricator.services.mozilla.com/D295818
287 lines
8.7 KiB
C++
287 lines
8.7 KiB
C++
/* 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/. */
|
|
#ifndef nsListControlFrame_h_
|
|
#define nsListControlFrame_h_
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/ScrollContainerFrame.h"
|
|
|
|
class nsComboboxControlFrame;
|
|
class nsPresContext;
|
|
|
|
namespace mozilla {
|
|
class PresShell;
|
|
|
|
class WidgetMouseEvent;
|
|
|
|
namespace dom {
|
|
class HTMLOptionElement;
|
|
class HTMLSelectElement;
|
|
class HTMLOptionsCollection;
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
/**
|
|
* Frame-based listbox.
|
|
*/
|
|
|
|
class nsListControlFrame final : public mozilla::ScrollContainerFrame {
|
|
public:
|
|
using HTMLOptionElement = mozilla::dom::HTMLOptionElement;
|
|
|
|
friend nsListControlFrame* NS_NewListControlFrame(
|
|
mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
|
|
|
|
NS_DECL_QUERYFRAME
|
|
NS_DECL_FRAMEARENA_HELPERS(nsListControlFrame)
|
|
|
|
Maybe<nscoord> GetNaturalBaselineBOffset(
|
|
mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
|
|
BaselineExportContext) const override;
|
|
|
|
// nsIFrame
|
|
nsresult HandleEvent(nsPresContext* aPresContext,
|
|
mozilla::WidgetGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus) final;
|
|
|
|
nscoord IntrinsicISize(const mozilla::IntrinsicSizeInput& aInput,
|
|
mozilla::IntrinsicISizeType aType) final;
|
|
|
|
void Reflow(nsPresContext* aCX, ReflowOutput& aDesiredSize,
|
|
const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final;
|
|
|
|
void Init(nsIContent* aContent, nsContainerFrame* aParent,
|
|
nsIFrame* aPrevInFlow) final;
|
|
|
|
bool ReflowFinished() final;
|
|
void Destroy(DestroyContext&) override;
|
|
|
|
int32_t GetEndSelectionIndex() const { return mEndSelectionIndex; }
|
|
|
|
mozilla::dom::HTMLOptionElement* GetCurrentOption() const;
|
|
|
|
#ifdef DEBUG_FRAME_DUMP
|
|
nsresult GetFrameName(nsAString& aResult) const final;
|
|
#endif
|
|
|
|
void ElementStateChanged(mozilla::dom::ElementState aStates) final;
|
|
|
|
// for accessibility purposes
|
|
#ifdef ACCESSIBILITY
|
|
mozilla::a11y::AccType AccessibleType() final;
|
|
#endif
|
|
|
|
int32_t GetSelectedIndex();
|
|
|
|
/**
|
|
* Gets the text of the currently selected item.
|
|
* If the there are zero items then an empty string is returned
|
|
* If there is nothing selected, then the 0th item's text is returned.
|
|
*/
|
|
void GetOptionText(uint32_t aIndex, nsAString& aStr);
|
|
|
|
void CaptureMouseEvents(bool aGrabMouseEvents);
|
|
nscoord GetBSizeOfARow();
|
|
uint32_t GetNumberOfOptions();
|
|
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY void OnContentReset();
|
|
void OptionsAdded();
|
|
void RemoveOption(int32_t aIndex);
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
|
void DoneAddingChildren();
|
|
|
|
/**
|
|
* Gets the content (an option) by index and then set it as
|
|
* being selected or not selected.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
|
void OnOptionSelected(int32_t aIndex, bool aSelected);
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
|
void OnSetSelectedIndex(int32_t aNewIndex);
|
|
|
|
/**
|
|
* Mouse event listeners.
|
|
* @note These methods might destroy the frame, pres shell and other objects.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT
|
|
nsresult HandleLeftButtonMouseDown(const mozilla::WidgetMouseEvent&);
|
|
MOZ_CAN_RUN_SCRIPT
|
|
nsresult HandleLeftButtonMouseUp();
|
|
MOZ_CAN_RUN_SCRIPT
|
|
nsresult DragMove(const mozilla::WidgetMouseEvent&);
|
|
MOZ_CAN_RUN_SCRIPT
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
bool PerformSelection(int32_t aClickedIndex, bool aIsShift, bool aIsControl);
|
|
MOZ_CAN_RUN_SCRIPT
|
|
void UpdateSelectionAfterKeyEvent(int32_t aNewIndex, uint32_t aCharCode,
|
|
bool aIsShift, bool aIsControlOrMeta,
|
|
bool aIsControlSelectMode);
|
|
|
|
/**
|
|
* Returns the options collection for mContent, if any.
|
|
*/
|
|
mozilla::dom::HTMLOptionsCollection* GetOptions() const;
|
|
/**
|
|
* Returns the HTMLOptionElement for a given index in mContent's collection.
|
|
*/
|
|
HTMLOptionElement* GetOption(uint32_t aIndex) const;
|
|
|
|
// Helper
|
|
bool IsFocused() const;
|
|
|
|
/**
|
|
* Function to paint the focus rect when our nsSelectsAreaFrame is painting.
|
|
* @param aPt the offset of this frame, relative to the rendering reference
|
|
* frame
|
|
*/
|
|
void PaintFocus(mozilla::gfx::DrawTarget* aDrawTarget, nsPoint aPt);
|
|
|
|
/**
|
|
* If this frame IsFocused(), invalidates an area that includes anything
|
|
* that PaintFocus will or could have painted --- basically the whole
|
|
* GetOptionsContainer, plus some extra stuff if there are no options. This
|
|
* must be called every time mEndSelectionIndex changes.
|
|
*/
|
|
void InvalidateFocus();
|
|
|
|
/**
|
|
* Function to calculate the block size of a row, for use with the
|
|
* "size" attribute.
|
|
* Can't be const because GetNumberOfOptions() isn't const.
|
|
*/
|
|
nscoord CalcBSizeOfARow();
|
|
|
|
/**
|
|
* Function to ask whether we're currently in what might be the
|
|
* first pass of a two-pass reflow.
|
|
*/
|
|
bool MightNeedSecondPass() const { return mMightNeedSecondPass; }
|
|
|
|
/**
|
|
* Return the number of displayed rows in the list.
|
|
*/
|
|
uint32_t GetNumDisplayRows() const { return mNumDisplayRows; }
|
|
|
|
#ifdef ACCESSIBILITY
|
|
/**
|
|
* Post a custom DOM event for the change, so that accessibility can
|
|
* fire a native focus event for accessibility
|
|
* (Some 3rd party products need to track our focus)
|
|
*/
|
|
void FireMenuItemActiveEvent(
|
|
nsIContent* aPreviousOption); // Inform assistive tech what got focused
|
|
#endif
|
|
|
|
protected:
|
|
/**
|
|
* Updates the selected text in a combobox and then calls FireOnChange().
|
|
* @note This method might destroy the frame, pres shell and other objects.
|
|
* Returns false if calling it destroyed |this|.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT
|
|
bool UpdateSelection();
|
|
|
|
/**
|
|
* Returns whether mContent supports multiple selection.
|
|
*/
|
|
bool GetMultiple() const;
|
|
|
|
mozilla::dom::HTMLSelectElement& Select() const;
|
|
|
|
/**
|
|
* @return true if the <option> at aIndex is selectable by the user.
|
|
*/
|
|
bool IsOptionInteractivelySelectable(int32_t aIndex) const;
|
|
/**
|
|
* @return true if aOption in aSelect is selectable by the user.
|
|
*/
|
|
static bool IsOptionInteractivelySelectable(
|
|
mozilla::dom::HTMLSelectElement* aSelect,
|
|
mozilla::dom::HTMLOptionElement* aOption);
|
|
|
|
MOZ_CAN_RUN_SCRIPT void ScrollToFrame(HTMLOptionElement& aOptElement);
|
|
|
|
MOZ_CAN_RUN_SCRIPT void ScrollToIndex(int32_t anIndex);
|
|
|
|
public:
|
|
/**
|
|
* Resets the select back to it's original default values;
|
|
* those values as determined by the original HTML
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT void ResetList(bool aAllowScrolling);
|
|
|
|
protected:
|
|
explicit nsListControlFrame(ComputedStyle* aStyle,
|
|
nsPresContext* aPresContext);
|
|
virtual ~nsListControlFrame();
|
|
|
|
/**
|
|
* Sets the mSelectedIndex and mOldSelectedIndex from figuring out what
|
|
* item was selected using content
|
|
* @param aPoint the event point, in listcontrolframe coordinates
|
|
* @return NS_OK if it successfully found the selection
|
|
*/
|
|
nsresult GetIndexFromEvent(const mozilla::WidgetMouseEvent&,
|
|
int32_t& aCurIndex);
|
|
|
|
bool CheckIfAllFramesHere();
|
|
|
|
// guess at a row block size based on our own style.
|
|
nscoord CalcFallbackRowBSize(float aFontSizeInflation);
|
|
|
|
// CalcIntrinsicBSize computes our intrinsic block size (taking the
|
|
// "size" attribute into account). This should only be called in
|
|
// non-dropdown mode.
|
|
nscoord CalcIntrinsicBSize(nscoord aBSizeOfARow, int32_t aNumberOfOptions);
|
|
|
|
// Dropped down stuff
|
|
void SetComboboxItem(int32_t aIndex);
|
|
|
|
// Selection
|
|
bool SetOptionsSelectedFromFrame(int32_t aStartIndex, int32_t aEndIndex,
|
|
bool aValue, bool aClearAll);
|
|
bool ToggleOptionSelectedFromFrame(int32_t aIndex);
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
bool SingleSelection(int32_t aClickedIndex, bool aDoToggle);
|
|
bool ExtendedSelection(int32_t aStartIndex, int32_t aEndIndex,
|
|
bool aClearAll);
|
|
MOZ_CAN_RUN_SCRIPT
|
|
bool HandleListSelection(const mozilla::WidgetMouseEvent&,
|
|
int32_t aClickedIndex);
|
|
void InitSelectionRange(int32_t aClickedIndex);
|
|
|
|
public:
|
|
static constexpr int32_t kNothingSelected = -1;
|
|
|
|
protected:
|
|
nscoord BSizeOfARow() const { return mBSizeOfARow; }
|
|
|
|
/**
|
|
* @return how many displayable options/optgroups this frame has.
|
|
*/
|
|
uint32_t GetNumberOfRows();
|
|
|
|
// Data Members
|
|
int32_t mStartSelectionIndex = 0;
|
|
int32_t mEndSelectionIndex = 0;
|
|
|
|
uint32_t mNumDisplayRows = 0;
|
|
nscoord mBSizeOfARow = -1;
|
|
bool mChangesSinceDragStart : 1;
|
|
|
|
bool mNeedToReset : 1;
|
|
bool mPostChildrenLoadedReset : 1;
|
|
|
|
// True if we're in the middle of a reflow and might need a second
|
|
// pass. This only happens for auto heights.
|
|
bool mMightNeedSecondPass : 1;
|
|
|
|
// True if our reflow got interrupted.
|
|
bool mReflowWasInterrupted : 1;
|
|
};
|
|
|
|
#endif /* nsListControlFrame_h_ */
|