RestyleManager.cpp (158674B)
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 9 #include "ActiveLayerTracker.h" 10 #include "ScrollSnap.h" 11 #include "StickyScrollContainer.h" 12 #include "mozilla/AnimationUtils.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/ComputedStyle.h" 15 #include "mozilla/ComputedStyleInlines.h" 16 #include "mozilla/DocumentStyleRootIterator.h" 17 #include "mozilla/EffectSet.h" 18 #include "mozilla/GeckoBindings.h" 19 #include "mozilla/IntegerRange.h" 20 #include "mozilla/LayerAnimationInfo.h" 21 #include "mozilla/PresShell.h" 22 #include "mozilla/PresShellInlines.h" 23 #include "mozilla/ProfilerLabels.h" 24 #include "mozilla/SVGIntegrationUtils.h" 25 #include "mozilla/SVGObserverUtils.h" 26 #include "mozilla/SVGTextFrame.h" 27 #include "mozilla/SVGUtils.h" 28 #include "mozilla/ScrollContainerFrame.h" 29 #include "mozilla/ServoBindings.h" 30 #include "mozilla/ServoStyleSetInlines.h" 31 #include "mozilla/StaticPrefs_layout.h" 32 #include "mozilla/ViewportFrame.h" 33 #include "mozilla/dom/ChildIterator.h" 34 #include "mozilla/dom/DocumentInlines.h" 35 #include "mozilla/dom/ElementInlines.h" 36 #include "mozilla/dom/HTMLBodyElement.h" 37 #include "mozilla/dom/HTMLInputElement.h" 38 #include "mozilla/layers/AnimationInfo.h" 39 #include "mozilla/layout/ScrollAnchorContainer.h" 40 #include "nsAnimationManager.h" 41 #include "nsBlockFrame.h" 42 #include "nsCSSFrameConstructor.h" 43 #include "nsCSSRendering.h" 44 #include "nsContentUtils.h" 45 #include "nsDocShell.h" 46 #include "nsIFrame.h" 47 #include "nsIFrameInlines.h" 48 #include "nsImageFrame.h" 49 #include "nsPlaceholderFrame.h" 50 #include "nsPrintfCString.h" 51 #include "nsRefreshDriver.h" 52 #include "nsStyleChangeList.h" 53 #include "nsStyleUtil.h" 54 #include "nsTableWrapperFrame.h" 55 #include "nsTransitionManager.h" 56 57 #ifdef ACCESSIBILITY 58 # include "nsAccessibilityService.h" 59 #endif 60 61 using mozilla::layers::AnimationInfo; 62 using mozilla::layout::ScrollAnchorContainer; 63 64 using namespace mozilla::dom; 65 using namespace mozilla::layers; 66 67 namespace mozilla { 68 69 RestyleManager::RestyleManager(nsPresContext* aPresContext) 70 : mPresContext(aPresContext), 71 mRestyleGeneration(1), 72 mUndisplayedRestyleGeneration(1), 73 mInStyleRefresh(false), 74 mAnimationGeneration(0) { 75 MOZ_ASSERT(mPresContext); 76 } 77 78 void RestyleManager::ContentInserted(nsIContent* aChild) { 79 MOZ_ASSERT(aChild->GetParentNode()); 80 if (aChild->IsElement()) { 81 StyleSet()->MaybeInvalidateForElementInsertion(*aChild->AsElement()); 82 } 83 RestyleForInsertOrChange(aChild); 84 } 85 86 void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) { 87 MOZ_ASSERT(aFirstNewContent->GetParentNode()); 88 89 #ifdef DEBUG 90 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { 91 NS_ASSERTION(cur->IsRootOfNativeAnonymousSubtree() == 92 aFirstNewContent->IsRootOfNativeAnonymousSubtree(), 93 "anonymous nodes should not be in child lists"); 94 } 95 #endif 96 97 // We get called explicitly with NAC by editor and view transitions code, but 98 // in those cases we don't need to do any invalidation. 99 if (MOZ_UNLIKELY(aFirstNewContent->IsRootOfNativeAnonymousSubtree())) { 100 return; 101 } 102 103 StyleSet()->MaybeInvalidateForElementAppend(*aFirstNewContent); 104 105 auto* container = aFirstNewContent->GetParentNode(); 106 const auto selectorFlags = container->GetSelectorFlags() & 107 NodeSelectorFlags::AllSimpleRestyleFlagsForAppend; 108 if (!selectorFlags) { 109 return; 110 } 111 112 // The container cannot be a document. 113 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); 114 115 if (selectorFlags & NodeSelectorFlags::HasEmptySelector) { 116 // see whether we need to restyle the container 117 bool wasEmpty = true; // :empty or :-moz-only-whitespace 118 for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent; 119 cur = cur->GetNextSibling()) { 120 // We don't know whether we're testing :empty or :-moz-only-whitespace, 121 // so be conservative and assume :-moz-only-whitespace (i.e., make 122 // IsSignificantChild less likely to be true, and thus make us more 123 // likely to restyle). 124 if (nsStyleUtil::IsSignificantChild(cur, false)) { 125 wasEmpty = false; 126 break; 127 } 128 } 129 if (wasEmpty && container->IsElement()) { 130 RestyleForEmptyChange(container->AsElement()); 131 return; 132 } 133 } 134 135 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) { 136 RestyleWholeContainer(container, selectorFlags); 137 // Restyling the container is the most we can do here, so we're done. 138 return; 139 } 140 141 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) { 142 // restyle the last element child before this node 143 for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur; 144 cur = cur->GetPreviousSibling()) { 145 if (cur->IsElement()) { 146 auto* element = cur->AsElement(); 147 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), 148 nsChangeHint(0)); 149 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency( 150 *element, StyleRelativeSelectorNthEdgeInvalidateFor::Last); 151 break; 152 } 153 } 154 } 155 } 156 157 void RestyleManager::RestylePreviousSiblings(nsIContent* aStartingSibling) { 158 for (nsIContent* sibling = aStartingSibling; sibling; 159 sibling = sibling->GetPreviousSibling()) { 160 if (auto* element = Element::FromNode(sibling)) { 161 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0)); 162 } 163 } 164 } 165 166 void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) { 167 for (nsIContent* sibling = aStartingSibling; sibling; 168 sibling = sibling->GetNextSibling()) { 169 if (auto* element = Element::FromNode(sibling)) { 170 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0)); 171 } 172 } 173 } 174 175 void RestyleManager::RestyleWholeContainer(nsINode* aContainer, 176 NodeSelectorFlags aSelectorFlags) { 177 if (!mRestyledAsWholeContainer.EnsureInserted(aContainer)) { 178 return; 179 } 180 if (auto* containerElement = Element::FromNode(aContainer)) { 181 PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(), 182 nsChangeHint(0)); 183 if (aSelectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) { 184 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling( 185 containerElement->GetFirstElementChild(), 186 /* aForceRestyleSiblings = */ false); 187 } 188 } else { 189 RestyleSiblingsStartingWith(aContainer->GetFirstChild()); 190 } 191 } 192 193 void RestyleManager::RestyleForEmptyChange(Element* aContainer) { 194 PostRestyleEvent(aContainer, RestyleHint::RestyleSubtree(), nsChangeHint(0)); 195 StyleSet()->MaybeInvalidateRelativeSelectorForEmptyDependency(*aContainer); 196 197 // In some cases (:empty + E, :empty ~ E), a change in the content of 198 // an element requires restyling its parent's siblings. 199 nsIContent* grandparent = aContainer->GetParent(); 200 if (!grandparent || !(grandparent->GetSelectorFlags() & 201 NodeSelectorFlags::HasSlowSelectorLaterSiblings)) { 202 return; 203 } 204 RestyleSiblingsStartingWith(aContainer->GetNextSibling()); 205 } 206 207 void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer, 208 nsIContent* aChangedChild) { 209 MOZ_ASSERT(aContainer->GetSelectorFlags() & 210 NodeSelectorFlags::HasEdgeChildSelector); 211 MOZ_ASSERT(aChangedChild->GetParent() == aContainer); 212 // restyle the previously-first element child if it is after this node 213 bool passedChild = false; 214 for (nsIContent* content = aContainer->GetFirstChild(); content; 215 content = content->GetNextSibling()) { 216 if (content == aChangedChild) { 217 passedChild = true; 218 continue; 219 } 220 if (content->IsElement()) { 221 if (passedChild) { 222 auto* element = content->AsElement(); 223 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), 224 nsChangeHint(0)); 225 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency( 226 *element, StyleRelativeSelectorNthEdgeInvalidateFor::First); 227 } 228 break; 229 } 230 } 231 // restyle the previously-last element child if it is before this node 232 passedChild = false; 233 for (nsIContent* content = aContainer->GetLastChild(); content; 234 content = content->GetPreviousSibling()) { 235 if (content == aChangedChild) { 236 passedChild = true; 237 continue; 238 } 239 if (content->IsElement()) { 240 if (passedChild) { 241 auto* element = content->AsElement(); 242 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), 243 nsChangeHint(0)); 244 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency( 245 *element, StyleRelativeSelectorNthEdgeInvalidateFor::Last); 246 } 247 break; 248 } 249 } 250 } 251 252 template <typename CharT> 253 bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) { 254 for (auto index : IntegerRange(aUpTo)) { 255 if (!dom::IsSpaceCharacter(aBuffer[index])) { 256 return false; 257 } 258 } 259 return true; 260 } 261 262 template <typename CharT> 263 bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength, 264 size_t aNewLength) { 265 MOZ_ASSERT(aOldLength <= aNewLength); 266 if (!WhitespaceOnly(aBuffer, aOldLength)) { 267 // The old text was already not whitespace-only. 268 return false; 269 } 270 271 return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength); 272 } 273 274 static bool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) { 275 MOZ_ASSERT(aChild->GetParent() == aContainer); 276 for (nsIContent* child = aContainer->GetFirstChild(); child; 277 child = child->GetNextSibling()) { 278 if (child == aChild) { 279 continue; 280 } 281 // We don't know whether we're testing :empty or :-moz-only-whitespace, 282 // so be conservative and assume :-moz-only-whitespace (i.e., make 283 // IsSignificantChild less likely to be true, and thus make us more 284 // likely to restyle). 285 if (nsStyleUtil::IsSignificantChild(child, false)) { 286 return true; 287 } 288 } 289 290 return false; 291 } 292 293 void RestyleManager::CharacterDataChanged( 294 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { 295 nsINode* parent = aContent->GetParentNode(); 296 MOZ_ASSERT(parent, "How were we notified of a stray node?"); 297 298 const auto slowSelectorFlags = 299 parent->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags; 300 if (!(slowSelectorFlags & (NodeSelectorFlags::HasEmptySelector | 301 NodeSelectorFlags::HasEdgeChildSelector))) { 302 // Nothing to do, no other slow selector can change as a result of this. 303 return; 304 } 305 306 if (!aContent->IsText()) { 307 // Doesn't matter to styling (could be a processing instruction or a 308 // comment), it can't change whether any selectors match or don't. 309 return; 310 } 311 312 if (MOZ_UNLIKELY(!parent->IsElement())) { 313 MOZ_ASSERT(parent->IsShadowRoot()); 314 return; 315 } 316 317 if (MOZ_UNLIKELY(aContent->IsRootOfNativeAnonymousSubtree())) { 318 // This is an anonymous node and thus isn't in child lists, so isn't taken 319 // into account for selector matching the relevant selectors here. 320 return; 321 } 322 323 // Handle appends specially since they're common and we can know both the old 324 // and the new text exactly. 325 // 326 // TODO(emilio): This could be made much more general if :-moz-only-whitespace 327 // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only 328 // need to know whether we went from empty to non-empty, and that's trivial to 329 // know, with CharacterDataChangeInfo... 330 if (!aInfo.mAppend) { 331 // FIXME(emilio): This restyles unnecessarily if the text node is the only 332 // child of the parent element. Fortunately, it's uncommon to have such 333 // nodes and this not being an append. 334 // 335 // See the testcase in bug 1427625 for a test-case that triggers this. 336 RestyleForInsertOrChange(aContent); 337 return; 338 } 339 340 const CharacterDataBuffer* text = &aContent->AsText()->DataBuffer(); 341 342 const size_t oldLength = aInfo.mChangeStart; 343 const size_t newLength = text->GetLength(); 344 345 const bool emptyChanged = !oldLength && newLength; 346 347 const bool whitespaceOnlyChanged = 348 text->Is2b() 349 ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength) 350 : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength); 351 352 if (!emptyChanged && !whitespaceOnlyChanged) { 353 return; 354 } 355 356 if (slowSelectorFlags & NodeSelectorFlags::HasEmptySelector) { 357 if (!HasAnySignificantSibling(parent->AsElement(), aContent)) { 358 // We used to be empty, restyle the parent. 359 RestyleForEmptyChange(parent->AsElement()); 360 return; 361 } 362 } 363 364 if (slowSelectorFlags & NodeSelectorFlags::HasEdgeChildSelector) { 365 MaybeRestyleForEdgeChildChange(parent, aContent); 366 } 367 } 368 369 // Restyling for a ContentInserted or CharacterDataChanged notification. 370 // This could be used for ContentRemoved as well if we got the 371 // notification before the removal happened (and sometimes 372 // CharacterDataChanged is more like a removal than an addition). 373 // The comments are written and variables are named in terms of it being 374 // a ContentInserted notification. 375 void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) { 376 nsINode* container = aChild->GetParentNode(); 377 MOZ_ASSERT(container); 378 379 const auto selectorFlags = 380 container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags; 381 if (!selectorFlags) { 382 return; 383 } 384 385 NS_ASSERTION(!aChild->IsRootOfNativeAnonymousSubtree(), 386 "anonymous nodes should not be in child lists"); 387 388 // The container cannot be a document. 389 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); 390 391 if (selectorFlags & NodeSelectorFlags::HasEmptySelector && 392 container->IsElement()) { 393 // See whether we need to restyle the container due to :empty / 394 // :-moz-only-whitespace. 395 const bool wasEmpty = 396 !HasAnySignificantSibling(container->AsElement(), aChild); 397 if (wasEmpty) { 398 // FIXME(emilio): When coming from CharacterDataChanged this can restyle 399 // unnecessarily. Also can restyle unnecessarily if aChild is not 400 // significant anyway, though that's more unlikely. 401 RestyleForEmptyChange(container->AsElement()); 402 return; 403 } 404 } 405 406 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) { 407 RestyleWholeContainer(container, selectorFlags); 408 // Restyling the container is the most we can do here, so we're done. 409 return; 410 } 411 412 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) { 413 // Restyle all later siblings. 414 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) { 415 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling( 416 aChild->GetNextElementSibling(), /* aForceRestyleSiblings = */ true); 417 } else { 418 RestyleSiblingsStartingWith(aChild->GetNextSibling()); 419 } 420 } 421 422 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) { 423 MaybeRestyleForEdgeChildChange(container, aChild); 424 } 425 } 426 427 void RestyleManager::ContentWillBeRemoved(nsIContent* aOldChild) { 428 auto* container = aOldChild->GetParentNode(); 429 MOZ_ASSERT(container); 430 431 // Computed style data isn't useful for detached nodes, and we'll need to 432 // recompute it anyway if we ever insert the nodes back into a document. 433 if (auto* element = Element::FromNode(aOldChild)) { 434 RestyleManager::ClearServoDataFromSubtree(element); 435 // If this element is undisplayed or may have undisplayed descendants, we 436 // need to invalidate the cache, since there's the unlikely event of those 437 // elements getting destroyed and their addresses reused in a way that we 438 // look up the cache with their address for a different element before it's 439 // invalidated. 440 IncrementUndisplayedRestyleGeneration(); 441 } 442 443 // This is called with anonymous nodes explicitly by editor and view 444 // transitions code, which manage anon content manually. 445 // See similar code in ContentAppended. 446 if (MOZ_UNLIKELY(aOldChild->IsRootOfNativeAnonymousSubtree())) { 447 MOZ_ASSERT(!aOldChild->GetNextSibling(), "NAC doesn't have siblings"); 448 MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode), 449 "anonymous nodes should not be in child lists (bug 439258)"); 450 return; 451 } 452 453 if (aOldChild->IsElement()) { 454 StyleSet()->MaybeInvalidateForElementRemove(*aOldChild->AsElement()); 455 } 456 457 const auto selectorFlags = 458 container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags; 459 if (!selectorFlags) { 460 return; 461 } 462 463 // The container cannot be a document. 464 const bool containerIsElement = container->IsElement(); 465 MOZ_ASSERT(containerIsElement || container->IsShadowRoot()); 466 467 if (selectorFlags & NodeSelectorFlags::HasEmptySelector && 468 containerIsElement) { 469 // see whether we need to restyle the container 470 bool isEmpty = true; // :empty or :-moz-only-whitespace 471 for (nsIContent* child = container->GetFirstChild(); child; 472 child = child->GetNextSibling()) { 473 // We don't know whether we're testing :empty or :-moz-only-whitespace, 474 // so be conservative and assume :-moz-only-whitespace (i.e., make 475 // IsSignificantChild less likely to be true, and thus make us more 476 // likely to restyle). 477 if (child != aOldChild && nsStyleUtil::IsSignificantChild(child, false)) { 478 isEmpty = false; 479 break; 480 } 481 } 482 if (isEmpty && containerIsElement) { 483 RestyleForEmptyChange(container->AsElement()); 484 return; 485 } 486 } 487 488 // It is somewhat common to remove all nodes in a container from the 489 // beginning. If we're doing that, going through the 490 // HasSlowSelectorLaterSiblings code-path would be quadratic, so that's not 491 // amazing. Instead, we take the slower path (which also restyles the 492 // container) in that case. It restyles one more element, but it avoids the 493 // quadratic behavior. 494 const bool restyleWholeContainer = 495 (selectorFlags & NodeSelectorFlags::HasSlowSelector) || 496 (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings && 497 !aOldChild->GetPreviousSibling()); 498 499 if (restyleWholeContainer) { 500 RestyleWholeContainer(container, selectorFlags); 501 // Restyling the container is the most we can do here, so we're done. 502 return; 503 } 504 505 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) { 506 // Restyle all later siblings. 507 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) { 508 Element* nextSibling = aOldChild->GetNextElementSibling(); 509 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling( 510 nextSibling, /* aForceRestyleSiblings = */ true); 511 } else { 512 RestyleSiblingsStartingWith(aOldChild->GetNextSibling()); 513 } 514 } 515 516 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) { 517 const nsIContent* nextSibling = aOldChild->GetNextSibling(); 518 // restyle the now-first element child if it was after aOldChild 519 bool reachedFollowingSibling = false; 520 for (nsIContent* content = container->GetFirstChild(); content; 521 content = content->GetNextSibling()) { 522 if (content == aOldChild) { 523 // aOldChild is getting removed, so we don't want to account for it for 524 // the purposes of computing whether we're now the first / last child. 525 continue; 526 } 527 if (content == nextSibling) { 528 reachedFollowingSibling = true; 529 // do NOT continue here; we might want to restyle this node 530 } 531 if (content->IsElement()) { 532 if (reachedFollowingSibling) { 533 auto* element = content->AsElement(); 534 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), 535 nsChangeHint(0)); 536 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency( 537 *element, StyleRelativeSelectorNthEdgeInvalidateFor::First); 538 } 539 break; 540 } 541 } 542 // restyle the now-last element child if it was before aOldChild 543 reachedFollowingSibling = !nextSibling; 544 for (nsIContent* content = container->GetLastChild(); content; 545 content = content->GetPreviousSibling()) { 546 if (content == aOldChild) { 547 // See above. 548 continue; 549 } 550 if (content->IsElement()) { 551 if (reachedFollowingSibling) { 552 auto* element = content->AsElement(); 553 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), 554 nsChangeHint(0)); 555 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency( 556 *element, StyleRelativeSelectorNthEdgeInvalidateFor::Last); 557 } 558 break; 559 } 560 if (content == nextSibling) { 561 reachedFollowingSibling = true; 562 } 563 } 564 } 565 } 566 567 static bool StateChangeMayAffectFrame(const Element& aElement, 568 const nsIFrame& aFrame, 569 ElementState aStates) { 570 const bool brokenChanged = aStates.HasState(ElementState::BROKEN); 571 if (!brokenChanged) { 572 return false; 573 } 574 575 if (aFrame.IsGeneratedContentFrame()) { 576 // If it's other generated content, ignore state changes on it. 577 return aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage); 578 } 579 580 if (aElement.IsAnyOfHTMLElements(nsGkAtoms::object, nsGkAtoms::embed)) { 581 // Broken affects object fallback behavior. 582 return true; 583 } 584 585 const bool mightChange = [&] { 586 if (aElement.IsHTMLElement(nsGkAtoms::img)) { 587 return true; 588 } 589 const auto* input = HTMLInputElement::FromNode(aElement); 590 return input && input->ControlType() == FormControlType::InputImage; 591 }(); 592 593 if (!mightChange) { 594 return false; 595 } 596 597 const bool needsImageFrame = 598 nsImageFrame::ImageFrameTypeFor(aElement, *aFrame.Style()) != 599 nsImageFrame::ImageFrameType::None; 600 return needsImageFrame != aFrame.IsImageFrameOrSubclass(); 601 } 602 603 static bool RepaintForAppearance(nsIFrame& aFrame, const Element& aElement, 604 ElementState aStateMask) { 605 constexpr auto kThemingStates = 606 ElementState::HOVER | ElementState::ACTIVE | ElementState::FOCUSRING | 607 ElementState::DISABLED | ElementState::CHECKED | 608 ElementState::INDETERMINATE | ElementState::READONLY | 609 ElementState::FOCUS; 610 if (!aStateMask.HasAtLeastOneOfStates(kThemingStates)) { 611 return false; 612 } 613 614 if (aElement.IsAnyOfXULElements(nsGkAtoms::checkbox, nsGkAtoms::radio)) { 615 // The checkbox inside these elements inherit hover state and so on, see 616 // nsNativeTheme::GetContentState. 617 // FIXME(emilio): Would be nice to not have these hard-coded. 618 return true; 619 } 620 auto appearance = aFrame.StyleDisplay()->EffectiveAppearance(); 621 if (appearance == StyleAppearance::None) { 622 return false; 623 } 624 nsPresContext* pc = aFrame.PresContext(); 625 return pc->Theme()->ThemeSupportsWidget(pc, &aFrame, appearance); 626 } 627 628 /** 629 * Calculates the change hint and the restyle hint for a given content state 630 * change. 631 */ 632 static nsChangeHint ChangeForContentStateChange(const Element& aElement, 633 ElementState aStateMask) { 634 auto changeHint = nsChangeHint(0); 635 636 // Any change to a content state that affects which frames we construct 637 // must lead to a frame reconstruct here if we already have a frame. 638 // Note that we never decide through non-CSS means to not create frames 639 // based on content states, so if we already don't have a frame we don't 640 // need to force a reframe -- if it's needed, the HasStateDependentStyle 641 // call will handle things. 642 if (nsIFrame* primaryFrame = aElement.GetPrimaryFrame()) { 643 if (StateChangeMayAffectFrame(aElement, *primaryFrame, aStateMask)) { 644 return nsChangeHint_ReconstructFrame; 645 } 646 if (RepaintForAppearance(*primaryFrame, aElement, aStateMask)) { 647 changeHint |= nsChangeHint_RepaintFrame; 648 } 649 primaryFrame->ElementStateChanged(aStateMask); 650 } 651 652 if (aStateMask.HasState(ElementState::VISITED)) { 653 // Exposing information to the page about whether the link is 654 // visited or not isn't really something we can worry about here. 655 // FIXME: We could probably do this a bit better. 656 changeHint |= nsChangeHint_RepaintFrame; 657 } 658 659 // This changes the applicable text-transform in the editor root. 660 if (aStateMask.HasState(ElementState::REVEALED)) { 661 // This is the same change hint as tweaking text-transform. 662 changeHint |= NS_STYLE_HINT_REFLOW; 663 } 664 665 return changeHint; 666 } 667 668 #ifdef DEBUG 669 /* static */ 670 nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) { 671 nsCString result; 672 bool any = false; 673 const char* names[] = {"RepaintFrame", 674 "NeedReflow", 675 "ClearAncestorIntrinsics", 676 "ClearDescendantIntrinsics", 677 "NeedDirtyReflow", 678 "UpdateCursor", 679 "UpdateEffects", 680 "UpdateOpacityLayer", 681 "UpdateTransformLayer", 682 "ReconstructFrame", 683 "UpdateOverflow", 684 "UpdateSubtreeOverflow", 685 "UpdatePostTransformOverflow", 686 "UpdateParentOverflow", 687 "ChildrenOnlyTransform", 688 "RecomputePosition", 689 "UpdateContainingBlock", 690 "SchedulePaint", 691 "NeutralChange", 692 "InvalidateRenderingObservers", 693 "ReflowChangesSizeOrPosition", 694 "UpdateComputedBSize", 695 "UpdateUsesOpacity", 696 "UpdateBackgroundPosition", 697 "AddOrRemoveTransform", 698 "ScrollbarChange", 699 "UpdateTableCellSpans", 700 "VisibilityChange"}; 701 static_assert(nsChangeHint_AllHints == 702 static_cast<uint32_t>((1ull << std::size(names)) - 1), 703 "Name list doesn't match change hints."); 704 uint32_t hint = aHint & static_cast<uint32_t>((1ull << std::size(names)) - 1); 705 uint32_t rest = 706 aHint & ~static_cast<uint32_t>((1ull << std::size(names)) - 1); 707 if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) { 708 result.AppendLiteral("NS_STYLE_HINT_REFLOW"); 709 hint = hint & ~NS_STYLE_HINT_REFLOW; 710 any = true; 711 } else if ((hint & nsChangeHint_AllReflowHints) == 712 nsChangeHint_AllReflowHints) { 713 result.AppendLiteral("nsChangeHint_AllReflowHints"); 714 hint = hint & ~nsChangeHint_AllReflowHints; 715 any = true; 716 } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) { 717 result.AppendLiteral("NS_STYLE_HINT_VISUAL"); 718 hint = hint & ~NS_STYLE_HINT_VISUAL; 719 any = true; 720 } 721 for (uint32_t i = 0; i < std::size(names); i++) { 722 if (hint & (1u << i)) { 723 if (any) { 724 result.AppendLiteral(" | "); 725 } 726 result.AppendPrintf("nsChangeHint_%s", names[i]); 727 any = true; 728 } 729 } 730 if (rest) { 731 if (any) { 732 result.AppendLiteral(" | "); 733 } 734 result.AppendPrintf("0x%0x", rest); 735 } else { 736 if (!any) { 737 result.AppendLiteral("nsChangeHint(0)"); 738 } 739 } 740 return result; 741 } 742 #endif 743 744 /** 745 * Frame construction helpers follow. 746 */ 747 #ifdef DEBUG 748 static bool gInApplyRenderingChangeToTree = false; 749 #endif 750 751 /** 752 * The change hint should be some combination of nsChangeHint_RepaintFrame, 753 * nsChangeHint_UpdateOpacityLayer and nsChangeHint_SchedulePaint, nothing else. 754 */ 755 static void InvalidateDescendants(nsIFrame*, nsChangeHint); 756 757 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint); 758 759 /** 760 * This helper function is used to find the correct SVG frame to target when we 761 * encounter nsChangeHint_ChildrenOnlyTransform; needed since sometimes we end 762 * up handling that hint while processing hints for one of the SVG frame's 763 * ancestor frames. 764 * 765 * The reason that we sometimes end up trying to process the hint for an 766 * ancestor of the SVG frame that the hint is intended for is due to the way we 767 * process restyle events. ApplyRenderingChangeToTree adjusts the frame from 768 * the restyled element's principle frame to one of its ancestor frames based 769 * on what nsCSSRendering::FindBackground returns, since the background style 770 * may have been propagated up to an ancestor frame. Processing hints using an 771 * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is 772 * a special case since it is intended to update a specific frame. 773 */ 774 static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) { 775 if (aFrame->IsViewportFrame()) { 776 // This happens if the root-<svg> is fixed positioned, in which case we 777 // can't use aFrame->GetContent() to find the primary frame, since 778 // GetContent() returns nullptr for ViewportFrame. 779 aFrame = aFrame->PrincipalChildList().FirstChild(); 780 } 781 // For a ScrollContainerFrame, this will get the SVG frame that has the 782 // children-only transforms: 783 aFrame = aFrame->GetContent()->GetPrimaryFrame(); 784 if (aFrame->IsSVGOuterSVGFrame()) { 785 aFrame = aFrame->PrincipalChildList().FirstChild(); 786 MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(), 787 "Where is the SVGOuterSVGFrame's anon child??"); 788 } 789 MOZ_ASSERT(aFrame->IsSVGContainerFrame(), 790 "Children-only transforms only expected on SVG frames"); 791 return aFrame; 792 } 793 794 // This function tries to optimize a position style change by either 795 // moving aFrame or ignoring the style change when it's safe to do so. 796 // It returns true when that succeeds, otherwise it posts a reflow request 797 // and returns false. 798 static bool RecomputePosition(nsIFrame* aFrame) { 799 // It's pointless to move around frames that have never been reflowed or 800 // are dirty (i.e. they will be reflowed), or aren't affected by position 801 // styles. 802 if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 803 NS_FRAME_SVG_LAYOUT)) { 804 return true; 805 } 806 807 // Don't process position changes on table frames, since we already handle 808 // the dynamic position change on the table wrapper frame, and the 809 // reflow-based fallback code path also ignores positions on inner table 810 // frames. 811 if (aFrame->IsTableFrame()) { 812 return true; 813 } 814 815 const nsStyleDisplay* display = aFrame->StyleDisplay(); 816 // Changes to the offsets of a non-positioned element can safely be ignored. 817 if (display->mPosition == StylePositionProperty::Static) { 818 return true; 819 } 820 821 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 822 // If the frame has an intrinsic block-size, we resolve its 'auto' margins 823 // after doing layout, since we need to know the frame's block size. See 824 // AbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(). 825 // 826 // Since the size of the frame doesn't change, we could modify the below 827 // computation to compute the margin correctly without doing a full reflow, 828 // however we decided to try doing a full reflow for now. 829 if (aFrame->HasIntrinsicKeywordForBSize()) { 830 WritingMode wm = aFrame->GetWritingMode(); 831 const auto* styleMargin = aFrame->StyleMargin(); 832 const auto anchorResolutionParams = 833 AnchorPosResolutionParams::From(aFrame); 834 if (styleMargin->HasBlockAxisAuto(wm, anchorResolutionParams)) { 835 return false; 836 } 837 } 838 // Flexbox and Grid layout supports CSS Align and the optimizations below 839 // don't support that yet. 840 nsIFrame* ph = aFrame->GetPlaceholderFrame(); 841 if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) { 842 return false; 843 } 844 } 845 846 // If we need to reposition any descendant that depends on our static 847 // position, then we also can't take the optimized path. 848 // 849 // TODO(emilio): It may be worth trying to find them and try to call 850 // RecomputePosition on them too instead of disabling the optimization... 851 if (aFrame->DescendantMayDependOnItsStaticPosition()) { 852 return false; 853 } 854 855 aFrame->SchedulePaint(); 856 857 auto postPendingScrollAnchorOrResnap = [](nsIFrame* frame) { 858 if (frame->IsInScrollAnchorChain()) { 859 ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(frame); 860 frame->PresShell()->PostPendingScrollAnchorAdjustment(container); 861 } 862 863 // We need to trigger re-snapping to this content if we snapped to the 864 // content on the last scroll operation. 865 ScrollSnapUtils::PostPendingResnapIfNeededFor(frame); 866 }; 867 868 // For relative positioning, we can simply update the frame rect 869 if (display->IsRelativelyOrStickyPositionedStyle()) { 870 if (aFrame->IsGridItem()) { 871 // A grid item's CB is its grid area, not the parent frame content area 872 // as is assumed below. 873 return false; 874 } 875 // Move the frame 876 if (display->mPosition == StylePositionProperty::Sticky) { 877 // Update sticky positioning for an entire element at once, starting with 878 // the first continuation or ib-split sibling. 879 // It's rare that the frame we already have isn't already the first 880 // continuation or ib-split sibling, but it can happen when styles differ 881 // across continuations such as ::first-line or ::first-letter, and in 882 // those cases we will generally (but maybe not always) do the work twice. 883 nsIFrame* firstContinuation = 884 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 885 886 StickyScrollContainer::ComputeStickyOffsets(firstContinuation); 887 auto* ssc = StickyScrollContainer::GetOrCreateForFrame(firstContinuation); 888 if (ssc) { 889 ssc->PositionContinuations(firstContinuation); 890 } 891 } else { 892 MOZ_ASSERT(display->IsRelativelyPositionedStyle(), 893 "Unexpected type of positioning"); 894 for (nsIFrame* cont = aFrame; cont; 895 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 896 nsIFrame* cb = cont->GetContainingBlock(); 897 WritingMode wm = cb->GetWritingMode(); 898 const LogicalSize cbSize = cb->ContentSize(); 899 const LogicalMargin newLogicalOffsets = 900 ReflowInput::ComputeRelativeOffsets(wm, cont, cbSize); 901 const nsMargin newOffsets = newLogicalOffsets.GetPhysicalMargin(wm); 902 903 // ReflowInput::ApplyRelativePositioning would work here, but 904 // since we've already checked mPosition and aren't changing the frame's 905 // normal position, go ahead and add the offsets directly. 906 // First, we need to ensure that the normal position is stored though. 907 bool hasProperty; 908 nsPoint normalPosition = cont->GetNormalPosition(&hasProperty); 909 if (!hasProperty) { 910 cont->AddProperty(nsIFrame::NormalPositionProperty(), normalPosition); 911 } 912 cont->SetPosition(normalPosition + 913 nsPoint(newOffsets.left, newOffsets.top)); 914 } 915 } 916 917 postPendingScrollAnchorOrResnap(aFrame); 918 return true; 919 } 920 921 // For the absolute positioning case, set up a fake HTML reflow input for 922 // the frame, and then get the offsets and size from it. If the frame's size 923 // doesn't need to change, we can simply update the frame position. Otherwise 924 // we fall back to a reflow. 925 UniquePtr<gfxContext> rc = 926 aFrame->PresShell()->CreateReferenceRenderingContext(); 927 928 // Construct a bogus parent reflow input so that there's a usable reflow input 929 // for the containing block. 930 nsIFrame* parentFrame = aFrame->GetParent(); 931 WritingMode parentWM = parentFrame->GetWritingMode(); 932 WritingMode frameWM = aFrame->GetWritingMode(); 933 LogicalSize parentSize = parentFrame->GetLogicalSize(); 934 935 nsFrameState savedState = parentFrame->GetStateBits(); 936 ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc.get(), 937 parentSize); 938 parentFrame->RemoveStateBits(~nsFrameState(0)); 939 parentFrame->AddStateBits(savedState); 940 941 // The bogus parent state here was created with no parent state of its own, 942 // and therefore it won't have an mCBReflowInput set up. 943 // But we may need one (for InitCBReflowInput in a child state), so let's 944 // try to create one here for the cases where it will be needed. 945 Maybe<ReflowInput> cbReflowInput; 946 nsIFrame* cbFrame = parentFrame->GetContainingBlock(); 947 if (cbFrame && (aFrame->GetContainingBlock() != parentFrame || 948 parentFrame->IsTableFrame())) { 949 const auto cbWM = cbFrame->GetWritingMode(); 950 LogicalSize cbSize = cbFrame->GetLogicalSize(); 951 cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc.get(), cbSize); 952 cbReflowInput->SetComputedLogicalMargin( 953 cbWM, cbFrame->GetLogicalUsedMargin(cbWM)); 954 cbReflowInput->SetComputedLogicalPadding( 955 cbWM, cbFrame->GetLogicalUsedPadding(cbWM)); 956 cbReflowInput->SetComputedLogicalBorderPadding( 957 cbWM, cbFrame->GetLogicalUsedBorderAndPadding(cbWM)); 958 parentReflowInput.mCBReflowInput = cbReflowInput.ptr(); 959 } 960 961 NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_UNCONSTRAINEDSIZE && 962 parentSize.BSize(parentWM) != NS_UNCONSTRAINEDSIZE, 963 "parentSize should be valid"); 964 parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0)); 965 parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0)); 966 parentReflowInput.SetComputedLogicalMargin(parentWM, LogicalMargin(parentWM)); 967 968 parentReflowInput.SetComputedLogicalPadding( 969 parentWM, parentFrame->GetLogicalUsedPadding(parentWM)); 970 parentReflowInput.SetComputedLogicalBorderPadding( 971 parentWM, parentFrame->GetLogicalUsedBorderAndPadding(parentWM)); 972 LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM); 973 availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE; 974 975 ViewportFrame* viewport = do_QueryFrame(parentFrame); 976 nsSize cbSize = 977 viewport 978 ? viewport->GetContainingBlockAdjustedForScrollbars(parentReflowInput) 979 .Size() 980 : aFrame->GetContainingBlock()->GetSize(); 981 const nsMargin& parentBorder = 982 parentReflowInput.mStyleBorder->GetComputedBorder(); 983 cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom()); 984 LogicalSize lcbSize(frameWM, cbSize); 985 ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame, 986 availSize, Some(lcbSize)); 987 nscoord computedISize = reflowInput.ComputedISize(); 988 nscoord computedBSize = reflowInput.ComputedBSize(); 989 const auto frameBP = reflowInput.ComputedLogicalBorderPadding(frameWM); 990 computedISize += frameBP.IStartEnd(frameWM); 991 if (computedBSize != NS_UNCONSTRAINEDSIZE) { 992 computedBSize += frameBP.BStartEnd(frameWM); 993 } 994 LogicalSize logicalSize = aFrame->GetLogicalSize(frameWM); 995 nsSize size = aFrame->GetSize(); 996 // The RecomputePosition hint is not used if any offset changed between auto 997 // and non-auto. If computedSize.height == NS_UNCONSTRAINEDSIZE then the new 998 // element height will be its intrinsic height, and since 'top' and 'bottom''s 999 // auto-ness hasn't changed, the old height must also be its intrinsic 1000 // height, which we can assume hasn't changed (or reflow would have 1001 // been triggered). 1002 if (computedISize == logicalSize.ISize(frameWM) && 1003 (computedBSize == NS_UNCONSTRAINEDSIZE || 1004 computedBSize == logicalSize.BSize(frameWM))) { 1005 // If we're solving for 'left' or 'top', then compute it here, in order to 1006 // match the reflow code path. 1007 // 1008 // TODO(emilio): It'd be nice if this did logical math instead, but it seems 1009 // to me the math should work out on vertical writing modes as well. See Bug 1010 // 1675861 for some hints. 1011 const nsMargin offset = reflowInput.ComputedPhysicalOffsets(); 1012 const nsMargin margin = reflowInput.ComputedPhysicalMargin(); 1013 1014 nscoord left = offset.left; 1015 if (left == NS_AUTOOFFSET) { 1016 left = 1017 cbSize.width - offset.right - margin.right - size.width - margin.left; 1018 } 1019 1020 nscoord top = offset.top; 1021 if (top == NS_AUTOOFFSET) { 1022 top = cbSize.height - offset.bottom - margin.bottom - size.height - 1023 margin.top; 1024 } 1025 1026 // Move the frame 1027 nsPoint pos(parentBorder.left + left + margin.left, 1028 parentBorder.top + top + margin.top); 1029 aFrame->SetPosition(pos); 1030 1031 postPendingScrollAnchorOrResnap(aFrame); 1032 return true; 1033 } 1034 1035 // Fall back to a reflow 1036 return false; 1037 } 1038 1039 /** 1040 * Return true if aFrame's subtree has placeholders for out-of-flow content 1041 * that would be affected due to the change to 1042 * `aPossiblyChangingContainingBlock` (and thus would need to get reframed). 1043 * 1044 * In particular, this function returns true if there are placeholders whose OOF 1045 * frames may need to be reparented (via reframing) as a result of whatever 1046 * change actually happened. 1047 * 1048 * The `aIs{Abs,Fixed}PosContainingBlock` params represent whether 1049 * `aPossiblyChangingContainingBlock` is a containing block for abs pos / fixed 1050 * pos stuff, respectively, for the _new_ style that the frame already has, not 1051 * the old one. 1052 */ 1053 static bool ContainingBlockChangeAffectsDescendants( 1054 nsIFrame* aPossiblyChangingContainingBlock, nsIFrame* aFrame, 1055 bool aIsAbsPosContainingBlock, bool aIsFixedPosContainingBlock) { 1056 // All fixed-pos containing blocks should also be abs-pos containing blocks. 1057 MOZ_ASSERT_IF(aIsFixedPosContainingBlock, aIsAbsPosContainingBlock); 1058 1059 for (const auto& childList : aFrame->ChildLists()) { 1060 for (nsIFrame* f : childList.mList) { 1061 if (f->IsPlaceholderFrame()) { 1062 nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); 1063 // If SVG text frames could appear here, they could confuse us since 1064 // they ignore their position style ... but they can't. 1065 NS_ASSERTION(!outOfFlow->IsInSVGTextSubtree(), 1066 "SVG text frames can't be out of flow"); 1067 // Top-layer frames don't change containing block based on direct 1068 // ancestors. 1069 auto* display = outOfFlow->StyleDisplay(); 1070 if (display->IsAbsolutelyPositionedStyle() && 1071 display->mTopLayer == StyleTopLayer::None) { 1072 const bool isContainingBlock = 1073 aIsFixedPosContainingBlock || 1074 (aIsAbsPosContainingBlock && 1075 display->mPosition == StylePositionProperty::Absolute); 1076 // NOTE(emilio): aPossiblyChangingContainingBlock is guaranteed to be 1077 // a first continuation, see the assertion in the caller. 1078 nsIFrame* parent = outOfFlow->GetParent()->FirstContinuation(); 1079 if (isContainingBlock) { 1080 // If we are becoming a containing block, we only need to reframe if 1081 // this oof's current containing block is an ancestor of the new 1082 // frame. 1083 if (parent != aPossiblyChangingContainingBlock && 1084 nsLayoutUtils::IsProperAncestorFrame( 1085 parent, aPossiblyChangingContainingBlock)) { 1086 return true; 1087 } 1088 } else { 1089 // If we are not a containing block anymore, we only need to reframe 1090 // if we are the current containing block of the oof frame. 1091 if (parent == aPossiblyChangingContainingBlock) { 1092 return true; 1093 } 1094 } 1095 } 1096 } 1097 // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or 1098 // f->IsFixedPosContainingBlock() here. However, that would only 1099 // be testing the *new* style of the frame, which might exclude 1100 // descendants that currently have this frame as an abs-pos 1101 // containing block. Taking the codepath where we don't reframe 1102 // could lead to an unsafe call to 1103 // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed 1104 // the descendant and taken it off the absolute list. 1105 if (ContainingBlockChangeAffectsDescendants( 1106 aPossiblyChangingContainingBlock, f, aIsAbsPosContainingBlock, 1107 aIsFixedPosContainingBlock)) { 1108 return true; 1109 } 1110 } 1111 } 1112 return false; 1113 } 1114 1115 // Returns the frame that would serve as the containing block for aFrame's 1116 // positioned descendants, if aFrame had styles to make it a CB for such 1117 // descendants. (Typically this is just aFrame itself, or its insertion frame). 1118 // 1119 // Returns nullptr if this frame can't be easily determined. 1120 static nsIFrame* ContainingBlockForFrame(nsIFrame* aFrame) { 1121 if (aFrame->IsFieldSetFrame()) { 1122 // FIXME: This should be easily implementable. 1123 return nullptr; 1124 } 1125 nsIFrame* insertionFrame = aFrame->GetContentInsertionFrame(); 1126 if (insertionFrame == aFrame) { 1127 return insertionFrame; 1128 } 1129 // Generally frames with a different insertion frame are hard to deal with, 1130 // but scrollframes are easy because the containing block is just the 1131 // insertion frame. 1132 if (aFrame->IsScrollContainerFrame()) { 1133 return insertionFrame; 1134 } 1135 // Table cell frames are also easy because the containing block is 1136 // the frame itself. 1137 if (aFrame->IsTableCellFrame()) { 1138 return aFrame; 1139 } 1140 return nullptr; 1141 } 1142 1143 static bool NeedToReframeToUpdateContainingBlock(nsIFrame* aFrame, 1144 nsIFrame* aMaybeChangingCB) { 1145 // NOTE: This looks at the new style. 1146 const bool isFixedContainingBlock = aFrame->IsFixedPosContainingBlock(); 1147 MOZ_ASSERT_IF(isFixedContainingBlock, aFrame->IsAbsPosContainingBlock()); 1148 1149 const bool isAbsPosContainingBlock = 1150 isFixedContainingBlock || aFrame->IsAbsPosContainingBlock(); 1151 1152 for (nsIFrame* f = aFrame; f; 1153 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { 1154 if (ContainingBlockChangeAffectsDescendants(aMaybeChangingCB, f, 1155 isAbsPosContainingBlock, 1156 isFixedContainingBlock)) { 1157 return true; 1158 } 1159 } 1160 return false; 1161 } 1162 1163 static void DoApplyRenderingChangeToTree(nsIFrame* aFrame, 1164 nsChangeHint aChange) { 1165 MOZ_ASSERT(gInApplyRenderingChangeToTree, 1166 "should only be called within ApplyRenderingChangeToTree"); 1167 1168 for (; aFrame; 1169 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) { 1170 // Invalidate and sync views on all descendant frames, following 1171 // placeholders. We don't need to update transforms in 1172 // InvalidateDescendants, because there can't be any 1173 // out-of-flows or popups that need to be transformed; all out-of-flow 1174 // descendants of the transformed element must also be descendants of the 1175 // transformed frame. 1176 InvalidateDescendants( 1177 aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame | 1178 nsChangeHint_UpdateOpacityLayer | 1179 nsChangeHint_SchedulePaint))); 1180 // This must be set to true if the rendering change needs to 1181 // invalidate content. If it's false, a composite-only paint 1182 // (empty transaction) will be scheduled. 1183 bool needInvalidatingPaint = false; 1184 1185 // if frame has view, will already be invalidated 1186 if (aChange & nsChangeHint_RepaintFrame) { 1187 // Note that this whole block will be skipped when painting is suppressed 1188 // (due to our caller ApplyRendingChangeToTree() discarding the 1189 // nsChangeHint_RepaintFrame hint). If you add handling for any other 1190 // hints within this block, be sure that they too should be ignored when 1191 // painting is suppressed. 1192 needInvalidatingPaint = true; 1193 aFrame->InvalidateFrameSubtree(); 1194 if ((aChange & nsChangeHint_UpdateEffects) && 1195 aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 1196 // Need to update our overflow rects: 1197 SVGUtils::ScheduleReflowSVG(aFrame); 1198 } 1199 1200 ActiveLayerTracker::NotifyNeedsRepaint(aFrame); 1201 } 1202 if (aChange & nsChangeHint_UpdateOpacityLayer) { 1203 // FIXME/bug 796697: we can get away with empty transactions for 1204 // opacity updates in many cases. 1205 needInvalidatingPaint = true; 1206 1207 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity); 1208 if (SVGIntegrationUtils::UsingEffectsForFrame(aFrame)) { 1209 // SVG effects paints the opacity without using 1210 // nsDisplayOpacity. We need to invalidate manually. 1211 aFrame->InvalidateFrameSubtree(); 1212 } 1213 } 1214 if ((aChange & nsChangeHint_UpdateTransformLayer) && 1215 aFrame->IsTransformed()) { 1216 // Note: All the transform-like properties should map to the same 1217 // layer activity index, so does the restyle count. Therefore, using 1218 // eCSSProperty_transform should be fine. 1219 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform); 1220 needInvalidatingPaint = true; 1221 } 1222 if (aChange & nsChangeHint_ChildrenOnlyTransform) { 1223 needInvalidatingPaint = true; 1224 nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame) 1225 ->PrincipalChildList() 1226 .FirstChild(); 1227 for (; childFrame; childFrame = childFrame->GetNextSibling()) { 1228 // Note: All the transform-like properties should map to the same 1229 // layer activity index, so does the restyle count. Therefore, using 1230 // eCSSProperty_transform should be fine. 1231 ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform); 1232 } 1233 } 1234 if (aChange & nsChangeHint_SchedulePaint) { 1235 needInvalidatingPaint = true; 1236 } 1237 aFrame->SchedulePaint(needInvalidatingPaint 1238 ? nsIFrame::PAINT_DEFAULT 1239 : nsIFrame::PAINT_COMPOSITE_ONLY); 1240 } 1241 } 1242 1243 static void InvalidateDescendants(nsIFrame* aFrame, nsChangeHint aChange) { 1244 MOZ_ASSERT(gInApplyRenderingChangeToTree, 1245 "should only be called within ApplyRenderingChangeToTree"); 1246 1247 NS_ASSERTION(nsChangeHint_size_t(aChange) == 1248 (aChange & (nsChangeHint_RepaintFrame | 1249 nsChangeHint_UpdateOpacityLayer | 1250 nsChangeHint_SchedulePaint)), 1251 "Invalid change flag"); 1252 1253 for (const auto& [list, listID] : aFrame->ChildLists()) { 1254 for (nsIFrame* child : list) { 1255 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 1256 // only do frames that don't have placeholders 1257 if (child->IsPlaceholderFrame()) { 1258 // do the out-of-flow frame and its continuations 1259 nsIFrame* outOfFlowFrame = 1260 nsPlaceholderFrame::GetRealFrameForPlaceholder(child); 1261 DoApplyRenderingChangeToTree(outOfFlowFrame, aChange); 1262 } else { // regular frame 1263 InvalidateDescendants(child, aChange); 1264 } 1265 } 1266 } 1267 } 1268 } 1269 1270 static void ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame, 1271 nsChangeHint aChange) { 1272 // We check StyleDisplay()->HasTransformStyle() in addition to checking 1273 // IsTransformed() since we can get here for some frames that don't support 1274 // CSS transforms, and table frames, which are their own odd-ball, since the 1275 // transform is handled by their wrapper, which _also_ gets a separate hint. 1276 NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) || 1277 aFrame->IsTransformed() || 1278 aFrame->StyleDisplay()->HasTransformStyle(), 1279 "Unexpected UpdateTransformLayer hint"); 1280 1281 if (aPresShell->IsPaintingSuppressed()) { 1282 // Don't allow synchronous rendering changes when painting is turned off. 1283 aChange &= ~nsChangeHint_RepaintFrame; 1284 if (!aChange) { 1285 return; 1286 } 1287 } 1288 1289 // Trigger rendering updates by damaging this frame and any 1290 // continuations of this frame. 1291 #ifdef DEBUG 1292 gInApplyRenderingChangeToTree = true; 1293 #endif 1294 if (aChange & nsChangeHint_RepaintFrame) { 1295 // If the frame is the primary frame of either the body element or 1296 // the html element, we propagate the repaint change hint to the 1297 // viewport. This is necessary for background and scrollbar colors 1298 // propagation. 1299 if (aFrame->ShouldPropagateRepaintsToRoot()) { 1300 nsIFrame* rootFrame = aPresShell->GetRootFrame(); 1301 MOZ_ASSERT(rootFrame, "No root frame?"); 1302 DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame); 1303 aChange &= ~nsChangeHint_RepaintFrame; 1304 if (!aChange) { 1305 return; 1306 } 1307 } 1308 } 1309 DoApplyRenderingChangeToTree(aFrame, aChange); 1310 #ifdef DEBUG 1311 gInApplyRenderingChangeToTree = false; 1312 #endif 1313 } 1314 1315 static void AddSubtreeToOverflowTracker( 1316 nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) { 1317 if (aFrame->FrameMaintainsOverflow()) { 1318 aOverflowChangedTracker.AddFrame(aFrame, 1319 OverflowChangedTracker::CHILDREN_CHANGED); 1320 } 1321 for (const auto& childList : aFrame->ChildLists()) { 1322 for (nsIFrame* child : childList.mList) { 1323 AddSubtreeToOverflowTracker(child, aOverflowChangedTracker); 1324 } 1325 } 1326 } 1327 1328 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) { 1329 IntrinsicDirty dirtyType; 1330 if (aHint & nsChangeHint_ClearDescendantIntrinsics) { 1331 NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics, 1332 "Please read the comments in nsChangeHint.h"); 1333 NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow, 1334 "ClearDescendantIntrinsics requires NeedDirtyReflow"); 1335 dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants; 1336 } else if ((aHint & nsChangeHint_UpdateComputedBSize) && 1337 aFrame->HasAnyStateBits( 1338 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { 1339 dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants; 1340 } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) { 1341 dirtyType = IntrinsicDirty::FrameAndAncestors; 1342 } else { 1343 dirtyType = IntrinsicDirty::None; 1344 } 1345 1346 if (aHint & nsChangeHint_UpdateComputedBSize) { 1347 aFrame->SetHasBSizeChange(true); 1348 } 1349 1350 nsFrameState dirtyBits; 1351 if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 1352 dirtyBits = nsFrameState(0); 1353 } else if ((aHint & nsChangeHint_NeedDirtyReflow) || 1354 dirtyType == IntrinsicDirty::FrameAncestorsAndDescendants) { 1355 dirtyBits = NS_FRAME_IS_DIRTY; 1356 } else { 1357 dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN; 1358 } 1359 1360 // If we're not going to clear any intrinsic sizes on the frames, and 1361 // there are no dirty bits to set, then there's nothing to do. 1362 if (dirtyType == IntrinsicDirty::None && !dirtyBits) { 1363 return; 1364 } 1365 1366 ReflowRootHandling rootHandling; 1367 if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) { 1368 rootHandling = ReflowRootHandling::PositionOrSizeChange; 1369 } else { 1370 rootHandling = ReflowRootHandling::NoPositionOrSizeChange; 1371 } 1372 1373 do { 1374 aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits, 1375 rootHandling); 1376 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); 1377 } while (aFrame); 1378 } 1379 1380 // Get the next sibling which might have a frame. This only considers siblings 1381 // that stylo post-traversal looks at, so only elements and text. In 1382 // particular, it ignores comments. 1383 static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) { 1384 for (nsIContent* next = aContent->GetNextSibling(); next; 1385 next = next->GetNextSibling()) { 1386 if (next->IsElement() || next->IsText()) { 1387 return next; 1388 } 1389 } 1390 1391 return nullptr; 1392 } 1393 1394 // If |aFrame| is dirty or has dirty children, then we can skip updating 1395 // overflows since that will happen when it's reflowed. 1396 static inline bool CanSkipOverflowUpdates(const nsIFrame* aFrame) { 1397 return aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | 1398 NS_FRAME_HAS_DIRTY_CHILDREN); 1399 } 1400 1401 static inline void TryToDealWithScrollbarChange(nsChangeHint& aHint, 1402 nsIContent* aContent, 1403 nsIFrame* aFrame, 1404 nsPresContext* aPc) { 1405 if (!(aHint & nsChangeHint_ScrollbarChange)) { 1406 return; 1407 } 1408 aHint &= ~nsChangeHint_ScrollbarChange; 1409 if (aHint & nsChangeHint_ReconstructFrame) { 1410 return; 1411 } 1412 1413 MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame"); 1414 1415 const bool isRoot = aContent->IsInUncomposedDoc() && !aContent->GetParent(); 1416 1417 // Only bother with this if we're the root or the body element, since: 1418 // (a) It'd be *expensive* to reframe these particular nodes. They're 1419 // at the root, so reframing would mean rebuilding the world. 1420 // (b) It's often *unnecessary* to reframe for "overflow" changes on 1421 // these particular nodes. In general, the only reason we reframe 1422 // for "overflow" changes is so we can construct (or destroy) a 1423 // scrollframe & scrollbars -- and the html/body nodes often don't 1424 // need their own scrollframe/scrollbars because they coopt the ones 1425 // on the viewport (which always exist). So depending on whether 1426 // that's happening, we can skip the reframe for these nodes. 1427 if (isRoot || aContent->IsHTMLElement(nsGkAtoms::body)) { 1428 // If the restyled element provided/provides the scrollbar styles for 1429 // the viewport before and/or after this restyle, AND it's not coopting 1430 // that responsibility from some other element (which would need 1431 // reconstruction to make its own scrollframe now), THEN: we don't need 1432 // to reconstruct - we can just reflow, because no scrollframe is being 1433 // added/removed. 1434 Element* prevOverride = aPc->GetViewportScrollStylesOverrideElement(); 1435 Element* newOverride = aPc->UpdateViewportScrollStylesOverride(); 1436 1437 const auto ProvidesScrollbarStyles = [&](nsIContent* aOverride) { 1438 if (aOverride) { 1439 return aOverride == aContent; 1440 } 1441 return isRoot; 1442 }; 1443 1444 if (ProvidesScrollbarStyles(prevOverride) || 1445 ProvidesScrollbarStyles(newOverride)) { 1446 // If we get here, the restyled element provided the scrollbar styles 1447 // for viewport before this restyle, OR it will provide them after. 1448 if (!prevOverride || !newOverride || prevOverride == newOverride) { 1449 // If we get here, the restyled element is NOT replacing (or being 1450 // replaced by) some other element as the viewport's 1451 // scrollbar-styles provider. (If it were, we'd potentially need to 1452 // reframe to create a dedicated scrollframe for whichever element 1453 // is being booted from providing viewport scrollbar styles.) 1454 // 1455 // Under these conditions, we're OK to assume that this "overflow" 1456 // change only impacts the root viewport's scrollframe, which 1457 // already exists, so we can simply reflow instead of reframing. 1458 if (ScrollContainerFrame* sf = do_QueryFrame(aFrame)) { 1459 sf->MarkScrollbarsDirtyForReflow(); 1460 } else if (ScrollContainerFrame* sf = 1461 aPc->PresShell()->GetRootScrollContainerFrame()) { 1462 sf->MarkScrollbarsDirtyForReflow(); 1463 } 1464 aHint |= nsChangeHint_ReflowHintsForScrollbarChange; 1465 } else { 1466 // If we changed the override element, we need to reconstruct as the old 1467 // override element might start / stop being scrollable. 1468 aHint |= nsChangeHint_ReconstructFrame; 1469 } 1470 return; 1471 } 1472 } 1473 1474 const bool scrollable = aFrame->StyleDisplay()->IsScrollableOverflow(); 1475 if (ScrollContainerFrame* sf = do_QueryFrame(aFrame)) { 1476 if (scrollable && sf->HasAllNeededScrollbars()) { 1477 sf->MarkScrollbarsDirtyForReflow(); 1478 // Once we've created scrollbars for a frame, don't bother reconstructing 1479 // it just to remove them if we still need a scroll frame. 1480 aHint |= nsChangeHint_ReflowHintsForScrollbarChange; 1481 return; 1482 } 1483 } else if (aFrame->IsTextInputFrame()) { 1484 // input / textarea for the most part don't honor overflow themselves, the 1485 // editor root will deal with the change if needed. 1486 // However the textarea intrinsic size relies on GetDesiredScrollbarSizes(), 1487 // so we need to reflow the textarea itself, not just the inner control. 1488 aHint |= nsChangeHint_ReflowHintsForScrollbarChange; 1489 return; 1490 } else if (!scrollable) { 1491 // Something changed, but we don't have nor will have a scroll frame, 1492 // there's nothing to do here. 1493 return; 1494 } 1495 1496 // Oh well, we couldn't optimize it out, just reconstruct frames for the 1497 // subtree. 1498 aHint |= nsChangeHint_ReconstructFrame; 1499 } 1500 1501 static void TryToHandleContainingBlockChange(nsChangeHint& aHint, 1502 nsIFrame* aFrame) { 1503 if (!(aHint & nsChangeHint_UpdateContainingBlock)) { 1504 return; 1505 } 1506 if (aHint & nsChangeHint_ReconstructFrame) { 1507 return; 1508 } 1509 MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame"); 1510 nsIFrame* containingBlock = ContainingBlockForFrame(aFrame); 1511 if (!containingBlock || 1512 NeedToReframeToUpdateContainingBlock(aFrame, containingBlock)) { 1513 // The frame has positioned children that need to be reparented, or it can't 1514 // easily be converted to/from being an abs-pos container correctly. 1515 aHint |= nsChangeHint_ReconstructFrame; 1516 return; 1517 } 1518 const bool isCb = aFrame->IsAbsPosContainingBlock(); 1519 1520 // The absolute container should be containingBlock. 1521 for (nsIFrame* cont = containingBlock; cont; 1522 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 1523 // Normally frame construction would set state bits as needed, 1524 // but we're not going to reconstruct the frame so we need to set 1525 // them. It's because we need to set this state on each affected frame 1526 // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up 1527 // to ancestors (i.e. it can't be an change hint that is handled for 1528 // descendants). 1529 if (isCb) { 1530 if (!cont->IsAbsoluteContainer() && 1531 cont->HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { 1532 cont->MarkAsAbsoluteContainingBlock(); 1533 } 1534 } else if (cont->IsAbsoluteContainer()) { 1535 if (cont->HasAbsolutelyPositionedChildren()) { 1536 // If |cont| still has absolutely positioned children, 1537 // we can't call MarkAsNotAbsoluteContainingBlock. This 1538 // will remove a frame list that still has children in 1539 // it that we need to keep track of. 1540 // The optimization of removing it isn't particularly 1541 // important, although it does mean we skip some tests. 1542 NS_WARNING("skipping removal of absolute containing block"); 1543 } else { 1544 cont->MarkAsNotAbsoluteContainingBlock(); 1545 } 1546 } 1547 } 1548 } 1549 1550 void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) { 1551 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), 1552 "Someone forgot a script blocker"); 1553 1554 // See bug 1378219 comment 9: 1555 // Recursive calls here are a bit worrying, but apparently do happen in the 1556 // wild (although not currently in any of our automated tests). Try to get a 1557 // stack from Nightly/Dev channel to figure out what's going on and whether 1558 // it's OK. 1559 MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion"); 1560 1561 if (aChangeList.IsEmpty()) { 1562 return; 1563 } 1564 1565 // If mDestroyedFrames is null, we want to create a new hashtable here 1566 // and destroy it on exit; but if it is already non-null (because we're in 1567 // a recursive call), we will continue to use the existing table to 1568 // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit. 1569 // We use a MaybeClearDestroyedFrames helper to conditionally reset the 1570 // mDestroyedFrames pointer when this method returns. 1571 typedef decltype(mDestroyedFrames) DestroyedFramesT; 1572 class MOZ_RAII MaybeClearDestroyedFrames { 1573 private: 1574 DestroyedFramesT& mDestroyedFramesRef; // ref to caller's mDestroyedFrames 1575 const bool mResetOnDestruction; 1576 1577 public: 1578 explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget) 1579 : mDestroyedFramesRef(aTarget), 1580 mResetOnDestruction(!aTarget) // reset only if target starts out null 1581 {} 1582 ~MaybeClearDestroyedFrames() { 1583 if (mResetOnDestruction) { 1584 mDestroyedFramesRef.reset(nullptr); 1585 } 1586 } 1587 }; 1588 1589 MaybeClearDestroyedFrames maybeClear(mDestroyedFrames); 1590 if (!mDestroyedFrames) { 1591 mDestroyedFrames = MakeUnique<nsTHashSet<const nsIFrame*>>(); 1592 } 1593 1594 AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", LAYOUT); 1595 1596 nsPresContext* presContext = PresContext(); 1597 nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor(); 1598 1599 bool didUpdateCursor = false; 1600 1601 for (size_t i = 0; i < aChangeList.Length(); ++i) { 1602 // Collect and coalesce adjacent siblings for lazy frame construction. 1603 // Eventually it would be even better to make RecreateFramesForContent 1604 // accept a range and coalesce all adjacent reconstructs (bug 1344139). 1605 size_t lazyRangeStart = i; 1606 while (i < aChangeList.Length() && aChangeList[i].mContent && 1607 aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) && 1608 (i == lazyRangeStart || 1609 NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) == 1610 aChangeList[i].mContent)) { 1611 MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame); 1612 MOZ_ASSERT(!aChangeList[i].mFrame); 1613 ++i; 1614 } 1615 if (i != lazyRangeStart) { 1616 nsIContent* start = aChangeList[lazyRangeStart].mContent; 1617 nsIContent* end = 1618 NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent); 1619 if (!end) { 1620 frameConstructor->ContentAppended( 1621 start, nsCSSFrameConstructor::InsertionKind::Sync); 1622 } else { 1623 frameConstructor->ContentRangeInserted( 1624 start, end, nsCSSFrameConstructor::InsertionKind::Sync); 1625 } 1626 } 1627 for (size_t j = lazyRangeStart; j < i; ++j) { 1628 MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() || 1629 !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME)); 1630 } 1631 if (i == aChangeList.Length()) { 1632 break; 1633 } 1634 1635 const nsStyleChangeData& data = aChangeList[i]; 1636 nsIFrame* frame = data.mFrame; 1637 nsIContent* content = data.mContent; 1638 nsChangeHint hint = data.mHint; 1639 bool didReflowThisFrame = false; 1640 1641 NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) || 1642 (hint & nsChangeHint_NeedReflow), 1643 "Reflow hint bits set without actually asking for a reflow"); 1644 1645 // skip any frame that has been destroyed due to a ripple effect 1646 if (frame && mDestroyedFrames->Contains(frame)) { 1647 continue; 1648 } 1649 1650 if (frame && frame->GetContent() != content) { 1651 // XXXbz this is due to image maps messing with the primary frame of 1652 // <area>s. See bug 135040. Remove this block once that's fixed. 1653 frame = nullptr; 1654 if (!(hint & nsChangeHint_ReconstructFrame)) { 1655 continue; 1656 } 1657 } 1658 1659 TryToDealWithScrollbarChange(hint, content, frame, presContext); 1660 TryToHandleContainingBlockChange(hint, frame); 1661 1662 if (hint & nsChangeHint_ReconstructFrame) { 1663 // If we ever start passing true here, be careful of restyles 1664 // that involve a reframe and animations. In particular, if the 1665 // restyle we're processing here is an animation restyle, but 1666 // the style resolution we will do for the frame construction 1667 // happens async when we're not in an animation restyle already, 1668 // problems could arise. 1669 // We could also have problems with triggering of CSS transitions 1670 // on elements whose frames are reconstructed, since we depend on 1671 // the reconstruction happening synchronously. 1672 frameConstructor->RecreateFramesForContent( 1673 content, nsCSSFrameConstructor::InsertionKind::Sync); 1674 continue; 1675 } 1676 1677 MOZ_ASSERT(frame, "This shouldn't happen"); 1678 if (hint & nsChangeHint_AddOrRemoveTransform) { 1679 for (nsIFrame* cont = frame; cont; 1680 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 1681 if (cont->StyleDisplay()->HasTransform(cont)) { 1682 cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); 1683 } 1684 // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be 1685 // transformed by other means. It's OK to have the bit even if it's 1686 // not needed. 1687 } 1688 // When dropping a running transform animation we will first add an 1689 // nsChangeHint_UpdateTransformLayer hint as part of the animation-only 1690 // restyle. During the subsequent regular restyle, if the animation was 1691 // the only reason the element had any transform applied, we will add 1692 // nsChangeHint_AddOrRemoveTransform as part of the regular restyle. 1693 // 1694 // With the Gecko backend, these two change hints are processed 1695 // after each restyle but when using the Servo backend they accumulate 1696 // and are processed together after we have already removed the 1697 // transform as part of the regular restyle. Since we don't actually 1698 // need the nsChangeHint_UpdateTransformLayer hint if we already have 1699 // a nsChangeHint_AddOrRemoveTransform hint, and since we 1700 // will fail an assertion in ApplyRenderingChangeToTree if we try 1701 // specify nsChangeHint_UpdateTransformLayer but don't have any 1702 // transform style, we just drop the unneeded hint here. 1703 hint &= ~nsChangeHint_UpdateTransformLayer; 1704 } 1705 1706 if (!frame->FrameMaintainsOverflow()) { 1707 // frame does not maintain overflow rects, so avoid calling 1708 // FinishAndStoreOverflow on it: 1709 hint &= 1710 ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform | 1711 nsChangeHint_UpdatePostTransformOverflow | 1712 nsChangeHint_UpdateParentOverflow | 1713 nsChangeHint_UpdateSubtreeOverflow); 1714 } 1715 1716 if (!frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) { 1717 // Frame can not be transformed, and thus a change in transform will 1718 // have no effect and we should not use either 1719 // nsChangeHint_UpdatePostTransformOverflow or 1720 // nsChangeHint_UpdateTransformLayerhint. 1721 hint &= ~(nsChangeHint_UpdatePostTransformOverflow | 1722 nsChangeHint_UpdateTransformLayer); 1723 } 1724 1725 if ((hint & nsChangeHint_UpdateEffects) && 1726 frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) { 1727 SVGObserverUtils::UpdateEffects(frame); 1728 } 1729 if ((hint & nsChangeHint_InvalidateRenderingObservers) || 1730 ((hint & nsChangeHint_UpdateOpacityLayer) && 1731 frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT))) { 1732 SVGObserverUtils::InvalidateRenderingObservers(frame); 1733 frame->SchedulePaint(); 1734 } 1735 if (hint & nsChangeHint_NeedReflow) { 1736 StyleChangeReflow(frame, hint); 1737 didReflowThisFrame = true; 1738 } 1739 1740 // Here we need to propagate repaint frame change hint instead of update 1741 // opacity layer change hint when we do opacity optimization for SVG. 1742 // We can't do it in nsStyleEffects::CalcDifference() just like we do 1743 // for the optimization for 0.99 over opacity values since we have no way 1744 // to call SVGUtils::CanOptimizeOpacity() there. 1745 if ((hint & nsChangeHint_UpdateOpacityLayer) && 1746 SVGUtils::CanOptimizeOpacity(frame)) { 1747 hint &= ~nsChangeHint_UpdateOpacityLayer; 1748 hint |= nsChangeHint_RepaintFrame; 1749 } 1750 1751 if ((hint & nsChangeHint_UpdateUsesOpacity) && frame->IsTablePart()) { 1752 NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer, 1753 "should only return UpdateUsesOpacity hint " 1754 "when also returning UpdateOpacityLayer hint"); 1755 // When an internal table part (including cells) changes between 1756 // having opacity 1 and non-1, it changes whether its 1757 // backgrounds (and those of table parts inside of it) are 1758 // painted as part of the table's nsDisplayTableBorderBackground 1759 // display item, or part of its own display item. That requires 1760 // invalidation, so change UpdateOpacityLayer to RepaintFrame. 1761 hint &= ~nsChangeHint_UpdateOpacityLayer; 1762 hint |= nsChangeHint_RepaintFrame; 1763 } 1764 1765 // Opacity disables preserve-3d, so if we toggle it, then we also need 1766 // to update the overflow areas of all potentially affected frames. 1767 if ((hint & nsChangeHint_UpdateUsesOpacity) && 1768 frame->StyleDisplay()->mTransformStyle == 1769 StyleTransformStyle::Preserve3d) { 1770 hint |= nsChangeHint_UpdateSubtreeOverflow; 1771 } 1772 1773 if (hint & nsChangeHint_UpdateBackgroundPosition) { 1774 // For most frame types, DLBI can detect background position changes, 1775 // so we only need to schedule a paint. 1776 hint |= nsChangeHint_SchedulePaint; 1777 if (frame->IsTablePart() || frame->IsMathMLFrame()) { 1778 // Table parts and MathML frames don't build display items for their 1779 // backgrounds, so DLBI can't detect background-position changes for 1780 // these frames. Repaint the whole frame. 1781 hint |= nsChangeHint_RepaintFrame; 1782 } 1783 } 1784 1785 if (hint & 1786 (nsChangeHint_RepaintFrame | nsChangeHint_UpdateOpacityLayer | 1787 nsChangeHint_UpdateTransformLayer | 1788 nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) { 1789 ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint); 1790 } 1791 1792 if (hint & (nsChangeHint_UpdateTransformLayer | 1793 nsChangeHint_AddOrRemoveTransform)) { 1794 // We need to trigger re-snapping to this content if we snapped to the 1795 // content on the last scroll operation. 1796 ScrollSnapUtils::PostPendingResnapIfNeededFor(frame); 1797 } 1798 1799 if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) { 1800 // It is possible for this to fall back to a reflow 1801 if (!RecomputePosition(frame)) { 1802 StyleChangeReflow(frame, nsChangeHint_NeedReflow | 1803 nsChangeHint_ReflowChangesSizeOrPosition); 1804 didReflowThisFrame = true; 1805 } 1806 } 1807 NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) || 1808 (hint & nsChangeHint_UpdateOverflow), 1809 "nsChangeHint_UpdateOverflow should be passed too"); 1810 if (!didReflowThisFrame && 1811 (hint & (nsChangeHint_UpdateOverflow | 1812 nsChangeHint_UpdatePostTransformOverflow | 1813 nsChangeHint_UpdateParentOverflow | 1814 nsChangeHint_UpdateSubtreeOverflow))) { 1815 if (hint & nsChangeHint_UpdateSubtreeOverflow) { 1816 for (nsIFrame* cont = frame; cont; 1817 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 1818 AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker); 1819 } 1820 // The work we just did in AddSubtreeToOverflowTracker 1821 // subsumes some of the other hints: 1822 hint &= ~(nsChangeHint_UpdateOverflow | 1823 nsChangeHint_UpdatePostTransformOverflow); 1824 } 1825 if (hint & nsChangeHint_ChildrenOnlyTransform) { 1826 // We need to update overflows. The correct frame(s) to update depends 1827 // on whether the ChangeHint came from an outer or an inner svg. 1828 nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame); 1829 NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame), 1830 "SVG frames should not have continuations " 1831 "or ib-split siblings"); 1832 NS_ASSERTION( 1833 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame), 1834 "SVG frames should not have continuations " 1835 "or ib-split siblings"); 1836 if (hintFrame->IsSVGOuterSVGAnonChildFrame()) { 1837 // The children only transform of an outer svg frame is applied to 1838 // the outer svg's anonymous child frame (instead of to the 1839 // anonymous child's children). 1840 1841 if (!CanSkipOverflowUpdates(hintFrame)) { 1842 mOverflowChangedTracker.AddFrame( 1843 hintFrame, OverflowChangedTracker::CHILDREN_CHANGED); 1844 } 1845 } else { 1846 // The children only transform is applied to the child frames of an 1847 // inner svg frame, so update the child overflows. 1848 nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild(); 1849 for (; childFrame; childFrame = childFrame->GetNextSibling()) { 1850 MOZ_ASSERT(childFrame->IsSVGFrame(), 1851 "Not expecting non-SVG children"); 1852 if (!CanSkipOverflowUpdates(childFrame)) { 1853 mOverflowChangedTracker.AddFrame( 1854 childFrame, OverflowChangedTracker::CHILDREN_CHANGED); 1855 } 1856 NS_ASSERTION( 1857 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame), 1858 "SVG frames should not have continuations " 1859 "or ib-split siblings"); 1860 NS_ASSERTION( 1861 childFrame->GetParent() == hintFrame, 1862 "SVG child frame not expected to have different parent"); 1863 } 1864 } 1865 } 1866 if (!CanSkipOverflowUpdates(frame)) { 1867 if (hint & (nsChangeHint_UpdateOverflow | 1868 nsChangeHint_UpdatePostTransformOverflow)) { 1869 OverflowChangedTracker::ChangeKind changeKind; 1870 // If we have both nsChangeHint_UpdateOverflow and 1871 // nsChangeHint_UpdatePostTransformOverflow, 1872 // CHILDREN_CHANGED is selected as it is 1873 // strictly stronger. 1874 if (hint & nsChangeHint_UpdateOverflow) { 1875 changeKind = OverflowChangedTracker::CHILDREN_CHANGED; 1876 } else { 1877 changeKind = OverflowChangedTracker::TRANSFORM_CHANGED; 1878 } 1879 for (nsIFrame* cont = frame; cont; 1880 cont = 1881 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 1882 mOverflowChangedTracker.AddFrame(cont, changeKind); 1883 } 1884 } 1885 // UpdateParentOverflow hints need to be processed in addition 1886 // to the above, since if the processing of the above hints 1887 // yields no change, the update will not propagate to the 1888 // parent. 1889 if (hint & nsChangeHint_UpdateParentOverflow) { 1890 MOZ_ASSERT(frame->GetParent(), 1891 "shouldn't get style hints for the root frame"); 1892 for (nsIFrame* cont = frame; cont; 1893 cont = 1894 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { 1895 mOverflowChangedTracker.AddFrame( 1896 cont->GetParent(), OverflowChangedTracker::CHILDREN_CHANGED); 1897 } 1898 } 1899 } 1900 } 1901 if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) { 1902 presContext->PresShell()->SynthesizeMouseMove(false); 1903 didUpdateCursor = true; 1904 } 1905 if (hint & nsChangeHint_UpdateTableCellSpans) { 1906 frameConstructor->UpdateTableCellSpans(content); 1907 } 1908 if (hint & nsChangeHint_VisibilityChange) { 1909 frame->UpdateVisibleDescendantsState(); 1910 } 1911 } 1912 1913 aChangeList.Clear(); 1914 FlushOverflowChangedTracker(); 1915 } 1916 1917 /* static */ 1918 uint64_t RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aStyleFrame) { 1919 EffectSet* effectSet = EffectSet::GetForStyleFrame(aStyleFrame); 1920 return effectSet ? effectSet->GetAnimationGeneration() : 0; 1921 } 1922 1923 /* static */ 1924 void RestyleManager::AddLayerChangesForAnimation( 1925 nsIFrame* aStyleFrame, nsIFrame* aPrimaryFrame, Element* aElement, 1926 nsChangeHint aHintForThisFrame, nsStyleChangeList& aChangeListToProcess) { 1927 MOZ_ASSERT(aElement); 1928 MOZ_ASSERT(!!aStyleFrame == !!aPrimaryFrame); 1929 if (!aStyleFrame) { 1930 return; 1931 } 1932 1933 uint64_t frameGeneration = 1934 RestyleManager::GetAnimationGenerationForFrame(aStyleFrame); 1935 1936 Maybe<nsCSSPropertyIDSet> effectiveAnimationProperties; 1937 1938 nsChangeHint hint = nsChangeHint(0); 1939 auto maybeApplyChangeHint = [&](const Maybe<uint64_t>& aGeneration, 1940 DisplayItemType aDisplayItemType) -> bool { 1941 if (aGeneration && frameGeneration != *aGeneration) { 1942 // If we have a transform layer but don't have any transform style, we 1943 // probably just removed the transform but haven't destroyed the layer 1944 // yet. In this case we will typically add the appropriate change hint 1945 // (nsChangeHint_UpdateContainingBlock) when we compare styles so in 1946 // theory we could skip adding any change hint here. 1947 // 1948 // However, sometimes when we compare styles we'll get no change. For 1949 // example, if the transform style was 'none' when we sent the transform 1950 // animation to the compositor and the current transform style is now 1951 // 'none' we'll think nothing changed but actually we still need to 1952 // trigger an update to clear whatever style the transform animation set 1953 // on the compositor. To handle this case we simply set all the change 1954 // hints relevant to removing transform style (since we don't know exactly 1955 // what changes happened while the animation was running on the 1956 // compositor). 1957 // 1958 // Note that we *don't* add nsChangeHint_UpdateTransformLayer since if we 1959 // did, ApplyRenderingChangeToTree would complain that we're updating a 1960 // transform layer without a transform. 1961 if (aDisplayItemType == DisplayItemType::TYPE_TRANSFORM && 1962 !aStyleFrame->StyleDisplay()->HasTransformStyle()) { 1963 // Add all the hints for a removing a transform if they are not already 1964 // set for this frame. 1965 if (!(NS_IsHintSubset(nsChangeHint_ComprehensiveAddOrRemoveTransform, 1966 aHintForThisFrame))) { 1967 hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform; 1968 } 1969 return true; 1970 } 1971 hint |= LayerAnimationInfo::GetChangeHintFor(aDisplayItemType); 1972 } 1973 1974 // We consider it's the first paint for the frame if we have an animation 1975 // for the property but have no layer, for the case of WebRender, no 1976 // corresponding animation info. 1977 // Note that in case of animations which has properties preventing running 1978 // on the compositor, e.g., width or height, corresponding layer is not 1979 // created at all, but even in such cases, we normally set valid change 1980 // hint for such animations in each tick, i.e. restyles in each tick. As 1981 // a result, we usually do restyles for such animations in every tick on 1982 // the main-thread. The only animations which will be affected by this 1983 // explicit change hint are animations that have opacity/transform but did 1984 // not have those properies just before. e.g, setting transform by 1985 // setKeyframes or changing target element from other target which prevents 1986 // running on the compositor, etc. 1987 if (!aGeneration) { 1988 nsChangeHint hintForDisplayItem = 1989 LayerAnimationInfo::GetChangeHintFor(aDisplayItemType); 1990 // We don't need to apply the corresponding change hint if we already have 1991 // it. 1992 if (NS_IsHintSubset(hintForDisplayItem, aHintForThisFrame)) { 1993 return true; 1994 } 1995 1996 if (!effectiveAnimationProperties) { 1997 effectiveAnimationProperties.emplace( 1998 nsLayoutUtils::GetAnimationPropertiesForCompositor(aStyleFrame)); 1999 } 2000 const nsCSSPropertyIDSet& propertiesForDisplayItem = 2001 LayerAnimationInfo::GetCSSPropertiesFor(aDisplayItemType); 2002 if (effectiveAnimationProperties->Intersects(propertiesForDisplayItem)) { 2003 hint |= hintForDisplayItem; 2004 } 2005 } 2006 return true; 2007 }; 2008 2009 AnimationInfo::EnumerateGenerationOnFrame( 2010 aStyleFrame, aElement, LayerAnimationInfo::sDisplayItemTypes, 2011 maybeApplyChangeHint); 2012 2013 if (hint) { 2014 // We apply the hint to the primary frame, not the style frame. Transform 2015 // and opacity hints apply to the table wrapper box, not the table box. 2016 aChangeListToProcess.AppendChange(aPrimaryFrame, aElement, hint); 2017 } 2018 } 2019 2020 RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame( 2021 RestyleManager* aRestyleManager) 2022 : mRestyleManager(aRestyleManager), 2023 mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) { 2024 MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame, 2025 "shouldn't construct recursively"); 2026 mRestyleManager->mAnimationsWithDestroyedFrame = this; 2027 } 2028 2029 void RestyleManager::AnimationsWithDestroyedFrame::Put( 2030 nsIContent* aContent, ComputedStyle* aComputedStyle) { 2031 MOZ_ASSERT(aContent); 2032 PseudoStyleType pseudoType = aComputedStyle->GetPseudoType(); 2033 nsIContent* target = aContent; 2034 if (pseudoType == PseudoStyleType::NotPseudo || 2035 !AnimationUtils::StoresAnimationsInParent(pseudoType)) { 2036 pseudoType = PseudoStyleType::NotPseudo; 2037 } else { 2038 target = aContent->GetParent(); 2039 } 2040 mContents.AppendElement(std::make_pair(target->AsElement(), pseudoType)); 2041 } 2042 2043 void RestyleManager::AnimationsWithDestroyedFrame:: 2044 StopAnimationsForElementsWithoutFrames() { 2045 nsPresContext* context = mRestyleManager->PresContext(); 2046 nsAnimationManager* animationManager = context->AnimationManager(); 2047 nsTransitionManager* transitionManager = context->TransitionManager(); 2048 const Document* doc = context->Document(); 2049 for (auto& [element, pseudoType] : mContents) { 2050 PseudoStyleRequest request(pseudoType); 2051 if (pseudoType == PseudoStyleType::NotPseudo) { 2052 if (element->GetPrimaryFrame()) { 2053 continue; 2054 } 2055 // The contents of view transition pseudos are put together with 2056 // NotPseudo. 2057 const auto type = element->GetPseudoElementType(); 2058 if (PseudoStyle::IsViewTransitionPseudoElement(type)) { 2059 request = {type, 2060 element->HasName() 2061 ? element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() 2062 : nullptr}; 2063 // View transition pseudo-elements use the document element to look up 2064 // their animations. 2065 element = doc->GetRootElement(); 2066 MOZ_ASSERT(element); 2067 } 2068 } else if (auto* pseudo = element->GetPseudoElement(request); 2069 pseudo && pseudo->GetPrimaryFrame()) { 2070 continue; 2071 } 2072 2073 animationManager->StopAnimationsForElement(element, request); 2074 transitionManager->StopAnimationsForElement(element, request); 2075 2076 // All other animations should keep running but not running on the 2077 // *compositor* at this point. 2078 if (EffectSet* effectSet = EffectSet::Get(element, request)) { 2079 for (KeyframeEffect* effect : *effectSet) { 2080 effect->ResetIsRunningOnCompositor(); 2081 } 2082 } 2083 } 2084 } 2085 2086 // When using handled hints by an ancestor, we need to make sure that our 2087 // ancestor in the DOM tree is actually our ancestor in the flat tree. 2088 // Otherwise, we can't guarantee that e.g. a repaint from an ancestor in the DOM 2089 // will really end up repainting us. 2090 static bool CanUseHandledHintsFromAncestors(const nsIFrame* aFrame) { 2091 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 2092 // An out of flow can be parented in other part of the tree. 2093 return false; 2094 } 2095 if (aFrame->IsColumnSpanInMulticolSubtree()) { 2096 // Any column-spanner's parent frame is not its DOM parent's primary frame. 2097 return false; 2098 } 2099 if (aFrame->IsTableCaption()) { 2100 // This one is more subtle. captions are in-flow children of the table 2101 // frame. But they are parented to the table wrapper. So hints handled for 2102 // the inner table might not be applicable to us. 2103 return false; 2104 } 2105 return true; 2106 } 2107 2108 #ifdef DEBUG 2109 static bool IsAnonBox(const nsIFrame* aFrame) { 2110 return aFrame->Style()->IsAnonBox(); 2111 } 2112 2113 static const nsIFrame* FirstContinuationOrPartOfIBSplit( 2114 const nsIFrame* aFrame) { 2115 if (!aFrame) { 2116 return nullptr; 2117 } 2118 2119 return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 2120 } 2121 2122 static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) { 2123 const nsIFrame* parent = aFrame->GetParent(); 2124 if (aFrame->IsTableFrame()) { 2125 MOZ_ASSERT(parent->IsTableWrapperFrame()); 2126 parent = parent->GetParent(); 2127 } 2128 2129 if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) { 2130 if (parent->IsLineFrame()) { 2131 parent = parent->GetParent(); 2132 } 2133 return parent->IsViewportFrame() ? nullptr 2134 : FirstContinuationOrPartOfIBSplit(parent); 2135 } 2136 2137 if (aFrame->IsLineFrame()) { 2138 // A ::first-line always ends up here via its block, which is therefore the 2139 // right expected owner. That block can be an 2140 // anonymous box. For example, we could have a ::first-line on a columnated 2141 // block; the blockframe is the column-content anonymous box in that case. 2142 // So we don't want to end up in the code below, which steps out of anon 2143 // boxes. Just return the parent of the line frame, which is the block. 2144 return parent; 2145 } 2146 2147 if (aFrame->IsLetterFrame()) { 2148 // Ditto for ::first-letter. A first-letter always arrives here via its 2149 // direct parent, except when it's parented to a ::first-line. 2150 if (parent->IsLineFrame()) { 2151 parent = parent->GetParent(); 2152 } 2153 return FirstContinuationOrPartOfIBSplit(parent); 2154 } 2155 2156 if (parent->IsLetterFrame()) { 2157 // Things never have ::first-letter as their expected parent. Go 2158 // on up to the ::first-letter's parent. 2159 parent = parent->GetParent(); 2160 } 2161 2162 parent = FirstContinuationOrPartOfIBSplit(parent); 2163 2164 // We've handled already anon boxes, so now we're looking at 2165 // a frame of a DOM element or pseudo. Hop through anon and line-boxes 2166 // generated by our DOM parent, and go find the owner frame for it. 2167 while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) { 2168 if (const nsTableWrapperFrame* wrapperFrame = do_QueryFrame(parent)) { 2169 const nsTableFrame* tableFrame = wrapperFrame->InnerTableFrame(); 2170 // Handle :-moz-table and :-moz-inline-table. 2171 parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame; 2172 } else { 2173 // We get the in-flow parent here so that we can handle the OOF anonymous 2174 // boxed to get the correct parent. 2175 parent = parent->GetInFlowParent(); 2176 } 2177 parent = FirstContinuationOrPartOfIBSplit(parent); 2178 } 2179 2180 return parent; 2181 } 2182 2183 // FIXME(emilio, bug 1633685): We should ideally figure out how to properly 2184 // restyle replicated fixed pos frames... We seem to assume everywhere that they 2185 // can't get restyled at the moment... 2186 static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) { 2187 if (!aFrame->PresContext()->IsPaginated()) { 2188 return false; 2189 } 2190 2191 for (; aFrame; aFrame = aFrame->GetParent()) { 2192 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && 2193 !aFrame->FirstContinuation()->IsPrimaryFrame() && 2194 nsLayoutUtils::IsReallyFixedPos(aFrame)) { 2195 return true; 2196 } 2197 } 2198 2199 return true; 2200 } 2201 2202 void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const { 2203 MOZ_ASSERT(mOwner); 2204 MOZ_ASSERT(CanUseHandledHintsFromAncestors(mOwner)); 2205 // We allow aParent.mOwner to be null, for cases when we're not starting at 2206 // the root of the tree. We also allow aParent.mOwner to be somewhere up our 2207 // expected owner chain not our immediate owner, which allows us creating long 2208 // chains of ServoRestyleStates in some cases where it's just not worth it. 2209 if (aParent.mOwner) { 2210 const nsIFrame* owner = ExpectedOwnerForChild(mOwner); 2211 if (owner != aParent.mOwner && !IsInReplicatedFixedPosTree(mOwner)) { 2212 MOZ_ASSERT(IsAnonBox(owner), 2213 "Should only have expected owner weirdness when anon boxes " 2214 "are involved"); 2215 bool found = false; 2216 for (; owner; owner = ExpectedOwnerForChild(owner)) { 2217 if (owner == aParent.mOwner) { 2218 found = true; 2219 break; 2220 } 2221 } 2222 MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain"); 2223 } 2224 } 2225 } 2226 2227 nsChangeHint ServoRestyleState::ChangesHandledFor( 2228 const nsIFrame* aFrame) const { 2229 if (!mOwner) { 2230 MOZ_ASSERT(!mChangesHandled); 2231 return mChangesHandled; 2232 } 2233 2234 MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame) || 2235 IsInReplicatedFixedPosTree(aFrame), 2236 "Missed some frame in the hierarchy?"); 2237 return mChangesHandled; 2238 } 2239 #endif 2240 2241 void ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) { 2242 MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(), 2243 "All our wrappers are anon boxes, and why would we restyle " 2244 "non-inheriting ones?"); 2245 MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(), 2246 "All our wrappers are anon boxes, and why would we restyle " 2247 "non-inheriting ones?"); 2248 MOZ_ASSERT( 2249 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::cellContent, 2250 "Someone should be using TableAwareParentFor"); 2251 MOZ_ASSERT( 2252 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::tableWrapper, 2253 "Someone should be using TableAwareParentFor"); 2254 // Make sure we only add first continuations. 2255 aWrapperFrame = aWrapperFrame->FirstContinuation(); 2256 nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr); 2257 if (last == aWrapperFrame) { 2258 // Already queued up, nothing to do. 2259 return; 2260 } 2261 2262 // Make sure to queue up parents before children. But don't queue up 2263 // ancestors of non-anonymous boxes here; those are handled when we traverse 2264 // their non-anonymous kids. 2265 if (aWrapperFrame->ParentIsWrapperAnonBox()) { 2266 AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame)); 2267 } 2268 2269 // If the append fails, we'll fail to restyle properly, but that's probably 2270 // better than crashing. 2271 if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) { 2272 aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true); 2273 } 2274 } 2275 2276 void ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) { 2277 size_t i = mPendingWrapperRestyleOffset; 2278 while (i < mPendingWrapperRestyles.Length()) { 2279 i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i); 2280 } 2281 2282 mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset); 2283 } 2284 2285 size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent, 2286 size_t aIndex) { 2287 // The frame at index aIndex is something we should restyle ourselves, but 2288 // following frames may need separate ServoRestyleStates to restyle. 2289 MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length()); 2290 2291 nsIFrame* cur = mPendingWrapperRestyles[aIndex]; 2292 MOZ_ASSERT(cur->Style()->IsWrapperAnonBox()); 2293 2294 // Where is cur supposed to inherit from? From its parent frame, except in 2295 // the case when cur is a table, in which case it should be its grandparent. 2296 // Also, not in the case when the resulting frame would be a first-line; in 2297 // that case we should be inheriting from the block, and the first-line will 2298 // do its fixup later if needed. 2299 // 2300 // Note that after we do all that fixup the parent we get might still not be 2301 // aParent; for example aParent could be a scrollframe, in which case we 2302 // should inherit from the scrollcontent frame. Or the parent might be some 2303 // continuation of aParent. 2304 // 2305 // Try to assert as much as we can about the parent we actually end up using 2306 // without triggering bogus asserts in all those various edge cases. 2307 nsIFrame* parent = cur->GetParent(); 2308 if (cur->IsTableFrame()) { 2309 MOZ_ASSERT(parent->IsTableWrapperFrame()); 2310 parent = parent->GetParent(); 2311 } 2312 if (parent->IsLineFrame()) { 2313 parent = parent->GetParent(); 2314 } 2315 MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent || 2316 (parent->Style()->IsInheritingAnonBox() && 2317 parent->GetContent() == aParent->GetContent())); 2318 2319 // Now "this" is a ServoRestyleState for aParent, so if parent is not a next 2320 // continuation (possibly across ib splits) of aParent we need a new 2321 // ServoRestyleState for the kid. 2322 Maybe<ServoRestyleState> parentRestyleState; 2323 nsIFrame* parentForRestyle = 2324 nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent); 2325 if (parentForRestyle != aParent) { 2326 parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty, 2327 CanUseHandledHints::Yes); 2328 } 2329 ServoRestyleState& curRestyleState = 2330 parentRestyleState ? *parentRestyleState : *this; 2331 2332 // This frame may already have been restyled. Even if it has, we can't just 2333 // return, because the next frame may be a kid of it that does need restyling. 2334 if (cur->IsWrapperAnonBoxNeedingRestyle()) { 2335 parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState); 2336 cur->SetIsWrapperAnonBoxNeedingRestyle(false); 2337 } 2338 2339 size_t numProcessed = 1; 2340 2341 // Note: no overflow possible here, since aIndex < length. 2342 if (aIndex + 1 < mPendingWrapperRestyles.Length()) { 2343 nsIFrame* next = mPendingWrapperRestyles[aIndex + 1]; 2344 if (TableAwareParentFor(next) == cur && 2345 next->IsWrapperAnonBoxNeedingRestyle()) { 2346 // It might be nice if we could do better than nsChangeHint_Empty. On 2347 // the other hand, presumably our mChangesHandled already has the bits 2348 // we really want here so in practice it doesn't matter. 2349 ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty, 2350 CanUseHandledHints::Yes, 2351 /* aAssertWrapperRestyleLength = */ false); 2352 numProcessed += 2353 childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1); 2354 } 2355 } 2356 2357 return numProcessed; 2358 } 2359 2360 nsIFrame* ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) { 2361 // We want to get the anon box parent for aChild. where aChild has 2362 // ParentIsWrapperAnonBox(). 2363 // 2364 // For the most part this is pretty straightforward, but there are two 2365 // wrinkles. First, if aChild is a table, then we really want the parent of 2366 // its table wrapper. 2367 if (aChild->IsTableFrame()) { 2368 aChild = aChild->GetParent(); 2369 MOZ_ASSERT(aChild->IsTableWrapperFrame()); 2370 } 2371 2372 nsIFrame* parent = aChild->GetParent(); 2373 // Now if parent is a cell-content frame, we actually want the cellframe. 2374 if (parent->Style()->GetPseudoType() == PseudoStyleType::cellContent) { 2375 return parent->GetParent(); 2376 } 2377 if (const nsTableWrapperFrame* wrapper = do_QueryFrame(parent)) { 2378 // Must be a caption. In that case we want the table here. 2379 MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption); 2380 return wrapper->InnerTableFrame(); 2381 } 2382 return parent; 2383 } 2384 2385 void RestyleManager::PostRestyleEvent(Element* aElement, 2386 RestyleHint aRestyleHint, 2387 nsChangeHint aMinChangeHint) { 2388 MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange), 2389 "Didn't expect explicit change hints to be neutral!"); 2390 if (MOZ_UNLIKELY(IsDisconnected()) || 2391 MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) { 2392 return; 2393 } 2394 2395 // We allow posting restyles from within change hint handling, but not from 2396 // within the restyle algorithm itself. 2397 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal()); 2398 2399 if (!aRestyleHint && !aMinChangeHint) { 2400 // FIXME(emilio): we should assert against this instead. 2401 return; // Nothing to do. 2402 } 2403 2404 // Assuming the restyle hints will invalidate cached style for 2405 // getComputedStyle, since we don't know if any of the restyling that we do 2406 // would affect undisplayed elements. 2407 if (aRestyleHint) { 2408 if (!(aRestyleHint & RestyleHint::ForAnimations())) { 2409 mHaveNonAnimationRestyles = true; 2410 } 2411 2412 IncrementUndisplayedRestyleGeneration(); 2413 } 2414 2415 // Processing change hints sometimes causes new change hints to be generated, 2416 // and very occasionally, additional restyle hints. We collect the change 2417 // hints manually to avoid re-traversing the DOM to find them. 2418 if (mReentrantChanges && !aRestyleHint) { 2419 mReentrantChanges->AppendElement(ReentrantChange{aElement, aMinChangeHint}); 2420 return; 2421 } 2422 2423 if (aRestyleHint || aMinChangeHint) { 2424 Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint); 2425 } 2426 } 2427 2428 void RestyleManager::PostRestyleEventForAnimations( 2429 Element* aElement, const PseudoStyleRequest& aPseudoRequest, 2430 RestyleHint aRestyleHint) { 2431 Element* elementToRestyle = aElement->GetPseudoElement(aPseudoRequest); 2432 if (!elementToRestyle) { 2433 // FIXME: Bug 1371107: When reframing happens, 2434 // EffectCompositor::mElementsToRestyle still has unbound old pseudo 2435 // element. We should drop it. 2436 return; 2437 } 2438 2439 mPresContext->TriggeredAnimationRestyle(); 2440 2441 Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0)); 2442 } 2443 2444 void RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint, 2445 RestyleHint aRestyleHint) { 2446 // NOTE(emilio): The semantics of these methods are quite funny, in the sense 2447 // that we're not supposed to need to rebuild the actual stylist data. 2448 // 2449 // That's handled as part of the MediumFeaturesChanged stuff, if needed. 2450 // 2451 // Clear the cached style data only if we are guaranteed to process the whole 2452 // DOM tree again. 2453 // 2454 // FIXME(emilio): Decouple this, probably. This probably just wants to reset 2455 // the "uses viewport units / uses rem" bits, and _maybe_ clear cached anon 2456 // box styles and such... But it doesn't really always need to clear the 2457 // initial style of the document and similar... 2458 if (aRestyleHint.DefinitelyRecascadesAllSubtree()) { 2459 StyleSet()->ClearCachedStyleData(); 2460 } 2461 2462 DocumentStyleRootIterator iter(mPresContext->Document()); 2463 while (Element* root = iter.GetNextStyleRoot()) { 2464 PostRestyleEvent(root, aRestyleHint, aExtraHint); 2465 } 2466 2467 // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect 2468 // non-inheriting anon boxes. It's not clear if we want to support that, but 2469 // if we do, we need to re-selector-match them here. 2470 } 2471 2472 /* static */ 2473 void RestyleManager::ClearServoDataFromSubtree(Element* aElement, 2474 IncludeRoot aIncludeRoot) { 2475 if (aElement->HasServoData()) { 2476 StyleChildrenIterator it(aElement); 2477 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { 2478 if (n->IsElement()) { 2479 ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes); 2480 } 2481 } 2482 } 2483 2484 if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) { 2485 aElement->ClearServoData(); 2486 MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits | 2487 NODE_NEEDS_FRAME)); 2488 MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot()); 2489 } 2490 } 2491 2492 /* static */ 2493 void RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) { 2494 if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) { 2495 StyleChildrenIterator it(aElement); 2496 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { 2497 if (n->IsElement()) { 2498 ClearRestyleStateFromSubtree(n->AsElement()); 2499 } 2500 } 2501 } 2502 2503 bool wasRestyled = false; 2504 (void)Servo_TakeChangeHint(aElement, &wasRestyled); 2505 aElement->UnsetFlags(Element::kAllServoDescendantBits); 2506 } 2507 2508 /** 2509 * This struct takes care of encapsulating some common state that text nodes may 2510 * need to track during the post-traversal. 2511 * 2512 * This is currently used to properly compute change hints when the parent 2513 * element of this node is a display: contents node, and also to avoid computing 2514 * the style for text children more than once per element. 2515 */ 2516 struct RestyleManager::TextPostTraversalState { 2517 public: 2518 TextPostTraversalState(Element& aParentElement, ComputedStyle* aParentContext, 2519 bool aDisplayContentsParentStyleChanged, 2520 ServoRestyleState& aParentRestyleState) 2521 : mParentElement(aParentElement), 2522 mParentContext(aParentContext), 2523 mParentRestyleState(aParentRestyleState), 2524 mStyle(nullptr), 2525 mShouldPostHints(aDisplayContentsParentStyleChanged), 2526 mShouldComputeHints(aDisplayContentsParentStyleChanged), 2527 mComputedHint(nsChangeHint_Empty) {} 2528 2529 nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); } 2530 2531 ComputedStyle& ComputeStyle(nsIContent* aTextNode) { 2532 if (!mStyle) { 2533 mStyle = mParentRestyleState.StyleSet().ResolveStyleForText( 2534 aTextNode, &ParentStyle()); 2535 } 2536 MOZ_ASSERT(mStyle); 2537 return *mStyle; 2538 } 2539 2540 void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame, 2541 ComputedStyle& aNewStyle) { 2542 MOZ_ASSERT(aTextFrame); 2543 MOZ_ASSERT(aNewStyle.GetPseudoType() == PseudoStyleType::mozText); 2544 2545 if (MOZ_LIKELY(!mShouldPostHints)) { 2546 return; 2547 } 2548 2549 ComputedStyle* oldStyle = aTextFrame->Style(); 2550 MOZ_ASSERT(oldStyle->GetPseudoType() == PseudoStyleType::mozText); 2551 2552 // We rely on the fact that all the text children for the same element share 2553 // style to avoid recomputing style differences for all of them. 2554 // 2555 // TODO(emilio): The above may not be true for ::first-{line,letter}, but 2556 // we'll cross that bridge when we support those in stylo. 2557 if (mShouldComputeHints) { 2558 mShouldComputeHints = false; 2559 uint32_t equalStructs; 2560 mComputedHint = oldStyle->CalcStyleDifference(aNewStyle, &equalStructs); 2561 mComputedHint = NS_RemoveSubsumedHints( 2562 mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame)); 2563 } 2564 2565 if (mComputedHint) { 2566 mParentRestyleState.ChangeList().AppendChange(aTextFrame, aContent, 2567 mComputedHint); 2568 } 2569 } 2570 2571 private: 2572 ComputedStyle& ParentStyle() { 2573 if (!mParentContext) { 2574 mLazilyResolvedParentContext = 2575 ServoStyleSet::ResolveServoStyle(mParentElement); 2576 mParentContext = mLazilyResolvedParentContext; 2577 } 2578 return *mParentContext; 2579 } 2580 2581 Element& mParentElement; 2582 ComputedStyle* mParentContext; 2583 RefPtr<ComputedStyle> mLazilyResolvedParentContext; 2584 ServoRestyleState& mParentRestyleState; 2585 RefPtr<ComputedStyle> mStyle; 2586 bool mShouldPostHints; 2587 bool mShouldComputeHints; 2588 nsChangeHint mComputedHint; 2589 }; 2590 2591 static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame, 2592 ServoRestyleState& aRestyleState) { 2593 MOZ_ASSERT( 2594 !aFrame->IsBlockFrameOrSubclass(), 2595 "You're probably duplicating work with UpdatePseudoElementStyles!"); 2596 if (!aFrame->HasFirstLetterChild()) { 2597 return; 2598 } 2599 2600 // We need to find the block the first-letter is associated with so we can 2601 // find the right element for the first-letter's style resolution. Might as 2602 // well just delegate the whole thing to that block. 2603 nsIFrame* block = aFrame->GetParent(); 2604 while (!block->IsBlockFrameOrSubclass()) { 2605 block = block->GetParent(); 2606 } 2607 2608 static_cast<nsBlockFrame*>(block->FirstContinuation()) 2609 ->UpdateFirstLetterStyle(aRestyleState); 2610 } 2611 2612 static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex, 2613 ComputedStyle& aOldContext, 2614 ServoRestyleState& aRestyleState) { 2615 auto pseudoType = aOldContext.GetPseudoType(); 2616 MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo); 2617 MOZ_ASSERT( 2618 !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType)); 2619 2620 RefPtr<ComputedStyle> newStyle = 2621 aRestyleState.StyleSet().ResolvePseudoElementStyle( 2622 *aFrame->GetContent()->AsElement(), pseudoType, nullptr, 2623 aFrame->Style()); 2624 2625 uint32_t equalStructs; // Not used, actually. 2626 nsChangeHint childHint = 2627 aOldContext.CalcStyleDifference(*newStyle, &equalStructs); 2628 if (CanUseHandledHintsFromAncestors(aFrame)) { 2629 childHint = NS_RemoveSubsumedHints(childHint, 2630 aRestyleState.ChangesHandledFor(aFrame)); 2631 } 2632 2633 if (childHint) { 2634 if (childHint & nsChangeHint_ReconstructFrame) { 2635 // If we generate a reconstruct here, remove any non-reconstruct hints we 2636 // may have already generated for this content. 2637 aRestyleState.ChangeList().PopChangesForContent(aFrame->GetContent()); 2638 } 2639 aRestyleState.ChangeList().AppendChange(aFrame, aFrame->GetContent(), 2640 childHint); 2641 } 2642 2643 aFrame->SetAdditionalComputedStyle(aIndex, newStyle); 2644 } 2645 2646 static void UpdateAdditionalComputedStyles(nsIFrame* aFrame, 2647 ServoRestyleState& aRestyleState) { 2648 MOZ_ASSERT(aFrame); 2649 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement()); 2650 2651 // FIXME(emilio): Consider adding a bit or something to avoid the initial 2652 // virtual call? 2653 uint32_t index = 0; 2654 while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) { 2655 UpdateOneAdditionalComputedStyle(aFrame, index++, *oldStyle, aRestyleState); 2656 } 2657 } 2658 2659 static void UpdateFramePseudoElementStyles(nsIFrame* aFrame, 2660 ServoRestyleState& aRestyleState) { 2661 if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) { 2662 blockFrame->UpdatePseudoElementStyles(aRestyleState); 2663 } else { 2664 UpdateFirstLetterIfNeeded(aFrame, aRestyleState); 2665 } 2666 } 2667 2668 enum class ServoPostTraversalFlags : uint32_t { 2669 Empty = 0, 2670 // Whether parent was restyled. 2671 ParentWasRestyled = 1 << 0, 2672 // Skip sending accessibility notifications for all descendants. 2673 SkipA11yNotifications = 1 << 1, 2674 // Always send accessibility notifications if the element is shown. 2675 // The SkipA11yNotifications flag above overrides this flag. 2676 SendA11yNotificationsIfShown = 1 << 2, 2677 }; 2678 2679 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags) 2680 2681 #ifdef ACCESSIBILITY 2682 static bool IsVisibleForA11y(const ComputedStyle& aStyle) { 2683 return aStyle.StyleVisibility()->IsVisible() && !aStyle.StyleUI()->IsInert(); 2684 } 2685 2686 static bool IsSubtreeVisibleForA11y(const ComputedStyle& aStyle) { 2687 return aStyle.StyleDisplay()->mContentVisibility != 2688 StyleContentVisibility::Hidden; 2689 } 2690 #endif 2691 2692 // Send proper accessibility notifications and return post traversal 2693 // flags for kids. 2694 static ServoPostTraversalFlags SendA11yNotifications( 2695 nsPresContext* aPresContext, Element* aElement, 2696 const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle, 2697 ServoPostTraversalFlags aFlags) { 2698 using Flags = ServoPostTraversalFlags; 2699 MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) || 2700 !(aFlags & Flags::SendA11yNotificationsIfShown), 2701 "The two a11y flags should never be set together"); 2702 2703 #ifdef ACCESSIBILITY 2704 nsAccessibilityService* accService = GetAccService(); 2705 if (!accService) { 2706 // If we don't have accessibility service, accessibility is not 2707 // enabled. Just skip everything. 2708 return Flags::Empty; 2709 } 2710 2711 if (aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually != 2712 aOldStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { 2713 if (aElement->GetParent() && 2714 aElement->GetParent()->IsXULElement(nsGkAtoms::tabpanels)) { 2715 accService->NotifyOfTabPanelVisibilityChange( 2716 aPresContext->PresShell(), aElement, 2717 aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually); 2718 } 2719 } 2720 2721 if (aFlags & Flags::SkipA11yNotifications) { 2722 // Propagate the skipping flag to descendants. 2723 return Flags::SkipA11yNotifications; 2724 } 2725 2726 bool needsNotify = false; 2727 const bool isVisible = IsVisibleForA11y(aNewStyle); 2728 const bool wasVisible = IsVisibleForA11y(aOldStyle); 2729 2730 if (aFlags & Flags::SendA11yNotificationsIfShown) { 2731 if (!isVisible) { 2732 // Propagate the sending-if-shown flag to descendants. 2733 return Flags::SendA11yNotificationsIfShown; 2734 } 2735 // We have asked accessibility service to remove the whole subtree 2736 // of element which becomes invisible from the accessible tree, but 2737 // this element is visible, so we need to add it back. 2738 needsNotify = true; 2739 } else { 2740 // If we shouldn't skip in any case, we need to check whether our own 2741 // visibility has changed. 2742 // Also notify if the subtree visibility change due to content-visibility. 2743 const bool isSubtreeVisible = IsSubtreeVisibleForA11y(aNewStyle); 2744 const bool wasSubtreeVisible = IsSubtreeVisibleForA11y(aOldStyle); 2745 needsNotify = 2746 wasVisible != isVisible || wasSubtreeVisible != isSubtreeVisible; 2747 } 2748 2749 if (needsNotify) { 2750 PresShell* presShell = aPresContext->PresShell(); 2751 if (isVisible) { 2752 accService->ContentRangeInserted(presShell, aElement, 2753 aElement->GetNextSibling()); 2754 // We are adding the subtree. Accessibility service would handle 2755 // descendants, so we should just skip them from notifying. 2756 return Flags::SkipA11yNotifications; 2757 } 2758 if (wasVisible) { 2759 // Remove the subtree of this invisible element, and ask any shown 2760 // descendant to add themselves back. 2761 accService->ContentRemoved(presShell, aElement); 2762 return Flags::SendA11yNotificationsIfShown; 2763 } 2764 } 2765 #endif 2766 2767 return Flags::Empty; 2768 } 2769 2770 static bool NeedsToReframeForConditionallyCreatedPseudoElement( 2771 Element* aElement, ComputedStyle* aNewStyle, nsIFrame* aStyleFrame, 2772 ServoRestyleState& aRestyleState) { 2773 if (MOZ_UNLIKELY(aStyleFrame->IsLeaf())) { 2774 return false; 2775 } 2776 const auto& disp = *aStyleFrame->StyleDisplay(); 2777 if (disp.IsListItem() && aStyleFrame->IsBlockFrameOrSubclass() && 2778 !nsLayoutUtils::GetMarkerPseudo(aElement)) { 2779 RefPtr<ComputedStyle> pseudoStyle = 2780 aRestyleState.StyleSet().ProbePseudoElementStyle( 2781 *aElement, PseudoStyleType::marker, nullptr, aNewStyle); 2782 if (pseudoStyle) { 2783 return true; 2784 } 2785 } 2786 if (disp.mTopLayer == StyleTopLayer::Auto && 2787 !aElement->IsInNativeAnonymousSubtree() && 2788 !nsLayoutUtils::GetBackdropPseudo(aElement)) { 2789 RefPtr<ComputedStyle> pseudoStyle = 2790 aRestyleState.StyleSet().ProbePseudoElementStyle( 2791 *aElement, PseudoStyleType::backdrop, nullptr, aNewStyle); 2792 if (pseudoStyle) { 2793 return true; 2794 } 2795 } 2796 return false; 2797 } 2798 2799 bool RestyleManager::ProcessPostTraversal(Element* aElement, 2800 ServoRestyleState& aRestyleState, 2801 ServoPostTraversalFlags aFlags) { 2802 nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement); 2803 nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); 2804 2805 MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(), 2806 "Element without Servo data on a post-traversal? How?"); 2807 2808 // NOTE(emilio): This is needed because for table frames the bit is set on the 2809 // table wrapper (which is the primary frame), not on the table itself. 2810 const bool isOutOfFlow = 2811 primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); 2812 2813 const bool canUseHandledHints = 2814 primaryFrame && CanUseHandledHintsFromAncestors(primaryFrame); 2815 2816 // Grab the change hint from Servo. 2817 bool wasRestyled = false; 2818 nsChangeHint changeHint = 2819 static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled)); 2820 2821 RefPtr<ComputedStyle> upToDateStyleIfRestyled = 2822 wasRestyled ? ServoStyleSet::ResolveServoStyle(*aElement) : nullptr; 2823 2824 // We should really fix the weird primary frame mapping for image maps 2825 // (bug 135040)... 2826 if (styleFrame && styleFrame->GetContent() != aElement) { 2827 MOZ_ASSERT(styleFrame->IsImageFrameOrSubclass()); 2828 styleFrame = nullptr; 2829 } 2830 2831 // Handle lazy frame construction by posting a reconstruct for any lazily- 2832 // constructed roots. 2833 if (aElement->HasFlag(NODE_NEEDS_FRAME)) { 2834 changeHint |= nsChangeHint_ReconstructFrame; 2835 MOZ_ASSERT(!styleFrame); 2836 } 2837 2838 if (styleFrame) { 2839 MOZ_ASSERT(primaryFrame); 2840 2841 nsIFrame* maybeAnonBoxChild; 2842 if (isOutOfFlow) { 2843 maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame(); 2844 } else { 2845 maybeAnonBoxChild = primaryFrame; 2846 } 2847 2848 // Do not subsume change hints for the column-spanner. 2849 if (canUseHandledHints) { 2850 changeHint = NS_RemoveSubsumedHints( 2851 changeHint, aRestyleState.ChangesHandledFor(styleFrame)); 2852 } 2853 2854 // If the parent wasn't restyled, the styles of our anon box parents won't 2855 // change either. 2856 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) && 2857 maybeAnonBoxChild->ParentIsWrapperAnonBox()) { 2858 aRestyleState.AddPendingWrapperRestyle( 2859 ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild)); 2860 } 2861 2862 // If we don't have a ::marker or ::backdrop pseudo-element, but need it, 2863 // then reconstruct the frame. (The opposite situation implies 'display' 2864 // changes so doesn't need to be handled explicitly here.) 2865 if (wasRestyled && !(changeHint & nsChangeHint_ReconstructFrame) && 2866 NeedsToReframeForConditionallyCreatedPseudoElement( 2867 aElement, upToDateStyleIfRestyled, styleFrame, aRestyleState)) { 2868 changeHint |= nsChangeHint_ReconstructFrame; 2869 } 2870 } 2871 2872 // Although we shouldn't generate non-ReconstructFrame hints for elements with 2873 // no frames, we can still get them here if they were explicitly posted by 2874 // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be 2875 // :visited. Skip processing these hints if there is no frame. 2876 if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) && 2877 changeHint) { 2878 aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint); 2879 } 2880 2881 // If our change hint is reconstruct, we delegate to the frame constructor, 2882 // which consumes the new style and expects the old style to be on the frame. 2883 // 2884 // XXXbholley: We should teach the frame constructor how to clear the dirty 2885 // descendants bit to avoid the traversal here. 2886 if (changeHint & nsChangeHint_ReconstructFrame) { 2887 if (wasRestyled && 2888 StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) { 2889 const bool wasAbsPos = 2890 styleFrame && 2891 styleFrame->StyleDisplay()->IsAbsolutelyPositionedStyle(); 2892 auto* newDisp = upToDateStyleIfRestyled->StyleDisplay(); 2893 // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers 2894 // 2895 // We need to do the position check here rather than in 2896 // DidSetComputedStyle because changing position reframes. 2897 // 2898 // We suppress adjustments whenever we change from being display: none to 2899 // be an abspos. 2900 // 2901 // Similarly, for other changes from abspos to non-abspos styles. 2902 // 2903 // TODO(emilio): I _think_ chrome won't suppress adjustments whenever 2904 // `display` changes. But that causes some infinite loops in cases like 2905 // bug 1568778. 2906 if (wasAbsPos != newDisp->IsAbsolutelyPositionedStyle()) { 2907 aRestyleState.AddPendingScrollAnchorSuppression(aElement); 2908 } 2909 } 2910 ClearRestyleStateFromSubtree(aElement); 2911 return true; 2912 } 2913 2914 // TODO(emilio): We could avoid some refcount traffic here, specially in the 2915 // ComputedStyle case, which uses atomic refcounting. 2916 // 2917 // Hold the ComputedStyle alive, because it could become a dangling pointer 2918 // during the replacement. In practice it's not a huge deal, but better not 2919 // playing with dangling pointers if not needed. 2920 // 2921 // NOTE(emilio): We could keep around the old computed style for display: 2922 // contents elements too, but we don't really need it right now. 2923 RefPtr<ComputedStyle> oldOrDisplayContentsStyle = 2924 styleFrame ? styleFrame->Style() : nullptr; 2925 2926 MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)), 2927 "display: contents node has a frame, yet we didn't reframe it" 2928 " above?"); 2929 const bool isDisplayContents = !styleFrame && aElement->HasServoData() && 2930 Servo_Element_IsDisplayContents(aElement); 2931 if (isDisplayContents) { 2932 oldOrDisplayContentsStyle = ServoStyleSet::ResolveServoStyle(*aElement); 2933 } 2934 2935 Maybe<ServoRestyleState> thisFrameRestyleState; 2936 if (styleFrame) { 2937 thisFrameRestyleState.emplace( 2938 *styleFrame, aRestyleState, changeHint, 2939 ServoRestyleState::CanUseHandledHints(canUseHandledHints)); 2940 } 2941 2942 // We can't really assume as used changes from display: contents elements (or 2943 // other elements without frames). 2944 ServoRestyleState& childrenRestyleState = 2945 thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState; 2946 2947 ComputedStyle* upToDateStyle = 2948 wasRestyled ? upToDateStyleIfRestyled : oldOrDisplayContentsStyle; 2949 2950 ServoPostTraversalFlags childrenFlags = 2951 wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled 2952 : ServoPostTraversalFlags::Empty; 2953 2954 if (wasRestyled && oldOrDisplayContentsStyle) { 2955 MOZ_ASSERT(styleFrame || isDisplayContents); 2956 2957 // We want to walk all the continuations here, even the ones with different 2958 // styles. In practice, the only reason we get continuations with different 2959 // styles here is ::first-line (::first-letter never affects element 2960 // styles). But in that case, newStyle is the right context for the 2961 // _later_ continuations anyway (the ones not affected by ::first-line), not 2962 // the earlier ones, so there is no point stopping right at the point when 2963 // we'd actually be setting the right ComputedStyle. 2964 // 2965 // This does mean that we may be setting the wrong ComputedStyle on our 2966 // initial continuations; ::first-line fixes that up after the fact. 2967 for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) { 2968 MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0)); 2969 f->SetComputedStyle(upToDateStyle); 2970 } 2971 2972 if (styleFrame) { 2973 UpdateAdditionalComputedStyles(styleFrame, aRestyleState); 2974 } 2975 2976 if (!aElement->GetParent()) { 2977 // This is the root. Update styles on the viewport as needed. 2978 ViewportFrame* viewport = 2979 do_QueryFrame(mPresContext->PresShell()->GetRootFrame()); 2980 if (viewport) { 2981 // NB: The root restyle state, not the one for our children! 2982 viewport->UpdateStyle(aRestyleState); 2983 } 2984 } 2985 2986 // Some changes to animations don't affect the computed style and yet still 2987 // require the layer to be updated. For example, pausing an animation via 2988 // the Web Animations API won't affect an element's style but still 2989 // requires to update the animation on the layer. 2990 // 2991 // We can sometimes reach this when the animated style is being removed. 2992 // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform 2993 // style or not, we need to call it *after* setting |newStyle| to 2994 // |styleFrame| to ensure the animated transform has been removed first. 2995 AddLayerChangesForAnimation(styleFrame, primaryFrame, aElement, changeHint, 2996 aRestyleState.ChangeList()); 2997 2998 childrenFlags |= SendA11yNotifications(mPresContext, aElement, 2999 *oldOrDisplayContentsStyle, 3000 *upToDateStyle, aFlags); 3001 } 3002 3003 const bool traverseElementChildren = 3004 aElement->HasAnyOfFlags(Element::kAllServoDescendantBits); 3005 const bool traverseTextChildren = 3006 wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES); 3007 bool recreatedAnyContext = wasRestyled; 3008 if (traverseElementChildren || traverseTextChildren) { 3009 StyleChildrenIterator it(aElement); 3010 TextPostTraversalState textState(*aElement, upToDateStyle, 3011 isDisplayContents && wasRestyled, 3012 childrenRestyleState); 3013 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { 3014 if (traverseElementChildren && n->IsElement()) { 3015 recreatedAnyContext |= ProcessPostTraversal( 3016 n->AsElement(), childrenRestyleState, childrenFlags); 3017 } else if (traverseTextChildren && n->IsText()) { 3018 recreatedAnyContext |= ProcessPostTraversalForText( 3019 n, textState, childrenRestyleState, childrenFlags); 3020 } 3021 } 3022 } 3023 3024 // We want to update frame pseudo-element styles after we've traversed our 3025 // kids, because some of those updates (::first-line/::first-letter) need to 3026 // modify the styles of the kids, and the child traversal above would just 3027 // clobber those modifications. 3028 if (styleFrame) { 3029 if (wasRestyled) { 3030 // Make sure to update anon boxes and pseudo bits after updating text, 3031 // otherwise ProcessPostTraversalForText could clobber first-letter 3032 // styles, for example. 3033 styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState); 3034 } 3035 // Process anon box wrapper frames before ::first-line bits, but _after_ 3036 // owned anon boxes, since the children wrapper anon boxes could be 3037 // inheriting from our own owned anon boxes. 3038 childrenRestyleState.ProcessWrapperRestyles(styleFrame); 3039 if (wasRestyled) { 3040 UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState); 3041 } else if (traverseElementChildren && 3042 styleFrame->IsBlockFrameOrSubclass()) { 3043 // Even if we were not restyled, if we're a block with a first-line and 3044 // one of our descendant elements which is on the first line was restyled, 3045 // we need to update the styles of things on the first line, because 3046 // they're wrong now. 3047 // 3048 // FIXME(bz) Could we do better here? For example, could we keep track of 3049 // frames that are "block with a ::first-line so we could avoid 3050 // IsFrameOfType() and digging about for the first-line frame if not? 3051 // Could we keep track of whether the element children we actually restyle 3052 // are affected by first-line? Something else? Bug 1385443 tracks making 3053 // this better. 3054 nsIFrame* firstLineFrame = 3055 static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame(); 3056 if (firstLineFrame) { 3057 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) { 3058 ReparentComputedStyleForFirstLine(kid); 3059 } 3060 } 3061 } 3062 } 3063 3064 aElement->UnsetFlags(Element::kAllServoDescendantBits); 3065 return recreatedAnyContext; 3066 } 3067 3068 bool RestyleManager::ProcessPostTraversalForText( 3069 nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState, 3070 ServoRestyleState& aRestyleState, ServoPostTraversalFlags aFlags) { 3071 // Handle lazy frame construction. 3072 if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) { 3073 aPostTraversalState.ChangeList().AppendChange( 3074 nullptr, aTextNode, nsChangeHint_ReconstructFrame); 3075 return true; 3076 } 3077 3078 // Handle restyle. 3079 nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame(); 3080 if (!primaryFrame) { 3081 return false; 3082 } 3083 3084 // If the parent wasn't restyled, the styles of our anon box parents won't 3085 // change either. 3086 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) && 3087 primaryFrame->ParentIsWrapperAnonBox()) { 3088 aRestyleState.AddPendingWrapperRestyle( 3089 ServoRestyleState::TableAwareParentFor(primaryFrame)); 3090 } 3091 3092 ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode); 3093 aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle); 3094 3095 // We want to walk all the continuations here, even the ones with different 3096 // styles. In practice, the only reasons we get continuations with different 3097 // styles are ::first-line and ::first-letter. But in those cases, 3098 // newStyle is the right context for the _later_ continuations anyway (the 3099 // ones not affected by ::first-line/::first-letter), not the earlier ones, 3100 // so there is no point stopping right at the point when we'd actually be 3101 // setting the right ComputedStyle. 3102 // 3103 // This does mean that we may be setting the wrong ComputedStyle on our 3104 // initial continuations; ::first-line/::first-letter fix that up after the 3105 // fact. 3106 for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) { 3107 f->SetComputedStyle(&newStyle); 3108 } 3109 3110 return true; 3111 } 3112 3113 void RestyleManager::ClearSnapshots() { 3114 for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) { 3115 iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT); 3116 iter.Remove(); 3117 } 3118 } 3119 3120 ServoElementSnapshot& RestyleManager::SnapshotFor(Element& aElement) { 3121 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh); 3122 3123 // NOTE(emilio): We can handle snapshots from a one-off restyle of those that 3124 // we do to restyle stuff for reconstruction, for example. 3125 // 3126 // It seems to be the case that we always flush in between that happens and 3127 // the next attribute change, so we can assert that we haven't handled the 3128 // snapshot here yet. If this assertion didn't hold, we'd need to unset that 3129 // flag from here too. 3130 // 3131 // Can't wait to make ProcessPendingRestyles the only entry-point for styling, 3132 // so this becomes much easier to reason about. Today is not that day though. 3133 MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT)); 3134 3135 ServoElementSnapshot* snapshot = 3136 mSnapshots.GetOrInsertNew(&aElement, aElement); 3137 aElement.SetFlags(ELEMENT_HAS_SNAPSHOT); 3138 3139 // Now that we have a snapshot, make sure a restyle is triggered. 3140 aElement.NoteDirtyForServo(); 3141 return *snapshot; 3142 } 3143 3144 void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) { 3145 nsPresContext* presContext = PresContext(); 3146 PresShell* presShell = presContext->PresShell(); 3147 3148 MOZ_ASSERT(presContext->Document(), "No document? Pshaw!"); 3149 // FIXME(emilio): In the "flush animations" case, ideally, we should only 3150 // recascade animation styles running on the compositor, so we shouldn't care 3151 // about other styles, or new rules that apply to the page... 3152 // 3153 // However, that's not true as of right now, see bug 1388031 and bug 1388692. 3154 MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) || 3155 !presContext->HasPendingMediaQueryUpdates(), 3156 "Someone forgot to update media queries?"); 3157 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!"); 3158 MOZ_RELEASE_ASSERT(!mInStyleRefresh, "Reentrant call?"); 3159 3160 if (MOZ_UNLIKELY(!presShell->DidInitialize())) { 3161 // PresShell::FlushPendingNotifications doesn't early-return in the case 3162 // where the PresShell hasn't yet been initialized (and therefore we haven't 3163 // yet done the initial style traversal of the DOM tree). We should arguably 3164 // fix up the callers and assert against this case, but we just detect and 3165 // handle it for now. 3166 return; 3167 } 3168 3169 // It'd be bad! 3170 PresShell::AutoAssertNoFlush noReentrantFlush(*presShell); 3171 3172 // Create a AnimationsWithDestroyedFrame during restyling process to 3173 // stop animations and transitions on elements that have no frame at the end 3174 // of the restyling process. 3175 AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this); 3176 3177 ServoStyleSet* styleSet = StyleSet(); 3178 Document* doc = presContext->Document(); 3179 3180 if (!doc->GetServoRestyleRoot()) { 3181 // This might post new restyles, so need to do it here. Don't do it if we're 3182 // already going to restyle tho, so that we don't potentially reflow with 3183 // dirty styling. 3184 presContext->UpdateContainerQueryStylesAndAnchorPosLayout(); 3185 presContext->FinishedContainerQueryUpdate(); 3186 } 3187 3188 // Perform the Servo traversal, and the post-traversal if required. We do this 3189 // in a loop because certain rare paths in the frame constructor can trigger 3190 // additional style invalidations. 3191 // 3192 // FIXME(emilio): Confirm whether that's still true now that XBL is gone. 3193 mInStyleRefresh = true; 3194 if (mHaveNonAnimationRestyles) { 3195 ++mAnimationGeneration; 3196 } 3197 3198 if (mRestyleForCSSRuleChanges) { 3199 aFlags |= ServoTraversalFlags::ForCSSRuleChanges; 3200 } 3201 3202 while (styleSet->StyleDocument(aFlags)) { 3203 ClearSnapshots(); 3204 mRestyledAsWholeContainer.Clear(); 3205 3206 // Select scroll anchors for frames that have been scrolled. Do this 3207 // before processing restyled frames so that anchor nodes are correctly 3208 // marked when directly moving frames with RecomputePosition. 3209 presContext->PresShell()->FlushPendingScrollAnchorSelections(); 3210 3211 nsStyleChangeList currentChanges; 3212 bool anyStyleChanged = false; 3213 3214 // Recreate styles , and queue up change hints (which also handle lazy frame 3215 // construction). 3216 nsTArray<RefPtr<Element>> anchorsToSuppress; 3217 3218 // Unfortunately, the frame constructor and ProcessPostTraversal can 3219 // generate new change hints while processing existing ones. We redirect 3220 // those into a secondary queue and iterate until there's nothing left. 3221 ReentrantChangeList newChanges; 3222 mReentrantChanges = &newChanges; 3223 3224 { 3225 DocumentStyleRootIterator iter(doc->GetServoRestyleRoot()); 3226 while (Element* root = iter.GetNextStyleRoot()) { 3227 nsTArray<nsIFrame*> wrappersToRestyle; 3228 ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle, 3229 anchorsToSuppress); 3230 ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty; 3231 anyStyleChanged |= ProcessPostTraversal(root, state, flags); 3232 } 3233 3234 // We want to suppress adjustments the current (before-change) scroll 3235 // anchor container now, and save a reference to the content node so that 3236 // we can suppress them in the after-change scroll anchor . 3237 for (Element* element : anchorsToSuppress) { 3238 if (nsIFrame* frame = element->GetPrimaryFrame()) { 3239 if (auto* container = ScrollAnchorContainer::FindFor(frame)) { 3240 container->SuppressAdjustments(); 3241 } 3242 } 3243 } 3244 } 3245 3246 doc->ClearServoRestyleRoot(); 3247 ClearSnapshots(); 3248 3249 while (!currentChanges.IsEmpty()) { 3250 ProcessRestyledFrames(currentChanges); 3251 MOZ_ASSERT(currentChanges.IsEmpty()); 3252 for (ReentrantChange& change : newChanges) { 3253 if (!(change.mHint & nsChangeHint_ReconstructFrame) && 3254 !change.mContent->GetPrimaryFrame()) { 3255 // SVG Elements post change hints without ensuring that the primary 3256 // frame will be there after that (see bug 1366142). 3257 // 3258 // Just ignore those, since we can't really process them. 3259 continue; 3260 } 3261 currentChanges.AppendChange(change.mContent->GetPrimaryFrame(), 3262 change.mContent, change.mHint); 3263 } 3264 newChanges.Clear(); 3265 } 3266 3267 mReentrantChanges = nullptr; 3268 3269 // Suppress adjustments in the after-change scroll anchors if needed, now 3270 // that we're done reframing everything. 3271 for (Element* element : anchorsToSuppress) { 3272 if (nsIFrame* frame = element->GetPrimaryFrame()) { 3273 if (auto* container = ScrollAnchorContainer::FindFor(frame)) { 3274 container->SuppressAdjustments(); 3275 } 3276 } 3277 } 3278 3279 if (anyStyleChanged) { 3280 // Maybe no styles changed when: 3281 // 3282 // * Only explicit change hints were posted in the first place. 3283 // * When an attribute or state change in the content happens not to need 3284 // a restyle after all. 3285 // 3286 // In any case, we don't need to increment the restyle generation in that 3287 // case. 3288 IncrementRestyleGeneration(); 3289 } 3290 3291 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1980206 3292 presContext->PresShell()->MergeAnchorPosAnchorChanges(); 3293 3294 mInStyleRefresh = false; 3295 presContext->UpdateContainerQueryStylesAndAnchorPosLayout(); 3296 mInStyleRefresh = true; 3297 } 3298 3299 doc->ClearServoRestyleRoot(); 3300 presContext->FinishedContainerQueryUpdate(); 3301 presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded(); 3302 ClearSnapshots(); 3303 mRestyledAsWholeContainer.Clear(); 3304 styleSet->AssertTreeIsClean(); 3305 3306 mHaveNonAnimationRestyles = false; 3307 mRestyleForCSSRuleChanges = false; 3308 mInStyleRefresh = false; 3309 3310 // Now that everything has settled, see if we have enough free rule nodes in 3311 // the tree to warrant sweeping them. 3312 styleSet->MaybeGCRuleTree(); 3313 3314 // Note: We are in the scope of |animationsWithDestroyedFrame|, so 3315 // |mAnimationsWithDestroyedFrame| is still valid. 3316 MOZ_ASSERT(mAnimationsWithDestroyedFrame); 3317 mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames(); 3318 } 3319 3320 #ifdef DEBUG 3321 static void VerifyFlatTree(const nsIContent& aContent) { 3322 StyleChildrenIterator iter(&aContent); 3323 3324 for (auto* content = iter.GetNextChild(); content; 3325 content = iter.GetNextChild()) { 3326 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent); 3327 VerifyFlatTree(*content); 3328 } 3329 } 3330 #endif 3331 3332 void RestyleManager::ProcessPendingRestyles() { 3333 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Styles", LAYOUT); 3334 #ifdef DEBUG 3335 if (auto* root = mPresContext->Document()->GetRootElement()) { 3336 VerifyFlatTree(*root); 3337 } 3338 #endif 3339 3340 DoProcessPendingRestyles(ServoTraversalFlags::Empty); 3341 } 3342 3343 void RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() { 3344 if (mSnapshots.IsEmpty()) { 3345 return; 3346 } 3347 for (const auto& key : mSnapshots.Keys()) { 3348 // Servo data for the element might have been dropped. (e.g. by removing 3349 // from its document) 3350 if (key->HasFlag(ELEMENT_HAS_SNAPSHOT)) { 3351 Servo_ProcessInvalidations(StyleSet()->RawData(), key, &mSnapshots); 3352 } 3353 } 3354 ClearSnapshots(); 3355 } 3356 3357 void RestyleManager::UpdateOnlyAnimationStyles() { 3358 bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates(); 3359 if (!doCSS) { 3360 return; 3361 } 3362 3363 DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations); 3364 } 3365 3366 void RestyleManager::ElementStateChanged(Element* aElement, 3367 ElementState aChangedBits) { 3368 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("ElementStateChanged", 3369 LAYOUT_StyleComputation); 3370 #ifdef NIGHTLY_BUILD 3371 if (MOZ_UNLIKELY(mInStyleRefresh)) { 3372 MOZ_CRASH_UNSAFE_PRINTF( 3373 "Element state change during style refresh (%" PRIu64 ")", 3374 aChangedBits.GetInternalValue()); 3375 } 3376 #endif // NIGHTLY_BUILD 3377 3378 const ElementState kVisitedAndUnvisited = 3379 ElementState::VISITED | ElementState::UNVISITED; 3380 3381 // We'll restyle when the relevant visited query finishes, regardless of the 3382 // style (see Link::VisitedQueryFinished). So there's no need to do anything 3383 // as a result of this state change just yet. 3384 // 3385 // Note that this check checks for _both_ bits: This is only true when visited 3386 // changes to unvisited or vice-versa, but not when we start or stop being a 3387 // link itself. 3388 if (aChangedBits.HasAllStates(kVisitedAndUnvisited)) { 3389 aChangedBits &= ~kVisitedAndUnvisited; 3390 if (aChangedBits.IsEmpty()) { 3391 return; 3392 } 3393 } 3394 3395 if (auto changeHint = ChangeForContentStateChange(*aElement, aChangedBits)) { 3396 Servo_NoteExplicitHints(aElement, RestyleHint{0}, changeHint); 3397 } 3398 3399 // Don't bother taking a snapshot if no rules depend on these state bits. 3400 // 3401 // We always take a snapshot for the LTR/RTL event states, since Servo doesn't 3402 // track those bits in the same way, and we know that :dir() rules are always 3403 // present in UA style sheets. 3404 if (!aChangedBits.HasAtLeastOneOfStates(ElementState::DIR_STATES) && 3405 !StyleSet()->HasStateDependency(*aElement, aChangedBits)) { 3406 return; 3407 } 3408 3409 // Assuming we need to invalidate cached style in getComputedStyle for 3410 // undisplayed elements, since we don't know if it is needed. 3411 IncrementUndisplayedRestyleGeneration(); 3412 3413 const bool hasData = aElement->HasServoData(); 3414 if (!hasData && 3415 !(aElement->GetSelectorFlags() & 3416 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) { 3417 return; 3418 } 3419 3420 ServoElementSnapshot& snapshot = SnapshotFor(*aElement); 3421 ElementState previousState = aElement->StyleState() ^ aChangedBits; 3422 snapshot.AddState(previousState); 3423 3424 ServoStyleSet& styleSet = *StyleSet(); 3425 if (hasData) { 3426 // If the element has no style data, the siblings don't need to be 3427 // invalidated, we're inside a display: none subtree anyways. We still need 3428 // to invalidate for :has() tho, because that might go upwards. 3429 MaybeRestyleForNthOfState(styleSet, aElement, aChangedBits); 3430 } 3431 MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits); 3432 } 3433 3434 void RestyleManager::CustomStatesWillChange(Element& aElement) { 3435 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh); 3436 3437 IncrementUndisplayedRestyleGeneration(); 3438 3439 // Relative selector invalidation travels ancestor and earlier sibling 3440 // direction, so it's very possible that it invalidates a styled element. 3441 if (!aElement.HasServoData() && 3442 !(aElement.GetSelectorFlags() & 3443 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) { 3444 return; 3445 } 3446 3447 ServoElementSnapshot& snapshot = SnapshotFor(aElement); 3448 snapshot.AddCustomStates(aElement); 3449 } 3450 3451 void RestyleManager::CustomStateChanged(Element& aElement, nsAtom* aState) { 3452 ServoStyleSet& styleSet = *StyleSet(); 3453 MaybeRestyleForNthOfCustomState(styleSet, aElement, aState); 3454 styleSet.MaybeInvalidateRelativeSelectorCustomStateDependency( 3455 aElement, aState, Snapshots()); 3456 } 3457 3458 void RestyleManager::MaybeRestyleForNthOfCustomState(ServoStyleSet& aStyleSet, 3459 Element& aChild, 3460 nsAtom* aState) { 3461 const auto* parentNode = aChild.GetParentNode(); 3462 MOZ_ASSERT(parentNode); 3463 const auto parentFlags = parentNode->GetSelectorFlags(); 3464 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) { 3465 return; 3466 } 3467 3468 if (aStyleSet.HasNthOfCustomStateDependency(aChild, aState)) { 3469 RestyleSiblingsForNthOf(&aChild, parentFlags); 3470 } 3471 } 3472 3473 void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet, 3474 Element* aChild, 3475 ElementState aChangedBits) { 3476 const auto* parentNode = aChild->GetParentNode(); 3477 MOZ_ASSERT(parentNode); 3478 const auto parentFlags = parentNode->GetSelectorFlags(); 3479 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) { 3480 return; 3481 } 3482 3483 if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) { 3484 RestyleSiblingsForNthOf(aChild, parentFlags); 3485 } 3486 } 3487 3488 static inline bool AttributeInfluencesOtherPseudoClassState( 3489 const Element& aElement, const nsAtom* aAttribute) { 3490 // We must record some state for :-moz-table-border-nonzero and 3491 // :-moz-select-list-box. 3492 3493 if (aAttribute == nsGkAtoms::border) { 3494 return aElement.IsHTMLElement(nsGkAtoms::table); 3495 } 3496 3497 if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) { 3498 return aElement.IsHTMLElement(nsGkAtoms::select); 3499 } 3500 3501 return false; 3502 } 3503 3504 static inline bool NeedToRecordAttrChange( 3505 const ServoStyleSet& aStyleSet, const Element& aElement, 3506 int32_t aNameSpaceID, nsAtom* aAttribute, 3507 bool* aInfluencesOtherPseudoClassState) { 3508 *aInfluencesOtherPseudoClassState = 3509 AttributeInfluencesOtherPseudoClassState(aElement, aAttribute); 3510 3511 // If the attribute influences one of the pseudo-classes that are backed by 3512 // attributes, we just record it. 3513 if (*aInfluencesOtherPseudoClassState) { 3514 return true; 3515 } 3516 3517 // We assume that id and class attributes are used in class/id selectors, and 3518 // thus record them. 3519 // 3520 // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet, 3521 // presumably we could try to filter the old and new id, but it's not clear 3522 // it's worth it. 3523 if (aNameSpaceID == kNameSpaceID_None && 3524 (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) { 3525 return true; 3526 } 3527 3528 // We always record lang="", even though we force a subtree restyle when it 3529 // changes, since it can change how its siblings match :lang(..) due to 3530 // selectors like :lang(..) + div. 3531 if (aAttribute == nsGkAtoms::lang) { 3532 return true; 3533 } 3534 3535 // Otherwise, just record the attribute change if a selector in the page may 3536 // reference it from an attribute selector. 3537 return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute); 3538 } 3539 3540 void RestyleManager::AttributeWillChange(Element* aElement, 3541 int32_t aNameSpaceID, 3542 nsAtom* aAttribute, 3543 AttrModType aModType) { 3544 TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute); 3545 } 3546 3547 void RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) { 3548 TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None, 3549 nsGkAtoms::_class); 3550 } 3551 3552 void RestyleManager::TakeSnapshotForAttributeChange(Element& aElement, 3553 int32_t aNameSpaceID, 3554 nsAtom* aAttribute) { 3555 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh); 3556 3557 bool influencesOtherPseudoClassState; 3558 if (!NeedToRecordAttrChange(*StyleSet(), aElement, aNameSpaceID, aAttribute, 3559 &influencesOtherPseudoClassState)) { 3560 return; 3561 } 3562 3563 // We cannot tell if the attribute change will affect the styles of 3564 // undisplayed elements, because we don't actually restyle those elements 3565 // during the restyle traversal. So just assume that the attribute change can 3566 // cause the style to change. 3567 IncrementUndisplayedRestyleGeneration(); 3568 3569 // Relative selector invalidation travels ancestor and earlier sibling 3570 // direction, so it's very possible that it invalidates a styled element. 3571 if (!aElement.HasServoData() && 3572 !(aElement.GetSelectorFlags() & 3573 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) { 3574 return; 3575 } 3576 3577 // Some other random attribute changes may also affect the transitions, 3578 // so we also set this true here. 3579 mHaveNonAnimationRestyles = true; 3580 3581 ServoElementSnapshot& snapshot = SnapshotFor(aElement); 3582 snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute); 3583 3584 if (influencesOtherPseudoClassState) { 3585 snapshot.AddOtherPseudoClassState(aElement); 3586 } 3587 } 3588 3589 // For some attribute changes we must restyle the whole subtree: 3590 // 3591 // * lang="" and xml:lang="" can affect all descendants due to :lang() 3592 // * exportparts can affect all descendant parts. We could certainly integrate 3593 // it better in the invalidation machinery if it was necessary. 3594 static inline bool AttributeChangeRequiresSubtreeRestyle( 3595 const Element& aElement, nsAtom* aAttr) { 3596 if (aAttr == nsGkAtoms::exportparts) { 3597 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for 3598 // exportparts attribute changes? 3599 return !!aElement.GetShadowRoot(); 3600 } 3601 return aAttr == nsGkAtoms::lang; 3602 } 3603 3604 void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID, 3605 nsAtom* aAttribute, AttrModType aModType, 3606 const nsAttrValue* aOldValue) { 3607 MOZ_ASSERT(!mInStyleRefresh); 3608 3609 auto changeHint = nsChangeHint(0); 3610 auto restyleHint = RestyleHint{0}; 3611 3612 changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType); 3613 3614 MaybeRestyleForNthOfAttribute(aElement, aNameSpaceID, aAttribute, aOldValue); 3615 MaybeRestyleForRelativeSelectorAttribute(aElement, aNameSpaceID, aAttribute, 3616 aOldValue); 3617 3618 if (aAttribute == nsGkAtoms::style) { 3619 restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE; 3620 } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) { 3621 restyleHint |= RestyleHint::RestyleSubtree(); 3622 } else if (aElement->IsInShadowTree() && aAttribute == nsGkAtoms::part) { 3623 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for part 3624 // attribute changes? 3625 restyleHint |= RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_PSEUDOS; 3626 } 3627 3628 if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) { 3629 // See if we have appearance information for a theme. 3630 StyleAppearance appearance = 3631 primaryFrame->StyleDisplay()->EffectiveAppearance(); 3632 if (appearance != StyleAppearance::None) { 3633 nsITheme* theme = PresContext()->Theme(); 3634 if (theme->ThemeSupportsWidget(PresContext(), primaryFrame, appearance) && 3635 theme->WidgetAttributeChangeRequiresRepaint(appearance, aAttribute)) { 3636 changeHint |= nsChangeHint_RepaintFrame; 3637 } 3638 } 3639 3640 primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType); 3641 } 3642 3643 if (restyleHint || changeHint) { 3644 Servo_NoteExplicitHints(aElement, restyleHint, changeHint); 3645 } 3646 3647 if (restyleHint) { 3648 // Assuming we need to invalidate cached style in getComputedStyle for 3649 // undisplayed elements, since we don't know if it is needed. 3650 IncrementUndisplayedRestyleGeneration(); 3651 3652 // If we change attributes, we have to mark this to be true, so we will 3653 // increase the animation generation for the new created transition if any. 3654 mHaveNonAnimationRestyles = true; 3655 } 3656 } 3657 3658 void RestyleManager::RestyleSiblingsForNthOf(Element* aChild, 3659 NodeSelectorFlags aParentFlags) { 3660 StyleSet()->RestyleSiblingsForNthOf(*aChild, 3661 static_cast<uint32_t>(aParentFlags)); 3662 } 3663 3664 void RestyleManager::MaybeRestyleForNthOfAttribute( 3665 Element* aChild, int32_t aNameSpaceID, nsAtom* aAttribute, 3666 const nsAttrValue* aOldValue) { 3667 const auto* parentNode = aChild->GetParentNode(); 3668 MOZ_ASSERT(parentNode); 3669 const auto parentFlags = parentNode->GetSelectorFlags(); 3670 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) { 3671 return; 3672 } 3673 if (!aChild->HasServoData()) { 3674 return; 3675 } 3676 3677 bool mightHaveNthOfDependency; 3678 auto& styleSet = *StyleSet(); 3679 if (aAttribute == nsGkAtoms::id && 3680 MOZ_LIKELY(aNameSpaceID == kNameSpaceID_None)) { 3681 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom 3682 ? aOldValue->GetAtomValue() 3683 : nullptr; 3684 mightHaveNthOfDependency = 3685 styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID()); 3686 } else if (aAttribute == nsGkAtoms::_class && 3687 MOZ_LIKELY(aNameSpaceID == kNameSpaceID_None)) { 3688 mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild); 3689 } else { 3690 mightHaveNthOfDependency = 3691 styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute); 3692 } 3693 3694 if (mightHaveNthOfDependency) { 3695 RestyleSiblingsForNthOf(aChild, parentFlags); 3696 } 3697 } 3698 3699 void RestyleManager::MaybeRestyleForRelativeSelectorAttribute( 3700 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, 3701 const nsAttrValue* aOldValue) { 3702 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) { 3703 return; 3704 } 3705 auto& styleSet = *StyleSet(); 3706 if (aAttribute == nsGkAtoms::id && 3707 MOZ_LIKELY(aNameSpaceID == kNameSpaceID_None)) { 3708 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom 3709 ? aOldValue->GetAtomValue() 3710 : nullptr; 3711 styleSet.MaybeInvalidateRelativeSelectorIDDependency( 3712 *aElement, oldAtom, aElement->GetID(), Snapshots()); 3713 } else if (aAttribute == nsGkAtoms::_class && 3714 MOZ_LIKELY(aNameSpaceID == kNameSpaceID_None)) { 3715 styleSet.MaybeInvalidateRelativeSelectorClassDependency(*aElement, 3716 Snapshots()); 3717 } else { 3718 styleSet.MaybeInvalidateRelativeSelectorAttributeDependency( 3719 *aElement, aAttribute, Snapshots()); 3720 } 3721 } 3722 3723 void RestyleManager::MaybeRestyleForRelativeSelectorState( 3724 ServoStyleSet& aStyleSet, Element* aElement, ElementState aChangedBits) { 3725 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) { 3726 return; 3727 } 3728 aStyleSet.MaybeInvalidateRelativeSelectorStateDependency( 3729 *aElement, aChangedBits, Snapshots()); 3730 } 3731 3732 void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) { 3733 // This is only called when moving frames in or out of the first-line 3734 // pseudo-element (or one of its descendants). We can't say much about 3735 // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into 3736 // something inside an inline-block on the first line the ancestors could be 3737 // totally arbitrary), but we will definitely find a line frame on the 3738 // ancestor chain. Note that the lineframe may not actually be the one that 3739 // corresponds to ::first-line; when we're moving _out_ of the ::first-line it 3740 // will be one of the continuations instead. 3741 #ifdef DEBUG 3742 { 3743 nsIFrame* f = aFrame->GetParent(); 3744 while (f && !f->IsLineFrame()) { 3745 f = f->GetParent(); 3746 } 3747 MOZ_ASSERT(f, "Must have found a first-line frame"); 3748 } 3749 #endif 3750 3751 DoReparentComputedStyleForFirstLine(aFrame, *StyleSet()); 3752 } 3753 3754 static bool IsFrameAboutToGoAway(nsIFrame* aFrame) { 3755 auto* element = Element::FromNode(aFrame->GetContent()); 3756 if (!element) { 3757 return false; 3758 } 3759 return !element->HasServoData(); 3760 } 3761 3762 void RestyleManager::DoReparentComputedStyleForFirstLine( 3763 nsIFrame* aFrame, ServoStyleSet& aStyleSet) { 3764 if (IsFrameAboutToGoAway(aFrame)) { 3765 // We're entering a display: none subtree, which we know it's going to get 3766 // rebuilt. Don't bother reparenting. 3767 return; 3768 } 3769 3770 if (aFrame->IsPlaceholderFrame()) { 3771 // Also reparent the out-of-flow and all its continuations. We're doing 3772 // this to match Gecko for now, but it's not clear that this behavior is 3773 // correct per spec. It's certainly pretty odd for out-of-flows whose 3774 // containing block is not within the first line. 3775 // 3776 // Right now we're somewhat inconsistent in this testcase: 3777 // 3778 // <style> 3779 // div { color: orange; clear: left; } 3780 // div::first-line { color: blue; } 3781 // </style> 3782 // <div> 3783 // <span style="float: left">What color is this text?</span> 3784 // </div> 3785 // <div> 3786 // <span><span style="float: left">What color is this text?</span></span> 3787 // </div> 3788 // 3789 // We make the first float orange and the second float blue. On the other 3790 // hand, if the float were within an inline-block that was on the first 3791 // line, arguably it _should_ inherit from the ::first-line... 3792 nsIFrame* outOfFlow = 3793 nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame); 3794 MOZ_ASSERT(outOfFlow, "no out-of-flow frame"); 3795 for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) { 3796 DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet); 3797 } 3798 } 3799 3800 nsIFrame* providerFrame; 3801 ComputedStyle* newParentStyle = 3802 aFrame->GetParentComputedStyle(&providerFrame); 3803 // If our provider is our child, we want to reparent it first, because we 3804 // inherit style from it. 3805 bool isChild = providerFrame && providerFrame->GetParent() == aFrame; 3806 nsIFrame* providerChild = nullptr; 3807 if (isChild) { 3808 DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet); 3809 // Get the style again after ReparentComputedStyle() which might have 3810 // changed it. 3811 newParentStyle = providerFrame->Style(); 3812 providerChild = providerFrame; 3813 MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), 3814 "Out of flow provider?"); 3815 } 3816 3817 if (!newParentStyle) { 3818 // No need to do anything here for this frame, but we should still reparent 3819 // its descendants, because those may have styles that inherit from the 3820 // parent of this frame (e.g. non-anonymous columns in an anonymous 3821 // colgroup). 3822 MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(), 3823 "Why did this frame not end up with a parent context?"); 3824 ReparentFrameDescendants(aFrame, providerChild, aStyleSet); 3825 return; 3826 } 3827 3828 bool isElement = aFrame->GetContent()->IsElement(); 3829 3830 // We probably don't want to initiate transitions from ReparentComputedStyle, 3831 // since we call it during frame construction rather than in response to 3832 // dynamic changes. 3833 // Also see the comment at the start of 3834 // nsTransitionManager::ConsiderInitiatingTransition. 3835 // 3836 // We don't try to do the fancy copying from previous continuations that 3837 // GeckoRestyleManager does here, because that relies on knowing the parents 3838 // of ComputedStyles, and we don't know those. 3839 ComputedStyle* oldStyle = aFrame->Style(); 3840 Element* ourElement = isElement ? aFrame->GetContent()->AsElement() : nullptr; 3841 ComputedStyle* newParent = newParentStyle; 3842 3843 if (!providerFrame) { 3844 // No providerFrame means we inherited from a display:contents thing. Our 3845 // layout parent style is the style of our nearest ancestor frame. But we 3846 // have to be careful to do that with our placeholder, not with us, if we're 3847 // out of flow. 3848 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 3849 aFrame->FirstContinuation() 3850 ->GetPlaceholderFrame() 3851 ->GetLayoutParentStyleForOutOfFlow(&providerFrame); 3852 } else { 3853 providerFrame = nsIFrame::CorrectStyleParentFrame( 3854 aFrame->GetParent(), oldStyle->GetPseudoType()); 3855 } 3856 } 3857 ComputedStyle* layoutParent = providerFrame->Style(); 3858 3859 RefPtr<ComputedStyle> newStyle = aStyleSet.ReparentComputedStyle( 3860 oldStyle, newParent, layoutParent, ourElement); 3861 aFrame->SetComputedStyle(newStyle); 3862 3863 // This logic somewhat mirrors the logic in 3864 // RestyleManager::ProcessPostTraversal. 3865 if (isElement) { 3866 // We can't use UpdateAdditionalComputedStyles as-is because it needs a 3867 // ServoRestyleState and maintaining one of those during a _frametree_ 3868 // traversal is basically impossible. 3869 int32_t index = 0; 3870 while (auto* oldAdditionalStyle = 3871 aFrame->GetAdditionalComputedStyle(index)) { 3872 RefPtr<ComputedStyle> newAdditionalContext = 3873 aStyleSet.ReparentComputedStyle(oldAdditionalStyle, newStyle, 3874 newStyle, nullptr); 3875 aFrame->SetAdditionalComputedStyle(index, newAdditionalContext); 3876 ++index; 3877 } 3878 } 3879 3880 // Generally, owned anon boxes are our descendants. The only exceptions are 3881 // tables (for the table wrapper) and inline frames (for the block part of the 3882 // block-in-inline split). We're going to update our descendants when looping 3883 // over kids, and we don't want to update the block part of a block-in-inline 3884 // split if the inline is on the first line but the block is not (and if the 3885 // block is, it's the child of something else on the first line and will get 3886 // updated as a child). And given how this method ends up getting called, if 3887 // we reach here for a table frame, we are already in the middle of 3888 // reparenting the table wrapper frame. So no need to 3889 // UpdateStyleOfOwnedAnonBoxes() here. 3890 3891 ReparentFrameDescendants(aFrame, providerChild, aStyleSet); 3892 3893 // We do not need to do the equivalent of UpdateFramePseudoElementStyles, 3894 // because those are handled by our descendant walk. 3895 } 3896 3897 void RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame, 3898 nsIFrame* aProviderChild, 3899 ServoStyleSet& aStyleSet) { 3900 for (const auto& childList : aFrame->ChildLists()) { 3901 for (nsIFrame* child : childList.mList) { 3902 // only do frames that are in flow 3903 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && 3904 child != aProviderChild) { 3905 DoReparentComputedStyleForFirstLine(child, aStyleSet); 3906 } 3907 } 3908 } 3909 } 3910 3911 } // namespace mozilla