Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  RestyleManager.cpp   Sprache: 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/ScrollContainerFrame.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 "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) {
  MOZ_ASSERT(aFirstNewContent->GetParentNode());

#ifdef DEBUG
  for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
    NS_ASSERTION(cur->IsRootOfNativeAnonymousSubtree() ==
                     aFirstNewContent->IsRootOfNativeAnonymousSubtree(),
                 "anonymous nodes should not be in child lists");
  }
#endif

  // We get called explicitly with NAC by editor and view transitions code, but
  // in those cases we don't need to do any invalidation.
  if (MOZ_UNLIKELY(aFirstNewContent->IsRootOfNativeAnonymousSubtree())) {
    return;
  }

  StyleSet()->MaybeInvalidateForElementAppend(*aFirstNewContent);

  auto* container = aFirstNewContent->GetParentNode();
  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) {
    RestyleWholeContainer(container, selectorFlags);
    // 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, StyleRelativeSelectorNthEdgeInvalidateFor::Last);
        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::RestyleWholeContainer(nsINode* aContainer,
                                           NodeSelectorFlags aSelectorFlags) {
  if (!mRestyledAsWholeContainer.EnsureInserted(aContainer)) {
    return;
  }
  if (auto* containerElement = Element::FromNode(aContainer)) {
    PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
                     nsChangeHint(0));
    if (aSelectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
      StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
          containerElement->GetFirstElementChild(),
          /* aForceRestyleSiblings = */ false);
    }
  } else {
    RestyleSiblingsStartingWith(aContainer->GetFirstChild());
  }
}

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, StyleRelativeSelectorNthEdgeInvalidateFor::First);
      }
      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, StyleRelativeSelectorNthEdgeInvalidateFor::Last);
      }
      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) {
    RestyleWholeContainer(container, selectorFlags);
    // Restyling the container is the most we can do here, so we're done.
    return;
  }

  if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
    // Restyle all later siblings.
    if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
      StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
          aChild->GetNextElementSibling(), /* aForceRestyleSiblings = */ true);
    } else {
      RestyleSiblingsStartingWith(aChild->GetNextSibling());
    }
  }

  if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
    MaybeRestyleForEdgeChildChange(container, aChild);
  }
}

void RestyleManager::ContentWillBeRemoved(nsIContent* aOldChild) {
  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();
  }

  // This is called with anonymous nodes explicitly by editor and view
  // transitions code, which manage anon content manually.
  // See similar code in ContentAppended.
  if (MOZ_UNLIKELY(aOldChild->IsRootOfNativeAnonymousSubtree())) {
    MOZ_ASSERT(!aOldChild->GetNextSibling(), "NAC doesn't have siblings");
    MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
               "anonymous nodes should not be in child lists (bug 439258)");
    return;
  }

  if (aOldChild->IsElement()) {
    StyleSet()->MaybeInvalidateForElementRemove(*aOldChild->AsElement());
  }

  const auto selectorFlags =
      container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
  if (!selectorFlags) {
    return;
  }

  // The container cannot be a document.
  const bool containerIsElement = container->IsElement();
  MOZ_ASSERT(containerIsElement || container->IsShadowRoot());

  if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
      containerIsElement) {
    // 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 (child != aOldChild && nsStyleUtil::IsSignificantChild(child, false)) {
        isEmpty = false;
        break;
      }
    }
    if (isEmpty && containerIsElement) {
      RestyleForEmptyChange(container->AsElement());
      return;
    }
  }

  // It is somewhat common to remove all nodes in a container from the
  // beginning. If we're doing that, going through the
  // HasSlowSelectorLaterSiblings code-path would be quadratic, so that's not
  // amazing. Instead, we take the slower path (which also restyles the
  // container) in that case. It restyles one more element, but it avoids the
  // quadratic behavior.
  const bool restyleWholeContainer =
      (selectorFlags & NodeSelectorFlags::HasSlowSelector) ||
      (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings &&
       !aOldChild->GetPreviousSibling());

  if (restyleWholeContainer) {
    RestyleWholeContainer(container, selectorFlags);
    // Restyling the container is the most we can do here, so we're done.
    return;
  }

  if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
    // Restyle all later siblings.
    if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
      Element* nextSibling = aOldChild->GetNextElementSibling();
      StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
          nextSibling, /* aForceRestyleSiblings = */ true);
    } else {
      RestyleSiblingsStartingWith(aOldChild->GetNextSibling());
    }
  }

  if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
    const nsIContent* nextSibling = aOldChild->GetNextSibling();
    // 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 == aOldChild) {
        // aOldChild is getting removed, so we don't want to account for it for
        // the purposes of computing whether we're now the first / last child.
        continue;
      }
      if (content == nextSibling) {
        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, StyleRelativeSelectorNthEdgeInvalidateFor::First);
        }
        break;
      }
    }
    // restyle the now-last element child if it was before aOldChild
    reachedFollowingSibling = !nextSibling;
    for (nsIContent* content = container->GetLastChild(); content;
         content = content->GetPreviousSibling()) {
      if (content == aOldChild) {
        // See above.
        continue;
      }
      if (content->IsElement()) {
        if (reachedFollowingSibling) {
          auto* element = content->AsElement();
          PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
                           nsChangeHint(0));
          StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
              *element, StyleRelativeSelectorNthEdgeInvalidateFor::Last);
        }
        break;
      }
      if (content == nextSibling) {
        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) {
  constexpr auto kThemingStates =
      ElementState::HOVER | ElementState::ACTIVE | ElementState::FOCUSRING |
      ElementState::DISABLED | ElementState::CHECKED |
      ElementState::INDETERMINATE | ElementState::READONLY |
      ElementState::FOCUS;
  if (!aStateMask.HasAtLeastOneOfStates(kThemingStates)) {
    return false;
  }

  if (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();
  return pc->Theme()->ThemeSupportsWidget(pc, &aFrame, appearance);
}

/**
 * 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 << std::size(names)) - 1),
                "Name list doesn't match change hints.");
  uint32_t hint = aHint & static_cast<uint32_t>((1ull << std::size(names)) - 1);
  uint32_t rest =
      aHint & ~static_cast<uint32_t>((1ull << std::size(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 < std::size(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 a ScrollContainerFrame, 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->IsScrollContainerFrame()) {
    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 {  // 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->ShouldPropagateRepaintsToRoot()) {
      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 (ScrollContainerFrame* sf = do_QueryFrame(aFrame)) {
          sf->MarkScrollbarsDirtyForReflow();
        } else if (ScrollContainerFrame* sf =
                       aPc->PresShell()->GetRootScrollContainerFrame()) {
          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 (ScrollContainerFrame* 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) {
--> --------------------

--> maximum size reached

--> --------------------

90%


¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge