Bug 1340723 part 3. Fix stylo to properly update styles on the anonymous blocks we create in a block-inside-inline situation when the style of the inline changes. r?emilio draft
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 03 Mar 2017 15:45:40 -0500
changeset 493311 889c5fb8f1a8791863f6c5f84ebcd6dd753cd2c0
parent 493310 f7ebe1bf7982cb75c233d887104ab9a58a9715f1
child 493312 3b585c9a48da63d6cc29baff9d156760bb6883e2
push id47736
push userbzbarsky@mozilla.com
push dateFri, 03 Mar 2017 20:52:34 +0000
reviewersemilio
bugs1340723
milestone54.0a1
Bug 1340723 part 3. Fix stylo to properly update styles on the anonymous blocks we create in a block-inside-inline situation when the style of the inline changes. r?emilio MozReview-Commit-ID: JYz6g1ZJInT
layout/base/ServoRestyleManager.cpp
layout/base/nsCSSFrameConstructor.cpp
layout/generic/nsInlineFrame.cpp
layout/generic/nsInlineFrame.h
layout/reftests/ib-split/reftest-stylo.list
layout/reftests/ib-split/reftest.list
layout/reftests/ib-split/relpos-inline-1-ref.html
layout/reftests/ib-split/relpos-inline-1a.html
layout/reftests/ib-split/relpos-inline-1b.html
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -231,16 +231,19 @@ ServoRestyleManager::RecreateStyleContex
       aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
                             CSSPseudoElementType::NotPseudo, aElement);
 
     newContext->EnsureStructsForServo(oldStyleContext);
 
     // XXX This could not always work as expected: there are kinds of content
     // with the first split and the last sharing style, but others not. We
     // should handle those properly.
+    // XXXbz I think the UpdateStyleOfOwnedAnonBoxes call below handles _that_
+    // right, but not other cases where we happen to have different styles on
+    // different continuations... (e.g. first-line).
     for (nsIFrame* f = styleFrame; f;
          f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
       f->SetStyleContext(newContext);
     }
 
     if (isTable) {
       nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
       MOZ_ASSERT(primaryFrame->StyleContext()->GetPseudo() ==
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -12124,16 +12124,17 @@ nsCSSFrameConstructor::ConstructInline(n
   // has to be chopped into several pieces, as described above.
 
   // Grab the first inline's kids
   nsFrameList firstInlineKids = childItems.ExtractHead(firstBlockEnumerator);
   newFrame->SetInitialChildList(kPrincipalList, firstInlineKids);
 
   aFrameItems.AddChild(newFrame);
 
+  newFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
   CreateIBSiblings(aState, newFrame, positioned, childItems, aFrameItems);
 
   return newFrame;
 }
 
 void
 nsCSSFrameConstructor::CreateIBSiblings(nsFrameConstructorState& aState,
                                         nsContainerFrame* aInitialInline,
--- a/layout/generic/nsInlineFrame.cpp
+++ b/layout/generic/nsInlineFrame.cpp
@@ -14,18 +14,20 @@
 #include "nsPresContext.h"
 #include "nsRenderingContext.h"
 #include "nsCSSAnonBoxes.h"
 #include "mozilla/RestyleManager.h"
 #include "mozilla/RestyleManagerInlines.h"
 #include "nsDisplayList.h"
 #include "mozilla/Likely.h"
 #include "SVGTextFrame.h"
+#include "nsStyleChangeList.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
+#include "mozilla/ServoStyleSet.h"
 
 #ifdef DEBUG
 #undef NOISY_PUSHING
 #endif
 
 using namespace mozilla;
 using namespace mozilla::layout;
 
@@ -1016,16 +1018,75 @@ nsInlineFrame::AccessibleType()
     return a11y::eHTMLButtonType;
   if (mContent->IsHTMLElement(nsGkAtoms::img))  // Create accessible for broken <img>
     return a11y::eHyperTextType;
 
   return a11y::eNoType;
 }
 #endif
 
+void
+nsInlineFrame::DoUpdateStyleOfOwnedAnonBoxes(ServoStyleSet& aStyleSet,
+                                             nsStyleChangeList& aChangeList,
+                                             nsChangeHint aHintForThisFrame)
+{
+  MOZ_ASSERT(GetStateBits() & NS_FRAME_OWNS_ANON_BOXES,
+             "Why did we get called?");
+  MOZ_ASSERT(GetStateBits() & NS_FRAME_PART_OF_IBSPLIT,
+             "Why did we have the NS_FRAME_OWNS_ANON_BOXES bit set?");
+  // Note: this assert _looks_ expensive, but it's cheap in all the cases when
+  // it passes!
+  MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(this) == this,
+             "Only the primary frame of the inline in a block-inside-inline "
+             "split should have NS_FRAME_OWNS_ANON_BOXES");
+  MOZ_ASSERT(mContent->GetPrimaryFrame() == this,
+             "We should be the primary frame for our element");
+
+  nsPresContext* presContext = PresContext();
+  // Get the FramePropertyTable up front, since we are likely to need it
+  // multiple times.  The other option would be to just call
+  // nsIFrame::Properties() every time we need to do a lookup, but that does
+  // more pointer-chasing.
+  FramePropertyTable* propTable = presContext->PropertyTable();
+  nsIFrame* blockFrame = propTable->Get(this, nsIFrame::IBSplitSibling());
+  MOZ_ASSERT(blockFrame, "Why did we have an IB split?");
+
+  nsIAtom* pseudo = blockFrame->StyleContext()->GetPseudo();
+  MOZ_ASSERT(pseudo == nsCSSAnonBoxes::mozAnonymousBlock ||
+             pseudo == nsCSSAnonBoxes::mozAnonymousPositionedBlock,
+             "Unexpected kind of style context");
+
+  // The anonymous block's style inherits from ours, and we already have our new
+  // style context.
+  RefPtr<nsStyleContext> newContext =
+    aStyleSet.ResolveAnonymousBoxStyle(pseudo, StyleContext());
+
+  // We're guaranteed that newContext only differs from the old style context on
+  // the block in things they might inherit from us.  And changehint processing
+  // guarantees walking the continuation and ib-sibling chains, so our existing
+  // changehint beign in aChangeList is good enough.  So we don't need to touch
+  // aChangeList at all here.
+
+  while (blockFrame) {
+    MOZ_ASSERT(!blockFrame->GetPrevContinuation(),
+               "Must be first continuation");
+    // We _could_ just walk along using GetNextContinuationWithSameStyle here,
+    // but it would involve going back to the first continuation every so often,
+    // which is a bit silly when we can just keep track of our first
+    // continuations.
+    for (nsIFrame* cont = blockFrame; cont; cont = cont->GetNextContinuation()) {
+      cont->SetStyleContext(newContext);
+    }
+
+    nsIFrame* nextInline = propTable->Get(blockFrame, nsIFrame::IBSplitSibling());
+    MOZ_ASSERT(nextInline, "There is always a trailing inline in an IB split");
+    blockFrame = propTable->Get(nextInline, nsIFrame::IBSplitSibling());
+  }
+}
+
 //////////////////////////////////////////////////////////////////////
 
 // nsLineFrame implementation
 
 nsFirstLineFrame*
 NS_NewFirstLineFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsFirstLineFrame(aContext);
--- a/layout/generic/nsInlineFrame.h
+++ b/layout/generic/nsInlineFrame.h
@@ -111,16 +111,23 @@ public:
   bool IsLast() const {
     // If the frame's bidi visual state is set, return is-last state
     // else return true if it's the last continuation.
     return (GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET)
              ? !!(GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST)
              : (!GetNextInFlow());
   }
 
+  // Update the style on the block wrappers around our non-inline-outside kids.
+  // This will only be called when such wrappers in fact exist.
+  virtual void DoUpdateStyleOfOwnedAnonBoxes(
+    mozilla::ServoStyleSet& aStyleSet,
+    nsStyleChangeList& aChangeList,
+    nsChangeHint aHintForThisFrame) override;
+
 protected:
   // Additional reflow state used during our reflow methods
   struct InlineReflowInput {
     nsIFrame* mPrevFrame;
     nsInlineFrame* mNextInFlow;
     nsIFrame*      mLineContainer;
     nsLineLayout*  mLineLayout;
     bool mSetParentPointer;  // when reflowing child frame first set its
--- a/layout/reftests/ib-split/reftest-stylo.list
+++ b/layout/reftests/ib-split/reftest-stylo.list
@@ -79,8 +79,10 @@ fails == float-inside-inline-between-blo
 == ignored-margins-1a.html ignored-margins-1a.html
 == ignored-margins-1b.html ignored-margins-1b.html
 == ignored-margins-2a.html ignored-margins-2a.html
 == ignored-margins-2b.html ignored-margins-2b.html
 == trailing-inline-with-continuations-1.html trailing-inline-with-continuations-1.html
 == append-to-empty-trailing-inline-1.html append-to-empty-trailing-inline-1.html
 == append-to-nested-split-inline-1.html append-to-nested-split-inline-1.html
 == append-to-nested-split-inline-1-ref.html append-to-nested-split-inline-1-ref.html
+== relpos-inline-1a.html relpos-inline-1a.html
+== relpos-inline-1b.html relpos-inline-1b.html
--- a/layout/reftests/ib-split/reftest.list
+++ b/layout/reftests/ib-split/reftest.list
@@ -78,8 +78,10 @@
 == ignored-margins-1a.html ignored-margins-1-ref.html
 == ignored-margins-1b.html ignored-margins-1-ref.html
 == ignored-margins-2a.html ignored-margins-2-ref.html
 == ignored-margins-2b.html ignored-margins-2-ref.html
 == trailing-inline-with-continuations-1.html trailing-inline-with-continuations-1-ref.html
 == append-to-empty-trailing-inline-1.html append-to-empty-trailing-inline-1-ref.html
 == append-to-nested-split-inline-1.html append-to-nested-split-inline-1-ref.html
 == append-to-nested-split-inline-1-ref.html append-to-nested-split-inline-1-noib-ref.html
+== relpos-inline-1a.html relpos-inline-1-ref.html
+== relpos-inline-1b.html relpos-inline-1-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/ib-split/relpos-inline-1-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span, div { position: relative; left: 200px }
+      html {
+        overflow: hidden;
+      }
+    </style>
+  </head>
+  <body>
+    <span>All text should be offset 200px.</span>
+    <div>Some more text</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/ib-split/relpos-inline-1a.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+    <link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu" />
+    <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
+    <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level"/>
+    <meta name="flags" content="" />
+    <style>
+      span { position: relative; left: 200px }
+      html {
+        overflow: hidden;
+      }
+    </style>
+  </head>
+  <body>
+    <span style="position: relative; left: 200px">
+      All text should be offset 200px.
+      <div>Some more text</div>
+    </span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/ib-split/relpos-inline-1b.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+    <link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu" />
+    <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
+    <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level"/>
+    <meta name="flags" content="" />
+    <style>
+      span { position: relative }
+      html {
+        overflow: hidden;
+      }
+    </style>
+  </head>
+  <body>
+    <span style="position: relative; left: 100px">
+      All text should be offset 200px.
+      <div>Some more text</div>
+    </span>
+    <script>
+      onload = function() {
+        var s = document.querySelector("span");
+        s.offsetWidth; // flush layout
+        s.style.left = "200px";
+        document.documentElement.className = "";
+      }
+    </script>
+  </body>
+</html>