Bug 1201327 - Don't repaint the whole frame subtree when background-position changes. r=dbaron
authorMarkus Stange <mstange@themasta.com>
Mon, 02 Nov 2015 17:36:35 +0100
changeset 270930 21cabf6ab3e9a254cdc9370986f300213b346cb2
parent 270929 b0a00d6915bb7c858ae7e1d94c7507b76e23eddd
child 270931 9e3455c9aa3c17d8a0c488071766396150ef7136
push id86
push usercbook@mozilla.com
push dateWed, 04 Nov 2015 14:00:24 +0000
reviewersdbaron
bugs1201327
milestone45.0a1
Bug 1201327 - Don't repaint the whole frame subtree when background-position changes. r=dbaron
layout/reftests/invalidation/background-position-1-ref.html
layout/reftests/invalidation/background-position-1.html
layout/reftests/invalidation/reftest.list
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/background-position-1-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Changes to background-position should not cause things to repaint that don't intersect the background image.</title>
+
+<style>
+
+body {
+  margin: 0;
+}
+
+#background {
+  height: 512px;
+  background-image: url(image_rgrg-256x256.png);
+  background-repeat: no-repeat;
+  background-position: 300px 100px;
+}
+
+#not-intersecting-background {
+  box-sizing: border-box;
+  width: 200px;
+  height: 200px;
+  margin: 50px;
+  border: 1px solid lime;
+}
+
+</style>
+
+<div id="background">
+  <div id="not-intersecting-background"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/background-position-1.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<meta charset="utf-8">
+<title>Changes to background-position should not cause things to repaint that don't intersect the background image.</title>
+
+<style>
+
+body {
+  margin: 0;
+}
+
+#background {
+  height: 512px;
+  background-image: url(image_rgrg-256x256.png);
+  background-repeat: no-repeat;
+  background-position: 300px 50px;
+}
+
+#not-intersecting-background {
+  box-sizing: border-box;
+  width: 200px;
+  height: 200px;
+  margin: 50px;
+  border: 1px solid lime;
+}
+
+</style>
+
+<div id="background">
+  <div id="not-intersecting-background" class="reftest-no-paint"></div>
+</div>
+
+<script>
+
+function doTest() {
+  document.querySelector("#background").style.backgroundPosition = "300px 100px";
+  document.documentElement.removeAttribute("class");
+}
+document.addEventListener("MozReftestInvalidate", doTest);
+
+</script>
--- a/layout/reftests/invalidation/reftest.list
+++ b/layout/reftests/invalidation/reftest.list
@@ -65,8 +65,9 @@ pref(layout.animated-image-layers.enable
 != layer-splitting-6.html about:blank
 != layer-splitting-7.html about:blank
 fuzzy-if(gtkWidget,2,4) fuzzy-if(asyncPan,2,3955) fuzzy-if(OSX,179,30) == image-scrolling-zoom-1.html image-scrolling-zoom-1-ref.html
 != image-scrolling-zoom-1-ref.html image-scrolling-zoom-1-notref.html
 != fast-scrolling.html about:blank
 != fractional-transform-1.html about:blank
 != fractional-transform-2.html about:blank
 != fractional-transform-3.html about:blank
+== background-position-1.html background-position-1-ref.html
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2246,37 +2246,42 @@ nsStyleBackground::Destroy(nsPresContext
 
 nsChangeHint nsStyleBackground::CalcDifference(const nsStyleBackground& aOther) const
 {
   const nsStyleBackground* moreLayers =
     mImageCount > aOther.mImageCount ? this : &aOther;
   const nsStyleBackground* lessLayers =
     mImageCount > aOther.mImageCount ? &aOther : this;
 
-  bool hasVisualDifference = false;
+  nsChangeHint hint = nsChangeHint(0);
 
   NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, moreLayers) {
     if (i < lessLayers->mImageCount) {
-      if (moreLayers->mLayers[i] != lessLayers->mLayers[i]) {
-        if ((moreLayers->mLayers[i].mImage.GetType() == eStyleImageType_Element) ||
-            (lessLayers->mLayers[i].mImage.GetType() == eStyleImageType_Element))
-          return NS_CombineHint(nsChangeHint_UpdateEffects,
-                                nsChangeHint_RepaintFrame);
-        hasVisualDifference = true;
+      nsChangeHint layerDifference = moreLayers->mLayers[i].CalcDifference(lessLayers->mLayers[i]);
+      hint |= layerDifference;
+      if (layerDifference &&
+          ((moreLayers->mLayers[i].mImage.GetType() == eStyleImageType_Element) ||
+           (lessLayers->mLayers[i].mImage.GetType() == eStyleImageType_Element))) {
+        hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
       }
     } else {
-      if (moreLayers->mLayers[i].mImage.GetType() == eStyleImageType_Element)
-        return NS_CombineHint(nsChangeHint_UpdateEffects,
-                              nsChangeHint_RepaintFrame);
-      hasVisualDifference = true;
+      hint |= nsChangeHint_RepaintFrame;
+      if (moreLayers->mLayers[i].mImage.GetType() == eStyleImageType_Element) {
+        hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
+      }
     }
   }
 
-  if (hasVisualDifference || mBackgroundColor != aOther.mBackgroundColor)
-    return nsChangeHint_RepaintFrame;
+  if (mBackgroundColor != aOther.mBackgroundColor) {
+    hint |= nsChangeHint_RepaintFrame;
+  }
+
+  if (hint) {
+    return hint;
+  }
 
   if (mAttachmentCount != aOther.mAttachmentCount ||
       mClipCount != aOther.mClipCount ||
       mOriginCount != aOther.mOriginCount ||
       mRepeatCount != aOther.mRepeatCount ||
       mPositionCount != aOther.mPositionCount ||
       mSizeCount != aOther.mSizeCount) {
     return nsChangeHint_NeutralChange;
@@ -2477,16 +2482,35 @@ nsStyleBackground::Layer::operator==(con
          mOrigin == aOther.mOrigin &&
          mRepeat == aOther.mRepeat &&
          mBlendMode == aOther.mBlendMode &&
          mPosition == aOther.mPosition &&
          mSize == aOther.mSize &&
          mImage == aOther.mImage;
 }
 
+nsChangeHint
+nsStyleBackground::Layer::CalcDifference(const Layer& aOther) const
+{
+  nsChangeHint hint = nsChangeHint(0);
+  if (mAttachment != aOther.mAttachment ||
+      mClip != aOther.mClip ||
+      mOrigin != aOther.mOrigin ||
+      mRepeat != aOther.mRepeat ||
+      mBlendMode != aOther.mBlendMode ||
+      mSize != aOther.mSize ||
+      mImage != aOther.mImage) {
+    hint |= nsChangeHint_RepaintFrame;
+  }
+  if (mPosition != aOther.mPosition) {
+    hint |= nsChangeHint_SchedulePaint;
+  }
+  return hint;
+}
+
 // --------------------
 // nsStyleDisplay
 //
 void nsTimingFunction::AssignFromKeyword(int32_t aTimingFunctionType)
 {
   switch (aTimingFunctionType) {
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START:
       mType = Type::StepStart;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -391,19 +391,20 @@ struct nsStyleBackground {
   void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW {
     return aContext->PresShell()->
       AllocateByObjectID(mozilla::eArenaObjectID_nsStyleBackground, sz);
   }
   void Destroy(nsPresContext* aContext);
 
   nsChangeHint CalcDifference(const nsStyleBackground& aOther) const;
   static nsChangeHint MaxDifference() {
-    return NS_CombineHint(nsChangeHint_UpdateEffects,
-                          NS_CombineHint(nsChangeHint_RepaintFrame,
-                                         nsChangeHint_NeutralChange));
+    return nsChangeHint_UpdateEffects |
+           nsChangeHint_RepaintFrame |
+           nsChangeHint_SchedulePaint |
+           nsChangeHint_NeutralChange;
   }
   static nsChangeHint DifferenceAlwaysHandledForDescendants() {
     // CalcDifference never returns the reflow hints that are sometimes
     // handled for descendants at all.
     return nsChangeHint(0);
   }
 
   struct Position;
@@ -547,16 +548,19 @@ struct nsStyleBackground {
 
     // True if the rendering of this layer might change when the size
     // of the background positioning area changes.  This is true for any
     // non-solid-color background whose position or size depends on
     // the size of the positioning area.  It's also true for SVG images
     // whose root <svg> node has a viewBox.
     bool RenderingMightDependOnPositioningAreaSizeChange() const;
 
+    // Compute the change hint required by changes in just this layer.
+    nsChangeHint CalcDifference(const Layer& aOther) const;
+
     // An equality operator that compares the images using URL-equality
     // rather than pointer-equality.
     bool operator==(const Layer& aOther) const;
     bool operator!=(const Layer& aOther) const {
       return !(*this == aOther);
     }
   };