Bug 1246836 - Fallback to render customized button when -moz-appearance is button with border or background is set. draft
authorLouis Chang <lochang@mozilla.com>
Tue, 19 Dec 2017 17:41:43 +0800
changeset 712975 863fc11f458a08a8464f2f6e6b8bb3a3fbf80a9e
parent 712974 12214b5efbe48a6e6ccb44ce33e9d4ffb591bb28
child 744209 b1d62f098e9eeb517edff9ee36da651c78256cf8
push id93508
push userlochang@mozilla.com
push dateTue, 19 Dec 2017 09:43:02 +0000
bugs1246836
milestone59.0a1
Bug 1246836 - Fallback to render customized button when -moz-appearance is button with border or background is set. MozReview-Commit-ID: 5oNmAuaHrwi
layout/base/nsCSSFrameConstructor.cpp
layout/forms/nsComboboxControlFrame.cpp
layout/forms/nsComboboxControlFrame.h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -3253,20 +3253,20 @@ nsCSSFrameConstructor::ConstructSelectFr
     // Create display and button frames from the combobox's anonymous content.
     // The anonymous content is appended to existing anonymous content for this
     // element (the scrollbars).
     nsFrameItems childItems;
 
     // nsComboboxControlFrame needs special frame creation behavior for its first
     // piece of anonymous content, which means that we can't take the normal
     // ProcessChildren path.
-    AutoTArray<nsIAnonymousContentCreator::ContentInfo, 2> newAnonymousItems;
+    AutoTArray<nsIAnonymousContentCreator::ContentInfo, 3> newAnonymousItems;
     DebugOnly<nsresult> rv = GetAnonymousContent(content, comboboxFrame, newAnonymousItems);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(newAnonymousItems.Length() == 2);
+    MOZ_ASSERT(newAnonymousItems.Length() == 3);
 
     // Manually create a frame for the special NAC.
     MOZ_ASSERT(newAnonymousItems[0].mContent == comboboxFrame->GetDisplayNode());
     newAnonymousItems.RemoveElementAt(0);
     nsIFrame* customFrame = comboboxFrame->CreateFrameForDisplayNode();
     MOZ_ASSERT(customFrame);
     customFrame->AddStateBits(NS_FRAME_ANONYMOUSCONTENTCREATOR_CONTENT);
     childItems.AddChild(customFrame);
--- a/layout/forms/nsComboboxControlFrame.cpp
+++ b/layout/forms/nsComboboxControlFrame.cpp
@@ -222,16 +222,17 @@ static int32_t gReflowInx = -1;
 //------------------------------------------------------
 //-- Done with macros
 //------------------------------------------------------
 
 nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
   : nsBlockFrame(aContext, kClassID)
   , mDisplayFrame(nullptr)
   , mButtonFrame(nullptr)
+  , mAppearanceFrame(nullptr)
   , mDropdownFrame(nullptr)
   , mListControlFrame(nullptr)
   , mDisplayISize(0)
   , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
   , mDisplayedIndex(-1)
   , mLastDropDownBeforeScreenBCoord(nscoord_MIN)
   , mLastDropDownAfterScreenBCoord(nscoord_MIN)
   , mDroppedDown(false)
@@ -813,16 +814,29 @@ nscoord
 nsComboboxControlFrame::GetPrefISize(gfxContext *aRenderingContext)
 {
   nscoord prefISize;
   DISPLAY_PREF_WIDTH(this, prefISize);
   prefISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
   return prefISize;
 }
 
+bool nsComboboxControlFrame::IsMenulistAppearance() {
+  return this->StyleDisplay()->mAppearance == NS_THEME_MENULIST;
+}
+
+nsAtom* nsComboboxControlFrame::GetSupportAppearanceType() {
+  switch (this->StyleDisplay()->mAppearance) {
+    case NS_THEME_BUTTON:
+      return nsGkAtoms::button;
+    default:
+      return nsGkAtoms::none;
+  }
+}
+
 void
 nsComboboxControlFrame::Reflow(nsPresContext*          aPresContext,
                                ReflowOutput&     aDesiredSize,
                                const ReflowInput& aReflowInput,
                                nsReflowStatus&          aStatus)
 {
   MarkInReflow();
   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
@@ -858,53 +872,71 @@ nsComboboxControlFrame::Reflow(nsPresCon
     // The reflow callback queue doesn't AddRef so we keep it alive until
     // it's released in its ReflowFinished / ReflowCallbackCanceled.
     Unused << resize.forget();
   }
 
   // Get the width of the vertical scrollbar.  That will be the inline
   // size of the dropdown button.
   WritingMode wm = aReflowInput.GetWritingMode();
-  nscoord buttonISize;
+  nscoord buttonISize, appearanceISize;
   const nsStyleDisplay *disp = StyleDisplay();
   if ((IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) ||
       StyleDisplay()->mAppearance == NS_THEME_NONE) {
     buttonISize = 0;
   }
   else {
     nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
     NS_ASSERTION(scrollable, "List must be a scrollable frame");
     buttonISize = scrollable->GetNondisappearingScrollbarWidth(
       PresContext(), aReflowInput.mRenderingContext, wm);
     if (buttonISize > aReflowInput.ComputedISize()) {
       buttonISize = 0;
     }
   }
 
-  mDisplayISize = aReflowInput.ComputedISize() - buttonISize;
+  if (!IsThemed() && !IsMenulistAppearance()) {
+    mDisplayISize = aReflowInput.ComputedISize();
+    buttonISize = 0;
+    appearanceISize = mDisplayISize;
+  } else {
+    mDisplayISize = aReflowInput.ComputedISize() - buttonISize;
+    appearanceISize = 0;
+  }
+
 
   nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
 
   // The button should occupy the same space as a scrollbar
   nsSize containerSize = aDesiredSize.PhysicalSize();
   LogicalRect buttonRect = mButtonFrame->GetLogicalRect(containerSize);
+  LogicalRect apprRect = mAppearanceFrame->GetLogicalRect(containerSize);
 
   buttonRect.IStart(wm) =
     aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) +
     mDisplayISize -
     (aReflowInput.ComputedLogicalBorderPadding().IEnd(wm) -
      aReflowInput.ComputedLogicalPadding().IEnd(wm));
   buttonRect.ISize(wm) = buttonISize;
 
   buttonRect.BStart(wm) = this->GetLogicalUsedBorder(wm).BStart(wm);
   buttonRect.BSize(wm) = mDisplayFrame->BSize(wm) +
                          this->GetLogicalUsedPadding(wm).BStartEnd(wm);
 
   mButtonFrame->SetRect(buttonRect, containerSize);
 
+  apprRect.IStart(wm) = aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
+  apprRect.ISize(wm) = appearanceISize;
+
+  apprRect.BStart(wm) = this->GetLogicalUsedBorder(wm).BStart(wm);
+  apprRect.BSize(wm) = mDisplayFrame->BSize(wm) +
+                       this->GetLogicalUsedPadding(wm).BStartEnd(wm);
+
+  mAppearanceFrame->SetRect(apprRect, containerSize);
+
   if (!aStatus.IsInlineBreakBefore() &&
       !aStatus.IsFullyComplete()) {
     // This frame didn't fit inside a fragmentation container.  Splitting
     // a nsComboboxControlFrame makes no sense, so we override the status here.
     aStatus.Reset();
   }
 }
 
@@ -1051,23 +1083,30 @@ nsComboboxControlFrame::HandleRedisplayT
                                                NS_FRAME_IS_DIRTY);
 
   mInRedisplayText = false;
 }
 
 void
 nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
 {
+  // Have to use a non-breaking space for line-block-size calculations
+  // to be right
+  static const char16_t space = 0xA0;
   if (mDisplayedOptionTextOrPreview.IsEmpty()) {
-    // Have to use a non-breaking space for line-block-size calculations
-    // to be right
-    static const char16_t space = 0xA0;
     mDisplayContent->SetText(&space, 1, aNotify);
+    mTextContent->SetText(&space, 1, aNotify);
   } else {
-    mDisplayContent->SetText(mDisplayedOptionTextOrPreview, aNotify);
+    if (!IsThemed() && !IsMenulistAppearance()) {
+      mDisplayContent->SetText(&space, 1, aNotify);
+      mTextContent->SetText(mDisplayedOptionTextOrPreview, aNotify);
+    } else {
+      mDisplayContent->SetText(mDisplayedOptionTextOrPreview, aNotify);
+      mTextContent->SetText(&space, 1, aNotify);
+    }
   }
 }
 
 int32_t
 nsComboboxControlFrame::GetIndexOfDisplayArea()
 {
   return mDisplayedIndex;
 }
@@ -1225,17 +1264,16 @@ nsComboboxControlFrame::CreateAnonymousC
 
   mDisplayContent = new nsTextNode(nimgr);
 
   // set the value of the text node
   mDisplayedIndex = mListControlFrame->GetSelectedIndex();
   if (mDisplayedIndex != -1) {
     mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
   }
-  ActuallyDisplayText(false);
 
   if (!aElements.AppendElement(mDisplayContent))
     return NS_ERROR_OUT_OF_MEMORY;
 
   mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
   if (!mButtonContent)
     return NS_ERROR_OUT_OF_MEMORY;
 
@@ -1257,30 +1295,59 @@ nsComboboxControlFrame::CreateAnonymousC
                             wm.IsVerticalRL() ? NS_LITERAL_STRING("left")
                                               : NS_LITERAL_STRING("right"),
                             false);
   }
 
   if (!aElements.AppendElement(mButtonContent))
     return NS_ERROR_OUT_OF_MEMORY;
 
+  nsAtom* apprType = GetSupportAppearanceType();
+  mAppearanceContent = mContent->OwnerDoc()->CreateHTMLElement(apprType);
+  if (!mAppearanceContent)
+    return NS_ERROR_OUT_OF_MEMORY;
+  mButtonListener = new nsComboButtonListener(this);
+  mAppearanceContent->AddEventListener(NS_LITERAL_STRING("click"),
+                                       mButtonListener, false, false);
+
+  mAppearanceContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
+                              NS_LITERAL_STRING("-1"), false);
+
+  if (wm.IsVertical()) {
+    mAppearanceContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orientation,
+                                wm.IsVerticalRL() ? NS_LITERAL_STRING("left")
+                                                  : NS_LITERAL_STRING("right"),
+                                false);
+  }
+
+  mTextContent = new nsTextNode(mAppearanceContent->NodeInfo()->NodeInfoManager());
+  mAppearanceContent->AppendChildTo(mTextContent, false);
+
+  if (!aElements.AppendElement(mAppearanceContent))
+    return NS_ERROR_OUT_OF_MEMORY;
+  ActuallyDisplayText(false);
+
   return NS_OK;
 }
 
 void
 nsComboboxControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
                                                  uint32_t aFilter)
 {
   if (mDisplayContent) {
     aElements.AppendElement(mDisplayContent);
   }
 
   if (mButtonContent) {
     aElements.AppendElement(mButtonContent);
   }
+
+  if (mAppearanceContent) {
+    aElements.AppendElement(mAppearanceContent);
+  }
 }
 
 // XXXbz this is a for-now hack.  Now that display:inline-block works,
 // need to revisit this.
 class nsComboboxDisplayFrame : public nsBlockFrame {
 public:
   NS_DECL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
 
@@ -1414,16 +1481,17 @@ nsComboboxControlFrame::DestroyFrom(nsIF
       widget->CaptureRollupEvents(this, false);
     }
   }
 
   // Cleanup frames in popup child list
   mPopupFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
   aPostDestroyData.AddAnonymousContent(mDisplayContent.forget());
   aPostDestroyData.AddAnonymousContent(mButtonContent.forget());
+  aPostDestroyData.AddAnonymousContent(mAppearanceContent.forget());
   nsBlockFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
 
 const nsFrameList&
 nsComboboxControlFrame::GetChildList(ChildListID aListID) const
 {
   if (kSelectPopupList == aListID) {
     return mPopupFrames;
@@ -1445,16 +1513,18 @@ nsComboboxControlFrame::SetInitialChildL
   if (kSelectPopupList == aListID) {
     mPopupFrames.SetFrames(aChildList);
   } else {
     for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
       nsCOMPtr<nsIFormControl> formControl =
         do_QueryInterface(e.get()->GetContent());
       if (formControl && formControl->ControlType() == NS_FORM_BUTTON_BUTTON) {
         mButtonFrame = e.get();
+        e.Next();
+        mAppearanceFrame = e.get();
         break;
       }
     }
     NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
     nsBlockFrame::SetInitialChildList(aListID, aChildList);
   }
 }
 
--- a/layout/forms/nsComboboxControlFrame.h
+++ b/layout/forms/nsComboboxControlFrame.h
@@ -26,16 +26,17 @@
 #include "nsBlockFrame.h"
 #include "nsIFormControlFrame.h"
 #include "nsIComboboxControlFrame.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsISelectControlFrame.h"
 #include "nsIRollupListener.h"
 #include "nsIStatefulFrame.h"
 #include "nsThreadUtils.h"
+#include "nsHTMLButtonControlFrame.h"
 
 class nsStyleContext;
 class nsIListControlFrame;
 class nsComboboxDisplayFrame;
 class nsIDOMEventListener;
 class nsIScrollableFrame;
 
 namespace mozilla {
@@ -271,27 +272,36 @@ protected:
    */
   bool ShowList(bool aShowList);
   void CheckFireOnChange();
   void FireValueChangeEvent();
   nsresult RedisplayText();
   void HandleRedisplayTextEvent();
   void ActuallyDisplayText(bool aNotify);
 
+  // Check if appearance is menulist
+  bool IsMenulistAppearance();
+
+  nsAtom* GetSupportAppearanceType();
+
 private:
   // If our total transform to the root frame of the root document is only a 2d
   // translation then return that translation, otherwise returns (0,0).
   nsPoint GetCSSTransformTranslation();
 
 protected:
   nsFrameList              mPopupFrames;             // additional named child list
   nsCOMPtr<nsIContent>     mDisplayContent;          // Anonymous content used to display the current selection
-  RefPtr<Element>     mButtonContent;                // Anonymous content for the button
+  RefPtr<Element>          mButtonContent;           // Anonymous content for the button
+  RefPtr<Element>     mAppearanceContent;            // Anonymous content for the appearance
+  nsCOMPtr<nsIContent>     mTextContent;             // Anonymous content used to display the current selection
+                                                     // when appearance is not menulist
   nsContainerFrame*        mDisplayFrame;            // frame to display selection
   nsIFrame*                mButtonFrame;             // button frame
+  nsIFrame*                mAppearanceFrame;         // appearance frame
   nsIFrame*                mDropdownFrame;           // dropdown list frame
   nsIListControlFrame *    mListControlFrame;        // ListControl Interface for the dropdown frame
 
   // The inline size of our display area.  Used by that frame's reflow
   // to size to the full inline size except the drop-marker.
   nscoord mDisplayISize;
 
   nsRevocableEventPtr<RedisplayTextEvent> mRedisplayTextEvent;