mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			536 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			536 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* 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 "nsComboboxControlFrame.h"
 | 
						|
 | 
						|
#include "gfxContext.h"
 | 
						|
#include "gfxUtils.h"
 | 
						|
#include "nsCOMPtr.h"
 | 
						|
#include "nsDeviceContext.h"
 | 
						|
#include "nsFocusManager.h"
 | 
						|
#include "nsGkAtoms.h"
 | 
						|
#include "nsHTMLParts.h"
 | 
						|
#include "nsIFormControl.h"
 | 
						|
#include "nsILayoutHistoryState.h"
 | 
						|
#include "nsListControlFrame.h"
 | 
						|
#include "nsPIDOMWindow.h"
 | 
						|
#include "nsView.h"
 | 
						|
#include "nsViewManager.h"
 | 
						|
#include "nsISelectControlFrame.h"
 | 
						|
#include "nsContentUtils.h"
 | 
						|
#include "mozilla/dom/Event.h"
 | 
						|
#include "mozilla/dom/HTMLSelectElement.h"
 | 
						|
#include "mozilla/dom/Document.h"
 | 
						|
#include "mozilla/ServoStyleSet.h"
 | 
						|
#include "nsNodeInfoManager.h"
 | 
						|
#include "nsContentCreatorFunctions.h"
 | 
						|
#include "nsLayoutUtils.h"
 | 
						|
#include "nsDisplayList.h"
 | 
						|
#include "nsITheme.h"
 | 
						|
#include "nsStyleConsts.h"
 | 
						|
#include "nsTextFrameUtils.h"
 | 
						|
#include "nsTextRunTransformations.h"
 | 
						|
#include "HTMLSelectEventListener.h"
 | 
						|
#include "mozilla/Likely.h"
 | 
						|
#include <algorithm>
 | 
						|
#include "nsTextNode.h"
 | 
						|
#include "mozilla/AsyncEventDispatcher.h"
 | 
						|
#include "mozilla/LookAndFeel.h"
 | 
						|
#include "mozilla/PresShell.h"
 | 
						|
#include "mozilla/PresShellInlines.h"
 | 
						|
 | 
						|
using namespace mozilla;
 | 
						|
using namespace mozilla::gfx;
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsComboboxControlFrame::RedisplayTextEvent::Run() {
 | 
						|
  if (mControlFrame) mControlFrame->HandleRedisplayTextEvent();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
// Drop down list event management.
 | 
						|
// The combo box uses the following strategy for managing the drop-down list.
 | 
						|
// If the combo box or its arrow button is clicked on the drop-down list is
 | 
						|
// displayed If mouse exits the combo box with the drop-down list displayed the
 | 
						|
// drop-down list is asked to capture events The drop-down list will capture all
 | 
						|
// events including mouse down and up and will always return with
 | 
						|
// ListWasSelected method call regardless of whether an item in the list was
 | 
						|
// actually selected.
 | 
						|
// The ListWasSelected code will turn off mouse-capture for the drop-down list.
 | 
						|
// The drop-down list does not explicitly set capture when it is in the
 | 
						|
// drop-down mode.
 | 
						|
 | 
						|
nsComboboxControlFrame* NS_NewComboboxControlFrame(PresShell* aPresShell,
 | 
						|
                                                   ComputedStyle* aStyle) {
 | 
						|
  nsComboboxControlFrame* it = new (aPresShell)
 | 
						|
      nsComboboxControlFrame(aStyle, aPresShell->GetPresContext());
 | 
						|
  return it;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
 | 
						|
 | 
						|
nsComboboxControlFrame::nsComboboxControlFrame(ComputedStyle* aStyle,
 | 
						|
                                               nsPresContext* aPresContext)
 | 
						|
    : nsHTMLButtonControlFrame(aStyle, aPresContext, kClassID) {}
 | 
						|
 | 
						|
nsComboboxControlFrame::~nsComboboxControlFrame() = default;
 | 
						|
 | 
						|
NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
 | 
						|
  NS_QUERYFRAME_ENTRY(nsComboboxControlFrame)
 | 
						|
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
 | 
						|
  NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
 | 
						|
NS_QUERYFRAME_TAIL_INHERITING(nsHTMLButtonControlFrame)
 | 
						|
 | 
						|
#ifdef ACCESSIBILITY
 | 
						|
a11y::AccType nsComboboxControlFrame::AccessibleType() {
 | 
						|
  return a11y::eHTMLComboboxType;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
bool nsComboboxControlFrame::HasDropDownButton() const {
 | 
						|
  const nsStyleDisplay* disp = StyleDisplay();
 | 
						|
  // FIXME(emilio): Blink also shows this for menulist-button and such... Seems
 | 
						|
  // more similar to our mac / linux implementation.
 | 
						|
  return disp->EffectiveAppearance() == StyleAppearance::Menulist &&
 | 
						|
         (!IsThemed(disp) ||
 | 
						|
          PresContext()->Theme()->ThemeNeedsComboboxDropmarker());
 | 
						|
}
 | 
						|
 | 
						|
nscoord nsComboboxControlFrame::DropDownButtonISize() {
 | 
						|
  if (!HasDropDownButton()) {
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  nsPresContext* pc = PresContext();
 | 
						|
  LayoutDeviceIntSize dropdownButtonSize = pc->Theme()->GetMinimumWidgetSize(
 | 
						|
      pc, this, StyleAppearance::MozMenulistArrowButton);
 | 
						|
  return pc->DevPixelsToAppUnits(dropdownButtonSize.width);
 | 
						|
}
 | 
						|
 | 
						|
int32_t nsComboboxControlFrame::CharCountOfLargestOptionForInflation() const {
 | 
						|
  uint32_t maxLength = 0;
 | 
						|
  nsAutoString label;
 | 
						|
  for (auto i : IntegerRange(Select().Options()->Length())) {
 | 
						|
    GetOptionText(i, label);
 | 
						|
    maxLength = std::max(
 | 
						|
        maxLength,
 | 
						|
        nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
 | 
						|
            label, StyleText()));
 | 
						|
  }
 | 
						|
  if (MOZ_UNLIKELY(maxLength > uint32_t(INT32_MAX))) {
 | 
						|
    return INT32_MAX;
 | 
						|
  }
 | 
						|
  return int32_t(maxLength);
 | 
						|
}
 | 
						|
 | 
						|
nscoord nsComboboxControlFrame::GetLongestOptionISize(
 | 
						|
    gfxContext* aRenderingContext) const {
 | 
						|
  // Compute the width of each option's (potentially text-transformed) text,
 | 
						|
  // and use the widest one as part of our intrinsic size.
 | 
						|
  nscoord maxOptionSize = 0;
 | 
						|
  nsAutoString label;
 | 
						|
  nsAutoString transformedLabel;
 | 
						|
  RefPtr<nsFontMetrics> fm =
 | 
						|
      nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
 | 
						|
  const nsStyleText* textStyle = StyleText();
 | 
						|
  auto textTransform = textStyle->mTextTransform.IsNone()
 | 
						|
                           ? Nothing()
 | 
						|
                           : Some(textStyle->mTextTransform);
 | 
						|
  nsAtom* language = StyleFont()->mLanguage;
 | 
						|
  AutoTArray<bool, 50> charsToMergeArray;
 | 
						|
  AutoTArray<bool, 50> deletedCharsArray;
 | 
						|
  for (auto i : IntegerRange(Select().Options()->Length())) {
 | 
						|
    GetOptionText(i, label);
 | 
						|
    const nsAutoString* stringToUse = &label;
 | 
						|
    if (textTransform ||
 | 
						|
        textStyle->mWebkitTextSecurity != StyleTextSecurity::None) {
 | 
						|
      transformedLabel.Truncate();
 | 
						|
      charsToMergeArray.SetLengthAndRetainStorage(0);
 | 
						|
      deletedCharsArray.SetLengthAndRetainStorage(0);
 | 
						|
      nsCaseTransformTextRunFactory::TransformString(
 | 
						|
          label, transformedLabel, textTransform,
 | 
						|
          textStyle->TextSecurityMaskChar(),
 | 
						|
          /* aCaseTransformsOnly = */ false, language, charsToMergeArray,
 | 
						|
          deletedCharsArray);
 | 
						|
      stringToUse = &transformedLabel;
 | 
						|
    }
 | 
						|
    maxOptionSize = std::max(maxOptionSize,
 | 
						|
                             nsLayoutUtils::AppUnitWidthOfStringBidi(
 | 
						|
                                 *stringToUse, this, *fm, *aRenderingContext));
 | 
						|
  }
 | 
						|
  if (maxOptionSize) {
 | 
						|
    // HACK: Add one app unit to workaround silly Netgear router styling, see
 | 
						|
    // bug 1769580. In practice since this comes from font metrics is unlikely
 | 
						|
    // to be perceivable.
 | 
						|
    maxOptionSize += 1;
 | 
						|
  }
 | 
						|
  return maxOptionSize;
 | 
						|
}
 | 
						|
 | 
						|
nscoord nsComboboxControlFrame::GetIntrinsicISize(gfxContext* aRenderingContext,
 | 
						|
                                                  IntrinsicISizeType aType) {
 | 
						|
  Maybe<nscoord> containISize = ContainIntrinsicISize(NS_UNCONSTRAINEDSIZE);
 | 
						|
  if (containISize && *containISize != NS_UNCONSTRAINEDSIZE) {
 | 
						|
    return *containISize;
 | 
						|
  }
 | 
						|
 | 
						|
  nscoord displayISize = 0;
 | 
						|
  if (!containISize && !StyleContent()->mContent.IsNone()) {
 | 
						|
    displayISize += GetLongestOptionISize(aRenderingContext);
 | 
						|
  }
 | 
						|
 | 
						|
  // Add room for the dropmarker button (if there is one).
 | 
						|
  displayISize += DropDownButtonISize();
 | 
						|
  return displayISize;
 | 
						|
}
 | 
						|
 | 
						|
nscoord nsComboboxControlFrame::GetMinISize(gfxContext* aRenderingContext) {
 | 
						|
  nscoord minISize;
 | 
						|
  minISize = GetIntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
 | 
						|
  return minISize;
 | 
						|
}
 | 
						|
 | 
						|
nscoord nsComboboxControlFrame::GetPrefISize(gfxContext* aRenderingContext) {
 | 
						|
  nscoord prefISize;
 | 
						|
  prefISize =
 | 
						|
      GetIntrinsicISize(aRenderingContext, IntrinsicISizeType::PrefISize);
 | 
						|
  return prefISize;
 | 
						|
}
 | 
						|
 | 
						|
dom::HTMLSelectElement& nsComboboxControlFrame::Select() const {
 | 
						|
  return *static_cast<dom::HTMLSelectElement*>(GetContent());
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::GetOptionText(uint32_t aIndex,
 | 
						|
                                           nsAString& aText) const {
 | 
						|
  aText.Truncate();
 | 
						|
  if (Element* el = Select().Options()->GetElementAt(aIndex)) {
 | 
						|
    static_cast<dom::HTMLOptionElement*>(el)->GetRenderedLabel(aText);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
 | 
						|
                                    ReflowOutput& aDesiredSize,
 | 
						|
                                    const ReflowInput& aReflowInput,
 | 
						|
                                    nsReflowStatus& aStatus) {
 | 
						|
  MarkInReflow();
 | 
						|
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 | 
						|
  // Constraints we try to satisfy:
 | 
						|
 | 
						|
  // 1) Default inline size of button is the vertical scrollbar size
 | 
						|
  // 2) If the inline size of button is bigger than our inline size, set
 | 
						|
  //    inline size of button to 0.
 | 
						|
  // 3) Default block size of button is block size of display area
 | 
						|
  // 4) Inline size of display area is whatever is left over from our
 | 
						|
  //    inline size after allocating inline size for the button.
 | 
						|
  // Make sure the displayed text is the same as the selected option,
 | 
						|
  // bug 297389.
 | 
						|
  mDisplayedIndex = Select().SelectedIndex();
 | 
						|
 | 
						|
  // In dropped down mode the "selected index" is the hovered menu item,
 | 
						|
  // we want the last selected item which is |mDisplayedIndex| in this case.
 | 
						|
  RedisplayText();
 | 
						|
 | 
						|
  WritingMode wm = aReflowInput.GetWritingMode();
 | 
						|
 | 
						|
  // Check if the theme specifies a minimum size for the dropdown button
 | 
						|
  // first.
 | 
						|
  const nscoord buttonISize = DropDownButtonISize();
 | 
						|
  const auto padding = aReflowInput.ComputedLogicalPadding(wm);
 | 
						|
 | 
						|
  // We ignore inline-end-padding (by adding it to our label box size) if we
 | 
						|
  // have a dropdown button, so that the button aligns with the end of the
 | 
						|
  // padding box.
 | 
						|
  mDisplayISize = aReflowInput.ComputedISize() - buttonISize;
 | 
						|
  if (buttonISize) {
 | 
						|
    mDisplayISize += padding.IEnd(wm);
 | 
						|
  }
 | 
						|
 | 
						|
  nsHTMLButtonControlFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
 | 
						|
                                   aStatus);
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::Init(nsIContent* aContent,
 | 
						|
                                  nsContainerFrame* aParent,
 | 
						|
                                  nsIFrame* aPrevInFlow) {
 | 
						|
  nsHTMLButtonControlFrame::Init(aContent, aParent, aPrevInFlow);
 | 
						|
 | 
						|
  mEventListener = new HTMLSelectEventListener(
 | 
						|
      Select(), HTMLSelectEventListener::SelectType::Combobox);
 | 
						|
}
 | 
						|
 | 
						|
nsresult nsComboboxControlFrame::RedisplaySelectedText() {
 | 
						|
  nsAutoScriptBlocker scriptBlocker;
 | 
						|
  mDisplayedIndex = Select().SelectedIndex();
 | 
						|
  return RedisplayText();
 | 
						|
}
 | 
						|
 | 
						|
nsresult nsComboboxControlFrame::RedisplayText() {
 | 
						|
  nsString previewValue;
 | 
						|
  nsString previousText(mDisplayedOptionTextOrPreview);
 | 
						|
 | 
						|
  Select().GetPreviewValue(previewValue);
 | 
						|
  // Get the text to display
 | 
						|
  if (!previewValue.IsEmpty()) {
 | 
						|
    mDisplayedOptionTextOrPreview = previewValue;
 | 
						|
  } else if (mDisplayedIndex != -1 && !StyleContent()->mContent.IsNone()) {
 | 
						|
    GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
 | 
						|
  } else {
 | 
						|
    mDisplayedOptionTextOrPreview.Truncate();
 | 
						|
  }
 | 
						|
 | 
						|
  // Send reflow command because the new text maybe larger
 | 
						|
  nsresult rv = NS_OK;
 | 
						|
  if (!previousText.Equals(mDisplayedOptionTextOrPreview)) {
 | 
						|
    // Don't call ActuallyDisplayText(true) directly here since that could cause
 | 
						|
    // recursive frame construction. See bug 283117 and the comment in
 | 
						|
    // HandleRedisplayTextEvent() below.
 | 
						|
 | 
						|
    // Revoke outstanding events to avoid out-of-order events which could mean
 | 
						|
    // displaying the wrong text.
 | 
						|
    mRedisplayTextEvent.Revoke();
 | 
						|
 | 
						|
    NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
 | 
						|
                 "If we happen to run our redisplay event now, we might kill "
 | 
						|
                 "ourselves!");
 | 
						|
    mRedisplayTextEvent = new RedisplayTextEvent(this);
 | 
						|
    nsContentUtils::AddScriptRunner(mRedisplayTextEvent.get());
 | 
						|
  }
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::HandleRedisplayTextEvent() {
 | 
						|
  // First, make sure that the content model is up to date and we've constructed
 | 
						|
  // the frames for all our content in the right places. Otherwise they'll end
 | 
						|
  // up under the wrong insertion frame when we ActuallyDisplayText, since that
 | 
						|
  // flushes out the content sink by calling SetText on a DOM node with aNotify
 | 
						|
  // set to true.  See bug 289730.
 | 
						|
  AutoWeakFrame weakThis(this);
 | 
						|
  PresContext()->Document()->FlushPendingNotifications(
 | 
						|
      FlushType::ContentAndNotify);
 | 
						|
  if (!weakThis.IsAlive()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  mRedisplayTextEvent.Forget();
 | 
						|
  ActuallyDisplayText(true);
 | 
						|
  // Note: `this` might be dead here.
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::ActuallyDisplayText(bool aNotify) {
 | 
						|
  RefPtr<dom::Text> displayContent = mDisplayLabel->GetFirstChild()->AsText();
 | 
						|
  // Have to use a space character of some sort for line-block-size calculations
 | 
						|
  // to be right. Also, the space character must be zero-width in order for the
 | 
						|
  // inline-size calculations to be consistent between size-contained comboboxes
 | 
						|
  // vs. empty comboboxes.
 | 
						|
  //
 | 
						|
  // XXXdholbert Does this space need to be "non-breaking"? I'm not sure if it
 | 
						|
  // matters, but we previously had a comment here (added in 2002) saying "Have
 | 
						|
  // to use a non-breaking space for line-height calculations to be right". So
 | 
						|
  // I'll stick with a non-breaking space for now...
 | 
						|
  displayContent->SetText(mDisplayedOptionTextOrPreview.IsEmpty()
 | 
						|
                              ? u"\ufeff"_ns
 | 
						|
                              : mDisplayedOptionTextOrPreview,
 | 
						|
                          aNotify);
 | 
						|
}
 | 
						|
 | 
						|
bool nsComboboxControlFrame::IsDroppedDown() const {
 | 
						|
  return Select().OpenInParentProcess();
 | 
						|
}
 | 
						|
 | 
						|
//----------------------------------------------------------------------
 | 
						|
// nsISelectControlFrame
 | 
						|
//----------------------------------------------------------------------
 | 
						|
NS_IMETHODIMP
 | 
						|
nsComboboxControlFrame::DoneAddingChildren(bool aIsDone) { return NS_OK; }
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsComboboxControlFrame::AddOption(int32_t aIndex) {
 | 
						|
  if (aIndex <= mDisplayedIndex) {
 | 
						|
    ++mDisplayedIndex;
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
nsComboboxControlFrame::RemoveOption(int32_t aIndex) {
 | 
						|
  if (Select().Options()->Length()) {
 | 
						|
    if (aIndex < mDisplayedIndex) {
 | 
						|
      --mDisplayedIndex;
 | 
						|
    } else if (aIndex == mDisplayedIndex) {
 | 
						|
      mDisplayedIndex = 0;  // IE6 compat
 | 
						|
      RedisplayText();
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // If we removed the last option, we need to blank things out
 | 
						|
    mDisplayedIndex = -1;
 | 
						|
    RedisplayText();
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP_(void)
 | 
						|
nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex,
 | 
						|
                                           int32_t aNewIndex) {
 | 
						|
  nsAutoScriptBlocker scriptBlocker;
 | 
						|
  mDisplayedIndex = aNewIndex;
 | 
						|
  RedisplayText();
 | 
						|
}
 | 
						|
 | 
						|
// End nsISelectControlFrame
 | 
						|
//----------------------------------------------------------------------
 | 
						|
 | 
						|
nsresult nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
 | 
						|
                                             WidgetGUIEvent* aEvent,
 | 
						|
                                             nsEventStatus* aEventStatus) {
 | 
						|
  NS_ENSURE_ARG_POINTER(aEventStatus);
 | 
						|
 | 
						|
  if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  return nsHTMLButtonControlFrame::HandleEvent(aPresContext, aEvent,
 | 
						|
                                               aEventStatus);
 | 
						|
}
 | 
						|
 | 
						|
nsresult nsComboboxControlFrame::CreateAnonymousContent(
 | 
						|
    nsTArray<ContentInfo>& aElements) {
 | 
						|
  dom::Document* doc = mContent->OwnerDoc();
 | 
						|
  mDisplayLabel = doc->CreateHTMLElement(nsGkAtoms::label);
 | 
						|
 | 
						|
  {
 | 
						|
    RefPtr<nsTextNode> text = doc->CreateEmptyTextNode();
 | 
						|
    mDisplayLabel->AppendChildTo(text, false, IgnoreErrors());
 | 
						|
  }
 | 
						|
 | 
						|
  // set the value of the text node
 | 
						|
  mDisplayedIndex = Select().SelectedIndex();
 | 
						|
  if (mDisplayedIndex != -1) {
 | 
						|
    GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
 | 
						|
  }
 | 
						|
  ActuallyDisplayText(false);
 | 
						|
 | 
						|
  aElements.AppendElement(mDisplayLabel);
 | 
						|
  if (HasDropDownButton()) {
 | 
						|
    mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
 | 
						|
    {
 | 
						|
      // This gives the button a reasonable height. This could be done via CSS
 | 
						|
      // instead, but relative font units like 1lh don't play very well with our
 | 
						|
      // font inflation implementation, so we do it this way instead.
 | 
						|
      RefPtr<nsTextNode> text = doc->CreateTextNode(u"\ufeff"_ns);
 | 
						|
      mButtonContent->AppendChildTo(text, false, IgnoreErrors());
 | 
						|
    }
 | 
						|
    // Make someone to listen to the button.
 | 
						|
    mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type, u"button"_ns,
 | 
						|
                            false);
 | 
						|
    // Set tabindex="-1" so that the button is not tabbable
 | 
						|
    mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, u"-1"_ns,
 | 
						|
                            false);
 | 
						|
    aElements.AppendElement(mButtonContent);
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::AppendAnonymousContentTo(
 | 
						|
    nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
 | 
						|
  if (mDisplayLabel) {
 | 
						|
    aElements.AppendElement(mDisplayLabel);
 | 
						|
  }
 | 
						|
 | 
						|
  if (mButtonContent) {
 | 
						|
    aElements.AppendElement(mButtonContent);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
class ComboboxLabelFrame final : public nsBlockFrame {
 | 
						|
 public:
 | 
						|
  NS_DECL_QUERYFRAME
 | 
						|
  NS_DECL_FRAMEARENA_HELPERS(ComboboxLabelFrame)
 | 
						|
 | 
						|
#ifdef DEBUG_FRAME_DUMP
 | 
						|
  nsresult GetFrameName(nsAString& aResult) const final {
 | 
						|
    return MakeFrameName(u"ComboboxLabel"_ns, aResult);
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
 | 
						|
              const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final;
 | 
						|
 | 
						|
 public:
 | 
						|
  ComboboxLabelFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
 | 
						|
      : nsBlockFrame(aStyle, aPresContext, kClassID) {}
 | 
						|
};
 | 
						|
 | 
						|
NS_QUERYFRAME_HEAD(ComboboxLabelFrame)
 | 
						|
  NS_QUERYFRAME_ENTRY(ComboboxLabelFrame)
 | 
						|
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
 | 
						|
NS_IMPL_FRAMEARENA_HELPERS(ComboboxLabelFrame)
 | 
						|
 | 
						|
void ComboboxLabelFrame::Reflow(nsPresContext* aPresContext,
 | 
						|
                                ReflowOutput& aDesiredSize,
 | 
						|
                                const ReflowInput& aReflowInput,
 | 
						|
                                nsReflowStatus& aStatus) {
 | 
						|
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 | 
						|
 | 
						|
  const nsComboboxControlFrame* combobox =
 | 
						|
      do_QueryFrame(GetParent()->GetParent());
 | 
						|
  MOZ_ASSERT(combobox, "Combobox's frame tree is wrong!");
 | 
						|
  MOZ_ASSERT(aReflowInput.ComputedPhysicalBorderPadding() == nsMargin(),
 | 
						|
             "We shouldn't have border and padding in UA!");
 | 
						|
 | 
						|
  ReflowInput state(aReflowInput);
 | 
						|
  state.SetComputedISize(combobox->mDisplayISize);
 | 
						|
  nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
 | 
						|
  aStatus.Reset();  // this type of frame can't be split
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla
 | 
						|
 | 
						|
nsIFrame* NS_NewComboboxLabelFrame(PresShell* aPresShell,
 | 
						|
                                   ComputedStyle* aStyle) {
 | 
						|
  return new (aPresShell)
 | 
						|
      ComboboxLabelFrame(aStyle, aPresShell->GetPresContext());
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::Destroy(DestroyContext& aContext) {
 | 
						|
  // Revoke any pending RedisplayTextEvent
 | 
						|
  mRedisplayTextEvent.Revoke();
 | 
						|
  mEventListener->Detach();
 | 
						|
 | 
						|
  aContext.AddAnonymousContent(mDisplayLabel.forget());
 | 
						|
  aContext.AddAnonymousContent(mButtonContent.forget());
 | 
						|
  nsHTMLButtonControlFrame::Destroy(aContext);
 | 
						|
}
 | 
						|
 | 
						|
//---------------------------------------------------------
 | 
						|
// gets the content (an option) by index and then set it as
 | 
						|
// being selected or not selected
 | 
						|
//---------------------------------------------------------
 | 
						|
NS_IMETHODIMP
 | 
						|
nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected) {
 | 
						|
  if (aSelected) {
 | 
						|
    nsAutoScriptBlocker blocker;
 | 
						|
    mDisplayedIndex = aIndex;
 | 
						|
    RedisplayText();
 | 
						|
  } else {
 | 
						|
    AutoWeakFrame weakFrame(this);
 | 
						|
    RedisplaySelectedText();
 | 
						|
    if (weakFrame.IsAlive()) {
 | 
						|
      FireValueChangeEvent();  // Fire after old option is unselected
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void nsComboboxControlFrame::FireValueChangeEvent() {
 | 
						|
  // Fire ValueChange event to indicate data value of combo box has changed
 | 
						|
  // FIXME(emilio): This shouldn't be exposed to content.
 | 
						|
  nsContentUtils::AddScriptRunner(new AsyncEventDispatcher(
 | 
						|
      mContent, u"ValueChange"_ns, CanBubble::eYes, ChromeOnlyDispatch::eNo));
 | 
						|
}
 |