Bug 1449010: Respect ::selection background styles for image overlays. r?mattwoodrow draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 27 Mar 2018 00:16:35 +0200
changeset 772796 d8cfde6770d8429c6335aa2212bd1a8ace28f1e8
parent 772739 08276f87bbfb1954019e09dcefe1c0dd444304c6
push id104048
push userbmo:emilio@crisal.io
push dateMon, 26 Mar 2018 23:07:24 +0000
reviewersmattwoodrow
bugs1449010, 509958
milestone61.0a1
Bug 1449010: Respect ::selection background styles for image overlays. r?mattwoodrow Note that this is on top of bug 509958, thus ::selection works in the test. You can apply that or change it to ::-moz-selection and CSSPseudoElementType::mozSelection to test locally just this patch if you want. I don't have a strong preference about blending with white vs. just doing alpha 0.5, so I kept doing what we were doing, since Blink and WebKit also apply the blending to the text background, and I'm not sure that's particularly desirable. Happy to change it if you think otherwise though. MozReview-Commit-ID: AwYtAgdlcxj
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/generic/nsImageFrame.cpp
layout/generic/nsTextFrame.cpp
testing/web-platform/meta/MANIFEST.json
testing/web-platform/mozilla/meta/MANIFEST.json
testing/web-platform/tests/css/selectors/resources/blue15x15.png
testing/web-platform/tests/css/selectors/selection-image-001-no-selection-noref.html
testing/web-platform/tests/css/selectors/selection-image-001-noref.html
testing/web-platform/tests/css/selectors/selection-image-001.html
testing/web-platform/tests/css/selectors/selection-image-002.html
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2179,34 +2179,61 @@ public:
                                mozilla::wr::IpcResourceUpdateQueue& aResources,
                                const StackingContextHelper& aSc,
                                mozilla::layers::WebRenderLayerManager* aManager,
                                nsDisplayListBuilder* aDisplayListBuilder) override;
   NS_DISPLAY_DECL_NAME("SelectionOverlay", TYPE_SELECTION_OVERLAY)
 private:
   Color ComputeColor() const;
 
+  static Color ComputeColorFromSelectionStyle(ComputedStyle&);
+  static Color ApplyTransparencyIfNecessary(nscolor);
+
   int16_t mSelectionValue;
 };
 
 Color
+nsDisplaySelectionOverlay::ApplyTransparencyIfNecessary(nscolor aColor)
+{
+  // If it has already alpha, leave it like that.
+  if (NS_GET_A(aColor) != 255) {
+    return ToDeviceColor(aColor);
+  }
+
+  // NOTE(emilio): Blink and WebKit do something slightly different here, and
+  // blend the color with white instead, both for overlays and text backgrounds.
+  auto color = Color::FromABGR(aColor);
+  color.a = 0.5;
+  return ToDeviceColor(color);
+}
+
+Color
+nsDisplaySelectionOverlay::ComputeColorFromSelectionStyle(ComputedStyle& aStyle)
+{
+  return ApplyTransparencyIfNecessary(
+    aStyle.GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor));
+}
+
+Color
 nsDisplaySelectionOverlay::ComputeColor() const
 {
   LookAndFeel::ColorID colorID;
   if (mSelectionValue == nsISelectionController::SELECTION_ON) {
+    if (RefPtr<ComputedStyle> style = mFrame->ComputeSelectionStyle()) {
+      return ComputeColorFromSelectionStyle(*style);
+    }
     colorID = LookAndFeel::eColorID_TextSelectBackground;
   } else if (mSelectionValue == nsISelectionController::SELECTION_ATTENTION) {
     colorID = LookAndFeel::eColorID_TextSelectBackgroundAttention;
   } else {
     colorID = LookAndFeel::eColorID_TextSelectBackgroundDisabled;
   }
 
-  Color c = Color::FromABGR(LookAndFeel::GetColor(colorID, NS_RGB(255, 255, 255)));
-  c.a = .5;
-  return ToDeviceColor(c);
+  return ApplyTransparencyIfNecessary(
+    LookAndFeel::GetColor(colorID, NS_RGB(255, 255, 255)));
 }
 
 void nsDisplaySelectionOverlay::Paint(nsDisplayListBuilder* aBuilder,
                                       gfxContext* aCtx)
 {
   DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
   ColorPattern color(ComputeColor());
 
@@ -2229,55 +2256,78 @@ nsDisplaySelectionOverlay::CreateWebRend
   wr::LayoutRect bounds = aSc.ToRelativeLayoutRect(
     LayoutDeviceRect::FromAppUnits(nsRect(ToReferenceFrame(), Frame()->GetSize()),
                                    mFrame->PresContext()->AppUnitsPerDevPixel()));
   aBuilder.PushRect(bounds, bounds, !BackfaceIsHidden(),
                     wr::ToColorF(ComputeColor()));
   return true;
 }
 
+static Element*
+FindElementAncestorForMozSelection(nsIContent* aContent)
+{
+  NS_ENSURE_TRUE(aContent, nullptr);
+  while (aContent && aContent->IsInNativeAnonymousSubtree()) {
+    aContent = aContent->GetBindingParent();
+  }
+  NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
+  while (aContent && !aContent->IsElement()) {
+    aContent = aContent->GetParent();
+  }
+  return aContent ? aContent->AsElement() : nullptr;
+}
+
+
+already_AddRefed<ComputedStyle>
+nsIFrame::ComputeSelectionStyle() const
+{
+  Element* element = FindElementAncestorForMozSelection(GetContent());
+  RefPtr<ComputedStyle> sc =
+    PresContext()->StyleSet()->ProbePseudoElementStyle(
+      element, CSSPseudoElementType::selection, Style());
+  return sc.forget();
+}
+
 /********************************************************
 * Refreshes each content's frame
 *********************************************************/
 
 void
 nsFrame::DisplaySelectionOverlay(nsDisplayListBuilder*   aBuilder,
                                  nsDisplayList*          aList,
                                  uint16_t                aContentType)
 {
-  if (!IsSelected() || !IsVisibleForPainting(aBuilder))
+  if (!IsSelected() || !IsVisibleForPainting(aBuilder)) {
     return;
-
-  nsPresContext* presContext = PresContext();
-  nsIPresShell *shell = presContext->PresShell();
-  if (!shell)
+  }
+
+  int16_t displaySelection = PresShell()->GetSelectionFlags();
+  if (!(displaySelection & aContentType)) {
     return;
-
-  int16_t displaySelection = shell->GetSelectionFlags();
-  if (!(displaySelection & aContentType))
-    return;
+  }
 
   const nsFrameSelection* frameSelection = GetConstFrameSelection();
   int16_t selectionValue = frameSelection->GetDisplaySelection();
 
-  if (selectionValue <= nsISelectionController::SELECTION_HIDDEN)
+  if (selectionValue <= nsISelectionController::SELECTION_HIDDEN) {
     return; // selection is hidden or off
-
-  nsIContent *newContent = mContent->GetParent();
+  }
+
+  nsIContent* newContent = mContent->GetParent();
 
   //check to see if we are anonymous content
   int32_t offset = 0;
   if (newContent) {
     // XXXbz there has GOT to be a better way of determining this!
     offset = newContent->ComputeIndexOf(mContent);
   }
 
   //look up to see what selection(s) are on this frame
-  UniquePtr<SelectionDetails> details
-    = frameSelection->LookUpSelection(newContent, offset, 1, false);
+  UniquePtr<SelectionDetails> details =
+    frameSelection->LookUpSelection(newContent, offset, 1, false);
   if (!details)
     return;
 
   bool normal = false;
   for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
     if (sd->mSelectionType == SelectionType::eNormal) {
       normal = true;
     }
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -868,16 +868,18 @@ public:
    * The indicies must be consecutive and implementations MUST return null if
    * asked for an index that is out of range.
    */
   virtual ComputedStyle* GetAdditionalComputedStyle(int32_t aIndex) const = 0;
 
   virtual void SetAdditionalComputedStyle(int32_t aIndex,
                                           ComputedStyle* aComputedStyle) = 0;
 
+  already_AddRefed<ComputedStyle> ComputeSelectionStyle() const;
+
   /**
    * Accessor functions for geometric parent.
    */
   nsContainerFrame* GetParent() const { return mParent; }
 
   /**
    * Gets the parent of a frame, using the parent of the placeholder for
    * out-of-flow frames.
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1894,24 +1894,20 @@ nsImageFrame::ShouldDisplaySelection()
 {
   nsPresContext* presContext = PresContext();
   int16_t displaySelection = presContext->PresShell()->GetSelectionFlags();
   if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES))
     return false;//no need to check the blue border, we cannot be drawn selected
 
   // If the image is the only selected node, don't draw the selection overlay.
   // This can happen when selecting an image in contenteditable context.
-  if (displaySelection == nsISelectionDisplay::DISPLAY_ALL)
-  {
-    const nsFrameSelection* frameSelection = GetConstFrameSelection();
-    if (frameSelection)
-    {
+  if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) {
+    if (const nsFrameSelection* frameSelection = GetConstFrameSelection()) {
       const Selection* selection = frameSelection->GetSelection(SelectionType::eNormal);
-      if (selection && selection->RangeCount() == 1)
-      {
+      if (selection && selection->RangeCount() == 1) {
         nsINode* parent = mContent->GetParent();
         int32_t thisOffset = parent->ComputeIndexOf(mContent);
         nsRange* range = selection->GetRangeAt(0);
         if (range->GetStartContainer() == parent &&
             range->GetEndContainer() == parent &&
             static_cast<int32_t>(range->StartOffset()) == thisOffset &&
             static_cast<int32_t>(range->EndOffset()) == thisOffset + 1) {
           return false;
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -4052,30 +4052,16 @@ nsTextPaintStyle::GetSystemFieldForegrou
 
 nscolor
 nsTextPaintStyle::GetSystemFieldBackgroundColor()
 {
   InitCommonColors();
   return mSystemFieldBackgroundColor;
 }
 
-static Element*
-FindElementAncestorForMozSelection(nsIContent* aContent)
-{
-  NS_ENSURE_TRUE(aContent, nullptr);
-  while (aContent && aContent->IsInNativeAnonymousSubtree()) {
-    aContent = aContent->GetBindingParent();
-  }
-  NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
-  while (aContent && !aContent->IsElement()) {
-    aContent = aContent->GetParent();
-  }
-  return aContent ? aContent->AsElement() : nullptr;
-}
-
 bool
 nsTextPaintStyle::InitSelectionColorsAndShadow()
 {
   if (mInitSelectionColorsAndShadow)
     return true;
 
   int16_t selectionFlags;
   int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
@@ -4084,35 +4070,25 @@ nsTextPaintStyle::InitSelectionColorsAnd
     // Not displaying the normal selection.
     // We're not caching this fact, so every call to GetSelectionColors
     // will come through here. We could avoid this, but it's not really worth it.
     return false;
   }
 
   mInitSelectionColorsAndShadow = true;
 
-  nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame);
-  Element* selectionElement =
-    FindElementAncestorForMozSelection(nonGeneratedAncestor->GetContent());
-
-  if (selectionElement &&
-      selectionStatus == nsISelectionController::SELECTION_ON) {
-    RefPtr<ComputedStyle> sc =
-      mPresContext->StyleSet()->
-        ProbePseudoElementStyle(selectionElement,
-                                CSSPseudoElementType::selection,
-                                mFrame->Style());
-    // Use -moz-selection pseudo class.
-    if (sc) {
+  if (selectionStatus == nsISelectionController::SELECTION_ON) {
+    // Use ::selection pseudo class.
+    if (RefPtr<ComputedStyle> style = mFrame->ComputeSelectionStyle()) {
       mSelectionBGColor =
-        sc->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
+        style->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
       mSelectionTextColor =
-        sc->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
+        style->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
       mHasSelectionShadow = true;
-      mSelectionShadow = sc->StyleText()->mTextShadow;
+      mSelectionShadow = style->StyleText()->mTextShadow;
       return true;
     }
   }
 
   nscolor selectionBGColor =
     LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
 
   if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -160624,16 +160624,40 @@
       [
        "/css/reference/ref-filled-green-100px-square.xht",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/selectors/selection-image-001.html": [
+    [
+     "/css/selectors/selection-image-001.html",
+     [
+      [
+       "/css/selectors/selection-image-001-noref.html",
+       "!="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/selectors/selection-image-002.html": [
+    [
+     "/css/selectors/selection-image-002.html",
+     [
+      [
+       "/css/selectors/selection-image-001-no-selection-noref.html",
+       "!="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/selectors/selector-placeholder-shown-type-change-001.html": [
     [
      "/css/selectors/selector-placeholder-shown-type-change-001.html",
      [
       [
        "/css/selectors/selector-placeholder-shown-type-change-001-ref.html",
        "=="
       ]
@@ -262149,16 +262173,21 @@
      {}
     ]
    ],
    "css/selectors/of-type-selectors-ref.xhtml": [
     [
      {}
     ]
    ],
+   "css/selectors/resources/blue15x15.png": [
+    [
+     {}
+    ]
+   ],
    "css/selectors/selector-placeholder-shown-type-change-001-ref.html": [
     [
      {}
     ]
    ],
    "css/selectors/selector-placeholder-shown-type-change-002-ref.html": [
     [
      {}
@@ -385648,16 +385677,28 @@
      {}
     ]
    ],
    "css/mediaqueries/media-queries-003.xht": [
     [
      "/css/mediaqueries/media-queries-003.xht",
      {}
     ]
+   ],
+   "css/selectors/selection-image-001-no-selection-noref.html": [
+    [
+     "/css/selectors/selection-image-001-no-selection-noref.html",
+     {}
+    ]
+   ],
+   "css/selectors/selection-image-001-noref.html": [
+    [
+     "/css/selectors/selection-image-001-noref.html",
+     {}
+    ]
    ]
   },
   "wdspec": {
    "webdriver/tests/actions/key.py": [
     [
      "/webdriver/tests/actions/key.py",
      {}
     ]
@@ -529060,24 +529101,44 @@
   "css/selectors/of-type-selectors-ref.xhtml": [
    "59f848418882c75898c422a9600c14ffab64c3d9",
    "support"
   ],
   "css/selectors/of-type-selectors.xhtml": [
    "607553f41a33ce3630752cdf027c9f904833a19d",
    "reftest"
   ],
+  "css/selectors/resources/blue15x15.png": [
+   "eb48032c07bfeb1d3b6be6e5c9c34d2fe2180767",
+   "support"
+  ],
   "css/selectors/root-siblings.htm": [
    "0d6e67589dab95d5362f82a99565947ebb487658",
    "reftest"
   ],
   "css/selectors/scope-without-scoping.html": [
    "f70b8d60543c5a28fcf955b1780f15c03d60991a",
    "reftest"
   ],
+  "css/selectors/selection-image-001-no-selection-noref.html": [
+   "b9a627630a8dcfaa70c74fc11ac9635aa00ac32c",
+   "visual"
+  ],
+  "css/selectors/selection-image-001-noref.html": [
+   "add1c00be4957ffef599aee52d061be7c09607bc",
+   "visual"
+  ],
+  "css/selectors/selection-image-001.html": [
+   "134b946744b45f488470dc5d1c690ee9a4855cbb",
+   "reftest"
+  ],
+  "css/selectors/selection-image-002.html": [
+   "5e2d33709b654a1f66eedcff995c8a3e9a8e01c5",
+   "reftest"
+  ],
   "css/selectors/selector-placeholder-shown-type-change-001-ref.html": [
    "92303d06943581738f58ff5d342ef1336539f66a",
    "support"
   ],
   "css/selectors/selector-placeholder-shown-type-change-001.html": [
    "6d84d237fe4b6d7f01e6c7c6129bdc8ccb06cf90",
    "reftest"
   ],
--- a/testing/web-platform/mozilla/meta/MANIFEST.json
+++ b/testing/web-platform/mozilla/meta/MANIFEST.json
@@ -1424,17 +1424,17 @@
    "3b21a62dbc8379a202e8a0beda813c89f762ae2d",
    "support"
   ],
   "wasm/js/get_local.wast.js": [
    "332a192149e2cede2d9219432fd3c6085ac69f68",
    "support"
   ],
   "wasm/js/globals.wast.js": [
-   "86b3c907edd8bb3867c5a5aaaebf6066a215b154",
+   "1cb0dc48bd61564325053bf49ae7a1bdb27f5c49",
    "support"
   ],
   "wasm/js/harness/index.js": [
    "c1f8ca410de85f15e1d9e57748a2fb9f607be40f",
    "support"
   ],
   "wasm/js/harness/wasm-constants.js": [
    "6aba4f5fb4127cd56e1381bfb18204edde41abb2",
new file mode 100644
index 0000000000000000000000000000000000000000..89de32fdb8a4e48b1320f40f5a75352773077cee
GIT binary patch
literal 185
zc%17D@N?(olHy`uVBq!ia0vp^{2<K11SGd?VUh(>oCO|{#S9F5he4R}c>anMpde#$
zkh>GZx^prwfgF}}M_)$<hK>E)e-c@Ne1&9>AYTTCDm4a%h86~fUqGRT7Yq!g1`G_Z
z5*Qe)W-u^_7tGleXakf`@^o<w(FjgXN%(Qzfs0|4n%u+%B2Ty;@ydnvnyztk0=k6z
V)g}Gv*iewk44$rjF6*2UngFS5E#&|J
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/selection-image-001-no-selection-noref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/selection-image-001-noref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
+<script>
+onload = () => {
+  getSelection().removeAllRanges();
+  let r = document.createRange();
+  r.selectNode(document.documentElement);
+  getSelection().addRange(r);
+}
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/selection-image-001.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>::selection is respected on images</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<link rel="mismatch" href="selection-image-001-noref.html">
+<style>
+img::selection {
+  background: green;
+}
+</style>
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
+<script>
+onload = () => {
+  getSelection().removeAllRanges();
+  let r = document.createRange();
+  r.selectNode(document.documentElement);
+  getSelection().addRange(r);
+}
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/selection-image-002.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS Test: Image and text selection is painted.</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<link rel="mismatch" href="selection-image-001-no-selection-noref.html">
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
+<script>
+onload = () => {
+  getSelection().removeAllRanges();
+  let r = document.createRange();
+  r.selectNode(document.documentElement);
+  getSelection().addRange(r);
+}
+</script>