1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/RestyleManager.h"
8 #include "mozilla/RestyleManagerInlines.h"
9 
10 #include "Layers.h"
11 #include "LayerAnimationInfo.h"  // For LayerAnimationInfo::sRecords
12 #include "mozilla/StyleSetHandleInlines.h"
13 #include "nsAnimationManager.h"
14 #include "nsCSSFrameConstructor.h"
15 #include "nsCSSRendering.h"
16 #include "nsIFrame.h"
17 #include "nsIFrameInlines.h"
18 #include "nsIPresShellInlines.h"
19 #include "nsPlaceholderFrame.h"
20 #include "nsStyleChangeList.h"
21 #include "nsStyleUtil.h"
22 #include "nsTransitionManager.h"
23 #include "StickyScrollContainer.h"
24 #include "mozilla/EffectSet.h"
25 #include "mozilla/IntegerRange.h"
26 #include "mozilla/ViewportFrame.h"
27 #include "SVGObserverUtils.h"
28 #include "SVGTextFrame.h"
29 #include "ActiveLayerTracker.h"
30 #include "nsSVGIntegrationUtils.h"
31 
32 using namespace mozilla::dom;
33 
34 namespace mozilla {
35 
RestyleManager(StyleBackendType aType,nsPresContext * aPresContext)36 RestyleManager::RestyleManager(StyleBackendType aType,
37                                nsPresContext* aPresContext)
38     : mPresContext(aPresContext),
39       mRestyleGeneration(1),
40       mUndisplayedRestyleGeneration(1),
41       mHoverGeneration(0),
42       mType(aType),
43       mInStyleRefresh(false),
44       mAnimationGeneration(0) {
45   MOZ_ASSERT(mPresContext);
46 }
47 
ContentInserted(nsINode * aContainer,nsIContent * aChild)48 void RestyleManager::ContentInserted(nsINode* aContainer, nsIContent* aChild) {
49   RestyleForInsertOrChange(aContainer, aChild);
50 }
51 
ContentAppended(nsIContent * aContainer,nsIContent * aFirstNewContent)52 void RestyleManager::ContentAppended(nsIContent* aContainer,
53                                      nsIContent* aFirstNewContent) {
54   // The container cannot be a document, but might be a ShadowRoot.
55   if (!aContainer->IsElement()) {
56     return;
57   }
58   Element* container = aContainer->AsElement();
59 
60 #ifdef DEBUG
61   {
62     for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
63       NS_ASSERTION(!cur->IsRootOfAnonymousSubtree(),
64                    "anonymous nodes should not be in child lists");
65     }
66   }
67 #endif
68   uint32_t selectorFlags =
69       container->GetFlags() &
70       (NODE_ALL_SELECTOR_FLAGS & ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
71   if (selectorFlags == 0) return;
72 
73   if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
74     // see whether we need to restyle the container
75     bool wasEmpty = true;  // :empty or :-moz-only-whitespace
76     for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent;
77          cur = cur->GetNextSibling()) {
78       // We don't know whether we're testing :empty or :-moz-only-whitespace,
79       // so be conservative and assume :-moz-only-whitespace (i.e., make
80       // IsSignificantChild less likely to be true, and thus make us more
81       // likely to restyle).
82       if (nsStyleUtil::IsSignificantChild(cur, false)) {
83         wasEmpty = false;
84         break;
85       }
86     }
87     if (wasEmpty) {
88       RestyleForEmptyChange(container);
89       return;
90     }
91   }
92 
93   if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
94     PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
95     // Restyling the container is the most we can do here, so we're done.
96     return;
97   }
98 
99   if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
100     // restyle the last element child before this node
101     for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur;
102          cur = cur->GetPreviousSibling()) {
103       if (cur->IsElement()) {
104         PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, nsChangeHint(0));
105         break;
106       }
107     }
108   }
109 }
110 
RestyleForEmptyChange(Element * aContainer)111 void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
112   // In some cases (:empty + E, :empty ~ E), a change in the content of
113   // an element requires restyling its parent's siblings.
114   nsRestyleHint hint = eRestyle_Subtree;
115   nsIContent* grandparent = aContainer->GetParent();
116   if (grandparent &&
117       (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) {
118     hint = nsRestyleHint(hint | eRestyle_LaterSiblings);
119   }
120   PostRestyleEvent(aContainer, hint, nsChangeHint(0));
121 }
122 
MaybeRestyleForEdgeChildChange(Element * aContainer,nsIContent * aChangedChild)123 void RestyleManager::MaybeRestyleForEdgeChildChange(Element* aContainer,
124                                                     nsIContent* aChangedChild) {
125   MOZ_ASSERT(aContainer->GetFlags() & NODE_HAS_EDGE_CHILD_SELECTOR);
126   MOZ_ASSERT(aChangedChild->GetParent() == aContainer);
127   // restyle the previously-first element child if it is after this node
128   bool passedChild = false;
129   for (nsIContent* content = aContainer->GetFirstChild(); content;
130        content = content->GetNextSibling()) {
131     if (content == aChangedChild) {
132       passedChild = true;
133       continue;
134     }
135     if (content->IsElement()) {
136       if (passedChild) {
137         PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
138                          nsChangeHint(0));
139       }
140       break;
141     }
142   }
143   // restyle the previously-last element child if it is before this node
144   passedChild = false;
145   for (nsIContent* content = aContainer->GetLastChild(); content;
146        content = content->GetPreviousSibling()) {
147     if (content == aChangedChild) {
148       passedChild = true;
149       continue;
150     }
151     if (content->IsElement()) {
152       if (passedChild) {
153         PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
154                          nsChangeHint(0));
155       }
156       break;
157     }
158   }
159 }
160 
161 // Needed since we can't use PostRestyleEvent on non-elements (with
162 // eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree |
163 // eRestyle_LaterSiblings) as appropriate).
RestyleSiblingsStartingWith(RestyleManager * aRestyleManager,nsIContent * aStartingSibling)164 static void RestyleSiblingsStartingWith(
165     RestyleManager* aRestyleManager,
166     nsIContent* aStartingSibling /* may be null */) {
167   for (nsIContent* sibling = aStartingSibling; sibling;
168        sibling = sibling->GetNextSibling()) {
169     if (sibling->IsElement()) {
170       aRestyleManager->PostRestyleEvent(
171           sibling->AsElement(),
172           nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings),
173           nsChangeHint(0));
174       break;
175     }
176   }
177 }
178 
179 template <typename CharT>
WhitespaceOnly(const CharT * aBuffer,size_t aUpTo)180 bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) {
181   for (auto index : IntegerRange(aUpTo)) {
182     if (!dom::IsSpaceCharacter(aBuffer[index])) {
183       return false;
184     }
185   }
186   return true;
187 }
188 
189 template <typename CharT>
WhitespaceOnlyChangedOnAppend(const CharT * aBuffer,size_t aOldLength,size_t aNewLength)190 bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength,
191                                    size_t aNewLength) {
192   MOZ_ASSERT(aOldLength <= aNewLength);
193   if (!WhitespaceOnly(aBuffer, aOldLength)) {
194     // The old text was already not whitespace-only.
195     return false;
196   }
197 
198   return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength);
199 }
200 
HasAnySignificantSibling(Element * aContainer,nsIContent * aChild)201 static bool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) {
202   MOZ_ASSERT(aChild->GetParent() == aContainer);
203   for (nsIContent* child = aContainer->GetFirstChild(); child;
204        child = child->GetNextSibling()) {
205     if (child == aChild) {
206       continue;
207     }
208     // We don't know whether we're testing :empty or :-moz-only-whitespace,
209     // so be conservative and assume :-moz-only-whitespace (i.e., make
210     // IsSignificantChild less likely to be true, and thus make us more
211     // likely to restyle).
212     if (nsStyleUtil::IsSignificantChild(child, false)) {
213       return true;
214     }
215   }
216 
217   return false;
218 }
219 
CharacterDataChanged(nsIContent * aContent,const CharacterDataChangeInfo & aInfo)220 void RestyleManager::CharacterDataChanged(
221     nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
222   nsINode* parent = aContent->GetParentNode();
223   MOZ_ASSERT(parent, "How were we notified of a stray node?");
224 
225   uint32_t slowSelectorFlags = parent->GetFlags() & NODE_ALL_SELECTOR_FLAGS;
226   if (!(slowSelectorFlags &
227         (NODE_HAS_EMPTY_SELECTOR | NODE_HAS_EDGE_CHILD_SELECTOR))) {
228     // Nothing to do, no other slow selector can change as a result of this.
229     return;
230   }
231 
232   if (!aContent->IsNodeOfType(nsINode::eTEXT)) {
233     // Doesn't matter to styling (could be a processing instruction or a
234     // comment), it can't change whether any selectors match or don't.
235     return;
236   }
237 
238   if (MOZ_UNLIKELY(!parent->IsElement())) {
239     MOZ_ASSERT(parent->IsShadowRoot());
240     return;
241   }
242 
243   if (MOZ_UNLIKELY(aContent->IsRootOfAnonymousSubtree())) {
244     // This is an anonymous node and thus isn't in child lists, so isn't taken
245     // into account for selector matching the relevant selectors here.
246     return;
247   }
248 
249   // Handle appends specially since they're common and we can know both the old
250   // and the new text exactly.
251   //
252   // TODO(emilio): This could be made much more general if :-moz-only-whitespace
253   // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only
254   // need to know whether we went from empty to non-empty, and that's trivial to
255   // know, with CharacterDataChangeInfo...
256   if (!aInfo.mAppend) {
257     // FIXME(emilio): This restyles unnecessarily if the text node is the only
258     // child of the parent element. Fortunately, it's uncommon to have such
259     // nodes and this not being an append.
260     //
261     // See the testcase in bug 1427625 for a test-case that triggers this.
262     RestyleForInsertOrChange(parent->AsElement(), aContent);
263     return;
264   }
265 
266   const nsTextFragment* text = aContent->GetText();
267 
268   const size_t oldLength = aInfo.mChangeStart;
269   const size_t newLength = text->GetLength();
270 
271   const bool emptyChanged = !oldLength && newLength;
272 
273   const bool whitespaceOnlyChanged =
274       text->Is2b()
275           ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength)
276           : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength);
277 
278   if (!emptyChanged && !whitespaceOnlyChanged) {
279     return;
280   }
281 
282   if (slowSelectorFlags & NODE_HAS_EMPTY_SELECTOR) {
283     if (!HasAnySignificantSibling(parent->AsElement(), aContent)) {
284       // We used to be empty, restyle the parent.
285       RestyleForEmptyChange(parent->AsElement());
286       return;
287     }
288   }
289 
290   if (slowSelectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
291     MaybeRestyleForEdgeChildChange(parent->AsElement(), aContent);
292   }
293 }
294 
295 // Restyling for a ContentInserted or CharacterDataChanged notification.
296 // This could be used for ContentRemoved as well if we got the
297 // notification before the removal happened (and sometimes
298 // CharacterDataChanged is more like a removal than an addition).
299 // The comments are written and variables are named in terms of it being
300 // a ContentInserted notification.
RestyleForInsertOrChange(nsINode * aContainer,nsIContent * aChild)301 void RestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
302                                               nsIContent* aChild) {
303   // The container might be a document or a ShadowRoot.
304   if (!aContainer->IsElement()) {
305     return;
306   }
307   Element* container = aContainer->AsElement();
308 
309   NS_ASSERTION(!aChild->IsRootOfAnonymousSubtree(),
310                "anonymous nodes should not be in child lists");
311   uint32_t selectorFlags = container->GetFlags() & NODE_ALL_SELECTOR_FLAGS;
312   if (selectorFlags == 0) return;
313 
314   if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
315     // See whether we need to restyle the container due to :empty /
316     // :-moz-only-whitespace.
317     const bool wasEmpty = !HasAnySignificantSibling(container, aChild);
318     if (wasEmpty) {
319       // FIXME(emilio): When coming from CharacterDataChanged this can restyle
320       // unnecessarily. Also can restyle unnecessarily if aChild is not
321       // significant anyway, though that's more unlikely.
322       RestyleForEmptyChange(container);
323       return;
324     }
325   }
326 
327   if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
328     PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
329     // Restyling the container is the most we can do here, so we're done.
330     return;
331   }
332 
333   if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
334     // Restyle all later siblings.
335     RestyleSiblingsStartingWith(this, aChild->GetNextSibling());
336   }
337 
338   if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
339     MaybeRestyleForEdgeChildChange(container, aChild);
340   }
341 }
342 
ContentRemoved(nsINode * aContainer,nsIContent * aOldChild,nsIContent * aFollowingSibling)343 void RestyleManager::ContentRemoved(nsINode* aContainer, nsIContent* aOldChild,
344                                     nsIContent* aFollowingSibling) {
345   // Computed style data isn't useful for detached nodes, and we'll need to
346   // recompute it anyway if we ever insert the nodes back into a document.
347   if (IsServo() && aOldChild->IsElement()) {
348     ServoRestyleManager::ClearServoDataFromSubtree(aOldChild->AsElement());
349   }
350 
351   // The container might be a document or a ShadowRoot.
352   if (!aContainer->IsElement()) {
353     return;
354   }
355   Element* container = aContainer->AsElement();
356 
357   if (aOldChild->IsRootOfAnonymousSubtree()) {
358     // This should be an assert, but this is called incorrectly in
359     // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
360     // up the logs.  Make it an assert again when that's fixed.
361     MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
362                "anonymous nodes should not be in child lists (bug 439258)");
363   }
364   uint32_t selectorFlags = container->GetFlags() & NODE_ALL_SELECTOR_FLAGS;
365   if (selectorFlags == 0) return;
366 
367   if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
368     // see whether we need to restyle the container
369     bool isEmpty = true;  // :empty or :-moz-only-whitespace
370     for (nsIContent* child = container->GetFirstChild(); child;
371          child = child->GetNextSibling()) {
372       // We don't know whether we're testing :empty or :-moz-only-whitespace,
373       // so be conservative and assume :-moz-only-whitespace (i.e., make
374       // IsSignificantChild less likely to be true, and thus make us more
375       // likely to restyle).
376       if (nsStyleUtil::IsSignificantChild(child, false)) {
377         isEmpty = false;
378         break;
379       }
380     }
381     if (isEmpty) {
382       RestyleForEmptyChange(container);
383       return;
384     }
385   }
386 
387   if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
388     PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
389     // Restyling the container is the most we can do here, so we're done.
390     return;
391   }
392 
393   if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
394     // Restyle all later siblings.
395     RestyleSiblingsStartingWith(this, aFollowingSibling);
396   }
397 
398   if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
399     // restyle the now-first element child if it was after aOldChild
400     bool reachedFollowingSibling = false;
401     for (nsIContent* content = container->GetFirstChild(); content;
402          content = content->GetNextSibling()) {
403       if (content == aFollowingSibling) {
404         reachedFollowingSibling = true;
405         // do NOT continue here; we might want to restyle this node
406       }
407       if (content->IsElement()) {
408         if (reachedFollowingSibling) {
409           PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
410                            nsChangeHint(0));
411         }
412         break;
413       }
414     }
415     // restyle the now-last element child if it was before aOldChild
416     reachedFollowingSibling = (aFollowingSibling == nullptr);
417     for (nsIContent* content = container->GetLastChild(); content;
418          content = content->GetPreviousSibling()) {
419       if (content->IsElement()) {
420         if (reachedFollowingSibling) {
421           PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
422                            nsChangeHint(0));
423         }
424         break;
425       }
426       if (content == aFollowingSibling) {
427         reachedFollowingSibling = true;
428       }
429     }
430   }
431 }
432 
433 /**
434  * Calculates the change hint and the restyle hint for a given content state
435  * change.
436  *
437  * This is called from both Restyle managers.
438  */
ContentStateChangedInternal(Element * aElement,EventStates aStateMask,nsChangeHint * aOutChangeHint)439 void RestyleManager::ContentStateChangedInternal(Element* aElement,
440                                                  EventStates aStateMask,
441                                                  nsChangeHint* aOutChangeHint) {
442   MOZ_ASSERT(!mInStyleRefresh);
443   MOZ_ASSERT(aOutChangeHint);
444 
445   *aOutChangeHint = nsChangeHint(0);
446   // Any change to a content state that affects which frames we construct
447   // must lead to a frame reconstruct here if we already have a frame.
448   // Note that we never decide through non-CSS means to not create frames
449   // based on content states, so if we already don't have a frame we don't
450   // need to force a reframe -- if it's needed, the HasStateDependentStyle
451   // call will handle things.
452   nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
453   if (primaryFrame) {
454     // If it's generated content, ignore LOADING/etc state changes on it.
455     if (!primaryFrame->IsGeneratedContentFrame() &&
456         aStateMask.HasAtLeastOneOfStates(
457             NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED |
458             NS_EVENT_STATE_SUPPRESSED | NS_EVENT_STATE_LOADING)) {
459       *aOutChangeHint = nsChangeHint_ReconstructFrame;
460     } else {
461       uint8_t app = primaryFrame->StyleDisplay()->mAppearance;
462       if (app) {
463         nsITheme* theme = PresContext()->GetTheme();
464         if (theme &&
465             theme->ThemeSupportsWidget(PresContext(), primaryFrame, app)) {
466           bool repaint = false;
467           theme->WidgetStateChanged(primaryFrame, app, nullptr, &repaint,
468                                     nullptr);
469           if (repaint) {
470             *aOutChangeHint |= nsChangeHint_RepaintFrame;
471           }
472         }
473       }
474     }
475 
476     primaryFrame->ContentStatesChanged(aStateMask);
477   }
478 
479   if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
480     // Exposing information to the page about whether the link is
481     // visited or not isn't really something we can worry about here.
482     // FIXME: We could probably do this a bit better.
483     *aOutChangeHint |= nsChangeHint_RepaintFrame;
484   }
485 }
486 
RestyleHintToString(nsRestyleHint aHint)487 /* static */ nsCString RestyleManager::RestyleHintToString(
488     nsRestyleHint aHint) {
489   nsCString result;
490   bool any = false;
491   const char* names[] = {"Self",           "SomeDescendants",
492                          "Subtree",        "LaterSiblings",
493                          "CSSTransitions", "CSSAnimations",
494                          "StyleAttribute", "StyleAttribute_Animations",
495                          "Force",          "ForceDescendants"};
496   uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
497   uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
498   for (uint32_t i = 0; i < ArrayLength(names); i++) {
499     if (hint & (1 << i)) {
500       if (any) {
501         result.AppendLiteral(" | ");
502       }
503       result.AppendPrintf("eRestyle_%s", names[i]);
504       any = true;
505     }
506   }
507   if (rest) {
508     if (any) {
509       result.AppendLiteral(" | ");
510     }
511     result.AppendPrintf("0x%0x", rest);
512   } else {
513     if (!any) {
514       result.AppendLiteral("0");
515     }
516   }
517   return result;
518 }
519 
520 #ifdef DEBUG
ChangeHintToString(nsChangeHint aHint)521 /* static */ nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) {
522   nsCString result;
523   bool any = false;
524   const char* names[] = {"RepaintFrame",
525                          "NeedReflow",
526                          "ClearAncestorIntrinsics",
527                          "ClearDescendantIntrinsics",
528                          "NeedDirtyReflow",
529                          "SyncFrameView",
530                          "UpdateCursor",
531                          "UpdateEffects",
532                          "UpdateOpacityLayer",
533                          "UpdateTransformLayer",
534                          "ReconstructFrame",
535                          "UpdateOverflow",
536                          "UpdateSubtreeOverflow",
537                          "UpdatePostTransformOverflow",
538                          "UpdateParentOverflow",
539                          "ChildrenOnlyTransform",
540                          "RecomputePosition",
541                          "UpdateContainingBlock",
542                          "BorderStyleNoneChange",
543                          "UpdateTextPath",
544                          "SchedulePaint",
545                          "NeutralChange",
546                          "InvalidateRenderingObservers",
547                          "ReflowChangesSizeOrPosition",
548                          "UpdateComputedBSize",
549                          "UpdateUsesOpacity",
550                          "UpdateBackgroundPosition",
551                          "AddOrRemoveTransform",
552                          "CSSOverflowChange",
553                          "UpdateWidgetProperties",
554                          "UpdateTableCellSpans",
555                          "VisibilityChange"};
556   static_assert(nsChangeHint_AllHints ==
557                     static_cast<uint32_t>((1ull << ArrayLength(names)) - 1),
558                 "Name list doesn't match change hints.");
559   uint32_t hint =
560       aHint & static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
561   uint32_t rest =
562       aHint & ~static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
563   if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
564     result.AppendLiteral("NS_STYLE_HINT_REFLOW");
565     hint = hint & ~NS_STYLE_HINT_REFLOW;
566     any = true;
567   } else if ((hint & nsChangeHint_AllReflowHints) ==
568              nsChangeHint_AllReflowHints) {
569     result.AppendLiteral("nsChangeHint_AllReflowHints");
570     hint = hint & ~nsChangeHint_AllReflowHints;
571     any = true;
572   } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
573     result.AppendLiteral("NS_STYLE_HINT_VISUAL");
574     hint = hint & ~NS_STYLE_HINT_VISUAL;
575     any = true;
576   }
577   for (uint32_t i = 0; i < ArrayLength(names); i++) {
578     if (hint & (1u << i)) {
579       if (any) {
580         result.AppendLiteral(" | ");
581       }
582       result.AppendPrintf("nsChangeHint_%s", names[i]);
583       any = true;
584     }
585   }
586   if (rest) {
587     if (any) {
588       result.AppendLiteral(" | ");
589     }
590     result.AppendPrintf("0x%0x", rest);
591   } else {
592     if (!any) {
593       result.AppendLiteral("nsChangeHint(0)");
594     }
595   }
596   return result;
597 }
598 #endif
599 
600 /**
601  * Frame construction helpers follow.
602  */
603 #ifdef DEBUG
604 static bool gInApplyRenderingChangeToTree = false;
605 #endif
606 
607 #ifdef DEBUG
608 #ifdef MOZ_OLD_STYLE
DumpContext(nsIFrame * aFrame,nsStyleContext * aContext)609 static void DumpContext(nsIFrame* aFrame, nsStyleContext* aContext) {
610   if (aFrame) {
611     fputs("frame: ", stdout);
612     nsAutoString name;
613     aFrame->GetFrameName(name);
614     fputs(NS_LossyConvertUTF16toASCII(name).get(), stdout);
615     fprintf(stdout, " (%p)", static_cast<void*>(aFrame));
616   }
617   if (aContext) {
618     fprintf(stdout, " style: %p ", static_cast<void*>(aContext));
619 
620     nsAtom* pseudoTag = aContext->GetPseudo();
621     if (pseudoTag) {
622       nsAutoString buffer;
623       pseudoTag->ToString(buffer);
624       fputs(NS_LossyConvertUTF16toASCII(buffer).get(), stdout);
625       fputs(" ", stdout);
626     }
627     fputs("{}\n", stdout);
628   }
629 }
630 
VerifySameTree(GeckoStyleContext * aContext1,GeckoStyleContext * aContext2)631 static void VerifySameTree(GeckoStyleContext* aContext1,
632                            GeckoStyleContext* aContext2) {
633   GeckoStyleContext* top1 = aContext1;
634   GeckoStyleContext* top2 = aContext2;
635   GeckoStyleContext* parent;
636   for (;;) {
637     parent = top1->GetParent();
638     if (!parent) break;
639     top1 = parent;
640   }
641   for (;;) {
642     parent = top2->GetParent();
643     if (!parent) break;
644     top2 = parent;
645   }
646   NS_ASSERTION(top1 == top2,
647                "Style contexts are not in the same style context tree");
648 }
649 
VerifyContextParent(nsIFrame * aFrame,GeckoStyleContext * aContext,GeckoStyleContext * aParentContext)650 static void VerifyContextParent(nsIFrame* aFrame, GeckoStyleContext* aContext,
651                                 GeckoStyleContext* aParentContext) {
652   // get the contexts not provided
653   if (!aContext) {
654     aContext = aFrame->StyleContext()->AsGecko();
655   }
656 
657   if (!aParentContext) {
658     nsIFrame* providerFrame;
659     nsStyleContext* parent = aFrame->GetParentStyleContext(&providerFrame);
660     aParentContext = parent ? parent->AsGecko() : nullptr;
661     // aParentContext could still be null
662   }
663 
664   NS_ASSERTION(aContext, "Failure to get required contexts");
665   GeckoStyleContext* actualParentContext = aContext->GetParent();
666 
667   if (aParentContext) {
668     if (aParentContext != actualParentContext) {
669       DumpContext(aFrame, aContext);
670       if (aContext == aParentContext) {
671         NS_ERROR("Using parent's style context");
672       } else {
673         NS_ERROR("Wrong parent style context");
674         fputs("Wrong parent style context: ", stdout);
675         DumpContext(nullptr, actualParentContext);
676         fputs("should be using: ", stdout);
677         DumpContext(nullptr, aParentContext);
678         VerifySameTree(actualParentContext, aParentContext);
679         fputs("\n", stdout);
680       }
681     }
682 
683   } else {
684     if (actualParentContext) {
685       NS_ERROR("Have parent context and shouldn't");
686       DumpContext(aFrame, aContext);
687       fputs("Has parent context: ", stdout);
688       DumpContext(nullptr, actualParentContext);
689       fputs("Should be null\n\n", stdout);
690     }
691   }
692 
693   GeckoStyleContext* childStyleIfVisited = aContext->GetStyleIfVisited();
694   // Either childStyleIfVisited has aContext->GetParent()->GetStyleIfVisited()
695   // as the parent or it has a different rulenode from aContext _and_ has
696   // aContext->GetParent() as the parent.
697   if (childStyleIfVisited &&
698       !((childStyleIfVisited->RuleNode() != aContext->RuleNode() &&
699          childStyleIfVisited->GetParent() == aContext->GetParent()) ||
700         childStyleIfVisited->GetParent() ==
701             aContext->GetParent()->GetStyleIfVisited())) {
702     NS_ERROR("Visited style has wrong parent");
703     DumpContext(aFrame, aContext);
704     fputs("\n", stdout);
705   }
706 }
707 
VerifyStyleTree(nsIFrame * aFrame)708 static void VerifyStyleTree(nsIFrame* aFrame) {
709   GeckoStyleContext* context = aFrame->StyleContext()->AsGecko();
710   VerifyContextParent(aFrame, context, nullptr);
711 
712   nsIFrame::ChildListIterator lists(aFrame);
713   for (; !lists.IsDone(); lists.Next()) {
714     for (nsIFrame* child : lists.CurrentList()) {
715       if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
716         // only do frames that are in flow
717         if (child->IsPlaceholderFrame()) {
718           // placeholder: first recurse and verify the out of flow frame,
719           // then verify the placeholder's context
720           nsIFrame* outOfFlowFrame =
721               nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
722 
723           // recurse to out of flow frame, letting the parent context get
724           // resolved
725           do {
726             VerifyStyleTree(outOfFlowFrame);
727           } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation()));
728 
729           // verify placeholder using the parent frame's context as
730           // parent context
731           VerifyContextParent(child, nullptr, nullptr);
732         } else {  // regular frame
733           VerifyStyleTree(child);
734         }
735       }
736     }
737   }
738 
739   // do additional contexts
740   int32_t contextIndex = 0;
741   for (nsStyleContext* extraContext;
742        (extraContext = aFrame->GetAdditionalStyleContext(contextIndex));
743        ++contextIndex) {
744     VerifyContextParent(aFrame, extraContext->AsGecko(), context);
745   }
746 }
747 #endif
748 
DebugVerifyStyleTree(nsIFrame * aFrame)749 void RestyleManager::DebugVerifyStyleTree(nsIFrame* aFrame) {
750   if (IsServo()) {
751     // XXXheycam For now, we know that we don't use the same inheritance
752     // hierarchy for certain cases, so just skip these assertions until
753     // we work out what we want to assert (bug 1322570).
754     return;
755   }
756 #ifdef MOZ_OLD_STYLE
757   if (aFrame) {
758     VerifyStyleTree(aFrame);
759   }
760 #else
761   MOZ_CRASH("old style system disabled");
762 #endif
763 }
764 
765 #endif  // DEBUG
766 
767 /**
768  * Sync views on aFrame and all of aFrame's descendants (following
769  * placeholders), if aChange has nsChangeHint_SyncFrameView. Calls
770  * DoApplyRenderingChangeToTree on all aFrame's out-of-flow descendants
771  * (following placeholders), if aChange has nsChangeHint_RepaintFrame.
772  * aFrame should be some combination of nsChangeHint_SyncFrameView,
773  * nsChangeHint_RepaintFrame, nsChangeHint_UpdateOpacityLayer and
774  * nsChangeHint_SchedulePaint, nothing else.
775  */
776 static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
777                                               nsChangeHint aChange);
778 
779 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint);
780 
781 /**
782  * To handle nsChangeHint_ChildrenOnlyTransform we must iterate over the child
783  * frames of the SVG frame concerned. This helper function is used to find that
784  * SVG frame when we encounter nsChangeHint_ChildrenOnlyTransform to ensure
785  * that we iterate over the intended children, since sometimes we end up
786  * handling that hint while processing hints for one of the SVG frame's
787  * ancestor frames.
788  *
789  * The reason that we sometimes end up trying to process the hint for an
790  * ancestor of the SVG frame that the hint is intended for is due to the way we
791  * process restyle events. ApplyRenderingChangeToTree adjusts the frame from
792  * the restyled element's principle frame to one of its ancestor frames based
793  * on what nsCSSRendering::FindBackground returns, since the background style
794  * may have been propagated up to an ancestor frame. Processing hints using an
795  * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
796  * a special case since it is intended to update the children of a specific
797  * frame.
798  */
GetFrameForChildrenOnlyTransformHint(nsIFrame * aFrame)799 static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) {
800   if (aFrame->IsViewportFrame()) {
801     // This happens if the root-<svg> is fixed positioned, in which case we
802     // can't use aFrame->GetContent() to find the primary frame, since
803     // GetContent() returns nullptr for ViewportFrame.
804     aFrame = aFrame->PrincipalChildList().FirstChild();
805   }
806   // For an nsHTMLScrollFrame, this will get the SVG frame that has the
807   // children-only transforms:
808   aFrame = aFrame->GetContent()->GetPrimaryFrame();
809   if (aFrame->IsSVGOuterSVGFrame()) {
810     aFrame = aFrame->PrincipalChildList().FirstChild();
811     MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(),
812                "Where is the nsSVGOuterSVGFrame's anon child??");
813   }
814   MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer),
815              "Children-only transforms only expected on SVG frames");
816   return aFrame;
817 }
818 
819 // Returns true if this function managed to successfully move a frame, and
820 // false if it could not process the position change, and a reflow should
821 // be performed instead.
RecomputePosition(nsIFrame * aFrame)822 static bool RecomputePosition(nsIFrame* aFrame) {
823   // Don't process position changes on table frames, since we already handle
824   // the dynamic position change on the table wrapper frame, and the
825   // reflow-based fallback code path also ignores positions on inner table
826   // frames.
827   if (aFrame->IsTableFrame()) {
828     return true;
829   }
830 
831   const nsStyleDisplay* display = aFrame->StyleDisplay();
832   // Changes to the offsets of a non-positioned element can safely be ignored.
833   if (display->mPosition == NS_STYLE_POSITION_STATIC) {
834     return true;
835   }
836 
837   // Don't process position changes on frames which have views or the ones which
838   // have a view somewhere in their descendants, because the corresponding view
839   // needs to be repositioned properly as well.
840   if (aFrame->HasView() ||
841       (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
842     StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
843     return false;
844   }
845 
846   // Flexbox and Grid layout supports CSS Align and the optimizations below
847   // don't support that yet.
848   if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
849     nsIFrame* ph = aFrame->GetPlaceholderFrame();
850     if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) {
851       StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
852       return false;
853     }
854   }
855 
856   aFrame->SchedulePaint();
857 
858   // For relative positioning, we can simply update the frame rect
859   if (display->IsRelativelyPositionedStyle()) {
860     // Move the frame
861     if (display->mPosition == NS_STYLE_POSITION_STICKY) {
862       // Update sticky positioning for an entire element at once, starting with
863       // the first continuation or ib-split sibling.
864       // It's rare that the frame we already have isn't already the first
865       // continuation or ib-split sibling, but it can happen when styles differ
866       // across continuations such as ::first-line or ::first-letter, and in
867       // those cases we will generally (but maybe not always) do the work twice.
868       nsIFrame* firstContinuation =
869           nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
870 
871       StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
872       StickyScrollContainer* ssc =
873           StickyScrollContainer::GetStickyScrollContainerForFrame(
874               firstContinuation);
875       if (ssc) {
876         ssc->PositionContinuations(firstContinuation);
877       }
878     } else {
879       MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition,
880                  "Unexpected type of positioning");
881       for (nsIFrame* cont = aFrame; cont;
882            cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
883         nsIFrame* cb = cont->GetContainingBlock();
884         nsMargin newOffsets;
885         WritingMode wm = cb->GetWritingMode();
886         const LogicalSize size(wm, cb->GetContentRectRelativeToSelf().Size());
887 
888         ReflowInput::ComputeRelativeOffsets(wm, cont, size, newOffsets);
889         NS_ASSERTION(newOffsets.left == -newOffsets.right &&
890                          newOffsets.top == -newOffsets.bottom,
891                      "ComputeRelativeOffsets should return valid results");
892 
893         // ReflowInput::ApplyRelativePositioning would work here, but
894         // since we've already checked mPosition and aren't changing the frame's
895         // normal position, go ahead and add the offsets directly.
896         // First, we need to ensure that the normal position is stored though.
897         bool hasProperty;
898         nsPoint normalPosition = cont->GetNormalPosition(&hasProperty);
899         if (!hasProperty) {
900           cont->AddProperty(nsIFrame::NormalPositionProperty(),
901                             new nsPoint(normalPosition));
902         }
903         cont->SetPosition(normalPosition +
904                           nsPoint(newOffsets.left, newOffsets.top));
905       }
906     }
907 
908     return true;
909   }
910 
911   // For the absolute positioning case, set up a fake HTML reflow state for
912   // the frame, and then get the offsets and size from it. If the frame's size
913   // doesn't need to change, we can simply update the frame position. Otherwise
914   // we fall back to a reflow.
915   RefPtr<gfxContext> rc =
916       aFrame->PresShell()->CreateReferenceRenderingContext();
917 
918   // Construct a bogus parent reflow state so that there's a usable
919   // containing block reflow state.
920   nsIFrame* parentFrame = aFrame->GetParent();
921   WritingMode parentWM = parentFrame->GetWritingMode();
922   WritingMode frameWM = aFrame->GetWritingMode();
923   LogicalSize parentSize = parentFrame->GetLogicalSize();
924 
925   nsFrameState savedState = parentFrame->GetStateBits();
926   ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc,
927                                 parentSize);
928   parentFrame->RemoveStateBits(~nsFrameState(0));
929   parentFrame->AddStateBits(savedState);
930 
931   // The bogus parent state here was created with no parent state of its own,
932   // and therefore it won't have an mCBReflowInput set up.
933   // But we may need one (for InitCBReflowInput in a child state), so let's
934   // try to create one here for the cases where it will be needed.
935   Maybe<ReflowInput> cbReflowInput;
936   nsIFrame* cbFrame = parentFrame->GetContainingBlock();
937   if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
938                   parentFrame->IsTableFrame())) {
939     LogicalSize cbSize = cbFrame->GetLogicalSize();
940     cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc, cbSize);
941     cbReflowInput->ComputedPhysicalMargin() = cbFrame->GetUsedMargin();
942     cbReflowInput->ComputedPhysicalPadding() = cbFrame->GetUsedPadding();
943     cbReflowInput->ComputedPhysicalBorderPadding() =
944         cbFrame->GetUsedBorderAndPadding();
945     parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
946   }
947 
948   NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_INTRINSICSIZE &&
949                            parentSize.BSize(parentWM) != NS_INTRINSICSIZE,
950                        "parentSize should be valid");
951   parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
952   parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
953   parentReflowInput.ComputedPhysicalMargin().SizeTo(0, 0, 0, 0);
954 
955   parentReflowInput.ComputedPhysicalPadding() = parentFrame->GetUsedPadding();
956   parentReflowInput.ComputedPhysicalBorderPadding() =
957       parentFrame->GetUsedBorderAndPadding();
958   LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM);
959   availSize.BSize(frameWM) = NS_INTRINSICSIZE;
960 
961   ViewportFrame* viewport = do_QueryFrame(parentFrame);
962   nsSize cbSize =
963       viewport
964           ? viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput)
965                 .Size()
966           : aFrame->GetContainingBlock()->GetSize();
967   const nsMargin& parentBorder =
968       parentReflowInput.mStyleBorder->GetComputedBorder();
969   cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
970   LogicalSize lcbSize(frameWM, cbSize);
971   ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
972                           availSize, &lcbSize);
973   nsSize computedSize(reflowInput.ComputedWidth(),
974                       reflowInput.ComputedHeight());
975   computedSize.width += reflowInput.ComputedPhysicalBorderPadding().LeftRight();
976   if (computedSize.height != NS_INTRINSICSIZE) {
977     computedSize.height +=
978         reflowInput.ComputedPhysicalBorderPadding().TopBottom();
979   }
980   nsSize size = aFrame->GetSize();
981   // The RecomputePosition hint is not used if any offset changed between auto
982   // and non-auto. If computedSize.height == NS_INTRINSICSIZE then the new
983   // element height will be its intrinsic height, and since 'top' and 'bottom''s
984   // auto-ness hasn't changed, the old height must also be its intrinsic
985   // height, which we can assume hasn't changed (or reflow would have
986   // been triggered).
987   if (computedSize.width == size.width &&
988       (computedSize.height == NS_INTRINSICSIZE ||
989        computedSize.height == size.height)) {
990     // If we're solving for 'left' or 'top', then compute it here, in order to
991     // match the reflow code path.
992     if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().left) {
993       reflowInput.ComputedPhysicalOffsets().left =
994           cbSize.width - reflowInput.ComputedPhysicalOffsets().right -
995           reflowInput.ComputedPhysicalMargin().right - size.width -
996           reflowInput.ComputedPhysicalMargin().left;
997     }
998 
999     if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().top) {
1000       reflowInput.ComputedPhysicalOffsets().top =
1001           cbSize.height - reflowInput.ComputedPhysicalOffsets().bottom -
1002           reflowInput.ComputedPhysicalMargin().bottom - size.height -
1003           reflowInput.ComputedPhysicalMargin().top;
1004     }
1005 
1006     // Move the frame
1007     nsPoint pos(parentBorder.left + reflowInput.ComputedPhysicalOffsets().left +
1008                     reflowInput.ComputedPhysicalMargin().left,
1009                 parentBorder.top + reflowInput.ComputedPhysicalOffsets().top +
1010                     reflowInput.ComputedPhysicalMargin().top);
1011     aFrame->SetPosition(pos);
1012 
1013     return true;
1014   }
1015 
1016   // Fall back to a reflow
1017   StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
1018   return false;
1019 }
1020 
HasBoxAncestor(nsIFrame * aFrame)1021 static bool HasBoxAncestor(nsIFrame* aFrame) {
1022   for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
1023     if (f->IsXULBoxFrame()) {
1024       return true;
1025     }
1026   }
1027   return false;
1028 }
1029 
1030 /**
1031  * Return true if aFrame's subtree has placeholders for out-of-flow content
1032  * whose 'position' style's bit in aPositionMask is set.
1033  */
FrameHasPositionedPlaceholderDescendants(nsIFrame * aFrame,uint32_t aPositionMask)1034 static bool FrameHasPositionedPlaceholderDescendants(nsIFrame* aFrame,
1035                                                      uint32_t aPositionMask) {
1036   MOZ_ASSERT(aPositionMask & (1 << NS_STYLE_POSITION_FIXED));
1037 
1038   for (nsIFrame::ChildListIterator lists(aFrame); !lists.IsDone();
1039        lists.Next()) {
1040     for (nsIFrame* f : lists.CurrentList()) {
1041       if (f->IsPlaceholderFrame()) {
1042         nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
1043         // If SVG text frames could appear here, they could confuse us since
1044         // they ignore their position style ... but they can't.
1045         NS_ASSERTION(!nsSVGUtils::IsInSVGTextSubtree(outOfFlow),
1046                      "SVG text frames can't be out of flow");
1047         if (aPositionMask & (1 << outOfFlow->StyleDisplay()->mPosition)) {
1048           return true;
1049         }
1050       }
1051       uint32_t positionMask = aPositionMask;
1052       // NOTE:  It's tempting to check f->IsAbsPosContainingBlock() or
1053       // f->IsFixedPosContainingBlock() here.  However, that would only
1054       // be testing the *new* style of the frame, which might exclude
1055       // descendants that currently have this frame as an abs-pos
1056       // containing block.  Taking the codepath where we don't reframe
1057       // could lead to an unsafe call to
1058       // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed
1059       // the descendant and taken it off the absolute list.
1060       if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) {
1061         return true;
1062       }
1063     }
1064   }
1065   return false;
1066 }
1067 
NeedToReframeForAddingOrRemovingTransform(nsIFrame * aFrame)1068 static bool NeedToReframeForAddingOrRemovingTransform(nsIFrame* aFrame) {
1069   static_assert(
1070       0 <= NS_STYLE_POSITION_ABSOLUTE && NS_STYLE_POSITION_ABSOLUTE < 32,
1071       "Style constant out of range");
1072   static_assert(0 <= NS_STYLE_POSITION_FIXED && NS_STYLE_POSITION_FIXED < 32,
1073                 "Style constant out of range");
1074 
1075   uint32_t positionMask;
1076   // Don't call aFrame->IsPositioned here, since that returns true if
1077   // the frame already has a transform, and we want to ignore that here
1078   if (aFrame->IsAbsolutelyPositioned() || aFrame->IsRelativelyPositioned()) {
1079     // This frame is a container for abs-pos descendants whether or not it
1080     // has a transform.
1081     // So abs-pos descendants are no problem; we only need to reframe if
1082     // we have fixed-pos descendants.
1083     positionMask = 1 << NS_STYLE_POSITION_FIXED;
1084   } else {
1085     // This frame may not be a container for abs-pos descendants already.
1086     // So reframe if we have abs-pos or fixed-pos descendants.
1087     positionMask =
1088         (1 << NS_STYLE_POSITION_FIXED) | (1 << NS_STYLE_POSITION_ABSOLUTE);
1089   }
1090   for (nsIFrame* f = aFrame; f;
1091        f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
1092     if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) {
1093       return true;
1094     }
1095   }
1096   return false;
1097 }
1098 
DoApplyRenderingChangeToTree(nsIFrame * aFrame,nsChangeHint aChange)1099 static void DoApplyRenderingChangeToTree(nsIFrame* aFrame,
1100                                          nsChangeHint aChange) {
1101   NS_PRECONDITION(gInApplyRenderingChangeToTree,
1102                   "should only be called within ApplyRenderingChangeToTree");
1103 
1104   for (; aFrame;
1105        aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) {
1106     // Invalidate and sync views on all descendant frames, following
1107     // placeholders. We don't need to update transforms in
1108     // SyncViewsAndInvalidateDescendants, because there can't be any
1109     // out-of-flows or popups that need to be transformed; all out-of-flow
1110     // descendants of the transformed element must also be descendants of the
1111     // transformed frame.
1112     SyncViewsAndInvalidateDescendants(
1113         aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
1114                                         nsChangeHint_SyncFrameView |
1115                                         nsChangeHint_UpdateOpacityLayer |
1116                                         nsChangeHint_SchedulePaint)));
1117     // This must be set to true if the rendering change needs to
1118     // invalidate content.  If it's false, a composite-only paint
1119     // (empty transaction) will be scheduled.
1120     bool needInvalidatingPaint = false;
1121 
1122     // if frame has view, will already be invalidated
1123     if (aChange & nsChangeHint_RepaintFrame) {
1124       // Note that this whole block will be skipped when painting is suppressed
1125       // (due to our caller ApplyRendingChangeToTree() discarding the
1126       // nsChangeHint_RepaintFrame hint).  If you add handling for any other
1127       // hints within this block, be sure that they too should be ignored when
1128       // painting is suppressed.
1129       needInvalidatingPaint = true;
1130       aFrame->InvalidateFrameSubtree();
1131       if ((aChange & nsChangeHint_UpdateEffects) &&
1132           aFrame->IsFrameOfType(nsIFrame::eSVG) &&
1133           !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
1134         // Need to update our overflow rects:
1135         nsSVGUtils::ScheduleReflowSVG(aFrame);
1136       }
1137     }
1138     if (aChange & nsChangeHint_UpdateTextPath) {
1139       if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1140         // Invalidate and reflow the entire SVGTextFrame:
1141         NS_ASSERTION(aFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath),
1142                      "expected frame for a <textPath> element");
1143         nsIFrame* text = nsLayoutUtils::GetClosestFrameOfType(
1144             aFrame, LayoutFrameType::SVGText);
1145         NS_ASSERTION(text, "expected to find an ancestor SVGTextFrame");
1146         static_cast<SVGTextFrame*>(text)->NotifyGlyphMetricsChange();
1147       } else {
1148         MOZ_ASSERT(false, "unexpected frame got nsChangeHint_UpdateTextPath");
1149       }
1150     }
1151     if (aChange & nsChangeHint_UpdateOpacityLayer) {
1152       // FIXME/bug 796697: we can get away with empty transactions for
1153       // opacity updates in many cases.
1154       needInvalidatingPaint = true;
1155 
1156       ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
1157       if (nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
1158         // SVG effects paints the opacity without using
1159         // nsDisplayOpacity. We need to invalidate manually.
1160         aFrame->InvalidateFrameSubtree();
1161       }
1162     }
1163     if ((aChange & nsChangeHint_UpdateTransformLayer) &&
1164         aFrame->IsTransformed()) {
1165       ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
1166       // If we're not already going to do an invalidating paint, see
1167       // if we can get away with only updating the transform on a
1168       // layer for this frame, and not scheduling an invalidating
1169       // paint.
1170       if (!needInvalidatingPaint) {
1171         nsDisplayItem::Layer* layer;
1172         needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly(&layer);
1173 
1174         if (!needInvalidatingPaint) {
1175           // Since we're not going to paint, we need to resend animation
1176           // data to the layer.
1177           MOZ_ASSERT(layer, "this can't happen if there's no layer");
1178           nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(
1179               layer, nullptr, nullptr, aFrame, eCSSProperty_transform);
1180         }
1181       }
1182     }
1183     if (aChange & nsChangeHint_ChildrenOnlyTransform) {
1184       needInvalidatingPaint = true;
1185       nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame)
1186                                  ->PrincipalChildList()
1187                                  .FirstChild();
1188       for (; childFrame; childFrame = childFrame->GetNextSibling()) {
1189         ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
1190       }
1191     }
1192     if (aChange & nsChangeHint_SchedulePaint) {
1193       needInvalidatingPaint = true;
1194     }
1195     aFrame->SchedulePaint(needInvalidatingPaint
1196                               ? nsIFrame::PAINT_DEFAULT
1197                               : nsIFrame::PAINT_COMPOSITE_ONLY);
1198   }
1199 }
1200 
SyncViewsAndInvalidateDescendants(nsIFrame * aFrame,nsChangeHint aChange)1201 static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
1202                                               nsChangeHint aChange) {
1203   NS_PRECONDITION(gInApplyRenderingChangeToTree,
1204                   "should only be called within ApplyRenderingChangeToTree");
1205   NS_ASSERTION(
1206       nsChangeHint_size_t(aChange) ==
1207           (aChange &
1208            (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView |
1209             nsChangeHint_UpdateOpacityLayer | nsChangeHint_SchedulePaint)),
1210       "Invalid change flag");
1211 
1212   if (aChange & nsChangeHint_SyncFrameView) {
1213     aFrame->SyncFrameViewProperties();
1214   }
1215 
1216   nsIFrame::ChildListIterator lists(aFrame);
1217   for (; !lists.IsDone(); lists.Next()) {
1218     for (nsIFrame* child : lists.CurrentList()) {
1219       if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
1220         // only do frames that don't have placeholders
1221         if (child->IsPlaceholderFrame()) {
1222           // do the out-of-flow frame and its continuations
1223           nsIFrame* outOfFlowFrame =
1224               nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
1225           DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
1226         } else if (lists.CurrentID() == nsIFrame::kPopupList) {
1227           DoApplyRenderingChangeToTree(child, aChange);
1228         } else {  // regular frame
1229           SyncViewsAndInvalidateDescendants(child, aChange);
1230         }
1231       }
1232     }
1233   }
1234 }
1235 
ApplyRenderingChangeToTree(nsIPresShell * aPresShell,nsIFrame * aFrame,nsChangeHint aChange)1236 static void ApplyRenderingChangeToTree(nsIPresShell* aPresShell,
1237                                        nsIFrame* aFrame, nsChangeHint aChange) {
1238   // We check StyleDisplay()->HasTransformStyle() in addition to checking
1239   // IsTransformed() since we can get here for some frames that don't support
1240   // CSS transforms.
1241   NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
1242                    aFrame->IsTransformed() ||
1243                    aFrame->StyleDisplay()->HasTransformStyle(),
1244                "Unexpected UpdateTransformLayer hint");
1245 
1246   if (aPresShell->IsPaintingSuppressed()) {
1247     // Don't allow synchronous rendering changes when painting is turned off.
1248     aChange &= ~nsChangeHint_RepaintFrame;
1249     if (!aChange) {
1250       return;
1251     }
1252   }
1253 
1254 // Trigger rendering updates by damaging this frame and any
1255 // continuations of this frame.
1256 #ifdef DEBUG
1257   gInApplyRenderingChangeToTree = true;
1258 #endif
1259   if (aChange & nsChangeHint_RepaintFrame) {
1260     // If the frame's background is propagated to an ancestor, walk up to
1261     // that ancestor and apply the RepaintFrame change hint to it.
1262     nsStyleContext* bgSC;
1263     nsIFrame* propagatedFrame = aFrame;
1264     while (!nsCSSRendering::FindBackground(propagatedFrame, &bgSC)) {
1265       propagatedFrame = propagatedFrame->GetParent();
1266       NS_ASSERTION(aFrame, "root frame must paint");
1267     }
1268 
1269     if (propagatedFrame != aFrame) {
1270       DoApplyRenderingChangeToTree(propagatedFrame, nsChangeHint_RepaintFrame);
1271       aChange &= ~nsChangeHint_RepaintFrame;
1272       if (!aChange) {
1273         return;
1274       }
1275     }
1276   }
1277   DoApplyRenderingChangeToTree(aFrame, aChange);
1278 #ifdef DEBUG
1279   gInApplyRenderingChangeToTree = false;
1280 #endif
1281 }
1282 
AddSubtreeToOverflowTracker(nsIFrame * aFrame,OverflowChangedTracker & aOverflowChangedTracker)1283 static void AddSubtreeToOverflowTracker(
1284     nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) {
1285   if (aFrame->FrameMaintainsOverflow()) {
1286     aOverflowChangedTracker.AddFrame(aFrame,
1287                                      OverflowChangedTracker::CHILDREN_CHANGED);
1288   }
1289   nsIFrame::ChildListIterator lists(aFrame);
1290   for (; !lists.IsDone(); lists.Next()) {
1291     for (nsIFrame* child : lists.CurrentList()) {
1292       AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
1293     }
1294   }
1295 }
1296 
StyleChangeReflow(nsIFrame * aFrame,nsChangeHint aHint)1297 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) {
1298   nsIPresShell::IntrinsicDirty dirtyType;
1299   if (aHint & nsChangeHint_ClearDescendantIntrinsics) {
1300     NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics,
1301                  "Please read the comments in nsChangeHint.h");
1302     NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow,
1303                  "ClearDescendantIntrinsics requires NeedDirtyReflow");
1304     dirtyType = nsIPresShell::eStyleChange;
1305   } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
1306              aFrame->HasAnyStateBits(
1307                  NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
1308     dirtyType = nsIPresShell::eStyleChange;
1309   } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) {
1310     dirtyType = nsIPresShell::eTreeChange;
1311   } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
1312              HasBoxAncestor(aFrame)) {
1313     // The frame's computed BSize is changing, and we have a box ancestor
1314     // whose cached intrinsic height may need to be updated.
1315     dirtyType = nsIPresShell::eTreeChange;
1316   } else {
1317     dirtyType = nsIPresShell::eResize;
1318   }
1319 
1320   nsFrameState dirtyBits;
1321   if (aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) {
1322     dirtyBits = nsFrameState(0);
1323   } else if ((aHint & nsChangeHint_NeedDirtyReflow) ||
1324              dirtyType == nsIPresShell::eStyleChange) {
1325     dirtyBits = NS_FRAME_IS_DIRTY;
1326   } else {
1327     dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN;
1328   }
1329 
1330   // If we're not going to clear any intrinsic sizes on the frames, and
1331   // there are no dirty bits to set, then there's nothing to do.
1332   if (dirtyType == nsIPresShell::eResize && !dirtyBits) return;
1333 
1334   nsIPresShell::ReflowRootHandling rootHandling;
1335   if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
1336     rootHandling = nsIPresShell::ePositionOrSizeChange;
1337   } else {
1338     rootHandling = nsIPresShell::eNoPositionOrSizeChange;
1339   }
1340 
1341   do {
1342     aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
1343                                           rootHandling);
1344     aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
1345   } while (aFrame);
1346 }
1347 
1348 // Get the next sibling which might have a frame.  This only considers siblings
1349 // that stylo post-traversal looks at, so only elements and text.  In
1350 // particular, it ignores comments.
NextSiblingWhichMayHaveFrame(nsIContent * aContent)1351 static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) {
1352   for (nsIContent* next = aContent->GetNextSibling(); next;
1353        next = next->GetNextSibling()) {
1354     if (next->IsElement() || next->IsNodeOfType(nsINode::eTEXT)) {
1355       return next;
1356     }
1357   }
1358 
1359   return nullptr;
1360 }
1361 
ProcessRestyledFrames(nsStyleChangeList & aChangeList)1362 void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
1363   NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1364                "Someone forgot a script blocker");
1365 
1366   // See bug 1378219 comment 9:
1367   // Recursive calls here are a bit worrying, but apparently do happen in the
1368   // wild (although not currently in any of our automated tests). Try to get a
1369   // stack from Nightly/Dev channel to figure out what's going on and whether
1370   // it's OK.
1371   MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion");
1372 
1373   if (aChangeList.IsEmpty()) {
1374     return;
1375   }
1376 
1377   // If mDestroyedFrames is null, we want to create a new hashtable here
1378   // and destroy it on exit; but if it is already non-null (because we're in
1379   // a recursive call), we will continue to use the existing table to
1380   // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit.
1381   // We use a MaybeClearDestroyedFrames helper to conditionally reset the
1382   // mDestroyedFrames pointer when this method returns.
1383   typedef decltype(mDestroyedFrames) DestroyedFramesT;
1384   class MOZ_RAII MaybeClearDestroyedFrames {
1385    private:
1386     DestroyedFramesT& mDestroyedFramesRef;  // ref to caller's mDestroyedFrames
1387     const bool mResetOnDestruction;
1388 
1389    public:
1390     explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget)
1391         : mDestroyedFramesRef(aTarget),
1392           mResetOnDestruction(!aTarget)  // reset only if target starts out null
1393     {}
1394     ~MaybeClearDestroyedFrames() {
1395       if (mResetOnDestruction) {
1396         mDestroyedFramesRef.reset(nullptr);
1397       }
1398     }
1399   };
1400 
1401   MaybeClearDestroyedFrames maybeClear(mDestroyedFrames);
1402   if (!mDestroyedFrames) {
1403     mDestroyedFrames = MakeUnique<nsTHashtable<nsPtrHashKey<const nsIFrame>>>();
1404   }
1405 
1406   AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", CSS);
1407 
1408   nsPresContext* presContext = PresContext();
1409   nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
1410 
1411   // Handle nsChangeHint_CSSOverflowChange, by either updating the
1412   // scrollbars on the viewport, or upgrading the change hint to
1413   // frame-reconstruct.
1414   for (nsStyleChangeData& data : aChangeList) {
1415     if (data.mHint & nsChangeHint_CSSOverflowChange) {
1416       data.mHint &= ~nsChangeHint_CSSOverflowChange;
1417       bool doReconstruct = true;  // assume the worst
1418 
1419       // Only bother with this if we're html/body, since:
1420       //  (a) It'd be *expensive* to reframe these particular nodes.  They're
1421       //      at the root, so reframing would mean rebuilding the world.
1422       //  (b) It's often *unnecessary* to reframe for "overflow" changes on
1423       //      these particular nodes.  In general, the only reason we reframe
1424       //      for "overflow" changes is so we can construct (or destroy) a
1425       //      scrollframe & scrollbars -- and the html/body nodes often don't
1426       //      need their own scrollframe/scrollbars because they coopt the ones
1427       //      on the viewport (which always exist). So depending on whether
1428       //      that's happening, we can skip the reframe for these nodes.
1429       if (data.mContent->IsAnyOfHTMLElements(nsGkAtoms::body,
1430                                              nsGkAtoms::html)) {
1431         // If the restyled element provided/provides the scrollbar styles for
1432         // the viewport before and/or after this restyle, AND it's not coopting
1433         // that responsibility from some other element (which would need
1434         // reconstruction to make its own scrollframe now), THEN: we don't need
1435         // to reconstruct - we can just reflow, because no scrollframe is being
1436         // added/removed.
1437         nsIContent* prevOverrideNode =
1438             presContext->GetViewportScrollbarStylesOverrideElement();
1439         nsIContent* newOverrideNode =
1440             presContext->UpdateViewportScrollbarStylesOverride();
1441 
1442         if (data.mContent == prevOverrideNode ||
1443             data.mContent == newOverrideNode) {
1444           // If we get here, the restyled element provided the scrollbar styles
1445           // for viewport before this restyle, OR it will provide them after.
1446           if (!prevOverrideNode || !newOverrideNode ||
1447               prevOverrideNode == newOverrideNode) {
1448             // If we get here, the restyled element is NOT replacing (or being
1449             // replaced by) some other element as the viewport's
1450             // scrollbar-styles provider. (If it were, we'd potentially need to
1451             // reframe to create a dedicated scrollframe for whichever element
1452             // is being booted from providing viewport scrollbar styles.)
1453             //
1454             // Under these conditions, we're OK to assume that this "overflow"
1455             // change only impacts the root viewport's scrollframe, which
1456             // already exists, so we can simply reflow instead of reframing.
1457             // When requesting this reflow, we send the exact same change hints
1458             // that "width" and "height" would send (since conceptually,
1459             // adding/removing scrollbars is like changing the available
1460             // space).
1461             data.mHint |= (nsChangeHint_ReflowHintsForISizeChange |
1462                            nsChangeHint_ReflowHintsForBSizeChange);
1463             doReconstruct = false;
1464           }
1465         }
1466       }
1467       if (doReconstruct) {
1468         data.mHint |= nsChangeHint_ReconstructFrame;
1469       }
1470     }
1471   }
1472 
1473   bool didUpdateCursor = false;
1474 
1475   for (size_t i = 0; i < aChangeList.Length(); ++i) {
1476     // Collect and coalesce adjacent siblings for lazy frame construction.
1477     // Eventually it would be even better to make RecreateFramesForContent
1478     // accept a range and coalesce all adjacent reconstructs (bug 1344139).
1479     size_t lazyRangeStart = i;
1480     while (i < aChangeList.Length() && aChangeList[i].mContent &&
1481            aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) &&
1482            (i == lazyRangeStart ||
1483             NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) ==
1484                 aChangeList[i].mContent)) {
1485       MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame);
1486       MOZ_ASSERT(!aChangeList[i].mFrame);
1487       ++i;
1488     }
1489     if (i != lazyRangeStart) {
1490       nsIContent* start = aChangeList[lazyRangeStart].mContent;
1491       nsIContent* end =
1492           NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent);
1493       nsIContent* container = start->GetParent();
1494       MOZ_ASSERT(container);
1495       if (!end) {
1496         frameConstructor->ContentAppended(
1497             container, start, nsCSSFrameConstructor::InsertionKind::Sync);
1498       } else {
1499         frameConstructor->ContentRangeInserted(
1500             container, start, end, nullptr,
1501             nsCSSFrameConstructor::InsertionKind::Sync);
1502       }
1503     }
1504     for (size_t j = lazyRangeStart; j < i; ++j) {
1505       MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() ||
1506                  !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME));
1507     }
1508     if (i == aChangeList.Length()) {
1509       break;
1510     }
1511 
1512     const nsStyleChangeData& data = aChangeList[i];
1513     nsIFrame* frame = data.mFrame;
1514     nsIContent* content = data.mContent;
1515     nsChangeHint hint = data.mHint;
1516     bool didReflowThisFrame = false;
1517 
1518     NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
1519                      (hint & nsChangeHint_NeedReflow),
1520                  "Reflow hint bits set without actually asking for a reflow");
1521 
1522     // skip any frame that has been destroyed due to a ripple effect
1523     if (frame && mDestroyedFrames->Contains(frame)) {
1524       continue;
1525     }
1526 
1527     if (frame && frame->GetContent() != content) {
1528       // XXXbz this is due to image maps messing with the primary frame of
1529       // <area>s.  See bug 135040.  Remove this block once that's fixed.
1530       frame = nullptr;
1531       if (!(hint & nsChangeHint_ReconstructFrame)) {
1532         continue;
1533       }
1534     }
1535 
1536     if ((hint & nsChangeHint_UpdateContainingBlock) && frame &&
1537         !(hint & nsChangeHint_ReconstructFrame)) {
1538       if (NeedToReframeForAddingOrRemovingTransform(frame) ||
1539           frame->IsFieldSetFrame() ||
1540           frame->GetContentInsertionFrame() != frame) {
1541         // The frame has positioned children that need to be reparented, or
1542         // it can't easily be converted to/from being an abs-pos container
1543         // correctly.
1544         hint |= nsChangeHint_ReconstructFrame;
1545       } else {
1546         for (nsIFrame* cont = frame; cont;
1547              cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1548           // Normally frame construction would set state bits as needed,
1549           // but we're not going to reconstruct the frame so we need to set
1550           // them. It's because we need to set this state on each affected frame
1551           // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
1552           // to ancestors (i.e. it can't be an change hint that is handled for
1553           // descendants).
1554           if (cont->IsAbsPosContainingBlock()) {
1555             if (!cont->IsAbsoluteContainer() &&
1556                 (cont->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
1557               cont->MarkAsAbsoluteContainingBlock();
1558             }
1559           } else {
1560             if (cont->IsAbsoluteContainer()) {
1561               if (cont->HasAbsolutelyPositionedChildren()) {
1562                 // If |cont| still has absolutely positioned children,
1563                 // we can't call MarkAsNotAbsoluteContainingBlock.  This
1564                 // will remove a frame list that still has children in
1565                 // it that we need to keep track of.
1566                 // The optimization of removing it isn't particularly
1567                 // important, although it does mean we skip some tests.
1568                 NS_WARNING("skipping removal of absolute containing block");
1569               } else {
1570                 cont->MarkAsNotAbsoluteContainingBlock();
1571               }
1572             }
1573           }
1574         }
1575       }
1576     }
1577 
1578     if ((hint & nsChangeHint_AddOrRemoveTransform) && frame &&
1579         !(hint & nsChangeHint_ReconstructFrame)) {
1580       for (nsIFrame* cont = frame; cont;
1581            cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1582         if (cont->StyleDisplay()->HasTransform(cont)) {
1583           cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
1584         }
1585         // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be
1586         // transformed by other means. It's OK to have the bit even if it's
1587         // not needed.
1588       }
1589     }
1590 
1591     if (hint & nsChangeHint_ReconstructFrame) {
1592       // If we ever start passing true here, be careful of restyles
1593       // that involve a reframe and animations.  In particular, if the
1594       // restyle we're processing here is an animation restyle, but
1595       // the style resolution we will do for the frame construction
1596       // happens async when we're not in an animation restyle already,
1597       // problems could arise.
1598       // We could also have problems with triggering of CSS transitions
1599       // on elements whose frames are reconstructed, since we depend on
1600       // the reconstruction happening synchronously.
1601       frameConstructor->RecreateFramesForContent(
1602           content, nsCSSFrameConstructor::InsertionKind::Sync);
1603     } else {
1604       NS_ASSERTION(frame, "This shouldn't happen");
1605 
1606       if (!frame->FrameMaintainsOverflow()) {
1607         // frame does not maintain overflow rects, so avoid calling
1608         // FinishAndStoreOverflow on it:
1609         hint &=
1610             ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform |
1611               nsChangeHint_UpdatePostTransformOverflow |
1612               nsChangeHint_UpdateParentOverflow);
1613       }
1614 
1615       if (!(frame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) {
1616         // Frame can not be transformed, and thus a change in transform will
1617         // have no effect and we should not use the
1618         // nsChangeHint_UpdatePostTransformOverflow hint.
1619         hint &= ~nsChangeHint_UpdatePostTransformOverflow;
1620       }
1621 
1622       if (hint & nsChangeHint_AddOrRemoveTransform) {
1623         // When dropping a running transform animation we will first add an
1624         // nsChangeHint_UpdateTransformLayer hint as part of the animation-only
1625         // restyle. During the subsequent regular restyle, if the animation was
1626         // the only reason the element had any transform applied, we will add
1627         // nsChangeHint_AddOrRemoveTransform as part of the regular restyle.
1628         //
1629         // With the Gecko backend, these two change hints are processed
1630         // after each restyle but when using the Servo backend they accumulate
1631         // and are processed together after we have already removed the
1632         // transform as part of the regular restyle. Since we don't actually
1633         // need the nsChangeHint_UpdateTransformLayer hint if we already have
1634         // a nsChangeHint_AddOrRemoveTransform hint, and since we
1635         // will fail an assertion in ApplyRenderingChangeToTree if we try
1636         // specify nsChangeHint_UpdateTransformLayer but don't have any
1637         // transform style, we just drop the unneeded hint here.
1638         hint &= ~nsChangeHint_UpdateTransformLayer;
1639       }
1640 
1641       if (hint & nsChangeHint_UpdateEffects) {
1642         for (nsIFrame* cont = frame; cont;
1643              cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1644           SVGObserverUtils::UpdateEffects(cont);
1645         }
1646       }
1647       if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
1648           ((hint & nsChangeHint_UpdateOpacityLayer) &&
1649            frame->IsFrameOfType(nsIFrame::eSVG) &&
1650            !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) {
1651         SVGObserverUtils::InvalidateRenderingObservers(frame);
1652         frame->SchedulePaint();
1653       }
1654       if (hint & nsChangeHint_NeedReflow) {
1655         StyleChangeReflow(frame, hint);
1656         didReflowThisFrame = true;
1657       }
1658 
1659       // Here we need to propagate repaint frame change hint instead of update
1660       // opacity layer change hint when we do opacity optimization for SVG.
1661       // We can't do it in nsStyleEffects::CalcDifference() just like we do
1662       // for the optimization for 0.99 over opacity values since we have no way
1663       // to call nsSVGUtils::CanOptimizeOpacity() there.
1664       if ((hint & nsChangeHint_UpdateOpacityLayer) &&
1665           nsSVGUtils::CanOptimizeOpacity(frame) &&
1666           frame->IsFrameOfType(nsIFrame::eSVGGeometry)) {
1667         hint &= ~nsChangeHint_UpdateOpacityLayer;
1668         hint |= nsChangeHint_RepaintFrame;
1669       }
1670 
1671       if ((hint & nsChangeHint_UpdateUsesOpacity) &&
1672           frame->IsFrameOfType(nsIFrame::eTablePart)) {
1673         NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
1674                      "should only return UpdateUsesOpacity hint "
1675                      "when also returning UpdateOpacityLayer hint");
1676         // When an internal table part (including cells) changes between
1677         // having opacity 1 and non-1, it changes whether its
1678         // backgrounds (and those of table parts inside of it) are
1679         // painted as part of the table's nsDisplayTableBorderBackground
1680         // display item, or part of its own display item.  That requires
1681         // invalidation, so change UpdateOpacityLayer to RepaintFrame.
1682         hint &= ~nsChangeHint_UpdateOpacityLayer;
1683         hint |= nsChangeHint_RepaintFrame;
1684       }
1685 
1686       // Opacity disables preserve-3d, so if we toggle it, then we also need
1687       // to update the overflow areas of all potentially affected frames.
1688       if ((hint & nsChangeHint_UpdateUsesOpacity) &&
1689           frame->StyleDisplay()->mTransformStyle ==
1690               NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
1691         hint |= nsChangeHint_UpdateSubtreeOverflow;
1692       }
1693 
1694       if (hint & nsChangeHint_UpdateBackgroundPosition) {
1695         // For most frame types, DLBI can detect background position changes,
1696         // so we only need to schedule a paint.
1697         hint |= nsChangeHint_SchedulePaint;
1698         if (frame->IsFrameOfType(nsIFrame::eTablePart) ||
1699             frame->IsFrameOfType(nsIFrame::eMathML)) {
1700           // Table parts and MathML frames don't build display items for their
1701           // backgrounds, so DLBI can't detect background-position changes for
1702           // these frames. Repaint the whole frame.
1703           hint |= nsChangeHint_RepaintFrame;
1704         }
1705       }
1706 
1707       if (hint &
1708           (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView |
1709            nsChangeHint_UpdateOpacityLayer | nsChangeHint_UpdateTransformLayer |
1710            nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
1711         ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint);
1712       }
1713       if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
1714         ActiveLayerTracker::NotifyOffsetRestyle(frame);
1715         // It is possible for this to fall back to a reflow
1716         if (!RecomputePosition(frame)) {
1717           didReflowThisFrame = true;
1718         }
1719       }
1720       NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) ||
1721                        (hint & nsChangeHint_UpdateOverflow),
1722                    "nsChangeHint_UpdateOverflow should be passed too");
1723       if (!didReflowThisFrame &&
1724           (hint & (nsChangeHint_UpdateOverflow |
1725                    nsChangeHint_UpdatePostTransformOverflow |
1726                    nsChangeHint_UpdateParentOverflow |
1727                    nsChangeHint_UpdateSubtreeOverflow))) {
1728         if (hint & nsChangeHint_UpdateSubtreeOverflow) {
1729           for (nsIFrame* cont = frame; cont;
1730                cont =
1731                    nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1732             AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker);
1733           }
1734           // The work we just did in AddSubtreeToOverflowTracker
1735           // subsumes some of the other hints:
1736           hint &= ~(nsChangeHint_UpdateOverflow |
1737                     nsChangeHint_UpdatePostTransformOverflow);
1738         }
1739         if (hint & nsChangeHint_ChildrenOnlyTransform) {
1740           // The overflow areas of the child frames need to be updated:
1741           nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame);
1742           nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild();
1743           NS_ASSERTION(
1744               !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame),
1745               "SVG frames should not have continuations "
1746               "or ib-split siblings");
1747           NS_ASSERTION(
1748               !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame),
1749               "SVG frames should not have continuations "
1750               "or ib-split siblings");
1751           for (; childFrame; childFrame = childFrame->GetNextSibling()) {
1752             MOZ_ASSERT(childFrame->IsFrameOfType(nsIFrame::eSVG),
1753                        "Not expecting non-SVG children");
1754             // If |childFrame| is dirty or has dirty children, we don't bother
1755             // updating overflows since that will happen when it's reflowed.
1756             if (!(childFrame->GetStateBits() &
1757                   (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) {
1758               mOverflowChangedTracker.AddFrame(
1759                   childFrame, OverflowChangedTracker::CHILDREN_CHANGED);
1760             }
1761             NS_ASSERTION(
1762                 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame),
1763                 "SVG frames should not have continuations "
1764                 "or ib-split siblings");
1765             NS_ASSERTION(
1766                 childFrame->GetParent() == hintFrame,
1767                 "SVG child frame not expected to have different parent");
1768           }
1769         }
1770         // If |frame| is dirty or has dirty children, we don't bother updating
1771         // overflows since that will happen when it's reflowed.
1772         if (!(frame->GetStateBits() &
1773               (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) {
1774           if (hint & (nsChangeHint_UpdateOverflow |
1775                       nsChangeHint_UpdatePostTransformOverflow)) {
1776             OverflowChangedTracker::ChangeKind changeKind;
1777             // If we have both nsChangeHint_UpdateOverflow and
1778             // nsChangeHint_UpdatePostTransformOverflow,
1779             // CHILDREN_CHANGED is selected as it is
1780             // strictly stronger.
1781             if (hint & nsChangeHint_UpdateOverflow) {
1782               changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
1783             } else {
1784               changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
1785             }
1786             for (nsIFrame* cont = frame; cont;
1787                  cont =
1788                      nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1789               mOverflowChangedTracker.AddFrame(cont, changeKind);
1790             }
1791           }
1792           // UpdateParentOverflow hints need to be processed in addition
1793           // to the above, since if the processing of the above hints
1794           // yields no change, the update will not propagate to the
1795           // parent.
1796           if (hint & nsChangeHint_UpdateParentOverflow) {
1797             MOZ_ASSERT(frame->GetParent(),
1798                        "shouldn't get style hints for the root frame");
1799             for (nsIFrame* cont = frame; cont;
1800                  cont =
1801                      nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1802               mOverflowChangedTracker.AddFrame(
1803                   cont->GetParent(), OverflowChangedTracker::CHILDREN_CHANGED);
1804             }
1805           }
1806         }
1807       }
1808       if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
1809         presContext->PresShell()->SynthesizeMouseMove(false);
1810         didUpdateCursor = true;
1811       }
1812       if (hint & nsChangeHint_UpdateWidgetProperties) {
1813         frame->UpdateWidgetProperties();
1814       }
1815       if (hint & nsChangeHint_UpdateTableCellSpans) {
1816         frameConstructor->UpdateTableCellSpans(content);
1817       }
1818       if (hint & nsChangeHint_VisibilityChange) {
1819         frame->UpdateVisibleDescendantsState();
1820       }
1821     }
1822   }
1823 
1824 #ifdef DEBUG
1825   // Verify the style tree.  Note that this needs to happen once we've
1826   // processed the whole list, since until then the tree is not in fact in a
1827   // consistent state.
1828   for (const nsStyleChangeData& data : aChangeList) {
1829     // reget frame from content since it may have been regenerated...
1830     if (data.mContent) {
1831       nsIFrame* frame = data.mContent->GetPrimaryFrame();
1832       if (frame) {
1833         DebugVerifyStyleTree(frame);
1834       }
1835     } else if (!data.mFrame || !data.mFrame->IsViewportFrame()) {
1836       NS_WARNING(
1837           "Unable to test style tree integrity -- no content node "
1838           "(and not a viewport frame)");
1839     }
1840   }
1841 #endif
1842 
1843   aChangeList.Clear();
1844 }
1845 
GetAnimationGenerationForFrame(nsIFrame * aFrame)1846 /* static */ uint64_t RestyleManager::GetAnimationGenerationForFrame(
1847     nsIFrame* aFrame) {
1848   EffectSet* effectSet = EffectSet::GetEffectSet(aFrame);
1849   return effectSet ? effectSet->GetAnimationGeneration() : 0;
1850 }
1851 
IncrementAnimationGeneration()1852 void RestyleManager::IncrementAnimationGeneration() {
1853   // We update the animation generation at start of each call to
1854   // ProcessPendingRestyles so we should ignore any subsequent (redundant)
1855   // calls that occur while we are still processing restyles.
1856   if (IsGecko()) {
1857 #ifdef MOZ_OLD_STYLE
1858     if (AsGecko()->IsProcessingRestyles()) {
1859       return;
1860     }
1861 #else
1862     MOZ_CRASH("old style system disabled");
1863 #endif
1864   } else {
1865     if (mInStyleRefresh) {
1866       return;
1867     }
1868   }
1869   ++mAnimationGeneration;
1870 }
1871 
AddLayerChangesForAnimation(nsIFrame * aFrame,nsIContent * aContent,nsStyleChangeList & aChangeListToProcess)1872 /* static */ void RestyleManager::AddLayerChangesForAnimation(
1873     nsIFrame* aFrame, nsIContent* aContent,
1874     nsStyleChangeList& aChangeListToProcess) {
1875   if (!aFrame || !aContent) {
1876     return;
1877   }
1878 
1879   uint64_t frameGeneration =
1880       RestyleManager::GetAnimationGenerationForFrame(aFrame);
1881 
1882   nsChangeHint hint = nsChangeHint(0);
1883   for (const LayerAnimationInfo::Record& layerInfo :
1884        LayerAnimationInfo::sRecords) {
1885     layers::Layer* layer =
1886         FrameLayerBuilder::GetDedicatedLayer(aFrame, layerInfo.mLayerType);
1887     if (layer && frameGeneration != layer->GetAnimationGeneration()) {
1888       // If we have a transform layer but don't have any transform style, we
1889       // probably just removed the transform but haven't destroyed the layer
1890       // yet. In this case we will add the appropriate change hint
1891       // (nsChangeHint_UpdateContainingBlock) when we compare style contexts
1892       // so we can skip adding any change hint here. (If we *were* to add
1893       // nsChangeHint_UpdateTransformLayer, ApplyRenderingChangeToTree would
1894       // complain that we're updating a transform layer without a transform).
1895       if (layerInfo.mLayerType == DisplayItemType::TYPE_TRANSFORM &&
1896           !aFrame->StyleDisplay()->HasTransformStyle()) {
1897         continue;
1898       }
1899       hint |= layerInfo.mChangeHint;
1900     }
1901 
1902     // We consider it's the first paint for the frame if we have an animation
1903     // for the property but have no layer.
1904     // Note that in case of animations which has properties preventing running
1905     // on the compositor, e.g., width or height, corresponding layer is not
1906     // created at all, but even in such cases, we normally set valid change
1907     // hint for such animations in each tick, i.e. restyles in each tick. As
1908     // a result, we usually do restyles for such animations in every tick on
1909     // the main-thread.  The only animations which will be affected by this
1910     // explicit change hint are animations that have opacity/transform but did
1911     // not have those properies just before. e.g, setting transform by
1912     // setKeyframes or changing target element from other target which prevents
1913     // running on the compositor, etc.
1914     if (!layer &&
1915         nsLayoutUtils::HasEffectiveAnimation(aFrame, layerInfo.mProperty)) {
1916       hint |= layerInfo.mChangeHint;
1917     }
1918   }
1919 
1920   if (hint) {
1921     aChangeListToProcess.AppendChange(aFrame, aContent, hint);
1922   }
1923 }
1924 
AnimationsWithDestroyedFrame(RestyleManager * aRestyleManager)1925 RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
1926     RestyleManager* aRestyleManager)
1927     : mRestyleManager(aRestyleManager),
1928       mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) {
1929   MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
1930              "shouldn't construct recursively");
1931   mRestyleManager->mAnimationsWithDestroyedFrame = this;
1932 }
1933 
1934 void RestyleManager::AnimationsWithDestroyedFrame ::
StopAnimationsForElementsWithoutFrames()1935     StopAnimationsForElementsWithoutFrames() {
1936   StopAnimationsWithoutFrame(mContents, CSSPseudoElementType::NotPseudo);
1937   StopAnimationsWithoutFrame(mBeforeContents, CSSPseudoElementType::before);
1938   StopAnimationsWithoutFrame(mAfterContents, CSSPseudoElementType::after);
1939 }
1940 
StopAnimationsWithoutFrame(nsTArray<RefPtr<nsIContent>> & aArray,CSSPseudoElementType aPseudoType)1941 void RestyleManager::AnimationsWithDestroyedFrame ::StopAnimationsWithoutFrame(
1942     nsTArray<RefPtr<nsIContent>>& aArray, CSSPseudoElementType aPseudoType) {
1943   nsAnimationManager* animationManager =
1944       mRestyleManager->PresContext()->AnimationManager();
1945   nsTransitionManager* transitionManager =
1946       mRestyleManager->PresContext()->TransitionManager();
1947   for (nsIContent* content : aArray) {
1948     if (aPseudoType == CSSPseudoElementType::NotPseudo) {
1949       if (content->GetPrimaryFrame()) {
1950         continue;
1951       }
1952     } else if (aPseudoType == CSSPseudoElementType::before) {
1953       if (nsLayoutUtils::GetBeforeFrame(content)) {
1954         continue;
1955       }
1956     } else if (aPseudoType == CSSPseudoElementType::after) {
1957       if (nsLayoutUtils::GetAfterFrame(content)) {
1958         continue;
1959       }
1960     }
1961     dom::Element* element = content->AsElement();
1962 
1963     animationManager->StopAnimationsForElement(element, aPseudoType);
1964     transitionManager->StopAnimationsForElement(element, aPseudoType);
1965 
1966     // All other animations should keep running but not running on the
1967     // *compositor* at this point.
1968     EffectSet* effectSet = EffectSet::GetEffectSet(element, aPseudoType);
1969     if (effectSet) {
1970       for (KeyframeEffectReadOnly* effect : *effectSet) {
1971         effect->ResetIsRunningOnCompositor();
1972       }
1973     }
1974   }
1975 }
1976 
1977 }  // namespace mozilla
1978