Bug 932410 - Implement the focus ring for -moz-appearance:none styled <input type=range> in a different way so that content authors can change/remove it. r=bz
authorJonathan Watt <jwatt@jwatt.org>
Tue, 18 Feb 2014 23:35:07 +0000
changeset 169445 cd1bb1422d60ebedb55a62956b4ba206b27ebe63
parent 169444 183cb1d53c3569186c4c7232c74da0bdec044e59
child 169446 66f9f6671c100bd4b643473a9505637be0b8de21
push id39951
push userjwatt@jwatt.org
push dateWed, 19 Feb 2014 04:08:06 +0000
treeherdermozilla-inbound@cd1bb1422d60 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs932410
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 932410 - Implement the focus ring for -moz-appearance:none styled <input type=range> in a different way so that content authors can change/remove it. r=bz
layout/forms/nsRangeFrame.cpp
layout/forms/nsRangeFrame.h
layout/style/forms.css
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -5,17 +5,17 @@
 
 #include "nsRangeFrame.h"
 
 #include "mozilla/TouchEvents.h"
 
 #include "nsContentCreatorFunctions.h"
 #include "nsContentList.h"
 #include "nsContentUtils.h"
-#include "nsCSSRenderingBorders.h"
+#include "nsCSSRendering.h"
 #include "nsFormControlFrame.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsINameSpaceManager.h"
 #include "nsINodeInfo.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/HTMLInputElement.h"
@@ -77,16 +77,24 @@ nsRangeFrame::Init(nsIContent* aContent,
     nsIDocument* document = presShell->GetDocument();
     if (document) {
       nsPIDOMWindow* innerWin = document->GetInnerWindow();
       if (innerWin) {
         innerWin->SetHasTouchEventListeners();
       }
     }
   }
+
+  nsStyleSet *styleSet = PresContext()->StyleSet();
+
+  mOuterFocusStyle =
+    styleSet->ProbePseudoElementStyle(aContent->AsElement(),
+                                      nsCSSPseudoElements::ePseudo_mozFocusOuter,
+                                      StyleContext());
+
   return nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
 }
 
 void
 nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
                "nsRangeFrame should not have continuations; if it does we "
@@ -161,55 +169,49 @@ public:
     : nsDisplayItem(aBuilder, aFrame) {
     MOZ_COUNT_CTOR(nsDisplayRangeFocusRing);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayRangeFocusRing() {
     MOZ_COUNT_DTOR(nsDisplayRangeFocusRing);
   }
 #endif
-  
+
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE;
   virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) MOZ_OVERRIDE;
   NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_OUTLINE)
 };
 
+nsRect
+nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+  *aSnap = false;
+  nsRect rect(ToReferenceFrame(), Frame()->GetSize());
+
+  // We want to paint as if specifying a border for ::-moz-focus-outer
+  // specifies an outline for our frame, so inflate by the border widths:
+  nsStyleContext* styleContext =
+    static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
+  MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
+  rect.Inflate(styleContext->StyleBorder()->GetComputedBorder());
+
+  return rect;
+}
+
 void
 nsDisplayRangeFocusRing::Paint(nsDisplayListBuilder* aBuilder,
                                nsRenderingContext* aCtx)
 {
-  nsPresContext *presContext = mFrame->PresContext();
-  nscoord appUnitsPerDevPixel = presContext->DevPixelsToAppUnits(1);
-  gfxContext* ctx = aCtx->ThebesContext();
-  nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize());
-  gfxRect pxRect(nsLayoutUtils::RectToGfxRect(r, appUnitsPerDevPixel));
-  uint8_t borderStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
-                              NS_STYLE_BORDER_STYLE_DOTTED,
-                              NS_STYLE_BORDER_STYLE_DOTTED,
-                              NS_STYLE_BORDER_STYLE_DOTTED };
-  gfxFloat borderWidths[4] = { 1, 1, 1, 1 };
-  gfxCornerSizes borderRadii(0);
-  nscolor borderColors[4] = { NS_RGB(0, 0, 0), NS_RGB(0, 0, 0),
-                              NS_RGB(0, 0, 0), NS_RGB(0, 0, 0) };
-  nsStyleContext* bgContext = mFrame->StyleContext();
-  nscolor bgColor =
-    bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
-
-  ctx->Save();
-  nsCSSBorderRenderer br(appUnitsPerDevPixel,
-                         ctx,
-                         pxRect,
-                         borderStyles,
-                         borderWidths,
-                         borderRadii,
-                         borderColors,
-                         nullptr,
-                         0,
-                         bgColor);
-  br.DrawBorders();
-  ctx->Restore();
+  bool unused;
+  nsStyleContext* styleContext =
+    static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
+  MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
+  nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
+                              mVisibleRect, GetBounds(aBuilder, &unused),
+                              styleContext);
 }
 
 void
 nsRangeFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                const nsRect&           aDirtyRect,
                                const nsDisplayListSet& aLists)
 {
   if (IsThemed()) {
@@ -226,29 +228,45 @@ nsRangeFrame::BuildDisplayList(nsDisplay
       nsDisplayListSet set(aLists, aLists.Content());
       BuildDisplayListForChild(aBuilder, thumb, aDirtyRect, set, DISPLAY_CHILD_INLINE);
     }
   } else {
     BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
   }
 
   // Draw a focus outline if appropriate:
-  nsEventStates eventStates = mContent->AsElement()->State();
-  if (!eventStates.HasState(NS_EVENT_STATE_FOCUSRING) ||
-      eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+
+  if (!aBuilder->IsForPainting() ||
+      !IsVisibleForPainting(aBuilder)) {
+    // we don't want the focus ring item for hit-testing or if the item isn't
+    // in the area being [re]painted
     return;
   }
-  nsPresContext *presContext = PresContext();
+
+  nsEventStates eventStates = mContent->AsElement()->State();
+  if (eventStates.HasState(NS_EVENT_STATE_DISABLED) ||
+      !eventStates.HasState(NS_EVENT_STATE_FOCUSRING)) {
+    return; // can't have focus or doesn't match :-moz-focusring
+  }
+
+  if (!mOuterFocusStyle ||
+      !mOuterFocusStyle->StyleBorder()->HasBorder()) {
+    // no ::-moz-focus-outer specified border (how style specifies a focus ring
+    // for range)
+    return;
+  }
+
   const nsStyleDisplay *disp = StyleDisplay();
-  if ((!IsThemed(disp) ||
-       !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
-      IsVisibleForPainting(aBuilder)) {
-    aLists.Content()->AppendNewToTop(
-      new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
+  if (IsThemed(disp) &&
+      PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
+    return; // the native theme displays its own visual indication of focus
   }
+
+  aLists.Content()->AppendNewToTop(
+    new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
 }
 
 nsresult
 nsRangeFrame::Reflow(nsPresContext*           aPresContext,
                      nsHTMLReflowMetrics&     aDesiredSize,
                      const nsHTMLReflowState& aReflowState,
                      nsReflowStatus&          aStatus)
 {
@@ -840,8 +858,28 @@ nsRangeFrame::GetPseudoElement(nsCSSPseu
   }
 
   if (aType == nsCSSPseudoElements::ePseudo_mozRangeProgress) {
     return mProgressDiv;
   }
 
   return nsContainerFrame::GetPseudoElement(aType);
 }
+
+nsStyleContext*
+nsRangeFrame::GetAdditionalStyleContext(int32_t aIndex) const
+{
+  // We only implement this so that SetAdditionalStyleContext will be
+  // called if style changes that would change the -moz-focus-outer
+  // pseudo-element have occurred.
+  return aIndex == 0 ? mOuterFocusStyle : nullptr;
+}
+
+void
+nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex,
+                                        nsStyleContext* aStyleContext)
+{
+  MOZ_ASSERT(aIndex == 0,
+             "GetAdditionalStyleContext is handling other indexes?");
+
+  // The -moz-focus-outer pseudo-element's style has changed.
+  mOuterFocusStyle = aStyleContext;
+}
--- a/layout/forms/nsRangeFrame.h
+++ b/layout/forms/nsRangeFrame.h
@@ -9,23 +9,26 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/Decimal.h"
 #include "mozilla/EventForwards.h"
 #include "nsContainerFrame.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsCOMPtr.h"
 
 class nsBaseContentList;
+class nsDisplayRangeFocusRing;
 
 class nsRangeFrame : public nsContainerFrame,
                      public nsIAnonymousContentCreator
 {
   friend nsIFrame*
   NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
+  friend class nsDisplayRangeFocusRing;
+
   nsRangeFrame(nsStyleContext* aContext);
   virtual ~nsRangeFrame();
 
   typedef mozilla::dom::Element Element;
 
 public:
   NS_DECL_QUERYFRAME_TARGET(nsRangeFrame)
   NS_DECL_QUERYFRAME
@@ -79,16 +82,20 @@ public:
   virtual nsIAtom* GetType() const MOZ_OVERRIDE;
 
   virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
   {
     return nsContainerFrame::IsFrameOfType(aFlags &
       ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
   }
 
+  nsStyleContext* GetAdditionalStyleContext(int32_t aIndex) const MOZ_OVERRIDE;
+  void SetAdditionalStyleContext(int32_t aIndex,
+                                 nsStyleContext* aStyleContext) MOZ_OVERRIDE;
+
   /**
    * Returns true if the slider's thumb moves horizontally, or else false if it
    * moves vertically.
    *
    * aOverrideFrameSize If specified, this will be used instead of the size of
    *   the frame's rect (i.e. the frame's border-box size) if the frame's
    *   rect would have otherwise been examined. This should only be specified
    *   during reflow when the frame's [new] border-box size has not yet been
@@ -157,11 +164,16 @@ private:
    */
   nsCOMPtr<Element> mProgressDiv;
 
   /**
    * The div used to show the ::-moz-range-thumb pseudo-element.
    * @see nsRangeFrame::CreateAnonymousContent
    */
   nsCOMPtr<Element> mThumbDiv;
+
+  /**
+   * Cached style context for -moz-focus-outer CSS pseudo-element style.
+   */
+  nsRefPtr<nsStyleContext> mOuterFocusStyle;
 };
 
 #endif
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -801,16 +801,26 @@ input[type=range] {
 }
 
 input[type=range][orient=vertical] {
   width: 1.3em;
   height: 12em;
 }
 
 /**
+ * Ideally we'd also require :-moz-focusring here, but that doesn't currently
+ * work. Instead we only use the -moz-focus-outer border style if
+ * NS_EVENT_STATE_FOCUSRING is set (the check is in
+ * nsRangeFrame::BuildDisplayList).
+ */
+input[type=range]::-moz-focus-outer {
+  border: 1px dotted black;
+}
+
+/**
  * Layout handles positioning of this pseudo-element specially (so that content
  * authors can concentrate on styling the thumb without worrying about the
  * logic to position it). Specifically the 'margin', 'top' and 'left'
  * properties are ignored.
  *
  * If content authors want to have a vertical range, they will also need to
  * set the width/height of this pseudo-element.
  */