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