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/RestyleManagerBase.h"
8 #include "mozilla/StyleSetHandle.h"
9 #include "nsIFrame.h"
10
11 namespace mozilla {
12
RestyleManagerBase(nsPresContext * aPresContext)13 RestyleManagerBase::RestyleManagerBase(nsPresContext* aPresContext)
14 : mPresContext(aPresContext)
15 , mRestyleGeneration(1)
16 , mHoverGeneration(0)
17 , mObservingRefreshDriver(false)
18 , mInStyleRefresh(false)
19 {
20 MOZ_ASSERT(mPresContext);
21 }
22
23 /**
24 * Calculates the change hint and the restyle hint for a given content state
25 * change.
26 *
27 * This is called from both Restyle managers.
28 */
29 void
ContentStateChangedInternal(Element * aElement,EventStates aStateMask,nsChangeHint * aOutChangeHint,nsRestyleHint * aOutRestyleHint)30 RestyleManagerBase::ContentStateChangedInternal(Element* aElement,
31 EventStates aStateMask,
32 nsChangeHint* aOutChangeHint,
33 nsRestyleHint* aOutRestyleHint)
34 {
35 MOZ_ASSERT(aOutChangeHint);
36 MOZ_ASSERT(aOutRestyleHint);
37
38 StyleSetHandle styleSet = PresContext()->StyleSet();
39 NS_ASSERTION(styleSet, "couldn't get style set");
40
41 *aOutChangeHint = nsChangeHint(0);
42 // Any change to a content state that affects which frames we construct
43 // must lead to a frame reconstruct here if we already have a frame.
44 // Note that we never decide through non-CSS means to not create frames
45 // based on content states, so if we already don't have a frame we don't
46 // need to force a reframe -- if it's needed, the HasStateDependentStyle
47 // call will handle things.
48 nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
49 CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
50 if (primaryFrame) {
51 // If it's generated content, ignore LOADING/etc state changes on it.
52 if (!primaryFrame->IsGeneratedContentFrame() &&
53 aStateMask.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN |
54 NS_EVENT_STATE_USERDISABLED |
55 NS_EVENT_STATE_SUPPRESSED |
56 NS_EVENT_STATE_LOADING)) {
57 *aOutChangeHint = nsChangeHint_ReconstructFrame;
58 } else {
59 uint8_t app = primaryFrame->StyleDisplay()->mAppearance;
60 if (app) {
61 nsITheme* theme = PresContext()->GetTheme();
62 if (theme &&
63 theme->ThemeSupportsWidget(PresContext(), primaryFrame, app)) {
64 bool repaint = false;
65 theme->WidgetStateChanged(primaryFrame, app, nullptr, &repaint,
66 nullptr);
67 if (repaint) {
68 *aOutChangeHint |= nsChangeHint_RepaintFrame;
69 }
70 }
71 }
72 }
73
74 pseudoType = primaryFrame->StyleContext()->GetPseudoType();
75
76 primaryFrame->ContentStatesChanged(aStateMask);
77 }
78
79 if (pseudoType >= CSSPseudoElementType::Count) {
80 *aOutRestyleHint = styleSet->HasStateDependentStyle(aElement, aStateMask);
81 } else if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(
82 pseudoType)) {
83 // If aElement is a pseudo-element, we want to check to see whether there
84 // are any state-dependent rules applying to that pseudo.
85 Element* ancestor =
86 ElementForStyleContext(nullptr, primaryFrame, pseudoType);
87 *aOutRestyleHint = styleSet->HasStateDependentStyle(ancestor, pseudoType,
88 aElement, aStateMask);
89 } else {
90 *aOutRestyleHint = nsRestyleHint(0);
91 }
92
93 if (aStateMask.HasState(NS_EVENT_STATE_HOVER) && *aOutRestyleHint != 0) {
94 IncrementHoverGeneration();
95 }
96
97 if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
98 // Exposing information to the page about whether the link is
99 // visited or not isn't really something we can worry about here.
100 // FIXME: We could probably do this a bit better.
101 *aOutChangeHint |= nsChangeHint_RepaintFrame;
102 }
103 }
104
105 /* static */ nsCString
RestyleHintToString(nsRestyleHint aHint)106 RestyleManagerBase::RestyleHintToString(nsRestyleHint aHint)
107 {
108 nsCString result;
109 bool any = false;
110 const char* names[] = {
111 "Self", "SomeDescendants", "Subtree", "LaterSiblings", "CSSTransitions",
112 "CSSAnimations", "SVGAttrAnimations", "StyleAttribute",
113 "StyleAttribute_Animations", "Force", "ForceDescendants"
114 };
115 uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
116 uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
117 for (uint32_t i = 0; i < ArrayLength(names); i++) {
118 if (hint & (1 << i)) {
119 if (any) {
120 result.AppendLiteral(" | ");
121 }
122 result.AppendPrintf("eRestyle_%s", names[i]);
123 any = true;
124 }
125 }
126 if (rest) {
127 if (any) {
128 result.AppendLiteral(" | ");
129 }
130 result.AppendPrintf("0x%0x", rest);
131 } else {
132 if (!any) {
133 result.AppendLiteral("0");
134 }
135 }
136 return result;
137 }
138
139 #ifdef DEBUG
140 /* static */ nsCString
ChangeHintToString(nsChangeHint aHint)141 RestyleManagerBase::ChangeHintToString(nsChangeHint aHint)
142 {
143 nsCString result;
144 bool any = false;
145 const char* names[] = {
146 "RepaintFrame", "NeedReflow", "ClearAncestorIntrinsics",
147 "ClearDescendantIntrinsics", "NeedDirtyReflow", "SyncFrameView",
148 "UpdateCursor", "UpdateEffects", "UpdateOpacityLayer",
149 "UpdateTransformLayer", "ReconstructFrame", "UpdateOverflow",
150 "UpdateSubtreeOverflow", "UpdatePostTransformOverflow",
151 "UpdateParentOverflow",
152 "ChildrenOnlyTransform", "RecomputePosition", "AddOrRemoveTransform",
153 "BorderStyleNoneChange", "UpdateTextPath", "SchedulePaint",
154 "NeutralChange", "InvalidateRenderingObservers",
155 "ReflowChangesSizeOrPosition", "UpdateComputedBSize",
156 "UpdateUsesOpacity", "UpdateBackgroundPosition",
157 "AddOrRemoveTransform"
158 };
159 static_assert(nsChangeHint_AllHints == (1 << ArrayLength(names)) - 1,
160 "Name list doesn't match change hints.");
161 uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
162 uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
163 if (hint == nsChangeHint_Hints_NotHandledForDescendants) {
164 result.AppendLiteral("nsChangeHint_Hints_NotHandledForDescendants");
165 hint = 0;
166 any = true;
167 } else {
168 if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
169 result.AppendLiteral("NS_STYLE_HINT_REFLOW");
170 hint = hint & ~NS_STYLE_HINT_REFLOW;
171 any = true;
172 } else if ((hint & nsChangeHint_AllReflowHints) == nsChangeHint_AllReflowHints) {
173 result.AppendLiteral("nsChangeHint_AllReflowHints");
174 hint = hint & ~nsChangeHint_AllReflowHints;
175 any = true;
176 } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
177 result.AppendLiteral("NS_STYLE_HINT_VISUAL");
178 hint = hint & ~NS_STYLE_HINT_VISUAL;
179 any = true;
180 }
181 }
182 for (uint32_t i = 0; i < ArrayLength(names); i++) {
183 if (hint & (1 << i)) {
184 if (any) {
185 result.AppendLiteral(" | ");
186 }
187 result.AppendPrintf("nsChangeHint_%s", names[i]);
188 any = true;
189 }
190 }
191 if (rest) {
192 if (any) {
193 result.AppendLiteral(" | ");
194 }
195 result.AppendPrintf("0x%0x", rest);
196 } else {
197 if (!any) {
198 result.AppendLiteral("nsChangeHint(0)");
199 }
200 }
201 return result;
202 }
203 #endif
204
205 void
PostRestyleEventInternal(bool aForLazyConstruction)206 RestyleManagerBase::PostRestyleEventInternal(bool aForLazyConstruction)
207 {
208 // Make sure we're not in a style refresh; if we are, we still have
209 // a call to ProcessPendingRestyles coming and there's no need to
210 // add ourselves as a refresh observer until then.
211 bool inRefresh = !aForLazyConstruction && mInStyleRefresh;
212 nsIPresShell* presShell = PresContext()->PresShell();
213 if (!ObservingRefreshDriver() && !inRefresh) {
214 SetObservingRefreshDriver(PresContext()->RefreshDriver()->
215 AddStyleFlushObserver(presShell));
216 }
217
218 // Unconditionally flag our document as needing a flush. The other
219 // option here would be a dedicated boolean to track whether we need
220 // to do so (set here and unset in ProcessPendingRestyles).
221 presShell->GetDocument()->SetNeedStyleFlush();
222 }
223
224 /**
225 * Frame construction helpers follow.
226 */
227 #ifdef DEBUG
228 static bool gInApplyRenderingChangeToTree = false;
229 #endif
230
231 #ifdef DEBUG
232 static void
DumpContext(nsIFrame * aFrame,nsStyleContext * aContext)233 DumpContext(nsIFrame* aFrame, nsStyleContext* aContext)
234 {
235 if (aFrame) {
236 fputs("frame: ", stdout);
237 nsAutoString name;
238 aFrame->GetFrameName(name);
239 fputs(NS_LossyConvertUTF16toASCII(name).get(), stdout);
240 fprintf(stdout, " (%p)", static_cast<void*>(aFrame));
241 }
242 if (aContext) {
243 fprintf(stdout, " style: %p ", static_cast<void*>(aContext));
244
245 nsIAtom* pseudoTag = aContext->GetPseudo();
246 if (pseudoTag) {
247 nsAutoString buffer;
248 pseudoTag->ToString(buffer);
249 fputs(NS_LossyConvertUTF16toASCII(buffer).get(), stdout);
250 fputs(" ", stdout);
251 }
252 fputs("{}\n", stdout);
253 }
254 }
255
256 static void
VerifySameTree(nsStyleContext * aContext1,nsStyleContext * aContext2)257 VerifySameTree(nsStyleContext* aContext1, nsStyleContext* aContext2)
258 {
259 nsStyleContext* top1 = aContext1;
260 nsStyleContext* top2 = aContext2;
261 nsStyleContext* parent;
262 for (;;) {
263 parent = top1->GetParent();
264 if (!parent)
265 break;
266 top1 = parent;
267 }
268 for (;;) {
269 parent = top2->GetParent();
270 if (!parent)
271 break;
272 top2 = parent;
273 }
274 NS_ASSERTION(top1 == top2,
275 "Style contexts are not in the same style context tree");
276 }
277
278 static void
VerifyContextParent(nsIFrame * aFrame,nsStyleContext * aContext,nsStyleContext * aParentContext)279 VerifyContextParent(nsIFrame* aFrame, nsStyleContext* aContext,
280 nsStyleContext* aParentContext)
281 {
282 // get the contexts not provided
283 if (!aContext) {
284 aContext = aFrame->StyleContext();
285 }
286
287 if (!aParentContext) {
288 nsIFrame* providerFrame;
289 aParentContext = aFrame->GetParentStyleContext(&providerFrame);
290 // aParentContext could still be null
291 }
292
293 NS_ASSERTION(aContext, "Failure to get required contexts");
294 nsStyleContext* actualParentContext = aContext->GetParent();
295
296 if (aParentContext) {
297 if (aParentContext != actualParentContext) {
298 DumpContext(aFrame, aContext);
299 if (aContext == aParentContext) {
300 NS_ERROR("Using parent's style context");
301 } else {
302 NS_ERROR("Wrong parent style context");
303 fputs("Wrong parent style context: ", stdout);
304 DumpContext(nullptr, actualParentContext);
305 fputs("should be using: ", stdout);
306 DumpContext(nullptr, aParentContext);
307 VerifySameTree(actualParentContext, aParentContext);
308 fputs("\n", stdout);
309 }
310 }
311
312 } else {
313 if (actualParentContext) {
314 NS_ERROR("Have parent context and shouldn't");
315 DumpContext(aFrame, aContext);
316 fputs("Has parent context: ", stdout);
317 DumpContext(nullptr, actualParentContext);
318 fputs("Should be null\n\n", stdout);
319 }
320 }
321
322 nsStyleContext* childStyleIfVisited = aContext->GetStyleIfVisited();
323 // Either childStyleIfVisited has aContext->GetParent()->GetStyleIfVisited()
324 // as the parent or it has a different rulenode from aContext _and_ has
325 // aContext->GetParent() as the parent.
326 if (childStyleIfVisited &&
327 !((childStyleIfVisited->RuleNode() != aContext->RuleNode() &&
328 childStyleIfVisited->GetParent() == aContext->GetParent()) ||
329 childStyleIfVisited->GetParent() ==
330 aContext->GetParent()->GetStyleIfVisited())) {
331 NS_ERROR("Visited style has wrong parent");
332 DumpContext(aFrame, aContext);
333 fputs("\n", stdout);
334 }
335 }
336
337 static void
VerifyStyleTree(nsIFrame * aFrame)338 VerifyStyleTree(nsIFrame* aFrame)
339 {
340 nsStyleContext* context = aFrame->StyleContext();
341 VerifyContextParent(aFrame, context, nullptr);
342
343 nsIFrame::ChildListIterator lists(aFrame);
344 for (; !lists.IsDone(); lists.Next()) {
345 for (nsIFrame* child : lists.CurrentList()) {
346 if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
347 // only do frames that are in flow
348 if (nsGkAtoms::placeholderFrame == child->GetType()) {
349 // placeholder: first recurse and verify the out of flow frame,
350 // then verify the placeholder's context
351 nsIFrame* outOfFlowFrame =
352 nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
353
354 // recurse to out of flow frame, letting the parent context get resolved
355 do {
356 VerifyStyleTree(outOfFlowFrame);
357 } while ((outOfFlowFrame = outOfFlowFrame->GetNextContinuation()));
358
359 // verify placeholder using the parent frame's context as
360 // parent context
361 VerifyContextParent(child, nullptr, nullptr);
362 } else { // regular frame
363 VerifyStyleTree(child);
364 }
365 }
366 }
367 }
368
369 // do additional contexts
370 int32_t contextIndex = 0;
371 for (nsStyleContext* extraContext;
372 (extraContext = aFrame->GetAdditionalStyleContext(contextIndex));
373 ++contextIndex) {
374 VerifyContextParent(aFrame, extraContext, context);
375 }
376 }
377
378 void
DebugVerifyStyleTree(nsIFrame * aFrame)379 RestyleManagerBase::DebugVerifyStyleTree(nsIFrame* aFrame)
380 {
381 if (aFrame) {
382 VerifyStyleTree(aFrame);
383 }
384 }
385
386 #endif // DEBUG
387
388 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ChangeListProperty, bool)
389
390 /**
391 * Sync views on aFrame and all of aFrame's descendants (following placeholders),
392 * if aChange has nsChangeHint_SyncFrameView.
393 * Calls DoApplyRenderingChangeToTree on all aFrame's out-of-flow descendants
394 * (following placeholders), if aChange has nsChangeHint_RepaintFrame.
395 * aFrame should be some combination of nsChangeHint_SyncFrameView,
396 * nsChangeHint_RepaintFrame, nsChangeHint_UpdateOpacityLayer and
397 * nsChangeHint_SchedulePaint, nothing else.
398 */
399 static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
400 nsChangeHint aChange);
401
402 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint);
403
404 /**
405 * To handle nsChangeHint_ChildrenOnlyTransform we must iterate over the child
406 * frames of the SVG frame concerned. This helper function is used to find that
407 * SVG frame when we encounter nsChangeHint_ChildrenOnlyTransform to ensure
408 * that we iterate over the intended children, since sometimes we end up
409 * handling that hint while processing hints for one of the SVG frame's
410 * ancestor frames.
411 *
412 * The reason that we sometimes end up trying to process the hint for an
413 * ancestor of the SVG frame that the hint is intended for is due to the way we
414 * process restyle events. ApplyRenderingChangeToTree adjusts the frame from
415 * the restyled element's principle frame to one of its ancestor frames based
416 * on what nsCSSRendering::FindBackground returns, since the background style
417 * may have been propagated up to an ancestor frame. Processing hints using an
418 * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
419 * a special case since it is intended to update the children of a specific
420 * frame.
421 */
422 static nsIFrame*
GetFrameForChildrenOnlyTransformHint(nsIFrame * aFrame)423 GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame)
424 {
425 if (aFrame->GetType() == nsGkAtoms::viewportFrame) {
426 // This happens if the root-<svg> is fixed positioned, in which case we
427 // can't use aFrame->GetContent() to find the primary frame, since
428 // GetContent() returns nullptr for ViewportFrame.
429 aFrame = aFrame->PrincipalChildList().FirstChild();
430 }
431 // For an nsHTMLScrollFrame, this will get the SVG frame that has the
432 // children-only transforms:
433 aFrame = aFrame->GetContent()->GetPrimaryFrame();
434 if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
435 aFrame = aFrame->PrincipalChildList().FirstChild();
436 MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::svgOuterSVGAnonChildFrame,
437 "Where is the nsSVGOuterSVGFrame's anon child??");
438 }
439 MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer),
440 "Children-only transforms only expected on SVG frames");
441 return aFrame;
442 }
443
444 // Returns true if this function managed to successfully move a frame, and
445 // false if it could not process the position change, and a reflow should
446 // be performed instead.
447 bool
RecomputePosition(nsIFrame * aFrame)448 RecomputePosition(nsIFrame* aFrame)
449 {
450 // Don't process position changes on table frames, since we already handle
451 // the dynamic position change on the table wrapper frame, and the
452 // reflow-based fallback code path also ignores positions on inner table
453 // frames.
454 if (aFrame->GetType() == nsGkAtoms::tableFrame) {
455 return true;
456 }
457
458 const nsStyleDisplay* display = aFrame->StyleDisplay();
459 // Changes to the offsets of a non-positioned element can safely be ignored.
460 if (display->mPosition == NS_STYLE_POSITION_STATIC) {
461 return true;
462 }
463
464 // Don't process position changes on frames which have views or the ones which
465 // have a view somewhere in their descendants, because the corresponding view
466 // needs to be repositioned properly as well.
467 if (aFrame->HasView() ||
468 (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
469 StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
470 return false;
471 }
472
473 aFrame->SchedulePaint();
474
475 // For relative positioning, we can simply update the frame rect
476 if (display->IsRelativelyPositionedStyle()) {
477 // Move the frame
478 if (display->mPosition == NS_STYLE_POSITION_STICKY) {
479 if (display->IsInnerTableStyle()) {
480 // We don't currently support sticky positioning of inner table
481 // elements (bug 975644). Bail.
482 //
483 // When this is fixed, remove the null-check for the computed
484 // offsets in nsTableRowFrame::ReflowChildren.
485 return true;
486 }
487
488 // Update sticky positioning for an entire element at once, starting with
489 // the first continuation or ib-split sibling.
490 // It's rare that the frame we already have isn't already the first
491 // continuation or ib-split sibling, but it can happen when styles differ
492 // across continuations such as ::first-line or ::first-letter, and in
493 // those cases we will generally (but maybe not always) do the work twice.
494 nsIFrame* firstContinuation =
495 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
496
497 StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
498 StickyScrollContainer* ssc =
499 StickyScrollContainer::GetStickyScrollContainerForFrame(
500 firstContinuation);
501 if (ssc) {
502 ssc->PositionContinuations(firstContinuation);
503 }
504 } else {
505 MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition,
506 "Unexpected type of positioning");
507 for (nsIFrame* cont = aFrame; cont;
508 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
509 nsIFrame* cb = cont->GetContainingBlock();
510 nsMargin newOffsets;
511 WritingMode wm = cb->GetWritingMode();
512 const LogicalSize size(wm, cb->GetContentRectRelativeToSelf().Size());
513
514 ReflowInput::ComputeRelativeOffsets(wm, cont, size, newOffsets);
515 NS_ASSERTION(newOffsets.left == -newOffsets.right &&
516 newOffsets.top == -newOffsets.bottom,
517 "ComputeRelativeOffsets should return valid results");
518
519 // ReflowInput::ApplyRelativePositioning would work here, but
520 // since we've already checked mPosition and aren't changing the frame's
521 // normal position, go ahead and add the offsets directly.
522 // First, we need to ensure that the normal position is stored though.
523 nsPoint normalPosition = cont->GetNormalPosition();
524 auto props = cont->Properties();
525 const auto& prop = nsIFrame::NormalPositionProperty();
526 if (!props.Get(prop)) {
527 props.Set(prop, new nsPoint(normalPosition));
528 }
529 cont->SetPosition(normalPosition +
530 nsPoint(newOffsets.left, newOffsets.top));
531 }
532 }
533
534 return true;
535 }
536
537 // For the absolute positioning case, set up a fake HTML reflow state for
538 // the frame, and then get the offsets and size from it. If the frame's size
539 // doesn't need to change, we can simply update the frame position. Otherwise
540 // we fall back to a reflow.
541 nsRenderingContext rc(
542 aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext());
543
544 // Construct a bogus parent reflow state so that there's a usable
545 // containing block reflow state.
546 nsIFrame* parentFrame = aFrame->GetParent();
547 WritingMode parentWM = parentFrame->GetWritingMode();
548 WritingMode frameWM = aFrame->GetWritingMode();
549 LogicalSize parentSize = parentFrame->GetLogicalSize();
550
551 nsFrameState savedState = parentFrame->GetStateBits();
552 ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, &rc,
553 parentSize);
554 parentFrame->RemoveStateBits(~nsFrameState(0));
555 parentFrame->AddStateBits(savedState);
556
557 // The bogus parent state here was created with no parent state of its own,
558 // and therefore it won't have an mCBReflowInput set up.
559 // But we may need one (for InitCBReflowInput in a child state), so let's
560 // try to create one here for the cases where it will be needed.
561 Maybe<ReflowInput> cbReflowInput;
562 nsIFrame* cbFrame = parentFrame->GetContainingBlock();
563 if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
564 parentFrame->GetType() == nsGkAtoms::tableFrame)) {
565 LogicalSize cbSize = cbFrame->GetLogicalSize();
566 cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, &rc, cbSize);
567 cbReflowInput->ComputedPhysicalMargin() = cbFrame->GetUsedMargin();
568 cbReflowInput->ComputedPhysicalPadding() = cbFrame->GetUsedPadding();
569 cbReflowInput->ComputedPhysicalBorderPadding() =
570 cbFrame->GetUsedBorderAndPadding();
571 parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
572 }
573
574 NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_INTRINSICSIZE &&
575 parentSize.BSize(parentWM) != NS_INTRINSICSIZE,
576 "parentSize should be valid");
577 parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
578 parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
579 parentReflowInput.ComputedPhysicalMargin().SizeTo(0, 0, 0, 0);
580
581 parentReflowInput.ComputedPhysicalPadding() = parentFrame->GetUsedPadding();
582 parentReflowInput.ComputedPhysicalBorderPadding() =
583 parentFrame->GetUsedBorderAndPadding();
584 LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM);
585 availSize.BSize(frameWM) = NS_INTRINSICSIZE;
586
587 ViewportFrame* viewport = do_QueryFrame(parentFrame);
588 nsSize cbSize = viewport ?
589 viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput).Size()
590 : aFrame->GetContainingBlock()->GetSize();
591 const nsMargin& parentBorder =
592 parentReflowInput.mStyleBorder->GetComputedBorder();
593 cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
594 LogicalSize lcbSize(frameWM, cbSize);
595 ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
596 availSize, &lcbSize);
597 nsSize computedSize(reflowInput.ComputedWidth(),
598 reflowInput.ComputedHeight());
599 computedSize.width += reflowInput.ComputedPhysicalBorderPadding().LeftRight();
600 if (computedSize.height != NS_INTRINSICSIZE) {
601 computedSize.height +=
602 reflowInput.ComputedPhysicalBorderPadding().TopBottom();
603 }
604 nsSize size = aFrame->GetSize();
605 // The RecomputePosition hint is not used if any offset changed between auto
606 // and non-auto. If computedSize.height == NS_INTRINSICSIZE then the new
607 // element height will be its intrinsic height, and since 'top' and 'bottom''s
608 // auto-ness hasn't changed, the old height must also be its intrinsic
609 // height, which we can assume hasn't changed (or reflow would have
610 // been triggered).
611 if (computedSize.width == size.width &&
612 (computedSize.height == NS_INTRINSICSIZE || computedSize.height == size.height)) {
613 // If we're solving for 'left' or 'top', then compute it here, in order to
614 // match the reflow code path.
615 if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().left) {
616 reflowInput.ComputedPhysicalOffsets().left = cbSize.width -
617 reflowInput.ComputedPhysicalOffsets().right -
618 reflowInput.ComputedPhysicalMargin().right -
619 size.width -
620 reflowInput.ComputedPhysicalMargin().left;
621 }
622
623 if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().top) {
624 reflowInput.ComputedPhysicalOffsets().top = cbSize.height -
625 reflowInput.ComputedPhysicalOffsets().bottom -
626 reflowInput.ComputedPhysicalMargin().bottom -
627 size.height -
628 reflowInput.ComputedPhysicalMargin().top;
629 }
630
631 // Move the frame
632 nsPoint pos(parentBorder.left + reflowInput.ComputedPhysicalOffsets().left +
633 reflowInput.ComputedPhysicalMargin().left,
634 parentBorder.top + reflowInput.ComputedPhysicalOffsets().top +
635 reflowInput.ComputedPhysicalMargin().top);
636 aFrame->SetPosition(pos);
637
638 return true;
639 }
640
641 // Fall back to a reflow
642 StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
643 return false;
644 }
645
646 static bool
HasBoxAncestor(nsIFrame * aFrame)647 HasBoxAncestor(nsIFrame* aFrame)
648 {
649 for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
650 if (f->IsXULBoxFrame()) {
651 return true;
652 }
653 }
654 return false;
655 }
656
657 /**
658 * Return true if aFrame's subtree has placeholders for out-of-flow content
659 * whose 'position' style's bit in aPositionMask is set.
660 */
661 static bool
FrameHasPositionedPlaceholderDescendants(nsIFrame * aFrame,uint32_t aPositionMask)662 FrameHasPositionedPlaceholderDescendants(nsIFrame* aFrame,
663 uint32_t aPositionMask)
664 {
665 MOZ_ASSERT(aPositionMask & (1 << NS_STYLE_POSITION_FIXED));
666
667 for (nsIFrame::ChildListIterator lists(aFrame); !lists.IsDone(); lists.Next()) {
668 for (nsIFrame* f : lists.CurrentList()) {
669 if (f->GetType() == nsGkAtoms::placeholderFrame) {
670 nsIFrame* outOfFlow =
671 nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
672 // If SVG text frames could appear here, they could confuse us since
673 // they ignore their position style ... but they can't.
674 NS_ASSERTION(!outOfFlow->IsSVGText(),
675 "SVG text frames can't be out of flow");
676 if (aPositionMask & (1 << outOfFlow->StyleDisplay()->mPosition)) {
677 return true;
678 }
679 }
680 uint32_t positionMask = aPositionMask;
681 // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or
682 // f->IsFixedPosContainingBlock() here. However, that would only
683 // be testing the *new* style of the frame, which might exclude
684 // descendants that currently have this frame as an abs-pos
685 // containing block. Taking the codepath where we don't reframe
686 // could lead to an unsafe call to
687 // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed
688 // the descendant and taken it off the absolute list.
689 if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) {
690 return true;
691 }
692 }
693 }
694 return false;
695 }
696
697 static bool
NeedToReframeForAddingOrRemovingTransform(nsIFrame * aFrame)698 NeedToReframeForAddingOrRemovingTransform(nsIFrame* aFrame)
699 {
700 static_assert(0 <= NS_STYLE_POSITION_ABSOLUTE &&
701 NS_STYLE_POSITION_ABSOLUTE < 32, "Style constant out of range");
702 static_assert(0 <= NS_STYLE_POSITION_FIXED &&
703 NS_STYLE_POSITION_FIXED < 32, "Style constant out of range");
704
705 uint32_t positionMask;
706 // Don't call aFrame->IsPositioned here, since that returns true if
707 // the frame already has a transform, and we want to ignore that here
708 if (aFrame->IsAbsolutelyPositioned() || aFrame->IsRelativelyPositioned()) {
709 // This frame is a container for abs-pos descendants whether or not it
710 // has a transform.
711 // So abs-pos descendants are no problem; we only need to reframe if
712 // we have fixed-pos descendants.
713 positionMask = 1 << NS_STYLE_POSITION_FIXED;
714 } else {
715 // This frame may not be a container for abs-pos descendants already.
716 // So reframe if we have abs-pos or fixed-pos descendants.
717 positionMask =
718 (1 << NS_STYLE_POSITION_FIXED) | (1 << NS_STYLE_POSITION_ABSOLUTE);
719 }
720 for (nsIFrame* f = aFrame; f;
721 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
722 if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) {
723 return true;
724 }
725 }
726 return false;
727 }
728
729 /* static */ nsIFrame*
GetNearestAncestorFrame(nsIContent * aContent)730 RestyleManagerBase::GetNearestAncestorFrame(nsIContent* aContent)
731 {
732 nsIFrame* ancestorFrame = nullptr;
733 for (nsIContent* ancestor = aContent->GetParent();
734 ancestor && !ancestorFrame;
735 ancestor = ancestor->GetParent()) {
736 ancestorFrame = ancestor->GetPrimaryFrame();
737 }
738 return ancestorFrame;
739 }
740
741 /* static */ nsIFrame*
GetNextBlockInInlineSibling(FramePropertyTable * aPropTable,nsIFrame * aFrame)742 RestyleManagerBase::GetNextBlockInInlineSibling(FramePropertyTable* aPropTable,
743 nsIFrame* aFrame)
744 {
745 NS_ASSERTION(!aFrame->GetPrevContinuation(),
746 "must start with the first continuation");
747 // Might we have ib-split siblings?
748 if (!(aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
749 // nothing more to do here
750 return nullptr;
751 }
752
753 return static_cast<nsIFrame*>
754 (aPropTable->Get(aFrame, nsIFrame::IBSplitSibling()));
755 }
756
757 static void
DoApplyRenderingChangeToTree(nsIFrame * aFrame,nsChangeHint aChange)758 DoApplyRenderingChangeToTree(nsIFrame* aFrame,
759 nsChangeHint aChange)
760 {
761 NS_PRECONDITION(gInApplyRenderingChangeToTree,
762 "should only be called within ApplyRenderingChangeToTree");
763
764 for ( ; aFrame; aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) {
765 // Invalidate and sync views on all descendant frames, following placeholders.
766 // We don't need to update transforms in SyncViewsAndInvalidateDescendants, because
767 // there can't be any out-of-flows or popups that need to be transformed;
768 // all out-of-flow descendants of the transformed element must also be
769 // descendants of the transformed frame.
770 SyncViewsAndInvalidateDescendants(aFrame,
771 nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
772 nsChangeHint_SyncFrameView |
773 nsChangeHint_UpdateOpacityLayer |
774 nsChangeHint_SchedulePaint)));
775 // This must be set to true if the rendering change needs to
776 // invalidate content. If it's false, a composite-only paint
777 // (empty transaction) will be scheduled.
778 bool needInvalidatingPaint = false;
779
780 // if frame has view, will already be invalidated
781 if (aChange & nsChangeHint_RepaintFrame) {
782 // Note that this whole block will be skipped when painting is suppressed
783 // (due to our caller ApplyRendingChangeToTree() discarding the
784 // nsChangeHint_RepaintFrame hint). If you add handling for any other
785 // hints within this block, be sure that they too should be ignored when
786 // painting is suppressed.
787 needInvalidatingPaint = true;
788 aFrame->InvalidateFrameSubtree();
789 if ((aChange & nsChangeHint_UpdateEffects) &&
790 aFrame->IsFrameOfType(nsIFrame::eSVG) &&
791 !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
792 // Need to update our overflow rects:
793 nsSVGUtils::ScheduleReflowSVG(aFrame);
794 }
795 }
796 if (aChange & nsChangeHint_UpdateTextPath) {
797 if (aFrame->IsSVGText()) {
798 // Invalidate and reflow the entire SVGTextFrame:
799 NS_ASSERTION(aFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath),
800 "expected frame for a <textPath> element");
801 nsIFrame* text =
802 nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::svgTextFrame);
803 NS_ASSERTION(text, "expected to find an ancestor SVGTextFrame");
804 static_cast<SVGTextFrame*>(text)->NotifyGlyphMetricsChange();
805 } else {
806 MOZ_ASSERT(false, "unexpected frame got nsChangeHint_UpdateTextPath");
807 }
808 }
809 if (aChange & nsChangeHint_UpdateOpacityLayer) {
810 // FIXME/bug 796697: we can get away with empty transactions for
811 // opacity updates in many cases.
812 needInvalidatingPaint = true;
813
814 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
815 if (nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
816 // SVG effects paints the opacity without using
817 // nsDisplayOpacity. We need to invalidate manually.
818 aFrame->InvalidateFrameSubtree();
819 }
820 }
821 if ((aChange & nsChangeHint_UpdateTransformLayer) &&
822 aFrame->IsTransformed()) {
823 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
824 // If we're not already going to do an invalidating paint, see
825 // if we can get away with only updating the transform on a
826 // layer for this frame, and not scheduling an invalidating
827 // paint.
828 if (!needInvalidatingPaint) {
829 Layer* layer;
830 needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly(&layer);
831
832 if (!needInvalidatingPaint) {
833 // Since we're not going to paint, we need to resend animation
834 // data to the layer.
835 MOZ_ASSERT(layer, "this can't happen if there's no layer");
836 nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(
837 layer, nullptr, nullptr, aFrame, eCSSProperty_transform);
838 }
839 }
840 }
841 if (aChange & nsChangeHint_ChildrenOnlyTransform) {
842 needInvalidatingPaint = true;
843 nsIFrame* childFrame =
844 GetFrameForChildrenOnlyTransformHint(aFrame)->PrincipalChildList().FirstChild();
845 for ( ; childFrame; childFrame = childFrame->GetNextSibling()) {
846 ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
847 }
848 }
849 if (aChange & nsChangeHint_SchedulePaint) {
850 needInvalidatingPaint = true;
851 }
852 aFrame->SchedulePaint(needInvalidatingPaint
853 ? nsIFrame::PAINT_DEFAULT
854 : nsIFrame::PAINT_COMPOSITE_ONLY);
855 }
856 }
857
858 static void
SyncViewsAndInvalidateDescendants(nsIFrame * aFrame,nsChangeHint aChange)859 SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, nsChangeHint aChange)
860 {
861 NS_PRECONDITION(gInApplyRenderingChangeToTree,
862 "should only be called within ApplyRenderingChangeToTree");
863 NS_ASSERTION(nsChangeHint_size_t(aChange) ==
864 (aChange & (nsChangeHint_RepaintFrame |
865 nsChangeHint_SyncFrameView |
866 nsChangeHint_UpdateOpacityLayer |
867 nsChangeHint_SchedulePaint)),
868 "Invalid change flag");
869
870 nsView* view = aFrame->GetView();
871 if (view) {
872 if (aChange & nsChangeHint_SyncFrameView) {
873 nsContainerFrame::SyncFrameViewProperties(aFrame->PresContext(), aFrame,
874 nullptr, view);
875 }
876 }
877
878 nsIFrame::ChildListIterator lists(aFrame);
879 for (; !lists.IsDone(); lists.Next()) {
880 for (nsIFrame* child : lists.CurrentList()) {
881 if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
882 // only do frames that don't have placeholders
883 if (nsGkAtoms::placeholderFrame == child->GetType()) {
884 // do the out-of-flow frame and its continuations
885 nsIFrame* outOfFlowFrame =
886 nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
887 DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
888 } else if (lists.CurrentID() == nsIFrame::kPopupList) {
889 DoApplyRenderingChangeToTree(child, aChange);
890 } else { // regular frame
891 SyncViewsAndInvalidateDescendants(child, aChange);
892 }
893 }
894 }
895 }
896 }
897
898 static void
ApplyRenderingChangeToTree(nsIPresShell * aPresShell,nsIFrame * aFrame,nsChangeHint aChange)899 ApplyRenderingChangeToTree(nsIPresShell* aPresShell,
900 nsIFrame* aFrame,
901 nsChangeHint aChange)
902 {
903 // We check StyleDisplay()->HasTransformStyle() in addition to checking
904 // IsTransformed() since we can get here for some frames that don't support
905 // CSS transforms.
906 NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
907 aFrame->IsTransformed() ||
908 aFrame->StyleDisplay()->HasTransformStyle(),
909 "Unexpected UpdateTransformLayer hint");
910
911 if (aPresShell->IsPaintingSuppressed()) {
912 // Don't allow synchronous rendering changes when painting is turned off.
913 aChange &= ~nsChangeHint_RepaintFrame;
914 if (!aChange) {
915 return;
916 }
917 }
918
919 // Trigger rendering updates by damaging this frame and any
920 // continuations of this frame.
921 #ifdef DEBUG
922 gInApplyRenderingChangeToTree = true;
923 #endif
924 if (aChange & nsChangeHint_RepaintFrame) {
925 // If the frame's background is propagated to an ancestor, walk up to
926 // that ancestor and apply the RepaintFrame change hint to it.
927 nsStyleContext* bgSC;
928 nsIFrame* propagatedFrame = aFrame;
929 while (!nsCSSRendering::FindBackground(propagatedFrame, &bgSC)) {
930 propagatedFrame = propagatedFrame->GetParent();
931 NS_ASSERTION(aFrame, "root frame must paint");
932 }
933
934 if (propagatedFrame != aFrame) {
935 DoApplyRenderingChangeToTree(propagatedFrame, nsChangeHint_RepaintFrame);
936 aChange &= ~nsChangeHint_RepaintFrame;
937 if (!aChange) {
938 return;
939 }
940 }
941 }
942 DoApplyRenderingChangeToTree(aFrame, aChange);
943 #ifdef DEBUG
944 gInApplyRenderingChangeToTree = false;
945 #endif
946 }
947
948 static void
AddSubtreeToOverflowTracker(nsIFrame * aFrame,OverflowChangedTracker & aOverflowChangedTracker)949 AddSubtreeToOverflowTracker(nsIFrame* aFrame,
950 OverflowChangedTracker& aOverflowChangedTracker)
951 {
952 if (aFrame->FrameMaintainsOverflow()) {
953 aOverflowChangedTracker.AddFrame(aFrame,
954 OverflowChangedTracker::CHILDREN_CHANGED);
955 }
956 nsIFrame::ChildListIterator lists(aFrame);
957 for (; !lists.IsDone(); lists.Next()) {
958 for (nsIFrame* child : lists.CurrentList()) {
959 AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
960 }
961 }
962 }
963
964 static void
StyleChangeReflow(nsIFrame * aFrame,nsChangeHint aHint)965 StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint)
966 {
967 nsIPresShell::IntrinsicDirty dirtyType;
968 if (aHint & nsChangeHint_ClearDescendantIntrinsics) {
969 NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics,
970 "Please read the comments in nsChangeHint.h");
971 NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow,
972 "ClearDescendantIntrinsics requires NeedDirtyReflow");
973 dirtyType = nsIPresShell::eStyleChange;
974 } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
975 aFrame->HasAnyStateBits(
976 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
977 dirtyType = nsIPresShell::eStyleChange;
978 } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) {
979 dirtyType = nsIPresShell::eTreeChange;
980 } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
981 HasBoxAncestor(aFrame)) {
982 // The frame's computed BSize is changing, and we have a box ancestor
983 // whose cached intrinsic height may need to be updated.
984 dirtyType = nsIPresShell::eTreeChange;
985 } else {
986 dirtyType = nsIPresShell::eResize;
987 }
988
989 nsFrameState dirtyBits;
990 if (aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) {
991 dirtyBits = nsFrameState(0);
992 } else if ((aHint & nsChangeHint_NeedDirtyReflow) ||
993 dirtyType == nsIPresShell::eStyleChange) {
994 dirtyBits = NS_FRAME_IS_DIRTY;
995 } else {
996 dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN;
997 }
998
999 // If we're not going to clear any intrinsic sizes on the frames, and
1000 // there are no dirty bits to set, then there's nothing to do.
1001 if (dirtyType == nsIPresShell::eResize && !dirtyBits)
1002 return;
1003
1004 nsIPresShell::ReflowRootHandling rootHandling;
1005 if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
1006 rootHandling = nsIPresShell::ePositionOrSizeChange;
1007 } else {
1008 rootHandling = nsIPresShell::eNoPositionOrSizeChange;
1009 }
1010
1011 do {
1012 aFrame->PresContext()->PresShell()->FrameNeedsReflow(
1013 aFrame, dirtyType, dirtyBits, rootHandling);
1014 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
1015 } while (aFrame);
1016 }
1017
1018 /* static */ nsIFrame*
GetNextContinuationWithSameStyle(nsIFrame * aFrame,nsStyleContext * aOldStyleContext,bool * aHaveMoreContinuations)1019 RestyleManagerBase::GetNextContinuationWithSameStyle(
1020 nsIFrame* aFrame, nsStyleContext* aOldStyleContext,
1021 bool* aHaveMoreContinuations)
1022 {
1023 // See GetPrevContinuationWithSameStyle about {ib} splits.
1024
1025 nsIFrame* nextContinuation = aFrame->GetNextContinuation();
1026 if (!nextContinuation &&
1027 (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
1028 // We're the last continuation, so we have to hop back to the first
1029 // before getting the frame property
1030 nextContinuation =
1031 aFrame->FirstContinuation()->Properties().Get(nsIFrame::IBSplitSibling());
1032 if (nextContinuation) {
1033 nextContinuation =
1034 nextContinuation->Properties().Get(nsIFrame::IBSplitSibling());
1035 }
1036 }
1037
1038 if (!nextContinuation) {
1039 return nullptr;
1040 }
1041
1042 NS_ASSERTION(nextContinuation->GetContent() == aFrame->GetContent(),
1043 "unexpected content mismatch");
1044
1045 nsStyleContext* nextStyle = nextContinuation->StyleContext();
1046 if (nextStyle != aOldStyleContext) {
1047 NS_ASSERTION(aOldStyleContext->GetPseudo() != nextStyle->GetPseudo() ||
1048 aOldStyleContext->GetParent() != nextStyle->GetParent(),
1049 "continuations should have the same style context");
1050 nextContinuation = nullptr;
1051 if (aHaveMoreContinuations) {
1052 *aHaveMoreContinuations = true;
1053 }
1054 }
1055 return nextContinuation;
1056 }
1057
1058 nsresult
ProcessRestyledFrames(nsStyleChangeList & aChangeList)1059 RestyleManagerBase::ProcessRestyledFrames(nsStyleChangeList& aChangeList)
1060 {
1061 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1062 "Someone forgot a script blocker");
1063 if (aChangeList.IsEmpty())
1064 return NS_OK;
1065
1066 PROFILER_LABEL("RestyleManager", "ProcessRestyledFrames",
1067 js::ProfileEntry::Category::CSS);
1068
1069 nsPresContext* presContext = PresContext();
1070 FramePropertyTable* propTable = presContext->PropertyTable();
1071 nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
1072
1073 // Make sure to not rebuild quote or counter lists while we're
1074 // processing restyles
1075 frameConstructor->BeginUpdate();
1076
1077 // Mark frames so that we skip frames that die along the way, bug 123049.
1078 // A frame can be in the list multiple times with different hints. Further
1079 // optmization is possible if nsStyleChangeList::AppendChange could coalesce
1080 for (const nsStyleChangeData& data : aChangeList) {
1081 if (data.mFrame) {
1082 propTable->Set(data.mFrame, ChangeListProperty(), true);
1083 }
1084 }
1085
1086 bool didUpdateCursor = false;
1087
1088 for (const nsStyleChangeData& data : aChangeList) {
1089 nsIFrame* frame = data.mFrame;
1090 nsIContent* content = data.mContent;
1091 nsChangeHint hint = data.mHint;
1092 bool didReflowThisFrame = false;
1093
1094 NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
1095 (hint & nsChangeHint_NeedReflow),
1096 "Reflow hint bits set without actually asking for a reflow");
1097
1098 // skip any frame that has been destroyed due to a ripple effect
1099 if (frame && !propTable->Get(frame, ChangeListProperty())) {
1100 continue;
1101 }
1102
1103 if (frame && frame->GetContent() != content) {
1104 // XXXbz this is due to image maps messing with the primary frame of
1105 // <area>s. See bug 135040. Remove this block once that's fixed.
1106 frame = nullptr;
1107 if (!(hint & nsChangeHint_ReconstructFrame)) {
1108 continue;
1109 }
1110 }
1111
1112 if ((hint & nsChangeHint_UpdateContainingBlock) && frame &&
1113 !(hint & nsChangeHint_ReconstructFrame)) {
1114 if (NeedToReframeForAddingOrRemovingTransform(frame) ||
1115 frame->GetType() == nsGkAtoms::fieldSetFrame ||
1116 frame->GetContentInsertionFrame() != frame) {
1117 // The frame has positioned children that need to be reparented, or
1118 // it can't easily be converted to/from being an abs-pos container correctly.
1119 hint |= nsChangeHint_ReconstructFrame;
1120 } else {
1121 for (nsIFrame* cont = frame; cont;
1122 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1123 // Normally frame construction would set state bits as needed,
1124 // but we're not going to reconstruct the frame so we need to set them.
1125 // It's because we need to set this state on each affected frame
1126 // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
1127 // to ancestors (i.e. it can't be an change hint that is handled for
1128 // descendants).
1129 if (cont->IsAbsPosContainingBlock()) {
1130 if (!cont->IsAbsoluteContainer() &&
1131 (cont->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
1132 cont->MarkAsAbsoluteContainingBlock();
1133 }
1134 } else {
1135 if (cont->IsAbsoluteContainer()) {
1136 if (cont->HasAbsolutelyPositionedChildren()) {
1137 // If |cont| still has absolutely positioned children,
1138 // we can't call MarkAsNotAbsoluteContainingBlock. This
1139 // will remove a frame list that still has children in
1140 // it that we need to keep track of.
1141 // The optimization of removing it isn't particularly
1142 // important, although it does mean we skip some tests.
1143 NS_WARNING("skipping removal of absolute containing block");
1144 } else {
1145 cont->MarkAsNotAbsoluteContainingBlock();
1146 }
1147 }
1148 }
1149 }
1150 }
1151 }
1152
1153 if ((hint & nsChangeHint_AddOrRemoveTransform) && frame &&
1154 !(hint & nsChangeHint_ReconstructFrame)) {
1155 for (nsIFrame* cont = frame; cont;
1156 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1157 if (cont->StyleDisplay()->HasTransform(cont)) {
1158 cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
1159 }
1160 // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be
1161 // transformed by other means. It's OK to have the bit even if it's
1162 // not needed.
1163 }
1164 }
1165
1166 if (hint & nsChangeHint_ReconstructFrame) {
1167 // If we ever start passing true here, be careful of restyles
1168 // that involve a reframe and animations. In particular, if the
1169 // restyle we're processing here is an animation restyle, but
1170 // the style resolution we will do for the frame construction
1171 // happens async when we're not in an animation restyle already,
1172 // problems could arise.
1173 // We could also have problems with triggering of CSS transitions
1174 // on elements whose frames are reconstructed, since we depend on
1175 // the reconstruction happening synchronously.
1176 frameConstructor->RecreateFramesForContent(content, false,
1177 nsCSSFrameConstructor::REMOVE_FOR_RECONSTRUCTION, nullptr);
1178 } else {
1179 NS_ASSERTION(frame, "This shouldn't happen");
1180
1181 if (!frame->FrameMaintainsOverflow()) {
1182 // frame does not maintain overflow rects, so avoid calling
1183 // FinishAndStoreOverflow on it:
1184 hint &= ~(nsChangeHint_UpdateOverflow |
1185 nsChangeHint_ChildrenOnlyTransform |
1186 nsChangeHint_UpdatePostTransformOverflow |
1187 nsChangeHint_UpdateParentOverflow);
1188 }
1189
1190 if (!(frame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) {
1191 // Frame can not be transformed, and thus a change in transform will
1192 // have no effect and we should not use the
1193 // nsChangeHint_UpdatePostTransformOverflow hint.
1194 hint &= ~nsChangeHint_UpdatePostTransformOverflow;
1195 }
1196
1197 if (hint & nsChangeHint_UpdateEffects) {
1198 for (nsIFrame* cont = frame; cont;
1199 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1200 nsSVGEffects::UpdateEffects(cont);
1201 }
1202 }
1203 if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
1204 ((hint & nsChangeHint_UpdateOpacityLayer) &&
1205 frame->IsFrameOfType(nsIFrame::eSVG) &&
1206 !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) {
1207 nsSVGEffects::InvalidateRenderingObservers(frame);
1208 }
1209 if (hint & nsChangeHint_NeedReflow) {
1210 StyleChangeReflow(frame, hint);
1211 didReflowThisFrame = true;
1212 }
1213
1214 if ((hint & nsChangeHint_UpdateUsesOpacity) &&
1215 frame->IsFrameOfType(nsIFrame::eTablePart)) {
1216 NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
1217 "should only return UpdateUsesOpacity hint "
1218 "when also returning UpdateOpacityLayer hint");
1219 // When an internal table part (including cells) changes between
1220 // having opacity 1 and non-1, it changes whether its
1221 // backgrounds (and those of table parts inside of it) are
1222 // painted as part of the table's nsDisplayTableBorderBackground
1223 // display item, or part of its own display item. That requires
1224 // invalidation, so change UpdateOpacityLayer to RepaintFrame.
1225 hint &= ~nsChangeHint_UpdateOpacityLayer;
1226 hint |= nsChangeHint_RepaintFrame;
1227 }
1228
1229 // Opacity disables preserve-3d, so if we toggle it, then we also need
1230 // to update the overflow areas of all potentially affected frames.
1231 if ((hint & nsChangeHint_UpdateUsesOpacity) &&
1232 frame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
1233 hint |= nsChangeHint_UpdateSubtreeOverflow;
1234 }
1235
1236 if (hint & nsChangeHint_UpdateBackgroundPosition) {
1237 // For most frame types, DLBI can detect background position changes,
1238 // so we only need to schedule a paint.
1239 hint |= nsChangeHint_SchedulePaint;
1240 if (frame->IsFrameOfType(nsIFrame::eTablePart) ||
1241 frame->IsFrameOfType(nsIFrame::eMathML)) {
1242 // Table parts and MathML frames don't build display items for their
1243 // backgrounds, so DLBI can't detect background-position changes for
1244 // these frames. Repaint the whole frame.
1245 hint |= nsChangeHint_RepaintFrame;
1246 }
1247 }
1248
1249 if (hint & (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView |
1250 nsChangeHint_UpdateOpacityLayer | nsChangeHint_UpdateTransformLayer |
1251 nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
1252 ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint);
1253 }
1254 if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
1255 ActiveLayerTracker::NotifyOffsetRestyle(frame);
1256 // It is possible for this to fall back to a reflow
1257 if (!RecomputePosition(frame)) {
1258 didReflowThisFrame = true;
1259 }
1260 }
1261 NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) ||
1262 (hint & nsChangeHint_UpdateOverflow),
1263 "nsChangeHint_UpdateOverflow should be passed too");
1264 if (!didReflowThisFrame &&
1265 (hint & (nsChangeHint_UpdateOverflow |
1266 nsChangeHint_UpdatePostTransformOverflow |
1267 nsChangeHint_UpdateParentOverflow |
1268 nsChangeHint_UpdateSubtreeOverflow))) {
1269 if (hint & nsChangeHint_UpdateSubtreeOverflow) {
1270 for (nsIFrame* cont = frame; cont; cont =
1271 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1272 AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker);
1273 }
1274 // The work we just did in AddSubtreeToOverflowTracker
1275 // subsumes some of the other hints:
1276 hint &= ~(nsChangeHint_UpdateOverflow |
1277 nsChangeHint_UpdatePostTransformOverflow);
1278 }
1279 if (hint & nsChangeHint_ChildrenOnlyTransform) {
1280 // The overflow areas of the child frames need to be updated:
1281 nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame);
1282 nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild();
1283 NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame),
1284 "SVG frames should not have continuations "
1285 "or ib-split siblings");
1286 NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame),
1287 "SVG frames should not have continuations "
1288 "or ib-split siblings");
1289 for ( ; childFrame; childFrame = childFrame->GetNextSibling()) {
1290 MOZ_ASSERT(childFrame->IsFrameOfType(nsIFrame::eSVG),
1291 "Not expecting non-SVG children");
1292 // If |childFrame| is dirty or has dirty children, we don't bother
1293 // updating overflows since that will happen when it's reflowed.
1294 if (!(childFrame->GetStateBits() &
1295 (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) {
1296 mOverflowChangedTracker.AddFrame(childFrame,
1297 OverflowChangedTracker::CHILDREN_CHANGED);
1298 }
1299 NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame),
1300 "SVG frames should not have continuations "
1301 "or ib-split siblings");
1302 NS_ASSERTION(childFrame->GetParent() == hintFrame,
1303 "SVG child frame not expected to have different parent");
1304 }
1305 }
1306 // If |frame| is dirty or has dirty children, we don't bother updating
1307 // overflows since that will happen when it's reflowed.
1308 if (!(frame->GetStateBits() &
1309 (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) {
1310 if (hint & (nsChangeHint_UpdateOverflow |
1311 nsChangeHint_UpdatePostTransformOverflow)) {
1312 OverflowChangedTracker::ChangeKind changeKind;
1313 // If we have both nsChangeHint_UpdateOverflow and
1314 // nsChangeHint_UpdatePostTransformOverflow,
1315 // CHILDREN_CHANGED is selected as it is
1316 // strictly stronger.
1317 if (hint & nsChangeHint_UpdateOverflow) {
1318 changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
1319 } else {
1320 changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
1321 }
1322 for (nsIFrame* cont = frame; cont; cont =
1323 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1324 mOverflowChangedTracker.AddFrame(cont, changeKind);
1325 }
1326 }
1327 // UpdateParentOverflow hints need to be processed in addition
1328 // to the above, since if the processing of the above hints
1329 // yields no change, the update will not propagate to the
1330 // parent.
1331 if (hint & nsChangeHint_UpdateParentOverflow) {
1332 MOZ_ASSERT(frame->GetParent(),
1333 "shouldn't get style hints for the root frame");
1334 for (nsIFrame* cont = frame; cont; cont =
1335 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1336 mOverflowChangedTracker.AddFrame(cont->GetParent(),
1337 OverflowChangedTracker::CHILDREN_CHANGED);
1338 }
1339 }
1340 }
1341 }
1342 if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
1343 presContext->PresShell()->SynthesizeMouseMove(false);
1344 didUpdateCursor = true;
1345 }
1346 }
1347 }
1348
1349 frameConstructor->EndUpdate();
1350
1351 // cleanup references and verify the style tree. Note that the latter needs
1352 // to happen once we've processed the whole list, since until then the tree
1353 // is not in fact in a consistent state.
1354 for (const nsStyleChangeData& data : aChangeList) {
1355 if (data.mFrame) {
1356 propTable->Delete(data.mFrame, ChangeListProperty());
1357 }
1358
1359 #ifdef DEBUG
1360 // reget frame from content since it may have been regenerated...
1361 if (data.mContent) {
1362 nsIFrame* frame = data.mContent->GetPrimaryFrame();
1363 if (frame) {
1364 DebugVerifyStyleTree(frame);
1365 }
1366 } else if (!data.mFrame ||
1367 data.mFrame->GetType() != nsGkAtoms::viewportFrame) {
1368 NS_WARNING("Unable to test style tree integrity -- no content node "
1369 "(and not a viewport frame)");
1370 }
1371 #endif
1372 }
1373
1374 aChangeList.Clear();
1375 return NS_OK;
1376 }
1377
1378 } // namespace mozilla
1379