forked from mirrors/gecko-dev
		
	 48b23ad561
			
		
	
	
		48b23ad561
		
	
	
	
	
		
			
			Depends on D175898 Differential Revision: https://phabricator.services.mozilla.com/D183229
		
			
				
	
	
		
			3880 lines
		
	
	
	
		
			154 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3880 lines
		
	
	
	
		
			154 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/RestyleManager.h"
 | |
| 
 | |
| #include "mozilla/AnimationUtils.h"
 | |
| #include "mozilla/Assertions.h"
 | |
| #include "mozilla/ComputedStyle.h"
 | |
| #include "mozilla/ComputedStyleInlines.h"
 | |
| #include "mozilla/DocumentStyleRootIterator.h"
 | |
| #include "mozilla/EffectSet.h"
 | |
| #include "mozilla/GeckoBindings.h"
 | |
| #include "mozilla/LayerAnimationInfo.h"
 | |
| #include "mozilla/layers/AnimationInfo.h"
 | |
| #include "mozilla/layout/ScrollAnchorContainer.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/PresShellInlines.h"
 | |
| #include "mozilla/ProfilerLabels.h"
 | |
| #include "mozilla/ServoBindings.h"
 | |
| #include "mozilla/ServoStyleSetInlines.h"
 | |
| #include "mozilla/StaticPrefs_layout.h"
 | |
| #include "mozilla/SVGIntegrationUtils.h"
 | |
| #include "mozilla/SVGObserverUtils.h"
 | |
| #include "mozilla/SVGTextFrame.h"
 | |
| #include "mozilla/SVGUtils.h"
 | |
| #include "mozilla/Unused.h"
 | |
| #include "mozilla/ViewportFrame.h"
 | |
| #include "mozilla/IntegerRange.h"
 | |
| #include "mozilla/dom/ChildIterator.h"
 | |
| #include "mozilla/dom/DocumentInlines.h"
 | |
| #include "mozilla/dom/ElementInlines.h"
 | |
| #include "mozilla/dom/HTMLBodyElement.h"
 | |
| #include "mozilla/dom/HTMLInputElement.h"
 | |
| 
 | |
| #include "ScrollSnap.h"
 | |
| #include "nsAnimationManager.h"
 | |
| #include "nsBlockFrame.h"
 | |
| #include "nsIScrollableFrame.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsCSSFrameConstructor.h"
 | |
| #include "nsCSSRendering.h"
 | |
| #include "nsDocShell.h"
 | |
| #include "nsIFrame.h"
 | |
| #include "nsIFrameInlines.h"
 | |
| #include "nsImageFrame.h"
 | |
| #include "nsPlaceholderFrame.h"
 | |
| #include "nsPrintfCString.h"
 | |
| #include "nsRefreshDriver.h"
 | |
| #include "nsStyleChangeList.h"
 | |
| #include "nsStyleUtil.h"
 | |
| #include "nsTransitionManager.h"
 | |
| #include "StickyScrollContainer.h"
 | |
| #include "ActiveLayerTracker.h"
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
| #  include "nsAccessibilityService.h"
 | |
| #endif
 | |
| 
 | |
| using mozilla::layers::AnimationInfo;
 | |
| using mozilla::layout::ScrollAnchorContainer;
 | |
| 
 | |
| using namespace mozilla::dom;
 | |
| using namespace mozilla::layers;
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| RestyleManager::RestyleManager(nsPresContext* aPresContext)
 | |
|     : mPresContext(aPresContext),
 | |
|       mRestyleGeneration(1),
 | |
|       mUndisplayedRestyleGeneration(1),
 | |
|       mInStyleRefresh(false),
 | |
|       mAnimationGeneration(0) {
 | |
|   MOZ_ASSERT(mPresContext);
 | |
| }
 | |
| 
 | |
| void RestyleManager::ContentInserted(nsIContent* aChild) {
 | |
|   MOZ_ASSERT(aChild->GetParentNode());
 | |
|   if (aChild->IsElement()) {
 | |
|     StyleSet()->MaybeInvalidateForElementInsertion(*aChild->AsElement());
 | |
|   }
 | |
|   RestyleForInsertOrChange(aChild);
 | |
| }
 | |
| 
 | |
| void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) {
 | |
|   auto* container = aFirstNewContent->GetParentNode();
 | |
|   MOZ_ASSERT(container);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   {
 | |
|     for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
 | |
|       NS_ASSERTION(!cur->IsRootOfNativeAnonymousSubtree(),
 | |
|                    "anonymous nodes should not be in child lists");
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   StyleSet()->MaybeInvalidateForElementAppend(*aFirstNewContent);
 | |
| 
 | |
|   const auto selectorFlags = container->GetSelectorFlags() &
 | |
|                              NodeSelectorFlags::AllSimpleRestyleFlagsForAppend;
 | |
|   if (!selectorFlags) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // The container cannot be a document.
 | |
|   MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasEmptySelector) {
 | |
|     // see whether we need to restyle the container
 | |
|     bool wasEmpty = true;  // :empty or :-moz-only-whitespace
 | |
|     for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent;
 | |
|          cur = cur->GetNextSibling()) {
 | |
|       // We don't know whether we're testing :empty or :-moz-only-whitespace,
 | |
|       // so be conservative and assume :-moz-only-whitespace (i.e., make
 | |
|       // IsSignificantChild less likely to be true, and thus make us more
 | |
|       // likely to restyle).
 | |
|       if (nsStyleUtil::IsSignificantChild(cur, false)) {
 | |
|         wasEmpty = false;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     if (wasEmpty && container->IsElement()) {
 | |
|       RestyleForEmptyChange(container->AsElement());
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
 | |
|     if (container->IsElement()) {
 | |
|       auto* containerElement = container->AsElement();
 | |
|       PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
 | |
|                        nsChangeHint(0));
 | |
|       if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
 | |
|         StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
 | |
|             containerElement->GetFirstElementChild());
 | |
|       }
 | |
|     } else {
 | |
|       RestylePreviousSiblings(aFirstNewContent);
 | |
|       RestyleSiblingsStartingWith(aFirstNewContent);
 | |
|     }
 | |
|     // Restyling the container is the most we can do here, so we're done.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
 | |
|     // restyle the last element child before this node
 | |
|     for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur;
 | |
|          cur = cur->GetPreviousSibling()) {
 | |
|       if (cur->IsElement()) {
 | |
|         auto* element = cur->AsElement();
 | |
|         PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
 | |
|                          nsChangeHint(0));
 | |
|         StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
 | |
|             *element);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::RestylePreviousSiblings(nsIContent* aStartingSibling) {
 | |
|   for (nsIContent* sibling = aStartingSibling; sibling;
 | |
|        sibling = sibling->GetPreviousSibling()) {
 | |
|     if (auto* element = Element::FromNode(sibling)) {
 | |
|       PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) {
 | |
|   for (nsIContent* sibling = aStartingSibling; sibling;
 | |
|        sibling = sibling->GetNextSibling()) {
 | |
|     if (auto* element = Element::FromNode(sibling)) {
 | |
|       PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
 | |
|   PostRestyleEvent(aContainer, RestyleHint::RestyleSubtree(), nsChangeHint(0));
 | |
|   StyleSet()->MaybeInvalidateRelativeSelectorForEmptyDependency(*aContainer);
 | |
| 
 | |
|   // In some cases (:empty + E, :empty ~ E), a change in the content of
 | |
|   // an element requires restyling its parent's siblings.
 | |
|   nsIContent* grandparent = aContainer->GetParent();
 | |
|   if (!grandparent || !(grandparent->GetSelectorFlags() &
 | |
|                         NodeSelectorFlags::HasSlowSelectorLaterSiblings)) {
 | |
|     return;
 | |
|   }
 | |
|   RestyleSiblingsStartingWith(aContainer->GetNextSibling());
 | |
| }
 | |
| 
 | |
| void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer,
 | |
|                                                     nsIContent* aChangedChild) {
 | |
|   MOZ_ASSERT(aContainer->GetSelectorFlags() &
 | |
|              NodeSelectorFlags::HasEdgeChildSelector);
 | |
|   MOZ_ASSERT(aChangedChild->GetParent() == aContainer);
 | |
|   // restyle the previously-first element child if it is after this node
 | |
|   bool passedChild = false;
 | |
|   for (nsIContent* content = aContainer->GetFirstChild(); content;
 | |
|        content = content->GetNextSibling()) {
 | |
|     if (content == aChangedChild) {
 | |
|       passedChild = true;
 | |
|       continue;
 | |
|     }
 | |
|     if (content->IsElement()) {
 | |
|       if (passedChild) {
 | |
|         auto* element = content->AsElement();
 | |
|         PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
 | |
|                          nsChangeHint(0));
 | |
|         StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
 | |
|             *element);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   // restyle the previously-last element child if it is before this node
 | |
|   passedChild = false;
 | |
|   for (nsIContent* content = aContainer->GetLastChild(); content;
 | |
|        content = content->GetPreviousSibling()) {
 | |
|     if (content == aChangedChild) {
 | |
|       passedChild = true;
 | |
|       continue;
 | |
|     }
 | |
|     if (content->IsElement()) {
 | |
|       if (passedChild) {
 | |
|         auto* element = content->AsElement();
 | |
|         PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
 | |
|                          nsChangeHint(0));
 | |
|         StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
 | |
|             *element);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| template <typename CharT>
 | |
| bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) {
 | |
|   for (auto index : IntegerRange(aUpTo)) {
 | |
|     if (!dom::IsSpaceCharacter(aBuffer[index])) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| template <typename CharT>
 | |
| bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength,
 | |
|                                    size_t aNewLength) {
 | |
|   MOZ_ASSERT(aOldLength <= aNewLength);
 | |
|   if (!WhitespaceOnly(aBuffer, aOldLength)) {
 | |
|     // The old text was already not whitespace-only.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength);
 | |
| }
 | |
| 
 | |
| static bool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) {
 | |
|   MOZ_ASSERT(aChild->GetParent() == aContainer);
 | |
|   for (nsIContent* child = aContainer->GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     if (child == aChild) {
 | |
|       continue;
 | |
|     }
 | |
|     // We don't know whether we're testing :empty or :-moz-only-whitespace,
 | |
|     // so be conservative and assume :-moz-only-whitespace (i.e., make
 | |
|     // IsSignificantChild less likely to be true, and thus make us more
 | |
|     // likely to restyle).
 | |
|     if (nsStyleUtil::IsSignificantChild(child, false)) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void RestyleManager::CharacterDataChanged(
 | |
|     nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
 | |
|   nsINode* parent = aContent->GetParentNode();
 | |
|   MOZ_ASSERT(parent, "How were we notified of a stray node?");
 | |
| 
 | |
|   const auto slowSelectorFlags =
 | |
|       parent->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
 | |
|   if (!(slowSelectorFlags & (NodeSelectorFlags::HasEmptySelector |
 | |
|                              NodeSelectorFlags::HasEdgeChildSelector))) {
 | |
|     // Nothing to do, no other slow selector can change as a result of this.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!aContent->IsText()) {
 | |
|     // Doesn't matter to styling (could be a processing instruction or a
 | |
|     // comment), it can't change whether any selectors match or don't.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (MOZ_UNLIKELY(!parent->IsElement())) {
 | |
|     MOZ_ASSERT(parent->IsShadowRoot());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (MOZ_UNLIKELY(aContent->IsRootOfNativeAnonymousSubtree())) {
 | |
|     // This is an anonymous node and thus isn't in child lists, so isn't taken
 | |
|     // into account for selector matching the relevant selectors here.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Handle appends specially since they're common and we can know both the old
 | |
|   // and the new text exactly.
 | |
|   //
 | |
|   // TODO(emilio): This could be made much more general if :-moz-only-whitespace
 | |
|   // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only
 | |
|   // need to know whether we went from empty to non-empty, and that's trivial to
 | |
|   // know, with CharacterDataChangeInfo...
 | |
|   if (!aInfo.mAppend) {
 | |
|     // FIXME(emilio): This restyles unnecessarily if the text node is the only
 | |
|     // child of the parent element. Fortunately, it's uncommon to have such
 | |
|     // nodes and this not being an append.
 | |
|     //
 | |
|     // See the testcase in bug 1427625 for a test-case that triggers this.
 | |
|     RestyleForInsertOrChange(aContent);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const nsTextFragment* text = &aContent->AsText()->TextFragment();
 | |
| 
 | |
|   const size_t oldLength = aInfo.mChangeStart;
 | |
|   const size_t newLength = text->GetLength();
 | |
| 
 | |
|   const bool emptyChanged = !oldLength && newLength;
 | |
| 
 | |
|   const bool whitespaceOnlyChanged =
 | |
|       text->Is2b()
 | |
|           ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength)
 | |
|           : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength);
 | |
| 
 | |
|   if (!emptyChanged && !whitespaceOnlyChanged) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (slowSelectorFlags & NodeSelectorFlags::HasEmptySelector) {
 | |
|     if (!HasAnySignificantSibling(parent->AsElement(), aContent)) {
 | |
|       // We used to be empty, restyle the parent.
 | |
|       RestyleForEmptyChange(parent->AsElement());
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (slowSelectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
 | |
|     MaybeRestyleForEdgeChildChange(parent, aContent);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Restyling for a ContentInserted or CharacterDataChanged notification.
 | |
| // This could be used for ContentRemoved as well if we got the
 | |
| // notification before the removal happened (and sometimes
 | |
| // CharacterDataChanged is more like a removal than an addition).
 | |
| // The comments are written and variables are named in terms of it being
 | |
| // a ContentInserted notification.
 | |
| void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
 | |
|   nsINode* container = aChild->GetParentNode();
 | |
|   MOZ_ASSERT(container);
 | |
| 
 | |
|   const auto selectorFlags =
 | |
|       container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
 | |
|   if (!selectorFlags) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NS_ASSERTION(!aChild->IsRootOfNativeAnonymousSubtree(),
 | |
|                "anonymous nodes should not be in child lists");
 | |
| 
 | |
|   // The container cannot be a document.
 | |
|   MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
 | |
|       container->IsElement()) {
 | |
|     // See whether we need to restyle the container due to :empty /
 | |
|     // :-moz-only-whitespace.
 | |
|     const bool wasEmpty =
 | |
|         !HasAnySignificantSibling(container->AsElement(), aChild);
 | |
|     if (wasEmpty) {
 | |
|       // FIXME(emilio): When coming from CharacterDataChanged this can restyle
 | |
|       // unnecessarily. Also can restyle unnecessarily if aChild is not
 | |
|       // significant anyway, though that's more unlikely.
 | |
|       RestyleForEmptyChange(container->AsElement());
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
 | |
|     if (container->IsElement()) {
 | |
|       auto* containerElement = container->AsElement();
 | |
|       PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
 | |
|                        nsChangeHint(0));
 | |
|       if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
 | |
|         StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
 | |
|             containerElement->GetFirstElementChild());
 | |
|       }
 | |
|     } else {
 | |
|       RestylePreviousSiblings(aChild);
 | |
|       RestyleSiblingsStartingWith(aChild);
 | |
|     }
 | |
|     // Restyling the container is the most we can do here, so we're done.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
 | |
|     // Restyle all later siblings.
 | |
|     RestyleSiblingsStartingWith(aChild->GetNextSibling());
 | |
|     if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
 | |
|       StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
 | |
|           aChild->GetNextElementSibling());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
 | |
|     MaybeRestyleForEdgeChildChange(container, aChild);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::ContentRemoved(nsIContent* aOldChild,
 | |
|                                     nsIContent* aFollowingSibling) {
 | |
|   auto* container = aOldChild->GetParentNode();
 | |
|   MOZ_ASSERT(container);
 | |
| 
 | |
|   // Computed style data isn't useful for detached nodes, and we'll need to
 | |
|   // recompute it anyway if we ever insert the nodes back into a document.
 | |
|   if (auto* element = Element::FromNode(aOldChild)) {
 | |
|     RestyleManager::ClearServoDataFromSubtree(element);
 | |
|     // If this element is undisplayed or may have undisplayed descendants, we
 | |
|     // need to invalidate the cache, since there's the unlikely event of those
 | |
|     // elements getting destroyed and their addresses reused in a way that we
 | |
|     // look up the cache with their address for a different element before it's
 | |
|     // invalidated.
 | |
|     IncrementUndisplayedRestyleGeneration();
 | |
|   }
 | |
|   if (aOldChild->IsElement()) {
 | |
|     StyleSet()->MaybeInvalidateForElementRemove(*aOldChild->AsElement(),
 | |
|                                                 aFollowingSibling);
 | |
|   }
 | |
| 
 | |
|   const auto selectorFlags =
 | |
|       container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
 | |
|   if (!selectorFlags) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aOldChild->IsRootOfNativeAnonymousSubtree()) {
 | |
|     // This should be an assert, but this is called incorrectly in
 | |
|     // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
 | |
|     // up the logs.  Make it an assert again when that's fixed.
 | |
|     MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
 | |
|                "anonymous nodes should not be in child lists (bug 439258)");
 | |
|   }
 | |
| 
 | |
|   // The container cannot be a document.
 | |
|   MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
 | |
|       container->IsElement()) {
 | |
|     // see whether we need to restyle the container
 | |
|     bool isEmpty = true;  // :empty or :-moz-only-whitespace
 | |
|     for (nsIContent* child = container->GetFirstChild(); child;
 | |
|          child = child->GetNextSibling()) {
 | |
|       // We don't know whether we're testing :empty or :-moz-only-whitespace,
 | |
|       // so be conservative and assume :-moz-only-whitespace (i.e., make
 | |
|       // IsSignificantChild less likely to be true, and thus make us more
 | |
|       // likely to restyle).
 | |
|       if (nsStyleUtil::IsSignificantChild(child, false)) {
 | |
|         isEmpty = false;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     if (isEmpty && container->IsElement()) {
 | |
|       RestyleForEmptyChange(container->AsElement());
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
 | |
|     if (container->IsElement()) {
 | |
|       auto* containerElement = container->AsElement();
 | |
|       PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
 | |
|                        nsChangeHint(0));
 | |
|       if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
 | |
|         StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
 | |
|             containerElement->GetFirstElementChild());
 | |
|       }
 | |
|     } else {
 | |
|       RestylePreviousSiblings(aOldChild);
 | |
|       RestyleSiblingsStartingWith(aOldChild);
 | |
|     }
 | |
|     // Restyling the container is the most we can do here, so we're done.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
 | |
|     // Restyle all later siblings.
 | |
|     RestyleSiblingsStartingWith(aFollowingSibling);
 | |
|     if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
 | |
|       Element* nextSibling =
 | |
|           aFollowingSibling ? aFollowingSibling->IsElement()
 | |
|                                   ? aFollowingSibling->AsElement()
 | |
|                                   : aFollowingSibling->GetNextElementSibling()
 | |
|                             : nullptr;
 | |
|       StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
 | |
|           nextSibling);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
 | |
|     // restyle the now-first element child if it was after aOldChild
 | |
|     bool reachedFollowingSibling = false;
 | |
|     for (nsIContent* content = container->GetFirstChild(); content;
 | |
|          content = content->GetNextSibling()) {
 | |
|       if (content == aFollowingSibling) {
 | |
|         reachedFollowingSibling = true;
 | |
|         // do NOT continue here; we might want to restyle this node
 | |
|       }
 | |
|       if (content->IsElement()) {
 | |
|         if (reachedFollowingSibling) {
 | |
|           auto* element = content->AsElement();
 | |
|           PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
 | |
|                            nsChangeHint(0));
 | |
|           StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
 | |
|               *element);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     // restyle the now-last element child if it was before aOldChild
 | |
|     reachedFollowingSibling = (aFollowingSibling == nullptr);
 | |
|     for (nsIContent* content = container->GetLastChild(); content;
 | |
|          content = content->GetPreviousSibling()) {
 | |
|       if (content->IsElement()) {
 | |
|         if (reachedFollowingSibling) {
 | |
|           auto* element = content->AsElement();
 | |
|           PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
 | |
|                            nsChangeHint(0));
 | |
|           StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
 | |
|               *element);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       if (content == aFollowingSibling) {
 | |
|         reachedFollowingSibling = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool StateChangeMayAffectFrame(const Element& aElement,
 | |
|                                       const nsIFrame& aFrame,
 | |
|                                       ElementState aStates) {
 | |
|   const bool brokenChanged = aStates.HasState(ElementState::BROKEN);
 | |
|   if (!brokenChanged) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (aFrame.IsGeneratedContentFrame()) {
 | |
|     // If it's other generated content, ignore state changes on it.
 | |
|     return aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage);
 | |
|   }
 | |
| 
 | |
|   if (aElement.IsAnyOfHTMLElements(nsGkAtoms::object, nsGkAtoms::embed)) {
 | |
|     // Broken affects object fallback behavior.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   const bool mightChange = [&] {
 | |
|     if (aElement.IsHTMLElement(nsGkAtoms::img)) {
 | |
|       return true;
 | |
|     }
 | |
|     const auto* input = HTMLInputElement::FromNode(aElement);
 | |
|     return input && input->ControlType() == FormControlType::InputImage;
 | |
|   }();
 | |
| 
 | |
|   if (!mightChange) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const bool needsImageFrame =
 | |
|       nsImageFrame::ImageFrameTypeFor(aElement, *aFrame.Style()) !=
 | |
|       nsImageFrame::ImageFrameType::None;
 | |
|   return needsImageFrame != aFrame.IsImageFrameOrSubclass();
 | |
| }
 | |
| 
 | |
| static bool RepaintForAppearance(nsIFrame& aFrame, const Element& aElement,
 | |
|                                  ElementState aStateMask) {
 | |
|   if (aStateMask.HasAtLeastOneOfStates(ElementState::HOVER |
 | |
|                                        ElementState::ACTIVE) &&
 | |
|       aElement.IsAnyOfXULElements(nsGkAtoms::checkbox, nsGkAtoms::radio)) {
 | |
|     // The checkbox inside these elements inherit hover state and so on, see
 | |
|     // nsNativeTheme::GetContentState.
 | |
|     // FIXME(emilio): Would be nice to not have these hard-coded.
 | |
|     return true;
 | |
|   }
 | |
|   auto appearance = aFrame.StyleDisplay()->EffectiveAppearance();
 | |
|   if (appearance == StyleAppearance::None) {
 | |
|     return false;
 | |
|   }
 | |
|   nsPresContext* pc = aFrame.PresContext();
 | |
|   nsITheme* theme = pc->Theme();
 | |
|   if (!theme->ThemeSupportsWidget(pc, &aFrame, appearance)) {
 | |
|     return false;
 | |
|   }
 | |
|   bool repaint = false;
 | |
|   theme->WidgetStateChanged(&aFrame, appearance, nullptr, &repaint, nullptr);
 | |
|   return repaint;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Calculates the change hint and the restyle hint for a given content state
 | |
|  * change.
 | |
|  */
 | |
| static nsChangeHint ChangeForContentStateChange(const Element& aElement,
 | |
|                                                 ElementState aStateMask) {
 | |
|   auto changeHint = nsChangeHint(0);
 | |
| 
 | |
|   // Any change to a content state that affects which frames we construct
 | |
|   // must lead to a frame reconstruct here if we already have a frame.
 | |
|   // Note that we never decide through non-CSS means to not create frames
 | |
|   // based on content states, so if we already don't have a frame we don't
 | |
|   // need to force a reframe -- if it's needed, the HasStateDependentStyle
 | |
|   // call will handle things.
 | |
|   if (nsIFrame* primaryFrame = aElement.GetPrimaryFrame()) {
 | |
|     if (StateChangeMayAffectFrame(aElement, *primaryFrame, aStateMask)) {
 | |
|       return nsChangeHint_ReconstructFrame;
 | |
|     }
 | |
|     if (RepaintForAppearance(*primaryFrame, aElement, aStateMask)) {
 | |
|       changeHint |= nsChangeHint_RepaintFrame;
 | |
|     }
 | |
|     primaryFrame->ElementStateChanged(aStateMask);
 | |
|   }
 | |
| 
 | |
|   if (aStateMask.HasState(ElementState::VISITED)) {
 | |
|     // Exposing information to the page about whether the link is
 | |
|     // visited or not isn't really something we can worry about here.
 | |
|     // FIXME: We could probably do this a bit better.
 | |
|     changeHint |= nsChangeHint_RepaintFrame;
 | |
|   }
 | |
| 
 | |
|   // This changes the applicable text-transform in the editor root.
 | |
|   if (aStateMask.HasState(ElementState::REVEALED)) {
 | |
|     // This is the same change hint as tweaking text-transform.
 | |
|     changeHint |= NS_STYLE_HINT_REFLOW;
 | |
|   }
 | |
| 
 | |
|   return changeHint;
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| /* static */
 | |
| nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) {
 | |
|   nsCString result;
 | |
|   bool any = false;
 | |
|   const char* names[] = {"RepaintFrame",
 | |
|                          "NeedReflow",
 | |
|                          "ClearAncestorIntrinsics",
 | |
|                          "ClearDescendantIntrinsics",
 | |
|                          "NeedDirtyReflow",
 | |
|                          "UpdateCursor",
 | |
|                          "UpdateEffects",
 | |
|                          "UpdateOpacityLayer",
 | |
|                          "UpdateTransformLayer",
 | |
|                          "ReconstructFrame",
 | |
|                          "UpdateOverflow",
 | |
|                          "UpdateSubtreeOverflow",
 | |
|                          "UpdatePostTransformOverflow",
 | |
|                          "UpdateParentOverflow",
 | |
|                          "ChildrenOnlyTransform",
 | |
|                          "RecomputePosition",
 | |
|                          "UpdateContainingBlock",
 | |
|                          "BorderStyleNoneChange",
 | |
|                          "SchedulePaint",
 | |
|                          "NeutralChange",
 | |
|                          "InvalidateRenderingObservers",
 | |
|                          "ReflowChangesSizeOrPosition",
 | |
|                          "UpdateComputedBSize",
 | |
|                          "UpdateUsesOpacity",
 | |
|                          "UpdateBackgroundPosition",
 | |
|                          "AddOrRemoveTransform",
 | |
|                          "ScrollbarChange",
 | |
|                          "UpdateTableCellSpans",
 | |
|                          "VisibilityChange"};
 | |
|   static_assert(nsChangeHint_AllHints ==
 | |
|                     static_cast<uint32_t>((1ull << ArrayLength(names)) - 1),
 | |
|                 "Name list doesn't match change hints.");
 | |
|   uint32_t hint =
 | |
|       aHint & static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
 | |
|   uint32_t rest =
 | |
|       aHint & ~static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
 | |
|   if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
 | |
|     result.AppendLiteral("NS_STYLE_HINT_REFLOW");
 | |
|     hint = hint & ~NS_STYLE_HINT_REFLOW;
 | |
|     any = true;
 | |
|   } else if ((hint & nsChangeHint_AllReflowHints) ==
 | |
|              nsChangeHint_AllReflowHints) {
 | |
|     result.AppendLiteral("nsChangeHint_AllReflowHints");
 | |
|     hint = hint & ~nsChangeHint_AllReflowHints;
 | |
|     any = true;
 | |
|   } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
 | |
|     result.AppendLiteral("NS_STYLE_HINT_VISUAL");
 | |
|     hint = hint & ~NS_STYLE_HINT_VISUAL;
 | |
|     any = true;
 | |
|   }
 | |
|   for (uint32_t i = 0; i < ArrayLength(names); i++) {
 | |
|     if (hint & (1u << i)) {
 | |
|       if (any) {
 | |
|         result.AppendLiteral(" | ");
 | |
|       }
 | |
|       result.AppendPrintf("nsChangeHint_%s", names[i]);
 | |
|       any = true;
 | |
|     }
 | |
|   }
 | |
|   if (rest) {
 | |
|     if (any) {
 | |
|       result.AppendLiteral(" | ");
 | |
|     }
 | |
|     result.AppendPrintf("0x%0x", rest);
 | |
|   } else {
 | |
|     if (!any) {
 | |
|       result.AppendLiteral("nsChangeHint(0)");
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|  * Frame construction helpers follow.
 | |
|  */
 | |
| #ifdef DEBUG
 | |
| static bool gInApplyRenderingChangeToTree = false;
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|  * Sync views on the frame and all of it's descendants (following placeholders).
 | |
|  * The change hint should be some combination of nsChangeHint_RepaintFrame,
 | |
|  * nsChangeHint_UpdateOpacityLayer and nsChangeHint_SchedulePaint, nothing else.
 | |
|  */
 | |
| static void SyncViewsAndInvalidateDescendants(nsIFrame*, nsChangeHint);
 | |
| 
 | |
| static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint);
 | |
| 
 | |
| /**
 | |
|  * This helper function is used to find the correct SVG frame to target when we
 | |
|  * encounter nsChangeHint_ChildrenOnlyTransform; needed since sometimes we end
 | |
|  * up handling that hint while processing hints for one of the SVG frame's
 | |
|  * ancestor frames.
 | |
|  *
 | |
|  * The reason that we sometimes end up trying to process the hint for an
 | |
|  * ancestor of the SVG frame that the hint is intended for is due to the way we
 | |
|  * process restyle events. ApplyRenderingChangeToTree adjusts the frame from
 | |
|  * the restyled element's principle frame to one of its ancestor frames based
 | |
|  * on what nsCSSRendering::FindBackground returns, since the background style
 | |
|  * may have been propagated up to an ancestor frame. Processing hints using an
 | |
|  * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
 | |
|  * a special case since it is intended to update a specific frame.
 | |
|  */
 | |
| static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) {
 | |
|   if (aFrame->IsViewportFrame()) {
 | |
|     // This happens if the root-<svg> is fixed positioned, in which case we
 | |
|     // can't use aFrame->GetContent() to find the primary frame, since
 | |
|     // GetContent() returns nullptr for ViewportFrame.
 | |
|     aFrame = aFrame->PrincipalChildList().FirstChild();
 | |
|   }
 | |
|   // For an nsHTMLScrollFrame, this will get the SVG frame that has the
 | |
|   // children-only transforms:
 | |
|   aFrame = aFrame->GetContent()->GetPrimaryFrame();
 | |
|   if (aFrame->IsSVGOuterSVGFrame()) {
 | |
|     aFrame = aFrame->PrincipalChildList().FirstChild();
 | |
|     MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(),
 | |
|                "Where is the SVGOuterSVGFrame's anon child??");
 | |
|   }
 | |
|   MOZ_ASSERT(aFrame->IsSVGContainerFrame(),
 | |
|              "Children-only transforms only expected on SVG frames");
 | |
|   return aFrame;
 | |
| }
 | |
| 
 | |
| // This function tries to optimize a position style change by either
 | |
| // moving aFrame or ignoring the style change when it's safe to do so.
 | |
| // It returns true when that succeeds, otherwise it posts a reflow request
 | |
| // and returns false.
 | |
| static bool RecomputePosition(nsIFrame* aFrame) {
 | |
|   // It's pointless to move around frames that have never been reflowed or
 | |
|   // are dirty (i.e. they will be reflowed), or aren't affected by position
 | |
|   // styles.
 | |
|   if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
 | |
|                               NS_FRAME_SVG_LAYOUT)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Don't process position changes on table frames, since we already handle
 | |
|   // the dynamic position change on the table wrapper frame, and the
 | |
|   // reflow-based fallback code path also ignores positions on inner table
 | |
|   // frames.
 | |
|   if (aFrame->IsTableFrame()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   const nsStyleDisplay* display = aFrame->StyleDisplay();
 | |
|   // Changes to the offsets of a non-positioned element can safely be ignored.
 | |
|   if (display->mPosition == StylePositionProperty::Static) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Don't process position changes on frames which have views or the ones which
 | |
|   // have a view somewhere in their descendants, because the corresponding view
 | |
|   // needs to be repositioned properly as well.
 | |
|   if (aFrame->HasView() ||
 | |
|       aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
 | |
|     // If the frame has an intrinsic block-size, we resolve its 'auto' margins
 | |
|     // after doing layout, since we need to know the frame's block size. See
 | |
|     // nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout().
 | |
|     //
 | |
|     // Since the size of the frame doesn't change, we could modify the below
 | |
|     // computation to compute the margin correctly without doing a full reflow,
 | |
|     // however we decided to try doing a full reflow for now.
 | |
|     if (aFrame->HasIntrinsicKeywordForBSize()) {
 | |
|       WritingMode wm = aFrame->GetWritingMode();
 | |
|       const auto* styleMargin = aFrame->StyleMargin();
 | |
|       if (styleMargin->HasBlockAxisAuto(wm)) {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|     // Flexbox and Grid layout supports CSS Align and the optimizations below
 | |
|     // don't support that yet.
 | |
|     nsIFrame* ph = aFrame->GetPlaceholderFrame();
 | |
|     if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If we need to reposition any descendant that depends on our static
 | |
|   // position, then we also can't take the optimized path.
 | |
|   //
 | |
|   // TODO(emilio): It may be worth trying to find them and try to call
 | |
|   // RecomputePosition on them too instead of disabling the optimization...
 | |
|   if (aFrame->DescendantMayDependOnItsStaticPosition()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   aFrame->SchedulePaint();
 | |
| 
 | |
|   auto postPendingScrollAnchorOrResnap = [](nsIFrame* frame) {
 | |
|     if (frame->IsInScrollAnchorChain()) {
 | |
|       ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(frame);
 | |
|       frame->PresShell()->PostPendingScrollAnchorAdjustment(container);
 | |
|     }
 | |
| 
 | |
|     // We need to trigger re-snapping to this content if we snapped to the
 | |
|     // content on the last scroll operation.
 | |
|     ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
 | |
|   };
 | |
| 
 | |
|   // For relative positioning, we can simply update the frame rect
 | |
|   if (display->IsRelativelyOrStickyPositionedStyle()) {
 | |
|     if (aFrame->IsGridItem()) {
 | |
|       // A grid item's CB is its grid area, not the parent frame content area
 | |
|       // as is assumed below.
 | |
|       return false;
 | |
|     }
 | |
|     // Move the frame
 | |
|     if (display->mPosition == StylePositionProperty::Sticky) {
 | |
|       // Update sticky positioning for an entire element at once, starting with
 | |
|       // the first continuation or ib-split sibling.
 | |
|       // It's rare that the frame we already have isn't already the first
 | |
|       // continuation or ib-split sibling, but it can happen when styles differ
 | |
|       // across continuations such as ::first-line or ::first-letter, and in
 | |
|       // those cases we will generally (but maybe not always) do the work twice.
 | |
|       nsIFrame* firstContinuation =
 | |
|           nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 | |
| 
 | |
|       StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
 | |
|       StickyScrollContainer* ssc =
 | |
|           StickyScrollContainer::GetStickyScrollContainerForFrame(
 | |
|               firstContinuation);
 | |
|       if (ssc) {
 | |
|         ssc->PositionContinuations(firstContinuation);
 | |
|       }
 | |
|     } else {
 | |
|       MOZ_ASSERT(display->IsRelativelyPositionedStyle(),
 | |
|                  "Unexpected type of positioning");
 | |
|       for (nsIFrame* cont = aFrame; cont;
 | |
|            cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
 | |
|         nsIFrame* cb = cont->GetContainingBlock();
 | |
|         WritingMode wm = cb->GetWritingMode();
 | |
|         const LogicalSize cbSize = cb->ContentSize();
 | |
|         const LogicalMargin newLogicalOffsets =
 | |
|             ReflowInput::ComputeRelativeOffsets(wm, cont, cbSize);
 | |
|         const nsMargin newOffsets = newLogicalOffsets.GetPhysicalMargin(wm);
 | |
| 
 | |
|         // ReflowInput::ApplyRelativePositioning would work here, but
 | |
|         // since we've already checked mPosition and aren't changing the frame's
 | |
|         // normal position, go ahead and add the offsets directly.
 | |
|         // First, we need to ensure that the normal position is stored though.
 | |
|         bool hasProperty;
 | |
|         nsPoint normalPosition = cont->GetNormalPosition(&hasProperty);
 | |
|         if (!hasProperty) {
 | |
|           cont->AddProperty(nsIFrame::NormalPositionProperty(), normalPosition);
 | |
|         }
 | |
|         cont->SetPosition(normalPosition +
 | |
|                           nsPoint(newOffsets.left, newOffsets.top));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     postPendingScrollAnchorOrResnap(aFrame);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // For the absolute positioning case, set up a fake HTML reflow input for
 | |
|   // the frame, and then get the offsets and size from it. If the frame's size
 | |
|   // doesn't need to change, we can simply update the frame position. Otherwise
 | |
|   // we fall back to a reflow.
 | |
|   UniquePtr<gfxContext> rc =
 | |
|       aFrame->PresShell()->CreateReferenceRenderingContext();
 | |
| 
 | |
|   // Construct a bogus parent reflow input so that there's a usable reflow input
 | |
|   // for the containing block.
 | |
|   nsIFrame* parentFrame = aFrame->GetParent();
 | |
|   WritingMode parentWM = parentFrame->GetWritingMode();
 | |
|   WritingMode frameWM = aFrame->GetWritingMode();
 | |
|   LogicalSize parentSize = parentFrame->GetLogicalSize();
 | |
| 
 | |
|   nsFrameState savedState = parentFrame->GetStateBits();
 | |
|   ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc.get(),
 | |
|                                 parentSize);
 | |
|   parentFrame->RemoveStateBits(~nsFrameState(0));
 | |
|   parentFrame->AddStateBits(savedState);
 | |
| 
 | |
|   // The bogus parent state here was created with no parent state of its own,
 | |
|   // and therefore it won't have an mCBReflowInput set up.
 | |
|   // But we may need one (for InitCBReflowInput in a child state), so let's
 | |
|   // try to create one here for the cases where it will be needed.
 | |
|   Maybe<ReflowInput> cbReflowInput;
 | |
|   nsIFrame* cbFrame = parentFrame->GetContainingBlock();
 | |
|   if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
 | |
|                   parentFrame->IsTableFrame())) {
 | |
|     const auto cbWM = cbFrame->GetWritingMode();
 | |
|     LogicalSize cbSize = cbFrame->GetLogicalSize();
 | |
|     cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc.get(), cbSize);
 | |
|     cbReflowInput->SetComputedLogicalMargin(
 | |
|         cbWM, cbFrame->GetLogicalUsedMargin(cbWM));
 | |
|     cbReflowInput->SetComputedLogicalPadding(
 | |
|         cbWM, cbFrame->GetLogicalUsedPadding(cbWM));
 | |
|     cbReflowInput->SetComputedLogicalBorderPadding(
 | |
|         cbWM, cbFrame->GetLogicalUsedBorderAndPadding(cbWM));
 | |
|     parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
 | |
|   }
 | |
| 
 | |
|   NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_UNCONSTRAINEDSIZE &&
 | |
|                            parentSize.BSize(parentWM) != NS_UNCONSTRAINEDSIZE,
 | |
|                        "parentSize should be valid");
 | |
|   parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
 | |
|   parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
 | |
|   parentReflowInput.SetComputedLogicalMargin(parentWM, LogicalMargin(parentWM));
 | |
| 
 | |
|   parentReflowInput.SetComputedLogicalPadding(
 | |
|       parentWM, parentFrame->GetLogicalUsedPadding(parentWM));
 | |
|   parentReflowInput.SetComputedLogicalBorderPadding(
 | |
|       parentWM, parentFrame->GetLogicalUsedBorderAndPadding(parentWM));
 | |
|   LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM);
 | |
|   availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
 | |
| 
 | |
|   ViewportFrame* viewport = do_QueryFrame(parentFrame);
 | |
|   nsSize cbSize =
 | |
|       viewport
 | |
|           ? viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput)
 | |
|                 .Size()
 | |
|           : aFrame->GetContainingBlock()->GetSize();
 | |
|   const nsMargin& parentBorder =
 | |
|       parentReflowInput.mStyleBorder->GetComputedBorder();
 | |
|   cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
 | |
|   LogicalSize lcbSize(frameWM, cbSize);
 | |
|   ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
 | |
|                           availSize, Some(lcbSize));
 | |
|   nscoord computedISize = reflowInput.ComputedISize();
 | |
|   nscoord computedBSize = reflowInput.ComputedBSize();
 | |
|   const auto frameBP = reflowInput.ComputedLogicalBorderPadding(frameWM);
 | |
|   computedISize += frameBP.IStartEnd(frameWM);
 | |
|   if (computedBSize != NS_UNCONSTRAINEDSIZE) {
 | |
|     computedBSize += frameBP.BStartEnd(frameWM);
 | |
|   }
 | |
|   LogicalSize logicalSize = aFrame->GetLogicalSize(frameWM);
 | |
|   nsSize size = aFrame->GetSize();
 | |
|   // The RecomputePosition hint is not used if any offset changed between auto
 | |
|   // and non-auto. If computedSize.height == NS_UNCONSTRAINEDSIZE then the new
 | |
|   // element height will be its intrinsic height, and since 'top' and 'bottom''s
 | |
|   // auto-ness hasn't changed, the old height must also be its intrinsic
 | |
|   // height, which we can assume hasn't changed (or reflow would have
 | |
|   // been triggered).
 | |
|   if (computedISize == logicalSize.ISize(frameWM) &&
 | |
|       (computedBSize == NS_UNCONSTRAINEDSIZE ||
 | |
|        computedBSize == logicalSize.BSize(frameWM))) {
 | |
|     // If we're solving for 'left' or 'top', then compute it here, in order to
 | |
|     // match the reflow code path.
 | |
|     //
 | |
|     // TODO(emilio): It'd be nice if this did logical math instead, but it seems
 | |
|     // to me the math should work out on vertical writing modes as well. See Bug
 | |
|     // 1675861 for some hints.
 | |
|     const nsMargin offset = reflowInput.ComputedPhysicalOffsets();
 | |
|     const nsMargin margin = reflowInput.ComputedPhysicalMargin();
 | |
| 
 | |
|     nscoord left = offset.left;
 | |
|     if (left == NS_AUTOOFFSET) {
 | |
|       left =
 | |
|           cbSize.width - offset.right - margin.right - size.width - margin.left;
 | |
|     }
 | |
| 
 | |
|     nscoord top = offset.top;
 | |
|     if (top == NS_AUTOOFFSET) {
 | |
|       top = cbSize.height - offset.bottom - margin.bottom - size.height -
 | |
|             margin.top;
 | |
|     }
 | |
| 
 | |
|     // Move the frame
 | |
|     nsPoint pos(parentBorder.left + left + margin.left,
 | |
|                 parentBorder.top + top + margin.top);
 | |
|     aFrame->SetPosition(pos);
 | |
| 
 | |
|     postPendingScrollAnchorOrResnap(aFrame);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Fall back to a reflow
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return true if aFrame's subtree has placeholders for out-of-flow content
 | |
|  * that would be affected due to the change to
 | |
|  * `aPossiblyChangingContainingBlock` (and thus would need to get reframed).
 | |
|  *
 | |
|  * In particular, this function returns true if there are placeholders whose OOF
 | |
|  * frames may need to be reparented (via reframing) as a result of whatever
 | |
|  * change actually happened.
 | |
|  *
 | |
|  * The `aIs{Abs,Fixed}PosContainingBlock` params represent whether
 | |
|  * `aPossiblyChangingContainingBlock` is a containing block for abs pos / fixed
 | |
|  * pos stuff, respectively, for the _new_ style that the frame already has, not
 | |
|  * the old one.
 | |
|  */
 | |
| static bool ContainingBlockChangeAffectsDescendants(
 | |
|     nsIFrame* aPossiblyChangingContainingBlock, nsIFrame* aFrame,
 | |
|     bool aIsAbsPosContainingBlock, bool aIsFixedPosContainingBlock) {
 | |
|   // All fixed-pos containing blocks should also be abs-pos containing blocks.
 | |
|   MOZ_ASSERT_IF(aIsFixedPosContainingBlock, aIsAbsPosContainingBlock);
 | |
| 
 | |
|   for (const auto& childList : aFrame->ChildLists()) {
 | |
|     for (nsIFrame* f : childList.mList) {
 | |
|       if (f->IsPlaceholderFrame()) {
 | |
|         nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
 | |
|         // If SVG text frames could appear here, they could confuse us since
 | |
|         // they ignore their position style ... but they can't.
 | |
|         NS_ASSERTION(!outOfFlow->IsInSVGTextSubtree(),
 | |
|                      "SVG text frames can't be out of flow");
 | |
|         // Top-layer frames don't change containing block based on direct
 | |
|         // ancestors.
 | |
|         auto* display = outOfFlow->StyleDisplay();
 | |
|         if (display->IsAbsolutelyPositionedStyle() &&
 | |
|             display->mTopLayer == StyleTopLayer::None) {
 | |
|           const bool isContainingBlock =
 | |
|               aIsFixedPosContainingBlock ||
 | |
|               (aIsAbsPosContainingBlock &&
 | |
|                display->mPosition == StylePositionProperty::Absolute);
 | |
|           // NOTE(emilio): aPossiblyChangingContainingBlock is guaranteed to be
 | |
|           // a first continuation, see the assertion in the caller.
 | |
|           nsIFrame* parent = outOfFlow->GetParent()->FirstContinuation();
 | |
|           if (isContainingBlock) {
 | |
|             // If we are becoming a containing block, we only need to reframe if
 | |
|             // this oof's current containing block is an ancestor of the new
 | |
|             // frame.
 | |
|             if (parent != aPossiblyChangingContainingBlock &&
 | |
|                 nsLayoutUtils::IsProperAncestorFrame(
 | |
|                     parent, aPossiblyChangingContainingBlock)) {
 | |
|               return true;
 | |
|             }
 | |
|           } else {
 | |
|             // If we are not a containing block anymore, we only need to reframe
 | |
|             // if we are the current containing block of the oof frame.
 | |
|             if (parent == aPossiblyChangingContainingBlock) {
 | |
|               return true;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       // NOTE:  It's tempting to check f->IsAbsPosContainingBlock() or
 | |
|       // f->IsFixedPosContainingBlock() here.  However, that would only
 | |
|       // be testing the *new* style of the frame, which might exclude
 | |
|       // descendants that currently have this frame as an abs-pos
 | |
|       // containing block.  Taking the codepath where we don't reframe
 | |
|       // could lead to an unsafe call to
 | |
|       // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed
 | |
|       // the descendant and taken it off the absolute list.
 | |
|       if (ContainingBlockChangeAffectsDescendants(
 | |
|               aPossiblyChangingContainingBlock, f, aIsAbsPosContainingBlock,
 | |
|               aIsFixedPosContainingBlock)) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // Returns the frame that would serve as the containing block for aFrame's
 | |
| // positioned descendants, if aFrame had styles to make it a CB for such
 | |
| // descendants. (Typically this is just aFrame itself, or its insertion frame).
 | |
| //
 | |
| // Returns nullptr if this frame can't be easily determined.
 | |
| static nsIFrame* ContainingBlockForFrame(nsIFrame* aFrame) {
 | |
|   if (aFrame->IsFieldSetFrame()) {
 | |
|     // FIXME: This should be easily implementable.
 | |
|     return nullptr;
 | |
|   }
 | |
|   nsIFrame* insertionFrame = aFrame->GetContentInsertionFrame();
 | |
|   if (insertionFrame == aFrame) {
 | |
|     return insertionFrame;
 | |
|   }
 | |
|   // Generally frames with a different insertion frame are hard to deal with,
 | |
|   // but scrollframes are easy because the containing block is just the
 | |
|   // insertion frame.
 | |
|   if (aFrame->IsScrollFrame()) {
 | |
|     return insertionFrame;
 | |
|   }
 | |
|   // Combobox frames are easy as well because they can't have positioned
 | |
|   // children anyways.
 | |
|   // Button and table cell frames are also easy because the containing block is
 | |
|   // the frame itself.
 | |
|   if (aFrame->IsComboboxControlFrame() || aFrame->IsHTMLButtonControlFrame() ||
 | |
|       aFrame->IsTableCellFrame()) {
 | |
|     return aFrame;
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| static bool NeedToReframeToUpdateContainingBlock(nsIFrame* aFrame,
 | |
|                                                  nsIFrame* aMaybeChangingCB) {
 | |
|   // NOTE: This looks at the new style.
 | |
|   const bool isFixedContainingBlock = aFrame->IsFixedPosContainingBlock();
 | |
|   MOZ_ASSERT_IF(isFixedContainingBlock, aFrame->IsAbsPosContainingBlock());
 | |
| 
 | |
|   const bool isAbsPosContainingBlock =
 | |
|       isFixedContainingBlock || aFrame->IsAbsPosContainingBlock();
 | |
| 
 | |
|   for (nsIFrame* f = aFrame; f;
 | |
|        f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
 | |
|     if (ContainingBlockChangeAffectsDescendants(aMaybeChangingCB, f,
 | |
|                                                 isAbsPosContainingBlock,
 | |
|                                                 isFixedContainingBlock)) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static void DoApplyRenderingChangeToTree(nsIFrame* aFrame,
 | |
|                                          nsChangeHint aChange) {
 | |
|   MOZ_ASSERT(gInApplyRenderingChangeToTree,
 | |
|              "should only be called within ApplyRenderingChangeToTree");
 | |
| 
 | |
|   for (; aFrame;
 | |
|        aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) {
 | |
|     // Invalidate and sync views on all descendant frames, following
 | |
|     // placeholders. We don't need to update transforms in
 | |
|     // SyncViewsAndInvalidateDescendants, because there can't be any
 | |
|     // out-of-flows or popups that need to be transformed; all out-of-flow
 | |
|     // descendants of the transformed element must also be descendants of the
 | |
|     // transformed frame.
 | |
|     SyncViewsAndInvalidateDescendants(
 | |
|         aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
 | |
|                                         nsChangeHint_UpdateOpacityLayer |
 | |
|                                         nsChangeHint_SchedulePaint)));
 | |
|     // This must be set to true if the rendering change needs to
 | |
|     // invalidate content.  If it's false, a composite-only paint
 | |
|     // (empty transaction) will be scheduled.
 | |
|     bool needInvalidatingPaint = false;
 | |
| 
 | |
|     // if frame has view, will already be invalidated
 | |
|     if (aChange & nsChangeHint_RepaintFrame) {
 | |
|       // Note that this whole block will be skipped when painting is suppressed
 | |
|       // (due to our caller ApplyRendingChangeToTree() discarding the
 | |
|       // nsChangeHint_RepaintFrame hint).  If you add handling for any other
 | |
|       // hints within this block, be sure that they too should be ignored when
 | |
|       // painting is suppressed.
 | |
|       needInvalidatingPaint = true;
 | |
|       aFrame->InvalidateFrameSubtree();
 | |
|       if ((aChange & nsChangeHint_UpdateEffects) &&
 | |
|           aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
 | |
|         // Need to update our overflow rects:
 | |
|         SVGUtils::ScheduleReflowSVG(aFrame);
 | |
|       }
 | |
| 
 | |
|       ActiveLayerTracker::NotifyNeedsRepaint(aFrame);
 | |
|     }
 | |
|     if (aChange & nsChangeHint_UpdateOpacityLayer) {
 | |
|       // FIXME/bug 796697: we can get away with empty transactions for
 | |
|       // opacity updates in many cases.
 | |
|       needInvalidatingPaint = true;
 | |
| 
 | |
|       ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
 | |
|       if (SVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
 | |
|         // SVG effects paints the opacity without using
 | |
|         // nsDisplayOpacity. We need to invalidate manually.
 | |
|         aFrame->InvalidateFrameSubtree();
 | |
|       }
 | |
|     }
 | |
|     if ((aChange & nsChangeHint_UpdateTransformLayer) &&
 | |
|         aFrame->IsTransformed()) {
 | |
|       // Note: All the transform-like properties should map to the same
 | |
|       // layer activity index, so does the restyle count. Therefore, using
 | |
|       // eCSSProperty_transform should be fine.
 | |
|       ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
 | |
|       needInvalidatingPaint = true;
 | |
|     }
 | |
|     if (aChange & nsChangeHint_ChildrenOnlyTransform) {
 | |
|       needInvalidatingPaint = true;
 | |
|       nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame)
 | |
|                                  ->PrincipalChildList()
 | |
|                                  .FirstChild();
 | |
|       for (; childFrame; childFrame = childFrame->GetNextSibling()) {
 | |
|         // Note: All the transform-like properties should map to the same
 | |
|         // layer activity index, so does the restyle count. Therefore, using
 | |
|         // eCSSProperty_transform should be fine.
 | |
|         ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
 | |
|       }
 | |
|     }
 | |
|     if (aChange & nsChangeHint_SchedulePaint) {
 | |
|       needInvalidatingPaint = true;
 | |
|     }
 | |
|     aFrame->SchedulePaint(needInvalidatingPaint
 | |
|                               ? nsIFrame::PAINT_DEFAULT
 | |
|                               : nsIFrame::PAINT_COMPOSITE_ONLY);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
 | |
|                                               nsChangeHint aChange) {
 | |
|   MOZ_ASSERT(gInApplyRenderingChangeToTree,
 | |
|              "should only be called within ApplyRenderingChangeToTree");
 | |
| 
 | |
|   NS_ASSERTION(nsChangeHint_size_t(aChange) ==
 | |
|                    (aChange & (nsChangeHint_RepaintFrame |
 | |
|                                nsChangeHint_UpdateOpacityLayer |
 | |
|                                nsChangeHint_SchedulePaint)),
 | |
|                "Invalid change flag");
 | |
| 
 | |
|   aFrame->SyncFrameViewProperties();
 | |
| 
 | |
|   for (const auto& [list, listID] : aFrame->ChildLists()) {
 | |
|     for (nsIFrame* child : list) {
 | |
|       if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
 | |
|         // only do frames that don't have placeholders
 | |
|         if (child->IsPlaceholderFrame()) {
 | |
|           // do the out-of-flow frame and its continuations
 | |
|           nsIFrame* outOfFlowFrame =
 | |
|               nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
 | |
|           DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
 | |
|         } else if (listID == FrameChildListID::Popup) {
 | |
|           DoApplyRenderingChangeToTree(child, aChange);
 | |
|         } else {  // regular frame
 | |
|           SyncViewsAndInvalidateDescendants(child, aChange);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame,
 | |
|                                        nsChangeHint aChange) {
 | |
|   // We check StyleDisplay()->HasTransformStyle() in addition to checking
 | |
|   // IsTransformed() since we can get here for some frames that don't support
 | |
|   // CSS transforms, and table frames, which are their own odd-ball, since the
 | |
|   // transform is handled by their wrapper, which _also_ gets a separate hint.
 | |
|   NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
 | |
|                    aFrame->IsTransformed() ||
 | |
|                    aFrame->StyleDisplay()->HasTransformStyle(),
 | |
|                "Unexpected UpdateTransformLayer hint");
 | |
| 
 | |
|   if (aPresShell->IsPaintingSuppressed()) {
 | |
|     // Don't allow synchronous rendering changes when painting is turned off.
 | |
|     aChange &= ~nsChangeHint_RepaintFrame;
 | |
|     if (!aChange) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| // Trigger rendering updates by damaging this frame and any
 | |
| // continuations of this frame.
 | |
| #ifdef DEBUG
 | |
|   gInApplyRenderingChangeToTree = true;
 | |
| #endif
 | |
|   if (aChange & nsChangeHint_RepaintFrame) {
 | |
|     // If the frame is the primary frame of either the body element or
 | |
|     // the html element, we propagate the repaint change hint to the
 | |
|     // viewport. This is necessary for background and scrollbar colors
 | |
|     // propagation.
 | |
|     if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
 | |
|       nsIFrame* rootFrame = aPresShell->GetRootFrame();
 | |
|       MOZ_ASSERT(rootFrame, "No root frame?");
 | |
|       DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame);
 | |
|       aChange &= ~nsChangeHint_RepaintFrame;
 | |
|       if (!aChange) {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   DoApplyRenderingChangeToTree(aFrame, aChange);
 | |
| #ifdef DEBUG
 | |
|   gInApplyRenderingChangeToTree = false;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static void AddSubtreeToOverflowTracker(
 | |
|     nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) {
 | |
|   if (aFrame->FrameMaintainsOverflow()) {
 | |
|     aOverflowChangedTracker.AddFrame(aFrame,
 | |
|                                      OverflowChangedTracker::CHILDREN_CHANGED);
 | |
|   }
 | |
|   for (const auto& childList : aFrame->ChildLists()) {
 | |
|     for (nsIFrame* child : childList.mList) {
 | |
|       AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) {
 | |
|   IntrinsicDirty dirtyType;
 | |
|   if (aHint & nsChangeHint_ClearDescendantIntrinsics) {
 | |
|     NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics,
 | |
|                  "Please read the comments in nsChangeHint.h");
 | |
|     NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow,
 | |
|                  "ClearDescendantIntrinsics requires NeedDirtyReflow");
 | |
|     dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
 | |
|   } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
 | |
|              aFrame->HasAnyStateBits(
 | |
|                  NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
 | |
|     dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
 | |
|   } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) {
 | |
|     dirtyType = IntrinsicDirty::FrameAndAncestors;
 | |
|   } else {
 | |
|     dirtyType = IntrinsicDirty::None;
 | |
|   }
 | |
| 
 | |
|   if (aHint & nsChangeHint_UpdateComputedBSize) {
 | |
|     aFrame->SetHasBSizeChange(true);
 | |
|   }
 | |
| 
 | |
|   nsFrameState dirtyBits;
 | |
|   if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
 | |
|     dirtyBits = nsFrameState(0);
 | |
|   } else if ((aHint & nsChangeHint_NeedDirtyReflow) ||
 | |
|              dirtyType == IntrinsicDirty::FrameAncestorsAndDescendants) {
 | |
|     dirtyBits = NS_FRAME_IS_DIRTY;
 | |
|   } else {
 | |
|     dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN;
 | |
|   }
 | |
| 
 | |
|   // If we're not going to clear any intrinsic sizes on the frames, and
 | |
|   // there are no dirty bits to set, then there's nothing to do.
 | |
|   if (dirtyType == IntrinsicDirty::None && !dirtyBits) return;
 | |
| 
 | |
|   ReflowRootHandling rootHandling;
 | |
|   if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
 | |
|     rootHandling = ReflowRootHandling::PositionOrSizeChange;
 | |
|   } else {
 | |
|     rootHandling = ReflowRootHandling::NoPositionOrSizeChange;
 | |
|   }
 | |
| 
 | |
|   do {
 | |
|     aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
 | |
|                                           rootHandling);
 | |
|     aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
 | |
|   } while (aFrame);
 | |
| }
 | |
| 
 | |
| // Get the next sibling which might have a frame.  This only considers siblings
 | |
| // that stylo post-traversal looks at, so only elements and text.  In
 | |
| // particular, it ignores comments.
 | |
| static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) {
 | |
|   for (nsIContent* next = aContent->GetNextSibling(); next;
 | |
|        next = next->GetNextSibling()) {
 | |
|     if (next->IsElement() || next->IsText()) {
 | |
|       return next;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| // If |aFrame| is dirty or has dirty children, then we can skip updating
 | |
| // overflows since that will happen when it's reflowed.
 | |
| static inline bool CanSkipOverflowUpdates(const nsIFrame* aFrame) {
 | |
|   return aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY |
 | |
|                                  NS_FRAME_HAS_DIRTY_CHILDREN);
 | |
| }
 | |
| 
 | |
| static inline void TryToDealWithScrollbarChange(nsChangeHint& aHint,
 | |
|                                                 nsIContent* aContent,
 | |
|                                                 nsIFrame* aFrame,
 | |
|                                                 nsPresContext* aPc) {
 | |
|   if (!(aHint & nsChangeHint_ScrollbarChange)) {
 | |
|     return;
 | |
|   }
 | |
|   aHint &= ~nsChangeHint_ScrollbarChange;
 | |
|   if (aHint & nsChangeHint_ReconstructFrame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
 | |
| 
 | |
|   const bool isRoot = aContent->IsInUncomposedDoc() && !aContent->GetParent();
 | |
| 
 | |
|   // Only bother with this if we're the root or the body element, since:
 | |
|   //  (a) It'd be *expensive* to reframe these particular nodes.  They're
 | |
|   //      at the root, so reframing would mean rebuilding the world.
 | |
|   //  (b) It's often *unnecessary* to reframe for "overflow" changes on
 | |
|   //      these particular nodes.  In general, the only reason we reframe
 | |
|   //      for "overflow" changes is so we can construct (or destroy) a
 | |
|   //      scrollframe & scrollbars -- and the html/body nodes often don't
 | |
|   //      need their own scrollframe/scrollbars because they coopt the ones
 | |
|   //      on the viewport (which always exist). So depending on whether
 | |
|   //      that's happening, we can skip the reframe for these nodes.
 | |
|   if (isRoot || aContent->IsHTMLElement(nsGkAtoms::body)) {
 | |
|     // If the restyled element provided/provides the scrollbar styles for
 | |
|     // the viewport before and/or after this restyle, AND it's not coopting
 | |
|     // that responsibility from some other element (which would need
 | |
|     // reconstruction to make its own scrollframe now), THEN: we don't need
 | |
|     // to reconstruct - we can just reflow, because no scrollframe is being
 | |
|     // added/removed.
 | |
|     Element* prevOverride = aPc->GetViewportScrollStylesOverrideElement();
 | |
|     Element* newOverride = aPc->UpdateViewportScrollStylesOverride();
 | |
| 
 | |
|     const auto ProvidesScrollbarStyles = [&](nsIContent* aOverride) {
 | |
|       if (aOverride) {
 | |
|         return aOverride == aContent;
 | |
|       }
 | |
|       return isRoot;
 | |
|     };
 | |
| 
 | |
|     if (ProvidesScrollbarStyles(prevOverride) ||
 | |
|         ProvidesScrollbarStyles(newOverride)) {
 | |
|       // If we get here, the restyled element provided the scrollbar styles
 | |
|       // for viewport before this restyle, OR it will provide them after.
 | |
|       if (!prevOverride || !newOverride || prevOverride == newOverride) {
 | |
|         // If we get here, the restyled element is NOT replacing (or being
 | |
|         // replaced by) some other element as the viewport's
 | |
|         // scrollbar-styles provider. (If it were, we'd potentially need to
 | |
|         // reframe to create a dedicated scrollframe for whichever element
 | |
|         // is being booted from providing viewport scrollbar styles.)
 | |
|         //
 | |
|         // Under these conditions, we're OK to assume that this "overflow"
 | |
|         // change only impacts the root viewport's scrollframe, which
 | |
|         // already exists, so we can simply reflow instead of reframing.
 | |
|         if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
 | |
|           sf->MarkScrollbarsDirtyForReflow();
 | |
|         } else if (nsIScrollableFrame* sf =
 | |
|                        aPc->PresShell()->GetRootScrollFrameAsScrollable()) {
 | |
|           sf->MarkScrollbarsDirtyForReflow();
 | |
|         }
 | |
|         aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
 | |
|       } else {
 | |
|         // If we changed the override element, we need to reconstruct as the old
 | |
|         // override element might start / stop being scrollable.
 | |
|         aHint |= nsChangeHint_ReconstructFrame;
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const bool scrollable = aFrame->StyleDisplay()->IsScrollableOverflow();
 | |
|   if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
 | |
|     if (scrollable && sf->HasAllNeededScrollbars()) {
 | |
|       sf->MarkScrollbarsDirtyForReflow();
 | |
|       // Once we've created scrollbars for a frame, don't bother reconstructing
 | |
|       // it just to remove them if we still need a scroll frame.
 | |
|       aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
 | |
|       return;
 | |
|     }
 | |
|   } else if (aFrame->IsTextInputFrame()) {
 | |
|     // input / textarea for the most part don't honor overflow themselves, the
 | |
|     // editor root will deal with the change if needed.
 | |
|     // However the textarea intrinsic size relies on GetDesiredScrollbarSizes(),
 | |
|     // so we need to reflow the textarea itself, not just the inner control.
 | |
|     aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
 | |
|     return;
 | |
|   } else if (!scrollable) {
 | |
|     // Something changed, but we don't have nor will have a scroll frame,
 | |
|     // there's nothing to do here.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Oh well, we couldn't optimize it out, just reconstruct frames for the
 | |
|   // subtree.
 | |
|   aHint |= nsChangeHint_ReconstructFrame;
 | |
| }
 | |
| 
 | |
| static void TryToHandleContainingBlockChange(nsChangeHint& aHint,
 | |
|                                              nsIFrame* aFrame) {
 | |
|   if (!(aHint & nsChangeHint_UpdateContainingBlock)) {
 | |
|     return;
 | |
|   }
 | |
|   if (aHint & nsChangeHint_ReconstructFrame) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
 | |
|   nsIFrame* containingBlock = ContainingBlockForFrame(aFrame);
 | |
|   if (!containingBlock ||
 | |
|       NeedToReframeToUpdateContainingBlock(aFrame, containingBlock)) {
 | |
|     // The frame has positioned children that need to be reparented, or it can't
 | |
|     // easily be converted to/from being an abs-pos container correctly.
 | |
|     aHint |= nsChangeHint_ReconstructFrame;
 | |
|     return;
 | |
|   }
 | |
|   const bool isCb = aFrame->IsAbsPosContainingBlock();
 | |
| 
 | |
|   // The absolute container should be containingBlock.
 | |
|   for (nsIFrame* cont = containingBlock; cont;
 | |
|        cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
 | |
|     // Normally frame construction would set state bits as needed,
 | |
|     // but we're not going to reconstruct the frame so we need to set
 | |
|     // them. It's because we need to set this state on each affected frame
 | |
|     // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
 | |
|     // to ancestors (i.e. it can't be an change hint that is handled for
 | |
|     // descendants).
 | |
|     if (isCb) {
 | |
|       if (!cont->IsAbsoluteContainer() &&
 | |
|           cont->HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
 | |
|         cont->MarkAsAbsoluteContainingBlock();
 | |
|       }
 | |
|     } else if (cont->IsAbsoluteContainer()) {
 | |
|       if (cont->HasAbsolutelyPositionedChildren()) {
 | |
|         // If |cont| still has absolutely positioned children,
 | |
|         // we can't call MarkAsNotAbsoluteContainingBlock.  This
 | |
|         // will remove a frame list that still has children in
 | |
|         // it that we need to keep track of.
 | |
|         // The optimization of removing it isn't particularly
 | |
|         // important, although it does mean we skip some tests.
 | |
|         NS_WARNING("skipping removal of absolute containing block");
 | |
|       } else {
 | |
|         cont->MarkAsNotAbsoluteContainingBlock();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
 | |
|   NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
 | |
|                "Someone forgot a script blocker");
 | |
| 
 | |
|   // See bug 1378219 comment 9:
 | |
|   // Recursive calls here are a bit worrying, but apparently do happen in the
 | |
|   // wild (although not currently in any of our automated tests). Try to get a
 | |
|   // stack from Nightly/Dev channel to figure out what's going on and whether
 | |
|   // it's OK.
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion");
 | |
| 
 | |
|   if (aChangeList.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If mDestroyedFrames is null, we want to create a new hashtable here
 | |
|   // and destroy it on exit; but if it is already non-null (because we're in
 | |
|   // a recursive call), we will continue to use the existing table to
 | |
|   // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit.
 | |
|   // We use a MaybeClearDestroyedFrames helper to conditionally reset the
 | |
|   // mDestroyedFrames pointer when this method returns.
 | |
|   typedef decltype(mDestroyedFrames) DestroyedFramesT;
 | |
|   class MOZ_RAII MaybeClearDestroyedFrames {
 | |
|    private:
 | |
|     DestroyedFramesT& mDestroyedFramesRef;  // ref to caller's mDestroyedFrames
 | |
|     const bool mResetOnDestruction;
 | |
| 
 | |
|    public:
 | |
|     explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget)
 | |
|         : mDestroyedFramesRef(aTarget),
 | |
|           mResetOnDestruction(!aTarget)  // reset only if target starts out null
 | |
|     {}
 | |
|     ~MaybeClearDestroyedFrames() {
 | |
|       if (mResetOnDestruction) {
 | |
|         mDestroyedFramesRef.reset(nullptr);
 | |
|       }
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   MaybeClearDestroyedFrames maybeClear(mDestroyedFrames);
 | |
|   if (!mDestroyedFrames) {
 | |
|     mDestroyedFrames = MakeUnique<nsTHashSet<const nsIFrame*>>();
 | |
|   }
 | |
| 
 | |
|   AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", LAYOUT);
 | |
| 
 | |
|   nsPresContext* presContext = PresContext();
 | |
|   nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
 | |
| 
 | |
|   bool didUpdateCursor = false;
 | |
| 
 | |
|   for (size_t i = 0; i < aChangeList.Length(); ++i) {
 | |
|     // Collect and coalesce adjacent siblings for lazy frame construction.
 | |
|     // Eventually it would be even better to make RecreateFramesForContent
 | |
|     // accept a range and coalesce all adjacent reconstructs (bug 1344139).
 | |
|     size_t lazyRangeStart = i;
 | |
|     while (i < aChangeList.Length() && aChangeList[i].mContent &&
 | |
|            aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) &&
 | |
|            (i == lazyRangeStart ||
 | |
|             NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) ==
 | |
|                 aChangeList[i].mContent)) {
 | |
|       MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame);
 | |
|       MOZ_ASSERT(!aChangeList[i].mFrame);
 | |
|       ++i;
 | |
|     }
 | |
|     if (i != lazyRangeStart) {
 | |
|       nsIContent* start = aChangeList[lazyRangeStart].mContent;
 | |
|       nsIContent* end =
 | |
|           NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent);
 | |
|       if (!end) {
 | |
|         frameConstructor->ContentAppended(
 | |
|             start, nsCSSFrameConstructor::InsertionKind::Sync);
 | |
|       } else {
 | |
|         frameConstructor->ContentRangeInserted(
 | |
|             start, end, nsCSSFrameConstructor::InsertionKind::Sync);
 | |
|       }
 | |
|     }
 | |
|     for (size_t j = lazyRangeStart; j < i; ++j) {
 | |
|       MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() ||
 | |
|                  !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME));
 | |
|     }
 | |
|     if (i == aChangeList.Length()) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     const nsStyleChangeData& data = aChangeList[i];
 | |
|     nsIFrame* frame = data.mFrame;
 | |
|     nsIContent* content = data.mContent;
 | |
|     nsChangeHint hint = data.mHint;
 | |
|     bool didReflowThisFrame = false;
 | |
| 
 | |
|     NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
 | |
|                      (hint & nsChangeHint_NeedReflow),
 | |
|                  "Reflow hint bits set without actually asking for a reflow");
 | |
| 
 | |
|     // skip any frame that has been destroyed due to a ripple effect
 | |
|     if (frame && mDestroyedFrames->Contains(frame)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (frame && frame->GetContent() != content) {
 | |
|       // XXXbz this is due to image maps messing with the primary frame of
 | |
|       // <area>s.  See bug 135040.  Remove this block once that's fixed.
 | |
|       frame = nullptr;
 | |
|       if (!(hint & nsChangeHint_ReconstructFrame)) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     TryToDealWithScrollbarChange(hint, content, frame, presContext);
 | |
|     TryToHandleContainingBlockChange(hint, frame);
 | |
| 
 | |
|     if (hint & nsChangeHint_ReconstructFrame) {
 | |
|       // If we ever start passing true here, be careful of restyles
 | |
|       // that involve a reframe and animations.  In particular, if the
 | |
|       // restyle we're processing here is an animation restyle, but
 | |
|       // the style resolution we will do for the frame construction
 | |
|       // happens async when we're not in an animation restyle already,
 | |
|       // problems could arise.
 | |
|       // We could also have problems with triggering of CSS transitions
 | |
|       // on elements whose frames are reconstructed, since we depend on
 | |
|       // the reconstruction happening synchronously.
 | |
|       frameConstructor->RecreateFramesForContent(
 | |
|           content, nsCSSFrameConstructor::InsertionKind::Sync);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     MOZ_ASSERT(frame, "This shouldn't happen");
 | |
|     if (hint & nsChangeHint_AddOrRemoveTransform) {
 | |
|       for (nsIFrame* cont = frame; cont;
 | |
|            cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
 | |
|         if (cont->StyleDisplay()->HasTransform(cont)) {
 | |
|           cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
 | |
|         }
 | |
|         // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be
 | |
|         // transformed by other means. It's OK to have the bit even if it's
 | |
|         // not needed.
 | |
|       }
 | |
|       // When dropping a running transform animation we will first add an
 | |
|       // nsChangeHint_UpdateTransformLayer hint as part of the animation-only
 | |
|       // restyle. During the subsequent regular restyle, if the animation was
 | |
|       // the only reason the element had any transform applied, we will add
 | |
|       // nsChangeHint_AddOrRemoveTransform as part of the regular restyle.
 | |
|       //
 | |
|       // With the Gecko backend, these two change hints are processed
 | |
|       // after each restyle but when using the Servo backend they accumulate
 | |
|       // and are processed together after we have already removed the
 | |
|       // transform as part of the regular restyle. Since we don't actually
 | |
|       // need the nsChangeHint_UpdateTransformLayer hint if we already have
 | |
|       // a nsChangeHint_AddOrRemoveTransform hint, and since we
 | |
|       // will fail an assertion in ApplyRenderingChangeToTree if we try
 | |
|       // specify nsChangeHint_UpdateTransformLayer but don't have any
 | |
|       // transform style, we just drop the unneeded hint here.
 | |
|       hint &= ~nsChangeHint_UpdateTransformLayer;
 | |
|     }
 | |
| 
 | |
|     if (!frame->FrameMaintainsOverflow()) {
 | |
|       // frame does not maintain overflow rects, so avoid calling
 | |
|       // FinishAndStoreOverflow on it:
 | |
|       hint &=
 | |
|           ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform |
 | |
|             nsChangeHint_UpdatePostTransformOverflow |
 | |
|             nsChangeHint_UpdateParentOverflow |
 | |
|             nsChangeHint_UpdateSubtreeOverflow);
 | |
|     }
 | |
| 
 | |
|     if (!frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
 | |
|       // Frame can not be transformed, and thus a change in transform will
 | |
|       // have no effect and we should not use either
 | |
|       // nsChangeHint_UpdatePostTransformOverflow or
 | |
|       // nsChangeHint_UpdateTransformLayerhint.
 | |
|       hint &= ~(nsChangeHint_UpdatePostTransformOverflow |
 | |
|                 nsChangeHint_UpdateTransformLayer);
 | |
|     }
 | |
| 
 | |
|     if ((hint & nsChangeHint_UpdateEffects) &&
 | |
|         frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) {
 | |
|       SVGObserverUtils::UpdateEffects(frame);
 | |
|     }
 | |
|     if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
 | |
|         ((hint & nsChangeHint_UpdateOpacityLayer) &&
 | |
|          frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT))) {
 | |
|       SVGObserverUtils::InvalidateRenderingObservers(frame);
 | |
|       frame->SchedulePaint();
 | |
|     }
 | |
|     if (hint & nsChangeHint_NeedReflow) {
 | |
|       StyleChangeReflow(frame, hint);
 | |
|       didReflowThisFrame = true;
 | |
|     }
 | |
| 
 | |
|     // Here we need to propagate repaint frame change hint instead of update
 | |
|     // opacity layer change hint when we do opacity optimization for SVG.
 | |
|     // We can't do it in nsStyleEffects::CalcDifference() just like we do
 | |
|     // for the optimization for 0.99 over opacity values since we have no way
 | |
|     // to call SVGUtils::CanOptimizeOpacity() there.
 | |
|     if ((hint & nsChangeHint_UpdateOpacityLayer) &&
 | |
|         SVGUtils::CanOptimizeOpacity(frame)) {
 | |
|       hint &= ~nsChangeHint_UpdateOpacityLayer;
 | |
|       hint |= nsChangeHint_RepaintFrame;
 | |
|     }
 | |
| 
 | |
|     if ((hint & nsChangeHint_UpdateUsesOpacity) && frame->IsTablePart()) {
 | |
|       NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
 | |
|                    "should only return UpdateUsesOpacity hint "
 | |
|                    "when also returning UpdateOpacityLayer hint");
 | |
|       // When an internal table part (including cells) changes between
 | |
|       // having opacity 1 and non-1, it changes whether its
 | |
|       // backgrounds (and those of table parts inside of it) are
 | |
|       // painted as part of the table's nsDisplayTableBorderBackground
 | |
|       // display item, or part of its own display item.  That requires
 | |
|       // invalidation, so change UpdateOpacityLayer to RepaintFrame.
 | |
|       hint &= ~nsChangeHint_UpdateOpacityLayer;
 | |
|       hint |= nsChangeHint_RepaintFrame;
 | |
|     }
 | |
| 
 | |
|     // Opacity disables preserve-3d, so if we toggle it, then we also need
 | |
|     // to update the overflow areas of all potentially affected frames.
 | |
|     if ((hint & nsChangeHint_UpdateUsesOpacity) &&
 | |
|         frame->StyleDisplay()->mTransformStyle ==
 | |
|             StyleTransformStyle::Preserve3d) {
 | |
|       hint |= nsChangeHint_UpdateSubtreeOverflow;
 | |
|     }
 | |
| 
 | |
|     if (hint & nsChangeHint_UpdateBackgroundPosition) {
 | |
|       // For most frame types, DLBI can detect background position changes,
 | |
|       // so we only need to schedule a paint.
 | |
|       hint |= nsChangeHint_SchedulePaint;
 | |
|       if (frame->IsTablePart() || frame->IsMathMLFrame()) {
 | |
|         // Table parts and MathML frames don't build display items for their
 | |
|         // backgrounds, so DLBI can't detect background-position changes for
 | |
|         // these frames. Repaint the whole frame.
 | |
|         hint |= nsChangeHint_RepaintFrame;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (hint &
 | |
|         (nsChangeHint_RepaintFrame | nsChangeHint_UpdateOpacityLayer |
 | |
|          nsChangeHint_UpdateTransformLayer |
 | |
|          nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
 | |
|       ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint);
 | |
|     }
 | |
| 
 | |
|     if (hint & (nsChangeHint_UpdateTransformLayer |
 | |
|                 nsChangeHint_AddOrRemoveTransform)) {
 | |
|       // We need to trigger re-snapping to this content if we snapped to the
 | |
|       // content on the last scroll operation.
 | |
|       ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
 | |
|     }
 | |
| 
 | |
|     if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
 | |
|       // It is possible for this to fall back to a reflow
 | |
|       if (!RecomputePosition(frame)) {
 | |
|         StyleChangeReflow(frame, nsChangeHint_NeedReflow |
 | |
|                                      nsChangeHint_ReflowChangesSizeOrPosition);
 | |
|         didReflowThisFrame = true;
 | |
|       }
 | |
|     }
 | |
|     NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) ||
 | |
|                      (hint & nsChangeHint_UpdateOverflow),
 | |
|                  "nsChangeHint_UpdateOverflow should be passed too");
 | |
|     if (!didReflowThisFrame &&
 | |
|         (hint & (nsChangeHint_UpdateOverflow |
 | |
|                  nsChangeHint_UpdatePostTransformOverflow |
 | |
|                  nsChangeHint_UpdateParentOverflow |
 | |
|                  nsChangeHint_UpdateSubtreeOverflow))) {
 | |
|       if (hint & nsChangeHint_UpdateSubtreeOverflow) {
 | |
|         for (nsIFrame* cont = frame; cont;
 | |
|              cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
 | |
|           AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker);
 | |
|         }
 | |
|         // The work we just did in AddSubtreeToOverflowTracker
 | |
|         // subsumes some of the other hints:
 | |
|         hint &= ~(nsChangeHint_UpdateOverflow |
 | |
|                   nsChangeHint_UpdatePostTransformOverflow);
 | |
|       }
 | |
|       if (hint & nsChangeHint_ChildrenOnlyTransform) {
 | |
|         // We need to update overflows. The correct frame(s) to update depends
 | |
|         // on whether the ChangeHint came from an outer or an inner svg.
 | |
|         nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame);
 | |
|         NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame),
 | |
|                      "SVG frames should not have continuations "
 | |
|                      "or ib-split siblings");
 | |
|         NS_ASSERTION(
 | |
|             !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame),
 | |
|             "SVG frames should not have continuations "
 | |
|             "or ib-split siblings");
 | |
|         if (hintFrame->IsSVGOuterSVGAnonChildFrame()) {
 | |
|           // The children only transform of an outer svg frame is applied to
 | |
|           // the outer svg's anonymous child frame (instead of to the
 | |
|           // anonymous child's children).
 | |
| 
 | |
|           if (!CanSkipOverflowUpdates(hintFrame)) {
 | |
|             mOverflowChangedTracker.AddFrame(
 | |
|                 hintFrame, OverflowChangedTracker::CHILDREN_CHANGED);
 | |
|           }
 | |
|         } else {
 | |
|           // The children only transform is applied to the child frames of an
 | |
|           // inner svg frame, so update the child overflows.
 | |
|           nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild();
 | |
|           for (; childFrame; childFrame = childFrame->GetNextSibling()) {
 | |
|             MOZ_ASSERT(childFrame->IsSVGFrame(),
 | |
|                        "Not expecting non-SVG children");
 | |
|             if (!CanSkipOverflowUpdates(childFrame)) {
 | |
|               mOverflowChangedTracker.AddFrame(
 | |
|                   childFrame, OverflowChangedTracker::CHILDREN_CHANGED);
 | |
|             }
 | |
|             NS_ASSERTION(
 | |
|                 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame),
 | |
|                 "SVG frames should not have continuations "
 | |
|                 "or ib-split siblings");
 | |
|             NS_ASSERTION(
 | |
|                 childFrame->GetParent() == hintFrame,
 | |
|                 "SVG child frame not expected to have different parent");
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       if (!CanSkipOverflowUpdates(frame)) {
 | |
|         if (hint & (nsChangeHint_UpdateOverflow |
 | |
|                     nsChangeHint_UpdatePostTransformOverflow)) {
 | |
|           OverflowChangedTracker::ChangeKind changeKind;
 | |
|           // If we have both nsChangeHint_UpdateOverflow and
 | |
|           // nsChangeHint_UpdatePostTransformOverflow,
 | |
|           // CHILDREN_CHANGED is selected as it is
 | |
|           // strictly stronger.
 | |
|           if (hint & nsChangeHint_UpdateOverflow) {
 | |
|             changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
 | |
|           } else {
 | |
|             changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
 | |
|           }
 | |
|           for (nsIFrame* cont = frame; cont;
 | |
|                cont =
 | |
|                    nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
 | |
|             mOverflowChangedTracker.AddFrame(cont, changeKind);
 | |
|           }
 | |
|         }
 | |
|         // UpdateParentOverflow hints need to be processed in addition
 | |
|         // to the above, since if the processing of the above hints
 | |
|         // yields no change, the update will not propagate to the
 | |
|         // parent.
 | |
|         if (hint & nsChangeHint_UpdateParentOverflow) {
 | |
|           MOZ_ASSERT(frame->GetParent(),
 | |
|                      "shouldn't get style hints for the root frame");
 | |
|           for (nsIFrame* cont = frame; cont;
 | |
|                cont =
 | |
|                    nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
 | |
|             mOverflowChangedTracker.AddFrame(
 | |
|                 cont->GetParent(), OverflowChangedTracker::CHILDREN_CHANGED);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
 | |
|       presContext->PresShell()->SynthesizeMouseMove(false);
 | |
|       didUpdateCursor = true;
 | |
|     }
 | |
|     if (hint & nsChangeHint_UpdateTableCellSpans) {
 | |
|       frameConstructor->UpdateTableCellSpans(content);
 | |
|     }
 | |
|     if (hint & nsChangeHint_VisibilityChange) {
 | |
|       frame->UpdateVisibleDescendantsState();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   aChangeList.Clear();
 | |
|   FlushOverflowChangedTracker();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| uint64_t RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aStyleFrame) {
 | |
|   EffectSet* effectSet = EffectSet::GetForStyleFrame(aStyleFrame);
 | |
|   return effectSet ? effectSet->GetAnimationGeneration() : 0;
 | |
| }
 | |
| 
 | |
| void RestyleManager::IncrementAnimationGeneration() {
 | |
|   // We update the animation generation at start of each call to
 | |
|   // ProcessPendingRestyles so we should ignore any subsequent (redundant)
 | |
|   // calls that occur while we are still processing restyles.
 | |
|   if (!mInStyleRefresh) {
 | |
|     ++mAnimationGeneration;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void RestyleManager::AddLayerChangesForAnimation(
 | |
|     nsIFrame* aStyleFrame, nsIFrame* aPrimaryFrame, Element* aElement,
 | |
|     nsChangeHint aHintForThisFrame, nsStyleChangeList& aChangeListToProcess) {
 | |
|   MOZ_ASSERT(aElement);
 | |
|   MOZ_ASSERT(!!aStyleFrame == !!aPrimaryFrame);
 | |
|   if (!aStyleFrame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint64_t frameGeneration =
 | |
|       RestyleManager::GetAnimationGenerationForFrame(aStyleFrame);
 | |
| 
 | |
|   Maybe<nsCSSPropertyIDSet> effectiveAnimationProperties;
 | |
| 
 | |
|   nsChangeHint hint = nsChangeHint(0);
 | |
|   auto maybeApplyChangeHint = [&](const Maybe<uint64_t>& aGeneration,
 | |
|                                   DisplayItemType aDisplayItemType) -> bool {
 | |
|     if (aGeneration && frameGeneration != *aGeneration) {
 | |
|       // If we have a transform layer but don't have any transform style, we
 | |
|       // probably just removed the transform but haven't destroyed the layer
 | |
|       // yet. In this case we will typically add the appropriate change hint
 | |
|       // (nsChangeHint_UpdateContainingBlock) when we compare styles so in
 | |
|       // theory we could skip adding any change hint here.
 | |
|       //
 | |
|       // However, sometimes when we compare styles we'll get no change. For
 | |
|       // example, if the transform style was 'none' when we sent the transform
 | |
|       // animation to the compositor and the current transform style is now
 | |
|       // 'none' we'll think nothing changed but actually we still need to
 | |
|       // trigger an update to clear whatever style the transform animation set
 | |
|       // on the compositor. To handle this case we simply set all the change
 | |
|       // hints relevant to removing transform style (since we don't know exactly
 | |
|       // what changes happened while the animation was running on the
 | |
|       // compositor).
 | |
|       //
 | |
|       // Note that we *don't* add nsChangeHint_UpdateTransformLayer since if we
 | |
|       // did, ApplyRenderingChangeToTree would complain that we're updating a
 | |
|       // transform layer without a transform.
 | |
|       if (aDisplayItemType == DisplayItemType::TYPE_TRANSFORM &&
 | |
|           !aStyleFrame->StyleDisplay()->HasTransformStyle()) {
 | |
|         // Add all the hints for a removing a transform if they are not already
 | |
|         // set for this frame.
 | |
|         if (!(NS_IsHintSubset(nsChangeHint_ComprehensiveAddOrRemoveTransform,
 | |
|                               aHintForThisFrame))) {
 | |
|           hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
 | |
|         }
 | |
|         return true;
 | |
|       }
 | |
|       hint |= LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
 | |
|     }
 | |
| 
 | |
|     // We consider it's the first paint for the frame if we have an animation
 | |
|     // for the property but have no layer, for the case of WebRender,  no
 | |
|     // corresponding animation info.
 | |
|     // Note that in case of animations which has properties preventing running
 | |
|     // on the compositor, e.g., width or height, corresponding layer is not
 | |
|     // created at all, but even in such cases, we normally set valid change
 | |
|     // hint for such animations in each tick, i.e. restyles in each tick. As
 | |
|     // a result, we usually do restyles for such animations in every tick on
 | |
|     // the main-thread.  The only animations which will be affected by this
 | |
|     // explicit change hint are animations that have opacity/transform but did
 | |
|     // not have those properies just before. e.g, setting transform by
 | |
|     // setKeyframes or changing target element from other target which prevents
 | |
|     // running on the compositor, etc.
 | |
|     if (!aGeneration) {
 | |
|       nsChangeHint hintForDisplayItem =
 | |
|           LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
 | |
|       // We don't need to apply the corresponding change hint if we already have
 | |
|       // it.
 | |
|       if (NS_IsHintSubset(hintForDisplayItem, aHintForThisFrame)) {
 | |
|         return true;
 | |
|       }
 | |
| 
 | |
|       if (!effectiveAnimationProperties) {
 | |
|         effectiveAnimationProperties.emplace(
 | |
|             nsLayoutUtils::GetAnimationPropertiesForCompositor(aStyleFrame));
 | |
|       }
 | |
|       const nsCSSPropertyIDSet& propertiesForDisplayItem =
 | |
|           LayerAnimationInfo::GetCSSPropertiesFor(aDisplayItemType);
 | |
|       if (effectiveAnimationProperties->Intersects(propertiesForDisplayItem)) {
 | |
|         hint |= hintForDisplayItem;
 | |
|       }
 | |
|     }
 | |
|     return true;
 | |
|   };
 | |
| 
 | |
|   AnimationInfo::EnumerateGenerationOnFrame(
 | |
|       aStyleFrame, aElement, LayerAnimationInfo::sDisplayItemTypes,
 | |
|       maybeApplyChangeHint);
 | |
| 
 | |
|   if (hint) {
 | |
|     // We apply the hint to the primary frame, not the style frame. Transform
 | |
|     // and opacity hints apply to the table wrapper box, not the table box.
 | |
|     aChangeListToProcess.AppendChange(aPrimaryFrame, aElement, hint);
 | |
|   }
 | |
| }
 | |
| 
 | |
| RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
 | |
|     RestyleManager* aRestyleManager)
 | |
|     : mRestyleManager(aRestyleManager),
 | |
|       mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) {
 | |
|   MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
 | |
|              "shouldn't construct recursively");
 | |
|   mRestyleManager->mAnimationsWithDestroyedFrame = this;
 | |
| }
 | |
| 
 | |
| void RestyleManager::AnimationsWithDestroyedFrame ::
 | |
|     StopAnimationsForElementsWithoutFrames() {
 | |
|   StopAnimationsWithoutFrame(mContents, PseudoStyleType::NotPseudo);
 | |
|   StopAnimationsWithoutFrame(mBeforeContents, PseudoStyleType::before);
 | |
|   StopAnimationsWithoutFrame(mAfterContents, PseudoStyleType::after);
 | |
|   StopAnimationsWithoutFrame(mMarkerContents, PseudoStyleType::marker);
 | |
| }
 | |
| 
 | |
| void RestyleManager::AnimationsWithDestroyedFrame ::StopAnimationsWithoutFrame(
 | |
|     nsTArray<RefPtr<nsIContent>>& aArray, PseudoStyleType aPseudoType) {
 | |
|   nsAnimationManager* animationManager =
 | |
|       mRestyleManager->PresContext()->AnimationManager();
 | |
|   nsTransitionManager* transitionManager =
 | |
|       mRestyleManager->PresContext()->TransitionManager();
 | |
|   for (nsIContent* content : aArray) {
 | |
|     if (aPseudoType == PseudoStyleType::NotPseudo) {
 | |
|       if (content->GetPrimaryFrame()) {
 | |
|         continue;
 | |
|       }
 | |
|     } else if (aPseudoType == PseudoStyleType::before) {
 | |
|       if (nsLayoutUtils::GetBeforeFrame(content)) {
 | |
|         continue;
 | |
|       }
 | |
|     } else if (aPseudoType == PseudoStyleType::after) {
 | |
|       if (nsLayoutUtils::GetAfterFrame(content)) {
 | |
|         continue;
 | |
|       }
 | |
|     } else if (aPseudoType == PseudoStyleType::marker) {
 | |
|       if (nsLayoutUtils::GetMarkerFrame(content)) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
|     dom::Element* element = content->AsElement();
 | |
| 
 | |
|     animationManager->StopAnimationsForElement(element, aPseudoType);
 | |
|     transitionManager->StopAnimationsForElement(element, aPseudoType);
 | |
| 
 | |
|     // All other animations should keep running but not running on the
 | |
|     // *compositor* at this point.
 | |
|     if (EffectSet* effectSet = EffectSet::Get(element, aPseudoType)) {
 | |
|       for (KeyframeEffect* effect : *effectSet) {
 | |
|         effect->ResetIsRunningOnCompositor();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| static bool IsAnonBox(const nsIFrame* aFrame) {
 | |
|   return aFrame->Style()->IsAnonBox();
 | |
| }
 | |
| 
 | |
| static const nsIFrame* FirstContinuationOrPartOfIBSplit(
 | |
|     const nsIFrame* aFrame) {
 | |
|   if (!aFrame) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 | |
| }
 | |
| 
 | |
| static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) {
 | |
|   const nsIFrame* parent = aFrame->GetParent();
 | |
|   if (aFrame->IsTableFrame()) {
 | |
|     MOZ_ASSERT(parent->IsTableWrapperFrame());
 | |
|     parent = parent->GetParent();
 | |
|   }
 | |
| 
 | |
|   if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) {
 | |
|     if (parent->IsLineFrame()) {
 | |
|       parent = parent->GetParent();
 | |
|     }
 | |
|     return parent->IsViewportFrame() ? nullptr
 | |
|                                      : FirstContinuationOrPartOfIBSplit(parent);
 | |
|   }
 | |
| 
 | |
|   if (aFrame->IsLineFrame()) {
 | |
|     // A ::first-line always ends up here via its block, which is therefore the
 | |
|     // right expected owner.  That block can be an
 | |
|     // anonymous box.  For example, we could have a ::first-line on a columnated
 | |
|     // block; the blockframe is the column-content anonymous box in that case.
 | |
|     // So we don't want to end up in the code below, which steps out of anon
 | |
|     // boxes.  Just return the parent of the line frame, which is the block.
 | |
|     return parent;
 | |
|   }
 | |
| 
 | |
|   if (aFrame->IsLetterFrame()) {
 | |
|     // Ditto for ::first-letter. A first-letter always arrives here via its
 | |
|     // direct parent, except when it's parented to a ::first-line.
 | |
|     if (parent->IsLineFrame()) {
 | |
|       parent = parent->GetParent();
 | |
|     }
 | |
|     return FirstContinuationOrPartOfIBSplit(parent);
 | |
|   }
 | |
| 
 | |
|   if (parent->IsLetterFrame()) {
 | |
|     // Things never have ::first-letter as their expected parent.  Go
 | |
|     // on up to the ::first-letter's parent.
 | |
|     parent = parent->GetParent();
 | |
|   }
 | |
| 
 | |
|   parent = FirstContinuationOrPartOfIBSplit(parent);
 | |
| 
 | |
|   // We've handled already anon boxes, so now we're looking at
 | |
|   // a frame of a DOM element or pseudo. Hop through anon and line-boxes
 | |
|   // generated by our DOM parent, and go find the owner frame for it.
 | |
|   while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) {
 | |
|     auto pseudo = parent->Style()->GetPseudoType();
 | |
|     if (pseudo == PseudoStyleType::tableWrapper) {
 | |
|       const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild();
 | |
|       MOZ_ASSERT(tableFrame->IsTableFrame());
 | |
|       // Handle :-moz-table and :-moz-inline-table.
 | |
|       parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame;
 | |
|     } else {
 | |
|       // We get the in-flow parent here so that we can handle the OOF anonymous
 | |
|       // boxed to get the correct parent.
 | |
|       parent = parent->GetInFlowParent();
 | |
|     }
 | |
|     parent = FirstContinuationOrPartOfIBSplit(parent);
 | |
|   }
 | |
| 
 | |
|   return parent;
 | |
| }
 | |
| 
 | |
| // FIXME(emilio, bug 1633685): We should ideally figure out how to properly
 | |
| // restyle replicated fixed pos frames... We seem to assume everywhere that they
 | |
| // can't get restyled at the moment...
 | |
| static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) {
 | |
|   if (!aFrame->PresContext()->IsPaginated()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   for (; aFrame; aFrame = aFrame->GetParent()) {
 | |
|     if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
 | |
|         !aFrame->FirstContinuation()->IsPrimaryFrame() &&
 | |
|         nsLayoutUtils::IsReallyFixedPos(aFrame)) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const {
 | |
|   MOZ_ASSERT(mOwner);
 | |
|   MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
 | |
|   MOZ_ASSERT(!mOwner->IsColumnSpanInMulticolSubtree());
 | |
|   // We allow aParent.mOwner to be null, for cases when we're not starting at
 | |
|   // the root of the tree.  We also allow aParent.mOwner to be somewhere up our
 | |
|   // expected owner chain not our immediate owner, which allows us creating long
 | |
|   // chains of ServoRestyleStates in some cases where it's just not worth it.
 | |
|   if (aParent.mOwner) {
 | |
|     const nsIFrame* owner = ExpectedOwnerForChild(mOwner);
 | |
|     if (owner != aParent.mOwner && !IsInReplicatedFixedPosTree(mOwner)) {
 | |
|       MOZ_ASSERT(IsAnonBox(owner),
 | |
|                  "Should only have expected owner weirdness when anon boxes "
 | |
|                  "are involved");
 | |
|       bool found = false;
 | |
|       for (; owner; owner = ExpectedOwnerForChild(owner)) {
 | |
|         if (owner == aParent.mOwner) {
 | |
|           found = true;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain");
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsChangeHint ServoRestyleState::ChangesHandledFor(
 | |
|     const nsIFrame* aFrame) const {
 | |
|   if (!mOwner) {
 | |
|     MOZ_ASSERT(!mChangesHandled);
 | |
|     return mChangesHandled;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame) ||
 | |
|                  IsInReplicatedFixedPosTree(aFrame),
 | |
|              "Missed some frame in the hierarchy?");
 | |
|   return mChangesHandled;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) {
 | |
|   MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(),
 | |
|              "All our wrappers are anon boxes, and why would we restyle "
 | |
|              "non-inheriting ones?");
 | |
|   MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(),
 | |
|              "All our wrappers are anon boxes, and why would we restyle "
 | |
|              "non-inheriting ones?");
 | |
|   MOZ_ASSERT(
 | |
|       aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::cellContent,
 | |
|       "Someone should be using TableAwareParentFor");
 | |
|   MOZ_ASSERT(
 | |
|       aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::tableWrapper,
 | |
|       "Someone should be using TableAwareParentFor");
 | |
|   // Make sure we only add first continuations.
 | |
|   aWrapperFrame = aWrapperFrame->FirstContinuation();
 | |
|   nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr);
 | |
|   if (last == aWrapperFrame) {
 | |
|     // Already queued up, nothing to do.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Make sure to queue up parents before children.  But don't queue up
 | |
|   // ancestors of non-anonymous boxes here; those are handled when we traverse
 | |
|   // their non-anonymous kids.
 | |
|   if (aWrapperFrame->ParentIsWrapperAnonBox()) {
 | |
|     AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame));
 | |
|   }
 | |
| 
 | |
|   // If the append fails, we'll fail to restyle properly, but that's probably
 | |
|   // better than crashing.
 | |
|   if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) {
 | |
|     aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) {
 | |
|   size_t i = mPendingWrapperRestyleOffset;
 | |
|   while (i < mPendingWrapperRestyles.Length()) {
 | |
|     i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i);
 | |
|   }
 | |
| 
 | |
|   mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset);
 | |
| }
 | |
| 
 | |
| size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
 | |
|                                                            size_t aIndex) {
 | |
|   // The frame at index aIndex is something we should restyle ourselves, but
 | |
|   // following frames may need separate ServoRestyleStates to restyle.
 | |
|   MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length());
 | |
| 
 | |
|   nsIFrame* cur = mPendingWrapperRestyles[aIndex];
 | |
|   MOZ_ASSERT(cur->Style()->IsWrapperAnonBox());
 | |
| 
 | |
|   // Where is cur supposed to inherit from?  From its parent frame, except in
 | |
|   // the case when cur is a table, in which case it should be its grandparent.
 | |
|   // Also, not in the case when the resulting frame would be a first-line; in
 | |
|   // that case we should be inheriting from the block, and the first-line will
 | |
|   // do its fixup later if needed.
 | |
|   //
 | |
|   // Note that after we do all that fixup the parent we get might still not be
 | |
|   // aParent; for example aParent could be a scrollframe, in which case we
 | |
|   // should inherit from the scrollcontent frame.  Or the parent might be some
 | |
|   // continuation of aParent.
 | |
|   //
 | |
|   // Try to assert as much as we can about the parent we actually end up using
 | |
|   // without triggering bogus asserts in all those various edge cases.
 | |
|   nsIFrame* parent = cur->GetParent();
 | |
|   if (cur->IsTableFrame()) {
 | |
|     MOZ_ASSERT(parent->IsTableWrapperFrame());
 | |
|     parent = parent->GetParent();
 | |
|   }
 | |
|   if (parent->IsLineFrame()) {
 | |
|     parent = parent->GetParent();
 | |
|   }
 | |
|   MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent ||
 | |
|              (parent->Style()->IsInheritingAnonBox() &&
 | |
|               parent->GetContent() == aParent->GetContent()));
 | |
| 
 | |
|   // Now "this" is a ServoRestyleState for aParent, so if parent is not a next
 | |
|   // continuation (possibly across ib splits) of aParent we need a new
 | |
|   // ServoRestyleState for the kid.
 | |
|   Maybe<ServoRestyleState> parentRestyleState;
 | |
|   nsIFrame* parentForRestyle =
 | |
|       nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent);
 | |
|   if (parentForRestyle != aParent) {
 | |
|     parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty,
 | |
|                                Type::InFlow);
 | |
|   }
 | |
|   ServoRestyleState& curRestyleState =
 | |
|       parentRestyleState ? *parentRestyleState : *this;
 | |
| 
 | |
|   // This frame may already have been restyled.  Even if it has, we can't just
 | |
|   // return, because the next frame may be a kid of it that does need restyling.
 | |
|   if (cur->IsWrapperAnonBoxNeedingRestyle()) {
 | |
|     parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState);
 | |
|     cur->SetIsWrapperAnonBoxNeedingRestyle(false);
 | |
|   }
 | |
| 
 | |
|   size_t numProcessed = 1;
 | |
| 
 | |
|   // Note: no overflow possible here, since aIndex < length.
 | |
|   if (aIndex + 1 < mPendingWrapperRestyles.Length()) {
 | |
|     nsIFrame* next = mPendingWrapperRestyles[aIndex + 1];
 | |
|     if (TableAwareParentFor(next) == cur &&
 | |
|         next->IsWrapperAnonBoxNeedingRestyle()) {
 | |
|       // It might be nice if we could do better than nsChangeHint_Empty.  On
 | |
|       // the other hand, presumably our mChangesHandled already has the bits
 | |
|       // we really want here so in practice it doesn't matter.
 | |
|       ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty,
 | |
|                                    Type::InFlow,
 | |
|                                    /* aAssertWrapperRestyleLength = */ false);
 | |
|       numProcessed +=
 | |
|           childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return numProcessed;
 | |
| }
 | |
| 
 | |
| nsIFrame* ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) {
 | |
|   // We want to get the anon box parent for aChild. where aChild has
 | |
|   // ParentIsWrapperAnonBox().
 | |
|   //
 | |
|   // For the most part this is pretty straightforward, but there are two
 | |
|   // wrinkles.  First, if aChild is a table, then we really want the parent of
 | |
|   // its table wrapper.
 | |
|   if (aChild->IsTableFrame()) {
 | |
|     aChild = aChild->GetParent();
 | |
|     MOZ_ASSERT(aChild->IsTableWrapperFrame());
 | |
|   }
 | |
| 
 | |
|   nsIFrame* parent = aChild->GetParent();
 | |
|   // Now if parent is a cell-content frame, we actually want the cellframe.
 | |
|   if (parent->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
 | |
|     parent = parent->GetParent();
 | |
|   } else if (parent->IsTableWrapperFrame()) {
 | |
|     // Must be a caption.  In that case we want the table here.
 | |
|     MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption);
 | |
|     parent = parent->PrincipalChildList().FirstChild();
 | |
|   }
 | |
|   return parent;
 | |
| }
 | |
| 
 | |
| void RestyleManager::PostRestyleEvent(Element* aElement,
 | |
|                                       RestyleHint aRestyleHint,
 | |
|                                       nsChangeHint aMinChangeHint) {
 | |
|   MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange),
 | |
|              "Didn't expect explicit change hints to be neutral!");
 | |
|   if (MOZ_UNLIKELY(IsDisconnected()) ||
 | |
|       MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We allow posting restyles from within change hint handling, but not from
 | |
|   // within the restyle algorithm itself.
 | |
|   MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
 | |
| 
 | |
|   if (!aRestyleHint && !aMinChangeHint) {
 | |
|     // FIXME(emilio): we should assert against this instead.
 | |
|     return;  // Nothing to do.
 | |
|   }
 | |
| 
 | |
|   // Assuming the restyle hints will invalidate cached style for
 | |
|   // getComputedStyle, since we don't know if any of the restyling that we do
 | |
|   // would affect undisplayed elements.
 | |
|   if (aRestyleHint) {
 | |
|     if (!(aRestyleHint & RestyleHint::ForAnimations())) {
 | |
|       mHaveNonAnimationRestyles = true;
 | |
|     }
 | |
| 
 | |
|     IncrementUndisplayedRestyleGeneration();
 | |
|   }
 | |
| 
 | |
|   // Processing change hints sometimes causes new change hints to be generated,
 | |
|   // and very occasionally, additional restyle hints. We collect the change
 | |
|   // hints manually to avoid re-traversing the DOM to find them.
 | |
|   if (mReentrantChanges && !aRestyleHint) {
 | |
|     mReentrantChanges->AppendElement(ReentrantChange{aElement, aMinChangeHint});
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aRestyleHint || aMinChangeHint) {
 | |
|     Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::PostRestyleEventForAnimations(Element* aElement,
 | |
|                                                    PseudoStyleType aPseudoType,
 | |
|                                                    RestyleHint aRestyleHint) {
 | |
|   Element* elementToRestyle =
 | |
|       AnimationUtils::GetElementForRestyle(aElement, aPseudoType);
 | |
| 
 | |
|   if (!elementToRestyle) {
 | |
|     // FIXME: Bug 1371107: When reframing happens,
 | |
|     // EffectCompositor::mElementsToRestyle still has unbound old pseudo
 | |
|     // element. We should drop it.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mPresContext->TriggeredAnimationRestyle();
 | |
| 
 | |
|   Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0));
 | |
| }
 | |
| 
 | |
| void RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
 | |
|                                          RestyleHint aRestyleHint) {
 | |
|   // NOTE(emilio): The semantics of these methods are quite funny, in the sense
 | |
|   // that we're not supposed to need to rebuild the actual stylist data.
 | |
|   //
 | |
|   // That's handled as part of the MediumFeaturesChanged stuff, if needed.
 | |
|   //
 | |
|   // Clear the cached style data only if we are guaranteed to process the whole
 | |
|   // DOM tree again.
 | |
|   //
 | |
|   // FIXME(emilio): Decouple this, probably. This probably just wants to reset
 | |
|   // the "uses viewport units / uses rem" bits, and _maybe_ clear cached anon
 | |
|   // box styles and such... But it doesn't really always need to clear the
 | |
|   // initial style of the document and similar...
 | |
|   if (aRestyleHint.DefinitelyRecascadesAllSubtree()) {
 | |
|     StyleSet()->ClearCachedStyleData();
 | |
|   }
 | |
| 
 | |
|   DocumentStyleRootIterator iter(mPresContext->Document());
 | |
|   while (Element* root = iter.GetNextStyleRoot()) {
 | |
|     PostRestyleEvent(root, aRestyleHint, aExtraHint);
 | |
|   }
 | |
| 
 | |
|   // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect
 | |
|   // non-inheriting anon boxes. It's not clear if we want to support that, but
 | |
|   // if we do, we need to re-selector-match them here.
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void RestyleManager::ClearServoDataFromSubtree(Element* aElement,
 | |
|                                                IncludeRoot aIncludeRoot) {
 | |
|   if (aElement->HasServoData()) {
 | |
|     StyleChildrenIterator it(aElement);
 | |
|     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
 | |
|       if (n->IsElement()) {
 | |
|         ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) {
 | |
|     aElement->ClearServoData();
 | |
|     MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits |
 | |
|                                         NODE_NEEDS_FRAME));
 | |
|     MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) {
 | |
|   if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) {
 | |
|     StyleChildrenIterator it(aElement);
 | |
|     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
 | |
|       if (n->IsElement()) {
 | |
|         ClearRestyleStateFromSubtree(n->AsElement());
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool wasRestyled = false;
 | |
|   Unused << Servo_TakeChangeHint(aElement, &wasRestyled);
 | |
|   aElement->UnsetFlags(Element::kAllServoDescendantBits);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This struct takes care of encapsulating some common state that text nodes may
 | |
|  * need to track during the post-traversal.
 | |
|  *
 | |
|  * This is currently used to properly compute change hints when the parent
 | |
|  * element of this node is a display: contents node, and also to avoid computing
 | |
|  * the style for text children more than once per element.
 | |
|  */
 | |
| struct RestyleManager::TextPostTraversalState {
 | |
|  public:
 | |
|   TextPostTraversalState(Element& aParentElement, ComputedStyle* aParentContext,
 | |
|                          bool aDisplayContentsParentStyleChanged,
 | |
|                          ServoRestyleState& aParentRestyleState)
 | |
|       : mParentElement(aParentElement),
 | |
|         mParentContext(aParentContext),
 | |
|         mParentRestyleState(aParentRestyleState),
 | |
|         mStyle(nullptr),
 | |
|         mShouldPostHints(aDisplayContentsParentStyleChanged),
 | |
|         mShouldComputeHints(aDisplayContentsParentStyleChanged),
 | |
|         mComputedHint(nsChangeHint_Empty) {}
 | |
| 
 | |
|   nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); }
 | |
| 
 | |
|   ComputedStyle& ComputeStyle(nsIContent* aTextNode) {
 | |
|     if (!mStyle) {
 | |
|       mStyle = mParentRestyleState.StyleSet().ResolveStyleForText(
 | |
|           aTextNode, &ParentStyle());
 | |
|     }
 | |
|     MOZ_ASSERT(mStyle);
 | |
|     return *mStyle;
 | |
|   }
 | |
| 
 | |
|   void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame,
 | |
|                            ComputedStyle& aNewStyle) {
 | |
|     MOZ_ASSERT(aTextFrame);
 | |
|     MOZ_ASSERT(aNewStyle.GetPseudoType() == PseudoStyleType::mozText);
 | |
| 
 | |
|     if (MOZ_LIKELY(!mShouldPostHints)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     ComputedStyle* oldStyle = aTextFrame->Style();
 | |
|     MOZ_ASSERT(oldStyle->GetPseudoType() == PseudoStyleType::mozText);
 | |
| 
 | |
|     // We rely on the fact that all the text children for the same element share
 | |
|     // style to avoid recomputing style differences for all of them.
 | |
|     //
 | |
|     // TODO(emilio): The above may not be true for ::first-{line,letter}, but
 | |
|     // we'll cross that bridge when we support those in stylo.
 | |
|     if (mShouldComputeHints) {
 | |
|       mShouldComputeHints = false;
 | |
|       uint32_t equalStructs;
 | |
|       mComputedHint = oldStyle->CalcStyleDifference(aNewStyle, &equalStructs);
 | |
|       mComputedHint = NS_RemoveSubsumedHints(
 | |
|           mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame));
 | |
|     }
 | |
| 
 | |
|     if (mComputedHint) {
 | |
|       mParentRestyleState.ChangeList().AppendChange(aTextFrame, aContent,
 | |
|                                                     mComputedHint);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ComputedStyle& ParentStyle() {
 | |
|     if (!mParentContext) {
 | |
|       mLazilyResolvedParentContext =
 | |
|           ServoStyleSet::ResolveServoStyle(mParentElement);
 | |
|       mParentContext = mLazilyResolvedParentContext;
 | |
|     }
 | |
|     return *mParentContext;
 | |
|   }
 | |
| 
 | |
|   Element& mParentElement;
 | |
|   ComputedStyle* mParentContext;
 | |
|   RefPtr<ComputedStyle> mLazilyResolvedParentContext;
 | |
|   ServoRestyleState& mParentRestyleState;
 | |
|   RefPtr<ComputedStyle> mStyle;
 | |
|   bool mShouldPostHints;
 | |
|   bool mShouldComputeHints;
 | |
|   nsChangeHint mComputedHint;
 | |
| };
 | |
| 
 | |
| static void UpdateBackdropIfNeeded(nsIFrame* aFrame, ServoStyleSet& aStyleSet,
 | |
|                                    nsStyleChangeList& aChangeList) {
 | |
|   const nsStyleDisplay* display = aFrame->Style()->StyleDisplay();
 | |
|   if (display->mTopLayer != StyleTopLayer::Top) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Elements in the top layer are guaranteed to have absolute or fixed
 | |
|   // position per https://fullscreen.spec.whatwg.org/#new-stacking-layer.
 | |
|   MOZ_ASSERT(display->IsAbsolutelyPositionedStyle());
 | |
| 
 | |
|   nsIFrame* backdropPlaceholder =
 | |
|       aFrame->GetChildList(FrameChildListID::Backdrop).FirstChild();
 | |
|   if (!backdropPlaceholder) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame());
 | |
|   nsIFrame* backdropFrame =
 | |
|       nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder);
 | |
|   MOZ_ASSERT(backdropFrame->IsBackdropFrame());
 | |
|   MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() ==
 | |
|              PseudoStyleType::backdrop);
 | |
| 
 | |
|   RefPtr<ComputedStyle> newStyle = aStyleSet.ResolvePseudoElementStyle(
 | |
|       *aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop, nullptr,
 | |
|       aFrame->Style());
 | |
| 
 | |
|   // NOTE(emilio): We can't use the changes handled for the owner of the
 | |
|   // backdrop frame, since it's out of flow, and parented to the viewport or
 | |
|   // canvas frame (depending on the `position` value).
 | |
|   MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() ||
 | |
|              backdropFrame->GetParent()->IsCanvasFrame());
 | |
|   nsTArray<nsIFrame*> wrappersToRestyle;
 | |
|   nsTArray<RefPtr<Element>> anchorsToSuppress;
 | |
|   ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle,
 | |
|                           anchorsToSuppress);
 | |
|   nsIFrame::UpdateStyleOfOwnedChildFrame(backdropFrame, newStyle, state);
 | |
|   MOZ_ASSERT(anchorsToSuppress.IsEmpty());
 | |
| }
 | |
| 
 | |
| static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame,
 | |
|                                       ServoRestyleState& aRestyleState) {
 | |
|   MOZ_ASSERT(
 | |
|       !aFrame->IsBlockFrameOrSubclass(),
 | |
|       "You're probably duplicating work with UpdatePseudoElementStyles!");
 | |
|   if (!aFrame->HasFirstLetterChild()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We need to find the block the first-letter is associated with so we can
 | |
|   // find the right element for the first-letter's style resolution.  Might as
 | |
|   // well just delegate the whole thing to that block.
 | |
|   nsIFrame* block = aFrame->GetParent();
 | |
|   while (!block->IsBlockFrameOrSubclass()) {
 | |
|     block = block->GetParent();
 | |
|   }
 | |
| 
 | |
|   static_cast<nsBlockFrame*>(block->FirstContinuation())
 | |
|       ->UpdateFirstLetterStyle(aRestyleState);
 | |
| }
 | |
| 
 | |
| static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex,
 | |
|                                              ComputedStyle& aOldContext,
 | |
|                                              ServoRestyleState& aRestyleState) {
 | |
|   auto pseudoType = aOldContext.GetPseudoType();
 | |
|   MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo);
 | |
|   MOZ_ASSERT(
 | |
|       !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType));
 | |
| 
 | |
|   RefPtr<ComputedStyle> newStyle =
 | |
|       aRestyleState.StyleSet().ResolvePseudoElementStyle(
 | |
|           *aFrame->GetContent()->AsElement(), pseudoType, nullptr,
 | |
|           aFrame->Style());
 | |
| 
 | |
|   uint32_t equalStructs;  // Not used, actually.
 | |
|   nsChangeHint childHint =
 | |
|       aOldContext.CalcStyleDifference(*newStyle, &equalStructs);
 | |
|   if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
 | |
|       !aFrame->IsColumnSpanInMulticolSubtree()) {
 | |
|     childHint = NS_RemoveSubsumedHints(childHint,
 | |
|                                        aRestyleState.ChangesHandledFor(aFrame));
 | |
|   }
 | |
| 
 | |
|   if (childHint) {
 | |
|     if (childHint & nsChangeHint_ReconstructFrame) {
 | |
|       // If we generate a reconstruct here, remove any non-reconstruct hints we
 | |
|       // may have already generated for this content.
 | |
|       aRestyleState.ChangeList().PopChangesForContent(aFrame->GetContent());
 | |
|     }
 | |
|     aRestyleState.ChangeList().AppendChange(aFrame, aFrame->GetContent(),
 | |
|                                             childHint);
 | |
|   }
 | |
| 
 | |
|   aFrame->SetAdditionalComputedStyle(aIndex, newStyle);
 | |
| }
 | |
| 
 | |
| static void UpdateAdditionalComputedStyles(nsIFrame* aFrame,
 | |
|                                            ServoRestyleState& aRestyleState) {
 | |
|   MOZ_ASSERT(aFrame);
 | |
|   MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement());
 | |
| 
 | |
|   // FIXME(emilio): Consider adding a bit or something to avoid the initial
 | |
|   // virtual call?
 | |
|   uint32_t index = 0;
 | |
|   while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) {
 | |
|     UpdateOneAdditionalComputedStyle(aFrame, index++, *oldStyle, aRestyleState);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void UpdateFramePseudoElementStyles(nsIFrame* aFrame,
 | |
|                                            ServoRestyleState& aRestyleState) {
 | |
|   if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) {
 | |
|     blockFrame->UpdatePseudoElementStyles(aRestyleState);
 | |
|   } else {
 | |
|     UpdateFirstLetterIfNeeded(aFrame, aRestyleState);
 | |
|   }
 | |
| 
 | |
|   UpdateBackdropIfNeeded(aFrame, aRestyleState.StyleSet(),
 | |
|                          aRestyleState.ChangeList());
 | |
| }
 | |
| 
 | |
| enum class ServoPostTraversalFlags : uint32_t {
 | |
|   Empty = 0,
 | |
|   // Whether parent was restyled.
 | |
|   ParentWasRestyled = 1 << 0,
 | |
|   // Skip sending accessibility notifications for all descendants.
 | |
|   SkipA11yNotifications = 1 << 1,
 | |
|   // Always send accessibility notifications if the element is shown.
 | |
|   // The SkipA11yNotifications flag above overrides this flag.
 | |
|   SendA11yNotificationsIfShown = 1 << 2,
 | |
| };
 | |
| 
 | |
| MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags)
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
| static bool IsVisibleForA11y(const ComputedStyle& aStyle) {
 | |
|   return aStyle.StyleVisibility()->IsVisible() && !aStyle.StyleUI()->IsInert();
 | |
| }
 | |
| 
 | |
| static bool IsSubtreeVisibleForA11y(const ComputedStyle& aStyle) {
 | |
|   return aStyle.StyleDisplay()->mContentVisibility !=
 | |
|          StyleContentVisibility::Hidden;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| // Send proper accessibility notifications and return post traversal
 | |
| // flags for kids.
 | |
| static ServoPostTraversalFlags SendA11yNotifications(
 | |
|     nsPresContext* aPresContext, Element* aElement,
 | |
|     const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle,
 | |
|     ServoPostTraversalFlags aFlags) {
 | |
|   using Flags = ServoPostTraversalFlags;
 | |
|   MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) ||
 | |
|                  !(aFlags & Flags::SendA11yNotificationsIfShown),
 | |
|              "The two a11y flags should never be set together");
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
|   nsAccessibilityService* accService = GetAccService();
 | |
|   if (!accService) {
 | |
|     // If we don't have accessibility service, accessibility is not
 | |
|     // enabled. Just skip everything.
 | |
|     return Flags::Empty;
 | |
|   }
 | |
| 
 | |
|   if (aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually !=
 | |
|       aOldStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
 | |
|     if (aElement->GetParent() &&
 | |
|         aElement->GetParent()->IsXULElement(nsGkAtoms::tabpanels)) {
 | |
|       accService->NotifyOfTabPanelVisibilityChange(
 | |
|           aPresContext->PresShell(), aElement,
 | |
|           aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aFlags & Flags::SkipA11yNotifications) {
 | |
|     // Propagate the skipping flag to descendants.
 | |
|     return Flags::SkipA11yNotifications;
 | |
|   }
 | |
| 
 | |
|   bool needsNotify = false;
 | |
|   const bool isVisible = IsVisibleForA11y(aNewStyle);
 | |
|   const bool wasVisible = IsVisibleForA11y(aOldStyle);
 | |
| 
 | |
|   if (aFlags & Flags::SendA11yNotificationsIfShown) {
 | |
|     if (!isVisible) {
 | |
|       // Propagate the sending-if-shown flag to descendants.
 | |
|       return Flags::SendA11yNotificationsIfShown;
 | |
|     }
 | |
|     // We have asked accessibility service to remove the whole subtree
 | |
|     // of element which becomes invisible from the accessible tree, but
 | |
|     // this element is visible, so we need to add it back.
 | |
|     needsNotify = true;
 | |
|   } else {
 | |
|     // If we shouldn't skip in any case, we need to check whether our own
 | |
|     // visibility has changed.
 | |
|     // Also notify if the subtree visibility change due to content-visibility.
 | |
|     const bool isSubtreeVisible = IsSubtreeVisibleForA11y(aNewStyle);
 | |
|     const bool wasSubtreeVisible = IsSubtreeVisibleForA11y(aOldStyle);
 | |
|     needsNotify =
 | |
|         wasVisible != isVisible || wasSubtreeVisible != isSubtreeVisible;
 | |
|   }
 | |
| 
 | |
|   if (needsNotify) {
 | |
|     PresShell* presShell = aPresContext->PresShell();
 | |
|     if (isVisible) {
 | |
|       accService->ContentRangeInserted(presShell, aElement,
 | |
|                                        aElement->GetNextSibling());
 | |
|       // We are adding the subtree. Accessibility service would handle
 | |
|       // descendants, so we should just skip them from notifying.
 | |
|       return Flags::SkipA11yNotifications;
 | |
|     }
 | |
|     if (wasVisible) {
 | |
|       // Remove the subtree of this invisible element, and ask any shown
 | |
|       // descendant to add themselves back.
 | |
|       accService->ContentRemoved(presShell, aElement);
 | |
|       return Flags::SendA11yNotificationsIfShown;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return Flags::Empty;
 | |
| }
 | |
| 
 | |
| bool RestyleManager::ProcessPostTraversal(Element* aElement,
 | |
|                                           ServoRestyleState& aRestyleState,
 | |
|                                           ServoPostTraversalFlags aFlags) {
 | |
|   nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
 | |
|   nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(),
 | |
|                         "Element without Servo data on a post-traversal? How?");
 | |
| 
 | |
|   // NOTE(emilio): This is needed because for table frames the bit is set on the
 | |
|   // table wrapper (which is the primary frame), not on the table itself.
 | |
|   const bool isOutOfFlow =
 | |
|       primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
 | |
| 
 | |
|   // We need this because any column-spanner's parent frame is not its DOM
 | |
|   // parent's primary frame. We need some special check similar to out-of-flow
 | |
|   // frames.
 | |
|   const bool isColumnSpan =
 | |
|       primaryFrame && primaryFrame->IsColumnSpanInMulticolSubtree();
 | |
| 
 | |
|   // Grab the change hint from Servo.
 | |
|   bool wasRestyled = false;
 | |
|   nsChangeHint changeHint =
 | |
|       static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled));
 | |
| 
 | |
|   RefPtr<ComputedStyle> upToDateStyleIfRestyled =
 | |
|       wasRestyled ? ServoStyleSet::ResolveServoStyle(*aElement) : nullptr;
 | |
| 
 | |
|   // We should really fix the weird primary frame mapping for image maps
 | |
|   // (bug 135040)...
 | |
|   if (styleFrame && styleFrame->GetContent() != aElement) {
 | |
|     MOZ_ASSERT(styleFrame->IsImageFrameOrSubclass());
 | |
|     styleFrame = nullptr;
 | |
|   }
 | |
| 
 | |
|   // Handle lazy frame construction by posting a reconstruct for any lazily-
 | |
|   // constructed roots.
 | |
|   if (aElement->HasFlag(NODE_NEEDS_FRAME)) {
 | |
|     changeHint |= nsChangeHint_ReconstructFrame;
 | |
|     MOZ_ASSERT(!styleFrame);
 | |
|   }
 | |
| 
 | |
|   if (styleFrame) {
 | |
|     MOZ_ASSERT(primaryFrame);
 | |
| 
 | |
|     nsIFrame* maybeAnonBoxChild;
 | |
|     if (isOutOfFlow) {
 | |
|       maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame();
 | |
|     } else {
 | |
|       maybeAnonBoxChild = primaryFrame;
 | |
|       // Do not subsume change hints for the column-spanner.
 | |
|       if (!isColumnSpan) {
 | |
|         changeHint = NS_RemoveSubsumedHints(
 | |
|             changeHint, aRestyleState.ChangesHandledFor(styleFrame));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // If the parent wasn't restyled, the styles of our anon box parents won't
 | |
|     // change either.
 | |
|     if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
 | |
|         maybeAnonBoxChild->ParentIsWrapperAnonBox()) {
 | |
|       aRestyleState.AddPendingWrapperRestyle(
 | |
|           ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild));
 | |
|     }
 | |
| 
 | |
|     // If we don't have a ::marker pseudo-element, but need it, then
 | |
|     // reconstruct the frame.  (The opposite situation implies 'display'
 | |
|     // changes so doesn't need to be handled explicitly here.)
 | |
|     if (wasRestyled && styleFrame->StyleDisplay()->IsListItem() &&
 | |
|         styleFrame->IsBlockFrameOrSubclass() &&
 | |
|         !nsLayoutUtils::GetMarkerPseudo(aElement)) {
 | |
|       RefPtr<ComputedStyle> pseudoStyle =
 | |
|           aRestyleState.StyleSet().ProbePseudoElementStyle(
 | |
|               *aElement, PseudoStyleType::marker, nullptr,
 | |
|               upToDateStyleIfRestyled);
 | |
|       if (pseudoStyle) {
 | |
|         changeHint |= nsChangeHint_ReconstructFrame;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Although we shouldn't generate non-ReconstructFrame hints for elements with
 | |
|   // no frames, we can still get them here if they were explicitly posted by
 | |
|   // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be
 | |
|   // :visited.  Skip processing these hints if there is no frame.
 | |
|   if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) &&
 | |
|       changeHint) {
 | |
|     aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint);
 | |
|   }
 | |
| 
 | |
|   // If our change hint is reconstruct, we delegate to the frame constructor,
 | |
|   // which consumes the new style and expects the old style to be on the frame.
 | |
|   //
 | |
|   // XXXbholley: We should teach the frame constructor how to clear the dirty
 | |
|   // descendants bit to avoid the traversal here.
 | |
|   if (changeHint & nsChangeHint_ReconstructFrame) {
 | |
|     if (wasRestyled &&
 | |
|         StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
 | |
|       const bool wasAbsPos =
 | |
|           styleFrame &&
 | |
|           styleFrame->StyleDisplay()->IsAbsolutelyPositionedStyle();
 | |
|       auto* newDisp = upToDateStyleIfRestyled->StyleDisplay();
 | |
|       // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
 | |
|       //
 | |
|       // We need to do the position check here rather than in
 | |
|       // DidSetComputedStyle because changing position reframes.
 | |
|       //
 | |
|       // We suppress adjustments whenever we change from being display: none to
 | |
|       // be an abspos.
 | |
|       //
 | |
|       // Similarly, for other changes from abspos to non-abspos styles.
 | |
|       //
 | |
|       // TODO(emilio): I _think_ chrome won't suppress adjustments whenever
 | |
|       // `display` changes. But that causes some infinite loops in cases like
 | |
|       // bug 1568778.
 | |
|       if (wasAbsPos != newDisp->IsAbsolutelyPositionedStyle()) {
 | |
|         aRestyleState.AddPendingScrollAnchorSuppression(aElement);
 | |
|       }
 | |
|     }
 | |
|     ClearRestyleStateFromSubtree(aElement);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // TODO(emilio): We could avoid some refcount traffic here, specially in the
 | |
|   // ComputedStyle case, which uses atomic refcounting.
 | |
|   //
 | |
|   // Hold the ComputedStyle alive, because it could become a dangling pointer
 | |
|   // during the replacement. In practice it's not a huge deal, but better not
 | |
|   // playing with dangling pointers if not needed.
 | |
|   //
 | |
|   // NOTE(emilio): We could keep around the old computed style for display:
 | |
|   // contents elements too, but we don't really need it right now.
 | |
|   RefPtr<ComputedStyle> oldOrDisplayContentsStyle =
 | |
|       styleFrame ? styleFrame->Style() : nullptr;
 | |
| 
 | |
|   MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)),
 | |
|              "display: contents node has a frame, yet we didn't reframe it"
 | |
|              " above?");
 | |
|   const bool isDisplayContents = !styleFrame && aElement->HasServoData() &&
 | |
|                                  Servo_Element_IsDisplayContents(aElement);
 | |
|   if (isDisplayContents) {
 | |
|     oldOrDisplayContentsStyle = ServoStyleSet::ResolveServoStyle(*aElement);
 | |
|   }
 | |
| 
 | |
|   Maybe<ServoRestyleState> thisFrameRestyleState;
 | |
|   if (styleFrame) {
 | |
|     auto type = isOutOfFlow || isColumnSpan ? ServoRestyleState::Type::OutOfFlow
 | |
|                                             : ServoRestyleState::Type::InFlow;
 | |
| 
 | |
|     thisFrameRestyleState.emplace(*styleFrame, aRestyleState, changeHint, type);
 | |
|   }
 | |
| 
 | |
|   // We can't really assume as used changes from display: contents elements (or
 | |
|   // other elements without frames).
 | |
|   ServoRestyleState& childrenRestyleState =
 | |
|       thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState;
 | |
| 
 | |
|   ComputedStyle* upToDateStyle =
 | |
|       wasRestyled ? upToDateStyleIfRestyled : oldOrDisplayContentsStyle;
 | |
| 
 | |
|   ServoPostTraversalFlags childrenFlags =
 | |
|       wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled
 | |
|                   : ServoPostTraversalFlags::Empty;
 | |
| 
 | |
|   if (wasRestyled && oldOrDisplayContentsStyle) {
 | |
|     MOZ_ASSERT(styleFrame || isDisplayContents);
 | |
| 
 | |
|     // We want to walk all the continuations here, even the ones with different
 | |
|     // styles.  In practice, the only reason we get continuations with different
 | |
|     // styles here is ::first-line (::first-letter never affects element
 | |
|     // styles).  But in that case, newStyle is the right context for the
 | |
|     // _later_ continuations anyway (the ones not affected by ::first-line), not
 | |
|     // the earlier ones, so there is no point stopping right at the point when
 | |
|     // we'd actually be setting the right ComputedStyle.
 | |
|     //
 | |
|     // This does mean that we may be setting the wrong ComputedStyle on our
 | |
|     // initial continuations; ::first-line fixes that up after the fact.
 | |
|     for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) {
 | |
|       MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0));
 | |
|       f->SetComputedStyle(upToDateStyle);
 | |
|     }
 | |
| 
 | |
|     if (styleFrame) {
 | |
|       UpdateAdditionalComputedStyles(styleFrame, aRestyleState);
 | |
|     }
 | |
| 
 | |
|     if (!aElement->GetParent()) {
 | |
|       // This is the root.  Update styles on the viewport as needed.
 | |
|       ViewportFrame* viewport =
 | |
|           do_QueryFrame(mPresContext->PresShell()->GetRootFrame());
 | |
|       if (viewport) {
 | |
|         // NB: The root restyle state, not the one for our children!
 | |
|         viewport->UpdateStyle(aRestyleState);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Some changes to animations don't affect the computed style and yet still
 | |
|     // require the layer to be updated. For example, pausing an animation via
 | |
|     // the Web Animations API won't affect an element's style but still
 | |
|     // requires to update the animation on the layer.
 | |
|     //
 | |
|     // We can sometimes reach this when the animated style is being removed.
 | |
|     // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform
 | |
|     // style or not, we need to call it *after* setting |newStyle| to
 | |
|     // |styleFrame| to ensure the animated transform has been removed first.
 | |
|     AddLayerChangesForAnimation(styleFrame, primaryFrame, aElement, changeHint,
 | |
|                                 aRestyleState.ChangeList());
 | |
| 
 | |
|     childrenFlags |= SendA11yNotifications(mPresContext, aElement,
 | |
|                                            *oldOrDisplayContentsStyle,
 | |
|                                            *upToDateStyle, aFlags);
 | |
|   }
 | |
| 
 | |
|   const bool traverseElementChildren =
 | |
|       aElement->HasAnyOfFlags(Element::kAllServoDescendantBits);
 | |
|   const bool traverseTextChildren =
 | |
|       wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
 | |
|   bool recreatedAnyContext = wasRestyled;
 | |
|   if (traverseElementChildren || traverseTextChildren) {
 | |
|     StyleChildrenIterator it(aElement);
 | |
|     TextPostTraversalState textState(*aElement, upToDateStyle,
 | |
|                                      isDisplayContents && wasRestyled,
 | |
|                                      childrenRestyleState);
 | |
|     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
 | |
|       if (traverseElementChildren && n->IsElement()) {
 | |
|         recreatedAnyContext |= ProcessPostTraversal(
 | |
|             n->AsElement(), childrenRestyleState, childrenFlags);
 | |
|       } else if (traverseTextChildren && n->IsText()) {
 | |
|         recreatedAnyContext |= ProcessPostTraversalForText(
 | |
|             n, textState, childrenRestyleState, childrenFlags);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // We want to update frame pseudo-element styles after we've traversed our
 | |
|   // kids, because some of those updates (::first-line/::first-letter) need to
 | |
|   // modify the styles of the kids, and the child traversal above would just
 | |
|   // clobber those modifications.
 | |
|   if (styleFrame) {
 | |
|     if (wasRestyled) {
 | |
|       // Make sure to update anon boxes and pseudo bits after updating text,
 | |
|       // otherwise ProcessPostTraversalForText could clobber first-letter
 | |
|       // styles, for example.
 | |
|       styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState);
 | |
|     }
 | |
|     // Process anon box wrapper frames before ::first-line bits, but _after_
 | |
|     // owned anon boxes, since the children wrapper anon boxes could be
 | |
|     // inheriting from our own owned anon boxes.
 | |
|     childrenRestyleState.ProcessWrapperRestyles(styleFrame);
 | |
|     if (wasRestyled) {
 | |
|       UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState);
 | |
|     } else if (traverseElementChildren &&
 | |
|                styleFrame->IsBlockFrameOrSubclass()) {
 | |
|       // Even if we were not restyled, if we're a block with a first-line and
 | |
|       // one of our descendant elements which is on the first line was restyled,
 | |
|       // we need to update the styles of things on the first line, because
 | |
|       // they're wrong now.
 | |
|       //
 | |
|       // FIXME(bz) Could we do better here?  For example, could we keep track of
 | |
|       // frames that are "block with a ::first-line so we could avoid
 | |
|       // IsFrameOfType() and digging about for the first-line frame if not?
 | |
|       // Could we keep track of whether the element children we actually restyle
 | |
|       // are affected by first-line?  Something else?  Bug 1385443 tracks making
 | |
|       // this better.
 | |
|       nsIFrame* firstLineFrame =
 | |
|           static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame();
 | |
|       if (firstLineFrame) {
 | |
|         for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
 | |
|           ReparentComputedStyleForFirstLine(kid);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   aElement->UnsetFlags(Element::kAllServoDescendantBits);
 | |
|   return recreatedAnyContext;
 | |
| }
 | |
| 
 | |
| bool RestyleManager::ProcessPostTraversalForText(
 | |
|     nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState,
 | |
|     ServoRestyleState& aRestyleState, ServoPostTraversalFlags aFlags) {
 | |
|   // Handle lazy frame construction.
 | |
|   if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
 | |
|     aPostTraversalState.ChangeList().AppendChange(
 | |
|         nullptr, aTextNode, nsChangeHint_ReconstructFrame);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Handle restyle.
 | |
|   nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
 | |
|   if (!primaryFrame) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // If the parent wasn't restyled, the styles of our anon box parents won't
 | |
|   // change either.
 | |
|   if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
 | |
|       primaryFrame->ParentIsWrapperAnonBox()) {
 | |
|     aRestyleState.AddPendingWrapperRestyle(
 | |
|         ServoRestyleState::TableAwareParentFor(primaryFrame));
 | |
|   }
 | |
| 
 | |
|   ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode);
 | |
|   aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle);
 | |
| 
 | |
|   // We want to walk all the continuations here, even the ones with different
 | |
|   // styles.  In practice, the only reasons we get continuations with different
 | |
|   // styles are ::first-line and ::first-letter.  But in those cases,
 | |
|   // newStyle is the right context for the _later_ continuations anyway (the
 | |
|   // ones not affected by ::first-line/::first-letter), not the earlier ones,
 | |
|   // so there is no point stopping right at the point when we'd actually be
 | |
|   // setting the right ComputedStyle.
 | |
|   //
 | |
|   // This does mean that we may be setting the wrong ComputedStyle on our
 | |
|   // initial continuations; ::first-line/::first-letter fix that up after the
 | |
|   // fact.
 | |
|   for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) {
 | |
|     f->SetComputedStyle(&newStyle);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void RestyleManager::ClearSnapshots() {
 | |
|   for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
 | |
|     iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT);
 | |
|     iter.Remove();
 | |
|   }
 | |
| }
 | |
| 
 | |
| ServoElementSnapshot& RestyleManager::SnapshotFor(Element& aElement) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
 | |
| 
 | |
|   // NOTE(emilio): We can handle snapshots from a one-off restyle of those that
 | |
|   // we do to restyle stuff for reconstruction, for example.
 | |
|   //
 | |
|   // It seems to be the case that we always flush in between that happens and
 | |
|   // the next attribute change, so we can assert that we haven't handled the
 | |
|   // snapshot here yet. If this assertion didn't hold, we'd need to unset that
 | |
|   // flag from here too.
 | |
|   //
 | |
|   // Can't wait to make ProcessPendingRestyles the only entry-point for styling,
 | |
|   // so this becomes much easier to reason about. Today is not that day though.
 | |
|   MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT));
 | |
| 
 | |
|   ServoElementSnapshot* snapshot =
 | |
|       mSnapshots.GetOrInsertNew(&aElement, aElement);
 | |
|   aElement.SetFlags(ELEMENT_HAS_SNAPSHOT);
 | |
| 
 | |
|   // Now that we have a snapshot, make sure a restyle is triggered.
 | |
|   aElement.NoteDirtyForServo();
 | |
|   return *snapshot;
 | |
| }
 | |
| 
 | |
| void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
 | |
|   nsPresContext* presContext = PresContext();
 | |
|   PresShell* presShell = presContext->PresShell();
 | |
| 
 | |
|   MOZ_ASSERT(presContext->Document(), "No document?  Pshaw!");
 | |
|   // FIXME(emilio): In the "flush animations" case, ideally, we should only
 | |
|   // recascade animation styles running on the compositor, so we shouldn't care
 | |
|   // about other styles, or new rules that apply to the page...
 | |
|   //
 | |
|   // However, that's not true as of right now, see bug 1388031 and bug 1388692.
 | |
|   MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) ||
 | |
|                  !presContext->HasPendingMediaQueryUpdates(),
 | |
|              "Someone forgot to update media queries?");
 | |
|   MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
 | |
|   MOZ_RELEASE_ASSERT(!mInStyleRefresh, "Reentrant call?");
 | |
| 
 | |
|   if (MOZ_UNLIKELY(!presShell->DidInitialize())) {
 | |
|     // PresShell::FlushPendingNotifications doesn't early-return in the case
 | |
|     // where the PresShell hasn't yet been initialized (and therefore we haven't
 | |
|     // yet done the initial style traversal of the DOM tree). We should arguably
 | |
|     // fix up the callers and assert against this case, but we just detect and
 | |
|     // handle it for now.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // It'd be bad!
 | |
|   PresShell::AutoAssertNoFlush noReentrantFlush(*presShell);
 | |
| 
 | |
|   // Create a AnimationsWithDestroyedFrame during restyling process to
 | |
|   // stop animations and transitions on elements that have no frame at the end
 | |
|   // of the restyling process.
 | |
|   AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this);
 | |
| 
 | |
|   ServoStyleSet* styleSet = StyleSet();
 | |
|   Document* doc = presContext->Document();
 | |
| 
 | |
|   // Ensure the refresh driver is active during traversal to avoid mutating
 | |
|   // mActiveTimer and mMostRecentRefresh time.
 | |
|   presContext->RefreshDriver()->MostRecentRefresh();
 | |
| 
 | |
|   if (!doc->GetServoRestyleRoot()) {
 | |
|     // This might post new restyles, so need to do it here. Don't do it if we're
 | |
|     // already going to restyle tho, so that we don't potentially reflow with
 | |
|     // dirty styling.
 | |
|     presContext->UpdateContainerQueryStyles();
 | |
|     presContext->FinishedContainerQueryUpdate();
 | |
|   }
 | |
| 
 | |
|   // Perform the Servo traversal, and the post-traversal if required. We do this
 | |
|   // in a loop because certain rare paths in the frame constructor can trigger
 | |
|   // additional style invalidations.
 | |
|   //
 | |
|   // FIXME(emilio): Confirm whether that's still true now that XBL is gone.
 | |
|   mInStyleRefresh = true;
 | |
|   if (mHaveNonAnimationRestyles) {
 | |
|     ++mAnimationGeneration;
 | |
|   }
 | |
| 
 | |
|   if (mRestyleForCSSRuleChanges) {
 | |
|     aFlags |= ServoTraversalFlags::ForCSSRuleChanges;
 | |
|   }
 | |
| 
 | |
|   while (styleSet->StyleDocument(aFlags)) {
 | |
|     ClearSnapshots();
 | |
| 
 | |
|     // Select scroll anchors for frames that have been scrolled. Do this
 | |
|     // before processing restyled frames so that anchor nodes are correctly
 | |
|     // marked when directly moving frames with RecomputePosition.
 | |
|     presContext->PresShell()->FlushPendingScrollAnchorSelections();
 | |
| 
 | |
|     nsStyleChangeList currentChanges;
 | |
|     bool anyStyleChanged = false;
 | |
| 
 | |
|     // Recreate styles , and queue up change hints (which also handle lazy frame
 | |
|     // construction).
 | |
|     nsTArray<RefPtr<Element>> anchorsToSuppress;
 | |
| 
 | |
|     {
 | |
|       DocumentStyleRootIterator iter(doc->GetServoRestyleRoot());
 | |
|       while (Element* root = iter.GetNextStyleRoot()) {
 | |
|         nsTArray<nsIFrame*> wrappersToRestyle;
 | |
|         ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle,
 | |
|                                 anchorsToSuppress);
 | |
|         ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty;
 | |
|         anyStyleChanged |= ProcessPostTraversal(root, state, flags);
 | |
|       }
 | |
| 
 | |
|       // We want to suppress adjustments the current (before-change) scroll
 | |
|       // anchor container now, and save a reference to the content node so that
 | |
|       // we can suppress them in the after-change scroll anchor .
 | |
|       for (Element* element : anchorsToSuppress) {
 | |
|         if (nsIFrame* frame = element->GetPrimaryFrame()) {
 | |
|           if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
 | |
|             container->SuppressAdjustments();
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     doc->ClearServoRestyleRoot();
 | |
|     ClearSnapshots();
 | |
| 
 | |
|     // Process the change hints.
 | |
|     //
 | |
|     // Unfortunately, the frame constructor can generate new change hints while
 | |
|     // processing existing ones. We redirect those into a secondary queue and
 | |
|     // iterate until there's nothing left.
 | |
|     {
 | |
|       ReentrantChangeList newChanges;
 | |
|       mReentrantChanges = &newChanges;
 | |
|       while (!currentChanges.IsEmpty()) {
 | |
|         ProcessRestyledFrames(currentChanges);
 | |
|         MOZ_ASSERT(currentChanges.IsEmpty());
 | |
|         for (ReentrantChange& change : newChanges) {
 | |
|           if (!(change.mHint & nsChangeHint_ReconstructFrame) &&
 | |
|               !change.mContent->GetPrimaryFrame()) {
 | |
|             // SVG Elements post change hints without ensuring that the primary
 | |
|             // frame will be there after that (see bug 1366142).
 | |
|             //
 | |
|             // Just ignore those, since we can't really process them.
 | |
|             continue;
 | |
|           }
 | |
|           currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
 | |
|                                       change.mContent, change.mHint);
 | |
|         }
 | |
|         newChanges.Clear();
 | |
|       }
 | |
|       mReentrantChanges = nullptr;
 | |
|     }
 | |
| 
 | |
|     // Suppress adjustments in the after-change scroll anchors if needed, now
 | |
|     // that we're done reframing everything.
 | |
|     for (Element* element : anchorsToSuppress) {
 | |
|       if (nsIFrame* frame = element->GetPrimaryFrame()) {
 | |
|         if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
 | |
|           container->SuppressAdjustments();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (anyStyleChanged) {
 | |
|       // Maybe no styles changed when:
 | |
|       //
 | |
|       //  * Only explicit change hints were posted in the first place.
 | |
|       //  * When an attribute or state change in the content happens not to need
 | |
|       //    a restyle after all.
 | |
|       //
 | |
|       // In any case, we don't need to increment the restyle generation in that
 | |
|       // case.
 | |
|       IncrementRestyleGeneration();
 | |
|     }
 | |
| 
 | |
|     mInStyleRefresh = false;
 | |
|     presContext->UpdateContainerQueryStyles();
 | |
|     mInStyleRefresh = true;
 | |
|   }
 | |
| 
 | |
|   doc->ClearServoRestyleRoot();
 | |
|   presContext->FinishedContainerQueryUpdate();
 | |
|   presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
 | |
|   ClearSnapshots();
 | |
|   styleSet->AssertTreeIsClean();
 | |
| 
 | |
|   mHaveNonAnimationRestyles = false;
 | |
|   mRestyleForCSSRuleChanges = false;
 | |
|   mInStyleRefresh = false;
 | |
| 
 | |
|   // Now that everything has settled, see if we have enough free rule nodes in
 | |
|   // the tree to warrant sweeping them.
 | |
|   styleSet->MaybeGCRuleTree();
 | |
| 
 | |
|   // Note: We are in the scope of |animationsWithDestroyedFrame|, so
 | |
|   //       |mAnimationsWithDestroyedFrame| is still valid.
 | |
|   MOZ_ASSERT(mAnimationsWithDestroyedFrame);
 | |
|   mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| static void VerifyFlatTree(const nsIContent& aContent) {
 | |
|   StyleChildrenIterator iter(&aContent);
 | |
| 
 | |
|   for (auto* content = iter.GetNextChild(); content;
 | |
|        content = iter.GetNextChild()) {
 | |
|     MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent);
 | |
|     VerifyFlatTree(*content);
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void RestyleManager::ProcessPendingRestyles() {
 | |
|   AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Styles", LAYOUT);
 | |
| #ifdef DEBUG
 | |
|   if (auto* root = mPresContext->Document()->GetRootElement()) {
 | |
|     VerifyFlatTree(*root);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   DoProcessPendingRestyles(ServoTraversalFlags::Empty);
 | |
| }
 | |
| 
 | |
| void RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() {
 | |
|   if (mSnapshots.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
|   for (const auto& key : mSnapshots.Keys()) {
 | |
|     // Servo data for the element might have been dropped. (e.g. by removing
 | |
|     // from its document)
 | |
|     if (key->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
 | |
|       Servo_ProcessInvalidations(StyleSet()->RawData(), key, &mSnapshots);
 | |
|     }
 | |
|   }
 | |
|   ClearSnapshots();
 | |
| }
 | |
| 
 | |
| void RestyleManager::UpdateOnlyAnimationStyles() {
 | |
|   bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
 | |
|   if (!doCSS) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations);
 | |
| }
 | |
| 
 | |
| void RestyleManager::ElementStateChanged(Element* aElement,
 | |
|                                          ElementState aChangedBits) {
 | |
| #ifdef EARLY_BETA_OR_EARLIER
 | |
|   if (MOZ_UNLIKELY(mInStyleRefresh)) {
 | |
|     MOZ_CRASH_UNSAFE_PRINTF(
 | |
|         "Element state change during style refresh (%" PRIu64 ")",
 | |
|         aChangedBits.GetInternalValue());
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   const ElementState kVisitedAndUnvisited =
 | |
|       ElementState::VISITED | ElementState::UNVISITED;
 | |
| 
 | |
|   // We'll restyle when the relevant visited query finishes, regardless of the
 | |
|   // style (see Link::VisitedQueryFinished). So there's no need to do anything
 | |
|   // as a result of this state change just yet.
 | |
|   //
 | |
|   // Note that this check checks for _both_ bits: This is only true when visited
 | |
|   // changes to unvisited or vice-versa, but not when we start or stop being a
 | |
|   // link itself.
 | |
|   if (aChangedBits.HasAllStates(kVisitedAndUnvisited)) {
 | |
|     aChangedBits &= ~kVisitedAndUnvisited;
 | |
|     if (aChangedBits.IsEmpty()) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (auto changeHint = ChangeForContentStateChange(*aElement, aChangedBits)) {
 | |
|     Servo_NoteExplicitHints(aElement, RestyleHint{0}, changeHint);
 | |
|   }
 | |
| 
 | |
|   // Don't bother taking a snapshot if no rules depend on these state bits.
 | |
|   //
 | |
|   // We always take a snapshot for the LTR/RTL event states, since Servo doesn't
 | |
|   // track those bits in the same way, and we know that :dir() rules are always
 | |
|   // present in UA style sheets.
 | |
|   if (!aChangedBits.HasAtLeastOneOfStates(ElementState::DIR_STATES) &&
 | |
|       !StyleSet()->HasStateDependency(*aElement, aChangedBits)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Assuming we need to invalidate cached style in getComputedStyle for
 | |
|   // undisplayed elements, since we don't know if it is needed.
 | |
|   IncrementUndisplayedRestyleGeneration();
 | |
| 
 | |
|   if (!aElement->HasServoData() &&
 | |
|       !(aElement->GetSelectorFlags() &
 | |
|         NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ServoElementSnapshot& snapshot = SnapshotFor(*aElement);
 | |
|   ElementState previousState = aElement->StyleState() ^ aChangedBits;
 | |
|   snapshot.AddState(previousState);
 | |
| 
 | |
|   ServoStyleSet& styleSet = *StyleSet();
 | |
|   MaybeRestyleForNthOfState(styleSet, aElement, aChangedBits);
 | |
|   MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits);
 | |
| }
 | |
| 
 | |
| void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
 | |
|                                                Element* aChild,
 | |
|                                                ElementState aChangedBits) {
 | |
|   const auto* parentNode = aChild->GetParentNode();
 | |
|   MOZ_ASSERT(parentNode);
 | |
|   const auto parentFlags = parentNode->GetSelectorFlags();
 | |
|   if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) {
 | |
|     RestyleSiblingsForNthOf(aChild, parentFlags);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static inline bool AttributeInfluencesOtherPseudoClassState(
 | |
|     const Element& aElement, const nsAtom* aAttribute) {
 | |
|   // We must record some state for :-moz-table-border-nonzero and
 | |
|   // :-moz-select-list-box.
 | |
| 
 | |
|   if (aAttribute == nsGkAtoms::border) {
 | |
|     return aElement.IsHTMLElement(nsGkAtoms::table);
 | |
|   }
 | |
| 
 | |
|   if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) {
 | |
|     return aElement.IsHTMLElement(nsGkAtoms::select);
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static inline bool NeedToRecordAttrChange(
 | |
|     const ServoStyleSet& aStyleSet, const Element& aElement,
 | |
|     int32_t aNameSpaceID, nsAtom* aAttribute,
 | |
|     bool* aInfluencesOtherPseudoClassState) {
 | |
|   *aInfluencesOtherPseudoClassState =
 | |
|       AttributeInfluencesOtherPseudoClassState(aElement, aAttribute);
 | |
| 
 | |
|   // If the attribute influences one of the pseudo-classes that are backed by
 | |
|   // attributes, we just record it.
 | |
|   if (*aInfluencesOtherPseudoClassState) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // We assume that id and class attributes are used in class/id selectors, and
 | |
|   // thus record them.
 | |
|   //
 | |
|   // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet,
 | |
|   // presumably we could try to filter the old and new id, but it's not clear
 | |
|   // it's worth it.
 | |
|   if (aNameSpaceID == kNameSpaceID_None &&
 | |
|       (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // We always record lang="", even though we force a subtree restyle when it
 | |
|   // changes, since it can change how its siblings match :lang(..) due to
 | |
|   // selectors like :lang(..) + div.
 | |
|   if (aAttribute == nsGkAtoms::lang) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Otherwise, just record the attribute change if a selector in the page may
 | |
|   // reference it from an attribute selector.
 | |
|   return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute);
 | |
| }
 | |
| 
 | |
| void RestyleManager::AttributeWillChange(Element* aElement,
 | |
|                                          int32_t aNameSpaceID,
 | |
|                                          nsAtom* aAttribute, int32_t aModType) {
 | |
|   TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute);
 | |
| }
 | |
| 
 | |
| void RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) {
 | |
|   TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None,
 | |
|                                  nsGkAtoms::_class);
 | |
| }
 | |
| 
 | |
| void RestyleManager::TakeSnapshotForAttributeChange(Element& aElement,
 | |
|                                                     int32_t aNameSpaceID,
 | |
|                                                     nsAtom* aAttribute) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
 | |
| 
 | |
|   bool influencesOtherPseudoClassState;
 | |
|   if (!NeedToRecordAttrChange(*StyleSet(), aElement, aNameSpaceID, aAttribute,
 | |
|                               &influencesOtherPseudoClassState)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We cannot tell if the attribute change will affect the styles of
 | |
|   // undisplayed elements, because we don't actually restyle those elements
 | |
|   // during the restyle traversal. So just assume that the attribute change can
 | |
|   // cause the style to change.
 | |
|   IncrementUndisplayedRestyleGeneration();
 | |
| 
 | |
|   // Relative selector invalidation travels ancestor and earlier sibling
 | |
|   // direction, so it's very possible that it invalidates a styled element.
 | |
|   if (!aElement.HasServoData() &&
 | |
|       !(aElement.GetSelectorFlags() &
 | |
|         NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Some other random attribute changes may also affect the transitions,
 | |
|   // so we also set this true here.
 | |
|   mHaveNonAnimationRestyles = true;
 | |
| 
 | |
|   ServoElementSnapshot& snapshot = SnapshotFor(aElement);
 | |
|   snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
 | |
| 
 | |
|   if (influencesOtherPseudoClassState) {
 | |
|     snapshot.AddOtherPseudoClassState(aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // For some attribute changes we must restyle the whole subtree:
 | |
| //
 | |
| // * lang="" and xml:lang="" can affect all descendants due to :lang()
 | |
| // * exportparts can affect all descendant parts. We could certainly integrate
 | |
| //   it better in the invalidation machinery if it was necessary.
 | |
| static inline bool AttributeChangeRequiresSubtreeRestyle(
 | |
|     const Element& aElement, nsAtom* aAttr) {
 | |
|   if (aAttr == nsGkAtoms::exportparts) {
 | |
|     // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for
 | |
|     // exportparts attribute changes?
 | |
|     return !!aElement.GetShadowRoot();
 | |
|   }
 | |
|   return aAttr == nsGkAtoms::lang;
 | |
| }
 | |
| 
 | |
| void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
 | |
|                                       nsAtom* aAttribute, int32_t aModType,
 | |
|                                       const nsAttrValue* aOldValue) {
 | |
|   MOZ_ASSERT(!mInStyleRefresh);
 | |
| 
 | |
|   auto changeHint = nsChangeHint(0);
 | |
|   auto restyleHint = RestyleHint{0};
 | |
| 
 | |
|   changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
 | |
| 
 | |
|   MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue);
 | |
|   MaybeRestyleForRelativeSelectorAttribute(aElement, aAttribute, aOldValue);
 | |
| 
 | |
|   if (aAttribute == nsGkAtoms::style) {
 | |
|     restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
 | |
|   } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) {
 | |
|     restyleHint |= RestyleHint::RestyleSubtree();
 | |
|   } else if (aElement->IsInShadowTree() && aAttribute == nsGkAtoms::part) {
 | |
|     // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for part
 | |
|     // attribute changes?
 | |
|     restyleHint |= RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_PSEUDOS;
 | |
|   }
 | |
| 
 | |
|   if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) {
 | |
|     // See if we have appearance information for a theme.
 | |
|     StyleAppearance appearance =
 | |
|         primaryFrame->StyleDisplay()->EffectiveAppearance();
 | |
|     if (appearance != StyleAppearance::None) {
 | |
|       nsITheme* theme = PresContext()->Theme();
 | |
|       if (theme->ThemeSupportsWidget(PresContext(), primaryFrame, appearance)) {
 | |
|         bool repaint = false;
 | |
|         theme->WidgetStateChanged(primaryFrame, appearance, aAttribute,
 | |
|                                   &repaint, aOldValue);
 | |
|         if (repaint) {
 | |
|           changeHint |= nsChangeHint_RepaintFrame;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
 | |
|   }
 | |
| 
 | |
|   if (restyleHint || changeHint) {
 | |
|     Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
 | |
|   }
 | |
| 
 | |
|   if (restyleHint) {
 | |
|     // Assuming we need to invalidate cached style in getComputedStyle for
 | |
|     // undisplayed elements, since we don't know if it is needed.
 | |
|     IncrementUndisplayedRestyleGeneration();
 | |
| 
 | |
|     // If we change attributes, we have to mark this to be true, so we will
 | |
|     // increase the animation generation for the new created transition if any.
 | |
|     mHaveNonAnimationRestyles = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::RestyleSiblingsForNthOf(Element* aChild,
 | |
|                                              NodeSelectorFlags aParentFlags) {
 | |
|   StyleSet()->RestyleSiblingsForNthOf(*aChild,
 | |
|                                       static_cast<uint32_t>(aParentFlags));
 | |
| }
 | |
| 
 | |
| void RestyleManager::MaybeRestyleForNthOfAttribute(
 | |
|     Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
 | |
|   const auto* parentNode = aChild->GetParentNode();
 | |
|   MOZ_ASSERT(parentNode);
 | |
|   const auto parentFlags = parentNode->GetSelectorFlags();
 | |
|   if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
 | |
|     return;
 | |
|   }
 | |
|   if (!aChild->HasServoData()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool mightHaveNthOfDependency;
 | |
|   auto& styleSet = *StyleSet();
 | |
|   if (aAttribute == nsGkAtoms::id) {
 | |
|     auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
 | |
|                               ? aOldValue->GetAtomValue()
 | |
|                               : nullptr;
 | |
|     mightHaveNthOfDependency =
 | |
|         styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID());
 | |
|   } else if (aAttribute == nsGkAtoms::_class) {
 | |
|     mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild);
 | |
|   } else {
 | |
|     mightHaveNthOfDependency =
 | |
|         styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute);
 | |
|   }
 | |
| 
 | |
|   if (mightHaveNthOfDependency) {
 | |
|     RestyleSiblingsForNthOf(aChild, parentFlags);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::MaybeRestyleForRelativeSelectorAttribute(
 | |
|     Element* aElement, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
 | |
|   if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
 | |
|     return;
 | |
|   }
 | |
|   auto& styleSet = *StyleSet();
 | |
|   if (aAttribute == nsGkAtoms::id) {
 | |
|     auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
 | |
|                               ? aOldValue->GetAtomValue()
 | |
|                               : nullptr;
 | |
|     styleSet.MaybeInvalidateRelativeSelectorIDDependency(
 | |
|         *aElement, oldAtom, aElement->GetID(), Snapshots());
 | |
|   } else if (aAttribute == nsGkAtoms::_class) {
 | |
|     styleSet.MaybeInvalidateRelativeSelectorClassDependency(*aElement,
 | |
|                                                             Snapshots());
 | |
|   } else {
 | |
|     styleSet.MaybeInvalidateRelativeSelectorAttributeDependency(
 | |
|         *aElement, aAttribute, Snapshots());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RestyleManager::MaybeRestyleForRelativeSelectorState(
 | |
|     ServoStyleSet& aStyleSet, Element* aElement, ElementState aChangedBits) {
 | |
|   if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
 | |
|     return;
 | |
|   }
 | |
|   aStyleSet.MaybeInvalidateRelativeSelectorStateDependency(
 | |
|       *aElement, aChangedBits, Snapshots());
 | |
| }
 | |
| 
 | |
| void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) {
 | |
|   // This is only called when moving frames in or out of the first-line
 | |
|   // pseudo-element (or one of its descendants).  We can't say much about
 | |
|   // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into
 | |
|   // something inside an inline-block on the first line the ancestors could be
 | |
|   // totally arbitrary), but we will definitely find a line frame on the
 | |
|   // ancestor chain.  Note that the lineframe may not actually be the one that
 | |
|   // corresponds to ::first-line; when we're moving _out_ of the ::first-line it
 | |
|   // will be one of the continuations instead.
 | |
| #ifdef DEBUG
 | |
|   {
 | |
|     nsIFrame* f = aFrame->GetParent();
 | |
|     while (f && !f->IsLineFrame()) {
 | |
|       f = f->GetParent();
 | |
|     }
 | |
|     MOZ_ASSERT(f, "Must have found a first-line frame");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   DoReparentComputedStyleForFirstLine(aFrame, *StyleSet());
 | |
| }
 | |
| 
 | |
| static bool IsFrameAboutToGoAway(nsIFrame* aFrame) {
 | |
|   auto* element = Element::FromNode(aFrame->GetContent());
 | |
|   if (!element) {
 | |
|     return false;
 | |
|   }
 | |
|   return !element->HasServoData();
 | |
| }
 | |
| 
 | |
| void RestyleManager::DoReparentComputedStyleForFirstLine(
 | |
|     nsIFrame* aFrame, ServoStyleSet& aStyleSet) {
 | |
|   if (aFrame->IsBackdropFrame()) {
 | |
|     // Style context of backdrop frame has no parent style, and thus we do not
 | |
|     // need to reparent it.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (IsFrameAboutToGoAway(aFrame)) {
 | |
|     // We're entering a display: none subtree, which we know it's going to get
 | |
|     // rebuilt. Don't bother reparenting.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aFrame->IsPlaceholderFrame()) {
 | |
|     // Also reparent the out-of-flow and all its continuations.  We're doing
 | |
|     // this to match Gecko for now, but it's not clear that this behavior is
 | |
|     // correct per spec.  It's certainly pretty odd for out-of-flows whose
 | |
|     // containing block is not within the first line.
 | |
|     //
 | |
|     // Right now we're somewhat inconsistent in this testcase:
 | |
|     //
 | |
|     //  <style>
 | |
|     //    div { color: orange; clear: left; }
 | |
|     //    div::first-line { color: blue; }
 | |
|     //  </style>
 | |
|     //  <div>
 | |
|     //    <span style="float: left">What color is this text?</span>
 | |
|     //  </div>
 | |
|     //  <div>
 | |
|     //    <span><span style="float: left">What color is this text?</span></span>
 | |
|     //  </div>
 | |
|     //
 | |
|     // We make the first float orange and the second float blue.  On the other
 | |
|     // hand, if the float were within an inline-block that was on the first
 | |
|     // line, arguably it _should_ inherit from the ::first-line...
 | |
|     nsIFrame* outOfFlow =
 | |
|         nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
 | |
|     MOZ_ASSERT(outOfFlow, "no out-of-flow frame");
 | |
|     for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) {
 | |
|       DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // FIXME(emilio): This is the only caller of GetParentComputedStyle, let's try
 | |
|   // to remove it?
 | |
|   nsIFrame* providerFrame;
 | |
|   ComputedStyle* newParentStyle =
 | |
|       aFrame->GetParentComputedStyle(&providerFrame);
 | |
|   // If our provider is our child, we want to reparent it first, because we
 | |
|   // inherit style from it.
 | |
|   bool isChild = providerFrame && providerFrame->GetParent() == aFrame;
 | |
|   nsIFrame* providerChild = nullptr;
 | |
|   if (isChild) {
 | |
|     DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet);
 | |
|     // Get the style again after ReparentComputedStyle() which might have
 | |
|     // changed it.
 | |
|     newParentStyle = providerFrame->Style();
 | |
|     providerChild = providerFrame;
 | |
|     MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
 | |
|                "Out of flow provider?");
 | |
|   }
 | |
| 
 | |
|   if (!newParentStyle) {
 | |
|     // No need to do anything here for this frame, but we should still reparent
 | |
|     // its descendants, because those may have styles that inherit from the
 | |
|     // parent of this frame (e.g. non-anonymous columns in an anonymous
 | |
|     // colgroup).
 | |
|     MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(),
 | |
|                "Why did this frame not end up with a parent context?");
 | |
|     ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool isElement = aFrame->GetContent()->IsElement();
 | |
| 
 | |
|   // We probably don't want to initiate transitions from ReparentComputedStyle,
 | |
|   // since we call it during frame construction rather than in response to
 | |
|   // dynamic changes.
 | |
|   // Also see the comment at the start of
 | |
|   // nsTransitionManager::ConsiderInitiatingTransition.
 | |
|   //
 | |
|   // We don't try to do the fancy copying from previous continuations that
 | |
|   // GeckoRestyleManager does here, because that relies on knowing the parents
 | |
|   // of ComputedStyles, and we don't know those.
 | |
|   ComputedStyle* oldStyle = aFrame->Style();
 | |
|   Element* ourElement = isElement ? aFrame->GetContent()->AsElement() : nullptr;
 | |
|   ComputedStyle* newParent = newParentStyle;
 | |
| 
 | |
|   if (!providerFrame) {
 | |
|     // No providerFrame means we inherited from a display:contents thing.  Our
 | |
|     // layout parent style is the style of our nearest ancestor frame.  But we
 | |
|     // have to be careful to do that with our placeholder, not with us, if we're
 | |
|     // out of flow.
 | |
|     if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
 | |
|       aFrame->FirstContinuation()
 | |
|           ->GetPlaceholderFrame()
 | |
|           ->GetLayoutParentStyleForOutOfFlow(&providerFrame);
 | |
|     } else {
 | |
|       providerFrame = nsIFrame::CorrectStyleParentFrame(
 | |
|           aFrame->GetParent(), oldStyle->GetPseudoType());
 | |
|     }
 | |
|   }
 | |
|   ComputedStyle* layoutParent = providerFrame->Style();
 | |
| 
 | |
|   RefPtr<ComputedStyle> newStyle = aStyleSet.ReparentComputedStyle(
 | |
|       oldStyle, newParent, layoutParent, ourElement);
 | |
|   aFrame->SetComputedStyle(newStyle);
 | |
| 
 | |
|   // This logic somewhat mirrors the logic in
 | |
|   // RestyleManager::ProcessPostTraversal.
 | |
|   if (isElement) {
 | |
|     // We can't use UpdateAdditionalComputedStyles as-is because it needs a
 | |
|     // ServoRestyleState and maintaining one of those during a _frametree_
 | |
|     // traversal is basically impossible.
 | |
|     int32_t index = 0;
 | |
|     while (auto* oldAdditionalStyle =
 | |
|                aFrame->GetAdditionalComputedStyle(index)) {
 | |
|       RefPtr<ComputedStyle> newAdditionalContext =
 | |
|           aStyleSet.ReparentComputedStyle(oldAdditionalStyle, newStyle,
 | |
|                                           newStyle, nullptr);
 | |
|       aFrame->SetAdditionalComputedStyle(index, newAdditionalContext);
 | |
|       ++index;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Generally, owned anon boxes are our descendants.  The only exceptions are
 | |
|   // tables (for the table wrapper) and inline frames (for the block part of the
 | |
|   // block-in-inline split).  We're going to update our descendants when looping
 | |
|   // over kids, and we don't want to update the block part of a block-in-inline
 | |
|   // split if the inline is on the first line but the block is not (and if the
 | |
|   // block is, it's the child of something else on the first line and will get
 | |
|   // updated as a child).  And given how this method ends up getting called, if
 | |
|   // we reach here for a table frame, we are already in the middle of
 | |
|   // reparenting the table wrapper frame.  So no need to
 | |
|   // UpdateStyleOfOwnedAnonBoxes() here.
 | |
| 
 | |
|   ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
 | |
| 
 | |
|   // We do not need to do the equivalent of UpdateFramePseudoElementStyles,
 | |
|   // because those are handled by our descendant walk.
 | |
| }
 | |
| 
 | |
| void RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame,
 | |
|                                               nsIFrame* aProviderChild,
 | |
|                                               ServoStyleSet& aStyleSet) {
 | |
|   for (const auto& childList : aFrame->ChildLists()) {
 | |
|     for (nsIFrame* child : childList.mList) {
 | |
|       // only do frames that are in flow
 | |
|       if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
 | |
|           child != aProviderChild) {
 | |
|         DoReparentComputedStyleForFirstLine(child, aStyleSet);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 |