Bug 1360500 - Allow custom colors on find selection type selections. r=jaws, r=masayuki, r=smaug draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Wed, 10 May 2017 10:48:50 -0400
changeset 577185 5cec11d1ba58914502a69fa27341bc096a6953da
parent 577084 96b36c5f527dd42e680a230839519eee1fc2c9f3
child 628459 6cdcfaf69f9f177b89167c91c60fe662bdc678c6
push id58646
push userbmo:timdream@gmail.com
push dateSat, 13 May 2017 01:59:15 +0000
reviewersjaws, masayuki, smaug
bugs1360500
milestone55.0a1
Bug 1360500 - Allow custom colors on find selection type selections. r=jaws, r=masayuki, r=smaug This patch implements chrome-only Selection#setColors and Selection#resetColors methods, and use it to set the background color of the preferences search highlight. MozReview-Commit-ID: 2U92aBCAyeh
browser/components/preferences/in-content/findInPage.js
dom/base/nsISelectionPrivate.idl
dom/webidl/Selection.webidl
layout/generic/Selection.h
layout/generic/nsFrameSelection.h
layout/generic/nsSelection.cpp
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -7,16 +7,17 @@
 var gSearchResultsPane = {
   findSelection: null,
   searchResultsCategory: null,
   searchInput: null,
 
   init() {
     let controller = this.getSelectionController();
     this.findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
+    this.findSelection.setColors("currentColor", "#ffe900", "currentColor", "#540ead");
     this.searchResultsCategory = document.getElementById("category-search-results");
 
     this.searchInput = document.getElementById("searchInput");
     this.searchInput.hidden = !Services.prefs.getBoolPref("browser.preferences.search");
     if (!this.searchInput.hidden) {
       this.searchInput.addEventListener("command", this);
       this.searchInput.addEventListener("focus", this);
     }
--- a/dom/base/nsISelectionPrivate.idl
+++ b/dom/base/nsISelectionPrivate.idl
@@ -156,10 +156,40 @@ interface nsISelectionPrivate : nsISelec
                                            in ScrollAxis aHorizontal);
 
     /**
      * Modifies the cursor Bidi level after a change in keyboard direction
      * @param langRTL is PR_TRUE if the new language is right-to-left or
      *                PR_FALSE if the new language is left-to-right.
      */
     [noscript] void selectionLanguageChange(in boolean langRTL);
+
+    /**
+     * setColors() sets custom colors for the selection.
+     * Currently, this is supported only when the selection type is SELECTION_FIND.
+     * Otherwise, throws an exception.
+     *
+     * @param aForegroundColor     The foreground color of the selection.
+     *                             If this is "currentColor", foreground color
+     *                             isn't changed by this selection.
+     * @param aBackgroundColor     The background color of the selection.
+     *                             If this is "transparent", background color is
+     *                             never painted.
+     * @param aAltForegroundColor  The alternative foreground color of the
+     *                             selection.
+     *                             If aBackgroundColor doesn't have sufficient
+     *                             contrast with its around or foreground color
+     *                             if "currentColor" is specified, alternative
+     *                             colors are used if it have higher contrast.
+     * @param aAltBackgroundColor  The alternative background color of the
+     *                             selection.
+     */
+    void setColors(in DOMString aForegroundColor,
+                   in DOMString aBackgroundColor,
+                   in DOMString aAltForegroundColor,
+                   in DOMString aAltBackgroundColor);
+
+    /**
+     * resetColors() forget the customized colors which were set by setColors().
+     */
+    void resetColors();
 };
 
--- a/dom/webidl/Selection.webidl
+++ b/dom/webidl/Selection.webidl
@@ -85,9 +85,16 @@ partial interface Selection {
   readonly attribute short type;
 
   [ChromeOnly,Throws,Pref="dom.testing.selection.GetRangesForInterval"]
   sequence<Range> GetRangesForInterval(Node beginNode, long beginOffset, Node endNode, long endOffset,
                                        boolean allowAdjacent);
 
   [ChromeOnly,Throws]
   void scrollIntoView(short aRegion, boolean aIsSynchronous, short aVPercent, short aHPercent);
+
+  [ChromeOnly,Throws]
+  void setColors(DOMString aForegroundColor, DOMString aBackgroundColor,
+                 DOMString aAltForegroundColor, DOMString aAltBackgroundColor);
+
+  [ChromeOnly,Throws]
+  void resetColors();
 };
--- a/layout/generic/Selection.h
+++ b/layout/generic/Selection.h
@@ -25,16 +25,17 @@ class nsAutoScrollTimer;
 class nsIContentIterator;
 class nsIDocument;
 class nsIEditor;
 class nsIFrame;
 class nsIHTMLEditor;
 class nsFrameSelection;
 class nsPIDOMWindowOuter;
 struct SelectionDetails;
+struct SelectionCustomColors;
 class nsCopySupport;
 class nsHTMLCopyEncoder;
 
 namespace mozilla {
 class ErrorResult;
 struct AutoPrepareFocusRange;
 } // namespace mozilla
 
@@ -246,16 +247,22 @@ public:
                             bool aAllowAdjacent,
                             nsTArray<RefPtr<nsRange>>& aReturn,
                             mozilla::ErrorResult& aRv);
 
   void ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
                       int16_t aVPercent, int16_t aHPercent,
                       mozilla::ErrorResult& aRv);
 
+  void SetColors(const nsAString& aForeColor, const nsAString& aBackColor,
+                 const nsAString& aAltForeColor, const nsAString& aAltBackColor,
+                 mozilla::ErrorResult& aRv);
+
+  void ResetColors(mozilla::ErrorResult& aRv);
+
   // Non-JS callers should use the following methods.
   void Collapse(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
   void CollapseToStart(mozilla::ErrorResult& aRv);
   void CollapseToEnd(mozilla::ErrorResult& aRv);
   void Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
   void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv);
   void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv);
   void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
@@ -279,16 +286,18 @@ private:
 
 public:
   SelectionType GetType() const { return mSelectionType; }
   void SetType(SelectionType aSelectionType)
   {
     mSelectionType = aSelectionType;
   }
 
+  SelectionCustomColors* GetCustomColors() const { return mCustomColors.get(); }
+
   nsresult NotifySelectionListeners(bool aCalledByJS);
   nsresult NotifySelectionListeners();
 
   friend struct AutoUserInitiated;
   struct MOZ_RAII AutoUserInitiated
   {
     explicit AutoUserInitiated(Selection* aSelection
                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
@@ -413,19 +422,21 @@ private:
   // O(log n) time, though this would require rebalancing and other overhead.
   nsTArray<RangeData> mRanges;
 
   RefPtr<nsRange> mAnchorFocusRange;
   RefPtr<nsFrameSelection> mFrameSelection;
   RefPtr<nsAutoScrollTimer> mAutoScrollTimer;
   nsCOMArray<nsISelectionListener> mSelectionListeners;
   nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
-  CachedOffsetForFrame *mCachedOffsetForFrame;
+  CachedOffsetForFrame* mCachedOffsetForFrame;
   nsDirection mDirection;
   SelectionType mSelectionType;
+  UniquePtr<SelectionCustomColors> mCustomColors;
+
   /**
    * True if the current selection operation was initiated by user action.
    * It determines whether we exclude -moz-user-select:none nodes or not,
    * as well as whether selectstart events will be fired.
    */
   bool mUserInitiated;
 
   /**
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.h
@@ -40,16 +40,34 @@ struct SelectionDetails
 #endif
   int32_t mStart;
   int32_t mEnd;
   mozilla::SelectionType mSelectionType;
   mozilla::TextRangeStyle mTextRangeStyle;
   mozilla::UniquePtr<SelectionDetails> mNext;
 };
 
+struct SelectionCustomColors
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+  SelectionCustomColors()
+  {
+    MOZ_COUNT_CTOR(SelectionCustomColors);
+  }
+  ~SelectionCustomColors()
+  {
+    MOZ_COUNT_DTOR(SelectionCustomColors);
+  }
+#endif
+  mozilla::Maybe<nscolor> mForegroundColor;
+  mozilla::Maybe<nscolor> mBackgroundColor;
+  mozilla::Maybe<nscolor> mAltForegroundColor;
+  mozilla::Maybe<nscolor> mAltBackgroundColor;
+};
+
 class nsIPresShell;
 
 /** PeekOffsetStruct is used to group various arguments (both input and output)
  *  that are passed to nsFrame::PeekOffset(). See below for the description of
  *  individual arguments.
  */
 struct MOZ_STACK_CLASS nsPeekOffsetStruct
 {
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -3485,27 +3485,29 @@ nsFrameSelection::DisconnectFromPresShel
 // mozilla::dom::Selection implementation
 
 // note: this can return a nil anchor node
 
 Selection::Selection()
   : mCachedOffsetForFrame(nullptr)
   , mDirection(eDirNext)
   , mSelectionType(SelectionType::eNormal)
+  , mCustomColors(nullptr)
   , mUserInitiated(false)
   , mCalledByJS(false)
   , mSelectionChangeBlockerCount(0)
 {
 }
 
 Selection::Selection(nsFrameSelection* aList)
   : mFrameSelection(aList)
   , mCachedOffsetForFrame(nullptr)
   , mDirection(eDirNext)
   , mSelectionType(SelectionType::eNormal)
+  , mCustomColors(nullptr)
   , mUserInitiated(false)
   , mCalledByJS(false)
   , mSelectionChangeBlockerCount(0)
 {
 }
 
 Selection::~Selection()
 {
@@ -6816,16 +6818,112 @@ Selection::SelectionLanguageChange(bool 
   
   // The caret might have moved, so invalidate the desired position
   // for future usages of up-arrow or down-arrow
   frameSelection->InvalidateDesiredPos();
   
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Selection::SetColors(const nsAString& aForegroundColor,
+                     const nsAString& aBackgroundColor,
+                     const nsAString& aAltForegroundColor,
+                     const nsAString& aAltBackgroundColor)
+{
+  ErrorResult result;
+  SetColors(aForegroundColor, aBackgroundColor,
+            aAltForegroundColor, aAltBackgroundColor, result);
+  return result.StealNSResult();
+}
+
+void
+Selection::SetColors(const nsAString& aForegroundColor,
+                     const nsAString& aBackgroundColor,
+                     const nsAString& aAltForegroundColor,
+                     const nsAString& aAltBackgroundColor,
+                     ErrorResult& aRv)
+{
+  if (mSelectionType != SelectionType::eFind) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  mCustomColors.reset(new SelectionCustomColors);
+
+  NS_NAMED_LITERAL_STRING(currentColorStr, "currentColor");
+  NS_NAMED_LITERAL_STRING(transparentStr, "transparent");
+
+  if (!aForegroundColor.Equals(currentColorStr)) {
+    nscolor foregroundColor;
+    nsAttrValue aForegroundColorValue;
+    aForegroundColorValue.ParseColor(aForegroundColor);
+    if (!aForegroundColorValue.GetColorValue(foregroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mForegroundColor = Some(foregroundColor);
+  } else {
+    mCustomColors->mForegroundColor = Nothing();
+  }
+
+  if (!aBackgroundColor.Equals(transparentStr)) {
+    nscolor backgroundColor;
+    nsAttrValue aBackgroundColorValue;
+    aBackgroundColorValue.ParseColor(aBackgroundColor);
+    if (!aBackgroundColorValue.GetColorValue(backgroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mBackgroundColor = Some(backgroundColor);
+  } else {
+    mCustomColors->mBackgroundColor = Nothing();
+  }
+
+  if (!aAltForegroundColor.Equals(currentColorStr)) {
+    nscolor altForegroundColor;
+    nsAttrValue aAltForegroundColorValue;
+    aAltForegroundColorValue.ParseColor(aAltForegroundColor);
+    if (!aAltForegroundColorValue.GetColorValue(altForegroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mAltForegroundColor = Some(altForegroundColor);
+  } else {
+    mCustomColors->mAltForegroundColor = Nothing();
+  }
+
+  if (!aAltBackgroundColor.Equals(transparentStr)) {
+    nscolor altBackgroundColor;
+    nsAttrValue aAltBackgroundColorValue;
+    aAltBackgroundColorValue.ParseColor(aAltBackgroundColor);
+    if (!aAltBackgroundColorValue.GetColorValue(altBackgroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mAltBackgroundColor = Some(altBackgroundColor);
+  } else {
+    mCustomColors->mAltBackgroundColor = Nothing();
+  }
+}
+
+NS_IMETHODIMP
+Selection::ResetColors()
+{
+  ErrorResult result;
+  ResetColors(result);
+  return result.StealNSResult();
+}
+
+void
+Selection::ResetColors(ErrorResult& aRv)
+{
+  mCustomColors = nullptr;
+}
+
 NS_IMETHODIMP_(nsDirection)
 Selection::GetSelectionDirection() {
   return mDirection;
 }
 
 NS_IMETHODIMP_(void)
 Selection::SetSelectionDirection(nsDirection aDirection) {
   mDirection = aDirection;
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -3823,23 +3823,96 @@ nsTextPaintStyle::GetSelectionColors(nsc
 
 void
 nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
                                      nscolor* aBackColor)
 {
   NS_ASSERTION(aForeColor, "aForeColor is null");
   NS_ASSERTION(aBackColor, "aBackColor is null");
 
-  nscolor backColor =
-    LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
-  nscolor foreColor =
-    LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
-  EnsureSufficientContrast(&foreColor, &backColor);
-  *aForeColor = foreColor;
-  *aBackColor = backColor;
+  const nsFrameSelection* frameSelection = mFrame->GetConstFrameSelection();
+  const Selection* selection =
+    frameSelection->GetSelection(SelectionType::eFind);
+  const SelectionCustomColors* customColors = nullptr;
+  if (selection) {
+    customColors = selection->GetCustomColors();
+  }
+
+  if (!customColors) {
+    nscolor backColor =
+      LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
+    nscolor foreColor =
+      LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
+    EnsureSufficientContrast(&foreColor, &backColor);
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+
+    return;
+  }
+
+  if (customColors->mForegroundColor && customColors->mBackgroundColor) {
+    nscolor foreColor = *customColors->mForegroundColor;
+    nscolor backColor = *customColors->mBackgroundColor;
+
+    if (EnsureSufficientContrast(&foreColor, &backColor) &&
+        customColors->mAltForegroundColor &&
+        customColors->mAltBackgroundColor) {
+      foreColor = *customColors->mAltForegroundColor;
+      backColor = *customColors->mAltBackgroundColor;
+    }
+
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+    return;
+  }
+
+  if (customColors->mBackgroundColor) {
+    // !mForegroundColor means "currentColor"; the current color of the text.
+    nscolor foreColor = GetTextColor();
+    nscolor backColor = *customColors->mBackgroundColor;
+
+    if (customColors->mAltBackgroundColor) {
+      int32_t foreLuminosityDifference =
+                NS_LUMINOSITY_DIFFERENCE(foreColor, backColor);
+
+      // The sufficient luminosity difference is based on the link color of
+      // about:preferences, so we don't invert the background color on these text.
+      // XXX: Make this more generic.
+      int32_t sufficientLuminosityDifference =
+                NS_LUMINOSITY_DIFFERENCE(NS_RGBA(23, 140, 229, 255), backColor);
+
+      if (foreLuminosityDifference < sufficientLuminosityDifference) {
+        backColor = *customColors->mAltBackgroundColor;
+      }
+    }
+
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+    return;
+  }
+
+  if (customColors->mForegroundColor) {
+    nscolor foreColor = *customColors->mForegroundColor;
+    // !mBackgroundColor means "transparent"; the current color of the background.
+    nscolor backColor = mFrameBackgroundColor;
+
+    if (customColors->mAltForegroundColor &&
+        EnsureSufficientContrast(&foreColor, &backColor)) {
+      foreColor = *customColors->mAltForegroundColor;
+      backColor = mFrameBackgroundColor;
+    }
+
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+    return;
+  }
+
+  // There are neither mForegroundColor nor mBackgroundColor.
+  *aForeColor = GetTextColor();
+  *aBackColor = NS_TRANSPARENT;
 }
 
 void
 nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
 {
   NS_ASSERTION(aForeColor, "aForeColor is null");
 
   nscolor textColor = GetTextColor();
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -6,16 +6,17 @@
 #ifndef nsTextFrame_h__
 #define nsTextFrame_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/UniquePtr.h"
 #include "nsFrame.h"
+#include "nsFrameSelection.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxSkipChars.h"
 #include "gfxTextRun.h"
 #include "nsDisplayList.h"
 #include "JustificationUtils.h"
 #include "RubyUtils.h"