layout/base/ServoRestyleManager.cpp
author Phil Ringnalda <philringnalda@gmail.com>
Fri, 19 Aug 2016 00:23:42 -0700
changeset 310244 43689d56cbf6891d3fb4cb3a3fff687884049313
parent 310234 928dd363efa0f375e5c35e3a40a660fb9d44d4a1
child 310484 d04597bd1109a39c70c2c76aa5bf22e0aec60a6a
permissions -rw-r--r--
Backed out 5 changesets (bug 1292618) for !mImageTracked assertion failures CLOSED TREE Backed out changeset 1d767147e160 (bug 1292618) Backed out changeset e6034e58efe4 (bug 1292618) Backed out changeset 928dd363efa0 (bug 1292618) Backed out changeset 8e274c66ae7f (bug 1292618) Backed out changeset 6c347701d343 (bug 1292618)

/* -*- 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/ServoRestyleManager.h"
#include "mozilla/ServoStyleSet.h"

using namespace mozilla::dom;

namespace mozilla {

ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
  : RestyleManagerBase(aPresContext)
{
}

void
ServoRestyleManager::PostRestyleEvent(Element* aElement,
                                      nsRestyleHint aRestyleHint,
                                      nsChangeHint aMinChangeHint)
{
  if (MOZ_UNLIKELY(IsDisconnected()) ||
      MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
    return;
  }

  if (aRestyleHint == 0 && !aMinChangeHint && !HasPendingRestyles()) {
    return; // Nothing to do.
  }

  // Note that unlike in Servo, we don't mark elements as dirty until we process
  // the restyle hints in ProcessPendingRestyles.
  if (aRestyleHint || aMinChangeHint) {
    ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
    snapshot->AddExplicitRestyleHint(aRestyleHint);
    snapshot->AddExplicitChangeHint(aMinChangeHint);
  }

  PostRestyleEventInternal(false);
}

void
ServoRestyleManager::PostRestyleEventForLazyConstruction()
{
  PostRestyleEventInternal(true);
}

void
ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
                                         nsRestyleHint aRestyleHint)
{
  NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData not implemented");
}

void
ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                                  nsRestyleHint aRestyleHint)
{
  NS_WARNING("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
}

void
ServoRestyleManager::RecreateStyleContexts(nsIContent* aContent,
                                           nsStyleContext* aParentContext,
                                           ServoStyleSet* aStyleSet,
                                           nsStyleChangeList& aChangeListToProcess)
{
  nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
  if (!primaryFrame && !aContent->IsDirtyForServo()) {
    // This happens when, for example, a display: none child of a
    // HAS_DIRTY_DESCENDANTS content is reached as part of the traversal.
    return;
  }

  if (aContent->IsDirtyForServo()) {
    RefPtr<ServoComputedValues> computedValues =
      Servo_GetComputedValues(aContent).Consume();
    MOZ_ASSERT(computedValues);

    // NB: Change hint processing only applies to elements, at least until we
    // support display: contents.
    if (aContent->IsElement()) {
      nsChangeHint changeHint = nsChangeHint(0);
      Element* element = aContent->AsElement();

      // Add an explicit change hint if appropriate.
      ServoElementSnapshot* snapshot;
      if (mModifiedElements.Get(element, &snapshot)) {
        changeHint |= snapshot->ExplicitChangeHint();
      }

      // Add the stored change hint if there's a frame. If there isn't a frame,
      // generate a ReconstructFrame change hint if the new display value
      // (which we can get from the ComputedValues stored on the node) is not
      // none.
      if (primaryFrame) {
        changeHint |= primaryFrame->StyleContext()->ConsumeStoredChangeHint();
      } else {
        const nsStyleDisplay* currentDisplay =
          Servo_GetStyleDisplay(computedValues);
        if (currentDisplay->mDisplay != NS_STYLE_DISPLAY_NONE) {
          changeHint |= nsChangeHint_ReconstructFrame;
        }
      }

      // Add the new change hint to the list of elements to process if
      // we need to do any work.
      if (changeHint) {
        aChangeListToProcess.AppendChange(primaryFrame, element, changeHint);
      }
    }

    // The frame reconstruction step (if needed) will ask for the descendants'
    // style correctly. If not needed, we're done too.
    if (!primaryFrame) {
      aContent->UnsetFlags(NODE_IS_DIRTY_FOR_SERVO);
      return;
    }

    // Hold the old style context alive, because it could become a dangling
    // pointer during the replacement. In practice it's not a huge deal (on
    // GetNextContinuationWithSameStyle the pointer is not dereferenced, only
    // compared), but better not playing with dangling pointers if not needed.
    RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
    MOZ_ASSERT(oldStyleContext);

    // TODO: Figure out what pseudos does this content have, and do the proper
    // thing with them.
    RefPtr<nsStyleContext> newContext =
      aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
                            CSSPseudoElementType::NotPseudo);

    // XXX This could not always work as expected: there are kinds of content
    // with the first split and the last sharing style, but others not. We
    // should handle those properly.
    for (nsIFrame* f = primaryFrame; f;
         f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
      f->SetStyleContext(newContext);
    }

    // TODO: There are other continuations we still haven't restyled, mostly
    // pseudo-elements. We have to deal with those, and with anonymous boxes.
    aContent->UnsetFlags(NODE_IS_DIRTY_FOR_SERVO);
  }

  if (aContent->HasDirtyDescendantsForServo()) {
    MOZ_ASSERT(primaryFrame,
               "Frame construction should be scheduled, and it takes the "
               "correct style for the children, so no need to be here.");
    FlattenedChildIterator it(aContent);
    for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
      RecreateStyleContexts(n, primaryFrame->StyleContext(),
                            aStyleSet, aChangeListToProcess);
    }
    aContent->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
  }
}

static void
MarkParentsAsHavingDirtyDescendants(Element* aElement)
{
  nsINode* cur = aElement;
  while ((cur = cur->GetParentNode())) {
    if (cur->HasDirtyDescendantsForServo()) {
      break;
    }

    cur->SetHasDirtyDescendantsForServo();
  }
}

static void
MarkChildrenAsDirtyForServo(nsIContent* aContent)
{
  FlattenedChildIterator it(aContent);

  nsIContent* n = it.GetNextChild();
  bool hadChildren = bool(n);
  for (; n; n = it.GetNextChild()) {
    n->SetIsDirtyForServo();
  }

  if (hadChildren) {
    aContent->SetHasDirtyDescendantsForServo();
  }
}

void
ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
{
  const nsRestyleHint HANDLED_RESTYLE_HINTS = eRestyle_Self |
                                              eRestyle_Subtree |
                                              eRestyle_LaterSiblings |
                                              eRestyle_SomeDescendants;
  // NB: For Servo, at least for now, restyling and running selector-matching
  // against the subtree is necessary as part of restyling the element, so
  // processing eRestyle_Self will perform at least as much work as
  // eRestyle_Subtree.
  if (aHint & (eRestyle_Self | eRestyle_Subtree)) {
    aElement->SetIsDirtyForServo();
    MarkParentsAsHavingDirtyDescendants(aElement);
  // NB: Servo gives us a eRestyle_SomeDescendants when it expects us to run
  // selector matching on all the descendants. There's a bug on Servo to align
  // meanings here (#12710) to avoid this potential source of confusion.
  } else if (aHint & eRestyle_SomeDescendants) {
    MarkChildrenAsDirtyForServo(aElement);
    MarkParentsAsHavingDirtyDescendants(aElement);
  }

  if (aHint & eRestyle_LaterSiblings) {
    MarkParentsAsHavingDirtyDescendants(aElement);
    for (nsIContent* cur = aElement->GetNextSibling(); cur;
         cur = cur->GetNextSibling()) {
      cur->SetIsDirtyForServo();
    }
  }

  // TODO: Handle all other nsRestyleHint values.
  if (aHint & ~HANDLED_RESTYLE_HINTS) {
    NS_WARNING(nsPrintfCString("stylo: Unhandled restyle hint %s",
                               RestyleManagerBase::RestyleHintToString(aHint).get()).get());
  }
}

void
ServoRestyleManager::ProcessPendingRestyles()
{
  MOZ_ASSERT(PresContext()->Document(), "No document?  Pshaw!");
  MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
  if (!HasPendingRestyles()) {
    return;
  }

  ServoStyleSet* styleSet = StyleSet();
  if (!styleSet->StylingStarted()) {
    // If something caused us to restyle, and we haven't started styling yet,
    // do nothing. Everything is dirty, and we'll style it all later.
    return;
  }

  nsIDocument* doc = PresContext()->Document();
  Element* root = doc->GetRootElement();
  if (root) {
    for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) {
      ServoElementSnapshot* snapshot = iter.UserData();
      Element* element = iter.Key();

      // TODO: avoid the ComputeRestyleHint call if we already have the highest
      // explicit restyle hint?
      nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
      hint |= snapshot->ExplicitRestyleHint();

      if (hint) {
        NoteRestyleHint(element, hint);
      }
    }

    if (root->IsDirtyForServo() || root->HasDirtyDescendantsForServo()) {
      mInStyleRefresh = true;
      styleSet->RestyleSubtree(root);

      // First do any queued-up frame creation. (see bugs 827239 and 997506).
      //
      // XXXEmilio I'm calling this to avoid random behavior changes, since we
      // delay frame construction after styling we should re-check once our
      // model is more stable whether we can skip this call.
      //
      // Note this has to be *after* restyling, because otherwise frame
      // construction will find unstyled nodes, and that's not funny.
      PresContext()->FrameConstructor()->CreateNeededFrames();

      nsStyleChangeList changeList;
      RecreateStyleContexts(root, nullptr, styleSet, changeList);
      ProcessRestyledFrames(changeList);

      mInStyleRefresh = false;
    }
  }

  mModifiedElements.Clear();

  // NB: we restyle from the root element, but the document also gets the
  // HAS_DIRTY_DESCENDANTS flag as part of the loop on PostRestyleEvent, and we
  // use that to check we have pending restyles.
  //
  // Thus, they need to get cleared here.
  MOZ_ASSERT(!doc->IsDirtyForServo());
  doc->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);

  IncrementRestyleGeneration();
}

void
ServoRestyleManager::RestyleForInsertOrChange(Element* aContainer,
                                              nsIContent* aChild)
{
  // XXX Emilio we can do way better.
  PostRestyleEvent(aContainer, eRestyle_Subtree, nsChangeHint(0));
}

void
ServoRestyleManager::RestyleForAppend(Element* aContainer,
                                      nsIContent* aFirstNewContent)
{
  // XXX Emilio we can do way better.
  PostRestyleEvent(aContainer, eRestyle_Subtree, nsChangeHint(0));
}

void
ServoRestyleManager::RestyleForRemove(Element* aContainer,
                                      nsIContent* aOldChild,
                                      nsIContent* aFollowingSibling)
{
  NS_WARNING("stylo: ServoRestyleManager::RestyleForRemove not implemented");
}

nsresult
ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
                                         EventStates aChangedBits)
{
  if (!aContent->IsElement()) {
    return NS_OK;
  }

  Element* aElement = aContent->AsElement();
  nsChangeHint changeHint;
  nsRestyleHint restyleHint;

  // NOTE: restyleHint here is effectively always 0, since that's what
  // ServoStyleSet::HasStateDependentStyle returns. Servo computes on
  // ProcessPendingRestyles using the ElementSnapshot, but in theory could
  // compute it sequentially easily.
  //
  // Determine what's the best way to do it, and how much work do we save
  // processing the restyle hint early (i.e., computing the style hint here
  // sequentially, potentially saving the snapshot), vs lazily (snapshot
  // approach).
  //
  // If we take the sequential approach we need to specialize Servo's restyle
  // hints system a bit more, and mesure whether we save something storing the
  // restyle hint in the table and deferring the dirtiness setting until
  // ProcessPendingRestyles (that's a requirement if we store snapshots though),
  // vs processing the restyle hint in-place, dirtying the nodes on
  // PostRestyleEvent.
  //
  // If we definitely take the snapshot approach, we should take rid of
  // HasStateDependentStyle, etc (though right now they're no-ops).
  ContentStateChangedInternal(aElement, aChangedBits, &changeHint,
                              &restyleHint);

  EventStates previousState = aElement->StyleState() ^ aChangedBits;
  ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
  snapshot->AddState(previousState);

  PostRestyleEvent(aElement, restyleHint, changeHint);
  return NS_OK;
}

void
ServoRestyleManager::AttributeWillChange(Element* aElement,
                                         int32_t aNameSpaceID,
                                         nsIAtom* aAttribute, int32_t aModType,
                                         const nsAttrValue* aNewValue)
{
  ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
  snapshot->AddAttrs(aElement);
}

nsresult
ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
{
  NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
  return NS_OK;
}

ServoElementSnapshot*
ServoRestyleManager::SnapshotForElement(Element* aElement)
{
  // NB: aElement is the argument for the construction of the snapshot in the
  // not found case.
  return mModifiedElements.LookupOrAdd(aElement, aElement);
}

} // namespace mozilla