/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef DEBUG for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
NS_ASSERTION(cur->IsRootOfNativeAnonymousSubtree() ==
aFirstNewContent->IsRootOfNativeAnonymousSubtree(), "anonymous nodes should not be in child lists");
} #endif
// We get called explicitly with NAC by editor and view transitions code, but // in those cases we don't need to do any invalidation. if (MOZ_UNLIKELY(aFirstNewContent->IsRootOfNativeAnonymousSubtree())) { return;
}
// The container cannot be a document.
MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
if (selectorFlags & NodeSelectorFlags::HasEmptySelector) { // see whether we need to restyle the container bool wasEmpty = true; // :empty or :-moz-only-whitespace for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent;
cur = cur->GetNextSibling()) { // We don't know whether we're testing :empty or :-moz-only-whitespace, // so be conservative and assume :-moz-only-whitespace (i.e., make // IsSignificantChild less likely to be true, and thus make us more // likely to restyle). if (nsStyleUtil::IsSignificantChild(cur, false)) {
wasEmpty = false; break;
}
} if (wasEmpty && container->IsElement()) {
RestyleForEmptyChange(container->AsElement()); return;
}
}
if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
RestyleWholeContainer(container, selectorFlags); // Restyling the container is the most we can do here, so we're done. return;
}
if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) { // restyle the last element child before this node for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur;
cur = cur->GetPreviousSibling()) { if (cur->IsElement()) { auto* element = cur->AsElement();
PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
nsChangeHint(0));
StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
*element, StyleRelativeSelectorNthEdgeInvalidateFor::Last); break;
}
}
}
}
void RestyleManager::RestylePreviousSiblings(nsIContent* aStartingSibling) { for (nsIContent* sibling = aStartingSibling; sibling;
sibling = sibling->GetPreviousSibling()) { if (auto* element = Element::FromNode(sibling)) {
PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
}
}
}
void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) { for (nsIContent* sibling = aStartingSibling; sibling;
sibling = sibling->GetNextSibling()) { if (auto* element = Element::FromNode(sibling)) {
PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
}
}
}
// In some cases (:empty + E, :empty ~ E), a change in the content of // an element requires restyling its parent's siblings.
nsIContent* grandparent = aContainer->GetParent(); if (!grandparent || !(grandparent->GetSelectorFlags() &
NodeSelectorFlags::HasSlowSelectorLaterSiblings)) { return;
}
RestyleSiblingsStartingWith(aContainer->GetNextSibling());
}
void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer,
nsIContent* aChangedChild) {
MOZ_ASSERT(aContainer->GetSelectorFlags() &
NodeSelectorFlags::HasEdgeChildSelector);
MOZ_ASSERT(aChangedChild->GetParent() == aContainer); // restyle the previously-first element child if it is after this node bool passedChild = false; for (nsIContent* content = aContainer->GetFirstChild(); content;
content = content->GetNextSibling()) { if (content == aChangedChild) {
passedChild = true; continue;
} if (content->IsElement()) { if (passedChild) { auto* element = content->AsElement();
PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
nsChangeHint(0));
StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
*element, StyleRelativeSelectorNthEdgeInvalidateFor::First);
} break;
}
} // restyle the previously-last element child if it is before this node
passedChild = false; for (nsIContent* content = aContainer->GetLastChild(); content;
content = content->GetPreviousSibling()) { if (content == aChangedChild) {
passedChild = true; continue;
} if (content->IsElement()) { if (passedChild) { auto* element = content->AsElement();
PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
nsChangeHint(0));
StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
*element, StyleRelativeSelectorNthEdgeInvalidateFor::Last);
} break;
}
}
}
template <typename CharT> bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) { for (auto index : IntegerRange(aUpTo)) { if (!dom::IsSpaceCharacter(aBuffer[index])) { returnfalse;
}
} returntrue;
}
template <typename CharT> bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength,
size_t aNewLength) {
MOZ_ASSERT(aOldLength <= aNewLength); if (!WhitespaceOnly(aBuffer, aOldLength)) { // The old text was already not whitespace-only. returnfalse;
}
staticbool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) {
MOZ_ASSERT(aChild->GetParent() == aContainer); for (nsIContent* child = aContainer->GetFirstChild(); child;
child = child->GetNextSibling()) { if (child == aChild) { continue;
} // We don't know whether we're testing :empty or :-moz-only-whitespace, // so be conservative and assume :-moz-only-whitespace (i.e., make // IsSignificantChild less likely to be true, and thus make us more // likely to restyle). if (nsStyleUtil::IsSignificantChild(child, false)) { returntrue;
}
}
returnfalse;
}
void RestyleManager::CharacterDataChanged(
nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
nsINode* parent = aContent->GetParentNode();
MOZ_ASSERT(parent, "How were we notified of a stray node?");
constauto slowSelectorFlags =
parent->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags; if (!(slowSelectorFlags & (NodeSelectorFlags::HasEmptySelector |
NodeSelectorFlags::HasEdgeChildSelector))) { // Nothing to do, no other slow selector can change as a result of this. return;
}
if (!aContent->IsText()) { // Doesn't matter to styling (could be a processing instruction or a // comment), it can't change whether any selectors match or don't. return;
}
if (MOZ_UNLIKELY(!parent->IsElement())) {
MOZ_ASSERT(parent->IsShadowRoot()); return;
}
if (MOZ_UNLIKELY(aContent->IsRootOfNativeAnonymousSubtree())) { // This is an anonymous node and thus isn't in child lists, so isn't taken // into account for selector matching the relevant selectors here. return;
}
// Handle appends specially since they're common and we can know both the old // and the new text exactly. // // TODO(emilio): This could be made much more general if :-moz-only-whitespace // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only // need to know whether we went from empty to non-empty, and that's trivial to // know, with CharacterDataChangeInfo... if (!aInfo.mAppend) { // FIXME(emilio): This restyles unnecessarily if the text node is the only // child of the parent element. Fortunately, it's uncommon to have such // nodes and this not being an append. // // See the testcase in bug 1427625 for a test-case that triggers this.
RestyleForInsertOrChange(aContent); return;
}
const nsTextFragment* text = &aContent->AsText()->TextFragment();
if (!emptyChanged && !whitespaceOnlyChanged) { return;
}
if (slowSelectorFlags & NodeSelectorFlags::HasEmptySelector) { if (!HasAnySignificantSibling(parent->AsElement(), aContent)) { // We used to be empty, restyle the parent.
RestyleForEmptyChange(parent->AsElement()); return;
}
}
if (slowSelectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
MaybeRestyleForEdgeChildChange(parent, aContent);
}
}
// Restyling for a ContentInserted or CharacterDataChanged notification. // This could be used for ContentRemoved as well if we got the // notification before the removal happened (and sometimes // CharacterDataChanged is more like a removal than an addition). // The comments are written and variables are named in terms of it being // a ContentInserted notification. void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
nsINode* container = aChild->GetParentNode();
MOZ_ASSERT(container);
NS_ASSERTION(!aChild->IsRootOfNativeAnonymousSubtree(), "anonymous nodes should not be in child lists");
// The container cannot be a document.
MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
container->IsElement()) { // See whether we need to restyle the container due to :empty / // :-moz-only-whitespace. constbool wasEmpty =
!HasAnySignificantSibling(container->AsElement(), aChild); if (wasEmpty) { // FIXME(emilio): When coming from CharacterDataChanged this can restyle // unnecessarily. Also can restyle unnecessarily if aChild is not // significant anyway, though that's more unlikely.
RestyleForEmptyChange(container->AsElement()); return;
}
}
if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
RestyleWholeContainer(container, selectorFlags); // Restyling the container is the most we can do here, so we're done. return;
}
if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) { // Restyle all later siblings. if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
aChild->GetNextElementSibling(), /* aForceRestyleSiblings = */ true);
} else {
RestyleSiblingsStartingWith(aChild->GetNextSibling());
}
}
if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
MaybeRestyleForEdgeChildChange(container, aChild);
}
}
// Computed style data isn't useful for detached nodes, and we'll need to // recompute it anyway if we ever insert the nodes back into a document. if (auto* element = Element::FromNode(aOldChild)) {
RestyleManager::ClearServoDataFromSubtree(element); // If this element is undisplayed or may have undisplayed descendants, we // need to invalidate the cache, since there's the unlikely event of those // elements getting destroyed and their addresses reused in a way that we // look up the cache with their address for a different element before it's // invalidated.
IncrementUndisplayedRestyleGeneration();
}
// This is called with anonymous nodes explicitly by editor and view // transitions code, which manage anon content manually. // See similar code in ContentAppended. if (MOZ_UNLIKELY(aOldChild->IsRootOfNativeAnonymousSubtree())) {
MOZ_ASSERT(!aOldChild->GetNextSibling(), "NAC doesn't have siblings");
MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode), "anonymous nodes should not be in child lists (bug 439258)"); return;
}
if (aOldChild->IsElement()) {
StyleSet()->MaybeInvalidateForElementRemove(*aOldChild->AsElement());
}
// The container cannot be a document. constbool containerIsElement = container->IsElement();
MOZ_ASSERT(containerIsElement || container->IsShadowRoot());
if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
containerIsElement) { // see whether we need to restyle the container bool isEmpty = true; // :empty or :-moz-only-whitespace for (nsIContent* child = container->GetFirstChild(); child;
child = child->GetNextSibling()) { // We don't know whether we're testing :empty or :-moz-only-whitespace, // so be conservative and assume :-moz-only-whitespace (i.e., make // IsSignificantChild less likely to be true, and thus make us more // likely to restyle). if (child != aOldChild && nsStyleUtil::IsSignificantChild(child, false)) {
isEmpty = false; break;
}
} if (isEmpty && containerIsElement) {
RestyleForEmptyChange(container->AsElement()); return;
}
}
// It is somewhat common to remove all nodes in a container from the // beginning. If we're doing that, going through the // HasSlowSelectorLaterSiblings code-path would be quadratic, so that's not // amazing. Instead, we take the slower path (which also restyles the // container) in that case. It restyles one more element, but it avoids the // quadratic behavior. constbool restyleWholeContainer =
(selectorFlags & NodeSelectorFlags::HasSlowSelector) ||
(selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings &&
!aOldChild->GetPreviousSibling());
if (restyleWholeContainer) {
RestyleWholeContainer(container, selectorFlags); // Restyling the container is the most we can do here, so we're done. return;
}
if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) { // Restyle all later siblings. if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
Element* nextSibling = aOldChild->GetNextElementSibling();
StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
nextSibling, /* aForceRestyleSiblings = */ true);
} else {
RestyleSiblingsStartingWith(aOldChild->GetNextSibling());
}
}
if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) { const nsIContent* nextSibling = aOldChild->GetNextSibling(); // restyle the now-first element child if it was after aOldChild bool reachedFollowingSibling = false; for (nsIContent* content = container->GetFirstChild(); content;
content = content->GetNextSibling()) { if (content == aOldChild) { // aOldChild is getting removed, so we don't want to account for it for // the purposes of computing whether we're now the first / last child. continue;
} if (content == nextSibling) {
reachedFollowingSibling = true; // do NOT continue here; we might want to restyle this node
} if (content->IsElement()) { if (reachedFollowingSibling) { auto* element = content->AsElement();
PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
nsChangeHint(0));
StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
*element, StyleRelativeSelectorNthEdgeInvalidateFor::First);
} break;
}
} // restyle the now-last element child if it was before aOldChild
reachedFollowingSibling = !nextSibling; for (nsIContent* content = container->GetLastChild(); content;
content = content->GetPreviousSibling()) { if (content == aOldChild) { // See above. continue;
} if (content->IsElement()) { if (reachedFollowingSibling) { auto* element = content->AsElement();
PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
nsChangeHint(0));
StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
*element, StyleRelativeSelectorNthEdgeInvalidateFor::Last);
} break;
} if (content == nextSibling) {
reachedFollowingSibling = true;
}
}
}
}
if (aFrame.IsGeneratedContentFrame()) { // If it's other generated content, ignore state changes on it. return aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage);
}
if (aElement.IsAnyOfXULElements(nsGkAtoms::checkbox, nsGkAtoms::radio)) { // The checkbox inside these elements inherit hover state and so on, see // nsNativeTheme::GetContentState. // FIXME(emilio): Would be nice to not have these hard-coded. returntrue;
} auto appearance = aFrame.StyleDisplay()->EffectiveAppearance(); if (appearance == StyleAppearance::None) { returnfalse;
}
nsPresContext* pc = aFrame.PresContext(); return pc->Theme()->ThemeSupportsWidget(pc, &aFrame, appearance);
}
/** * Calculates the change hint and the restyle hint for a given content state * change.
*/ static nsChangeHint ChangeForContentStateChange(const Element& aElement,
ElementState aStateMask) { auto changeHint = nsChangeHint(0);
// Any change to a content state that affects which frames we construct // must lead to a frame reconstruct here if we already have a frame. // Note that we never decide through non-CSS means to not create frames // based on content states, so if we already don't have a frame we don't // need to force a reframe -- if it's needed, the HasStateDependentStyle // call will handle things. if (nsIFrame* primaryFrame = aElement.GetPrimaryFrame()) { if (StateChangeMayAffectFrame(aElement, *primaryFrame, aStateMask)) { return nsChangeHint_ReconstructFrame;
} if (RepaintForAppearance(*primaryFrame, aElement, aStateMask)) {
changeHint |= nsChangeHint_RepaintFrame;
}
primaryFrame->ElementStateChanged(aStateMask);
}
if (aStateMask.HasState(ElementState::VISITED)) { // Exposing information to the page about whether the link is // visited or not isn't really something we can worry about here. // FIXME: We could probably do this a bit better.
changeHint |= nsChangeHint_RepaintFrame;
}
// This changes the applicable text-transform in the editor root. if (aStateMask.HasState(ElementState::REVEALED)) { // This is the same change hint as tweaking text-transform.
changeHint |= NS_STYLE_HINT_REFLOW;
}
return changeHint;
}
#ifdef DEBUG /* static */
nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) {
nsCString result; bool any = false; constchar* names[] = {"RepaintFrame", "NeedReflow", "ClearAncestorIntrinsics", "ClearDescendantIntrinsics", "NeedDirtyReflow", "UpdateCursor", "UpdateEffects", "UpdateOpacityLayer", "UpdateTransformLayer", "ReconstructFrame", "UpdateOverflow", "UpdateSubtreeOverflow", "UpdatePostTransformOverflow", "UpdateParentOverflow", "ChildrenOnlyTransform", "RecomputePosition", "UpdateContainingBlock", "BorderStyleNoneChange", "SchedulePaint", "NeutralChange", "InvalidateRenderingObservers", "ReflowChangesSizeOrPosition", "UpdateComputedBSize", "UpdateUsesOpacity", "UpdateBackgroundPosition", "AddOrRemoveTransform", "ScrollbarChange", "UpdateTableCellSpans", "VisibilityChange"};
static_assert(nsChangeHint_AllHints == static_cast<uint32_t>((1ull << std::size(names)) - 1), "Name list doesn't match change hints.");
uint32_t hint = aHint & static_cast<uint32_t>((1ull << std::size(names)) - 1);
uint32_t rest =
aHint & ~static_cast<uint32_t>((1ull << std::size(names)) - 1); if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
result.AppendLiteral("NS_STYLE_HINT_REFLOW");
hint = hint & ~NS_STYLE_HINT_REFLOW;
any = true;
} elseif ((hint & nsChangeHint_AllReflowHints) ==
nsChangeHint_AllReflowHints) {
result.AppendLiteral("nsChangeHint_AllReflowHints");
hint = hint & ~nsChangeHint_AllReflowHints;
any = true;
} elseif ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
result.AppendLiteral("NS_STYLE_HINT_VISUAL");
hint = hint & ~NS_STYLE_HINT_VISUAL;
any = true;
} for (uint32_t i = 0; i < std::size(names); i++) { if (hint & (1u << i)) { if (any) {
result.AppendLiteral(" | ");
}
result.AppendPrintf("nsChangeHint_%s", names[i]);
any = true;
}
} if (rest) { if (any) {
result.AppendLiteral(" | ");
}
result.AppendPrintf("0x%0x", rest);
} else { if (!any) {
result.AppendLiteral("nsChangeHint(0)");
}
} return result;
} #endif
/** * Sync views on the frame and all of it's descendants (following placeholders). * The change hint should be some combination of nsChangeHint_RepaintFrame, * nsChangeHint_UpdateOpacityLayer and nsChangeHint_SchedulePaint, nothing else.
*/ staticvoid SyncViewsAndInvalidateDescendants(nsIFrame*, nsChangeHint);
/** * This helper function is used to find the correct SVG frame to target when we * encounter nsChangeHint_ChildrenOnlyTransform; needed since sometimes we end * up handling that hint while processing hints for one of the SVG frame's * ancestor frames. * * The reason that we sometimes end up trying to process the hint for an * ancestor of the SVG frame that the hint is intended for is due to the way we * process restyle events. ApplyRenderingChangeToTree adjusts the frame from * the restyled element's principle frame to one of its ancestor frames based * on what nsCSSRendering::FindBackground returns, since the background style * may have been propagated up to an ancestor frame. Processing hints using an * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is * a special case since it is intended to update a specific frame.
*/ static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) { if (aFrame->IsViewportFrame()) { // This happens if the root-<svg> is fixed positioned, in which case we // can't use aFrame->GetContent() to find the primary frame, since // GetContent() returns nullptr for ViewportFrame.
aFrame = aFrame->PrincipalChildList().FirstChild();
} // For a ScrollContainerFrame, this will get the SVG frame that has the // children-only transforms:
aFrame = aFrame->GetContent()->GetPrimaryFrame(); if (aFrame->IsSVGOuterSVGFrame()) {
aFrame = aFrame->PrincipalChildList().FirstChild();
MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(), "Where is the SVGOuterSVGFrame's anon child??");
}
MOZ_ASSERT(aFrame->IsSVGContainerFrame(), "Children-only transforms only expected on SVG frames"); return aFrame;
}
// This function tries to optimize a position style change by either // moving aFrame or ignoring the style change when it's safe to do so. // It returns true when that succeeds, otherwise it posts a reflow request // and returns false. staticbool RecomputePosition(nsIFrame* aFrame) { // It's pointless to move around frames that have never been reflowed or // are dirty (i.e. they will be reflowed), or aren't affected by position // styles. if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
NS_FRAME_SVG_LAYOUT)) { returntrue;
}
// Don't process position changes on table frames, since we already handle // the dynamic position change on the table wrapper frame, and the // reflow-based fallback code path also ignores positions on inner table // frames. if (aFrame->IsTableFrame()) { returntrue;
}
const nsStyleDisplay* display = aFrame->StyleDisplay(); // Changes to the offsets of a non-positioned element can safely be ignored. if (display->mPosition == StylePositionProperty::Static) { returntrue;
}
// Don't process position changes on frames which have views or the ones which // have a view somewhere in their descendants, because the corresponding view // needs to be repositioned properly as well. if (aFrame->HasView() ||
aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) { returnfalse;
}
if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { // If the frame has an intrinsic block-size, we resolve its 'auto' margins // after doing layout, since we need to know the frame's block size. See // nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(). // // Since the size of the frame doesn't change, we could modify the below // computation to compute the margin correctly without doing a full reflow, // however we decided to try doing a full reflow for now. if (aFrame->HasIntrinsicKeywordForBSize()) {
WritingMode wm = aFrame->GetWritingMode(); constauto* styleMargin = aFrame->StyleMargin(); if (styleMargin->HasBlockAxisAuto(wm)) { returnfalse;
}
} // Flexbox and Grid layout supports CSS Align and the optimizations below // don't support that yet.
nsIFrame* ph = aFrame->GetPlaceholderFrame(); if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) { returnfalse;
}
}
// If we need to reposition any descendant that depends on our static // position, then we also can't take the optimized path. // // TODO(emilio): It may be worth trying to find them and try to call // RecomputePosition on them too instead of disabling the optimization... if (aFrame->DescendantMayDependOnItsStaticPosition()) { returnfalse;
}
aFrame->SchedulePaint();
auto postPendingScrollAnchorOrResnap = [](nsIFrame* frame) { if (frame->IsInScrollAnchorChain()) {
ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(frame);
frame->PresShell()->PostPendingScrollAnchorAdjustment(container);
}
// We need to trigger re-snapping to this content if we snapped to the // content on the last scroll operation.
ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
};
// For relative positioning, we can simply update the frame rect if (display->IsRelativelyOrStickyPositionedStyle()) { if (aFrame->IsGridItem()) { // A grid item's CB is its grid area, not the parent frame content area // as is assumed below. returnfalse;
} // Move the frame if (display->mPosition == StylePositionProperty::Sticky) { // Update sticky positioning for an entire element at once, starting with // the first continuation or ib-split sibling. // It's rare that the frame we already have isn't already the first // continuation or ib-split sibling, but it can happen when styles differ // across continuations such as ::first-line or ::first-letter, and in // those cases we will generally (but maybe not always) do the work twice.
nsIFrame* firstContinuation =
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
// ReflowInput::ApplyRelativePositioning would work here, but // since we've already checked mPosition and aren't changing the frame's // normal position, go ahead and add the offsets directly. // First, we need to ensure that the normal position is stored though. bool hasProperty;
nsPoint normalPosition = cont->GetNormalPosition(&hasProperty); if (!hasProperty) {
cont->AddProperty(nsIFrame::NormalPositionProperty(), normalPosition);
}
cont->SetPosition(normalPosition +
nsPoint(newOffsets.left, newOffsets.top));
}
}
// For the absolute positioning case, set up a fake HTML reflow input for // the frame, and then get the offsets and size from it. If the frame's size // doesn't need to change, we can simply update the frame position. Otherwise // we fall back to a reflow.
UniquePtr<gfxContext> rc =
aFrame->PresShell()->CreateReferenceRenderingContext();
// Construct a bogus parent reflow input so that there's a usable reflow input // for the containing block.
nsIFrame* parentFrame = aFrame->GetParent();
WritingMode parentWM = parentFrame->GetWritingMode();
WritingMode frameWM = aFrame->GetWritingMode();
LogicalSize parentSize = parentFrame->GetLogicalSize();
// The bogus parent state here was created with no parent state of its own, // and therefore it won't have an mCBReflowInput set up. // But we may need one (for InitCBReflowInput in a child state), so let's // try to create one here for the cases where it will be needed.
Maybe<ReflowInput> cbReflowInput;
nsIFrame* cbFrame = parentFrame->GetContainingBlock(); if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
parentFrame->IsTableFrame())) { constauto cbWM = cbFrame->GetWritingMode();
LogicalSize cbSize = cbFrame->GetLogicalSize();
cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc.get(), cbSize);
cbReflowInput->SetComputedLogicalMargin(
cbWM, cbFrame->GetLogicalUsedMargin(cbWM));
cbReflowInput->SetComputedLogicalPadding(
cbWM, cbFrame->GetLogicalUsedPadding(cbWM));
cbReflowInput->SetComputedLogicalBorderPadding(
cbWM, cbFrame->GetLogicalUsedBorderAndPadding(cbWM));
parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
}
NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_UNCONSTRAINEDSIZE &&
parentSize.BSize(parentWM) != NS_UNCONSTRAINEDSIZE, "parentSize should be valid");
parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
parentReflowInput.SetComputedLogicalMargin(parentWM, LogicalMargin(parentWM));
ViewportFrame* viewport = do_QueryFrame(parentFrame);
nsSize cbSize =
viewport
? viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput)
.Size()
: aFrame->GetContainingBlock()->GetSize(); const nsMargin& parentBorder =
parentReflowInput.mStyleBorder->GetComputedBorder();
cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
LogicalSize lcbSize(frameWM, cbSize);
ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
availSize, Some(lcbSize));
nscoord computedISize = reflowInput.ComputedISize();
nscoord computedBSize = reflowInput.ComputedBSize(); constauto frameBP = reflowInput.ComputedLogicalBorderPadding(frameWM);
computedISize += frameBP.IStartEnd(frameWM); if (computedBSize != NS_UNCONSTRAINEDSIZE) {
computedBSize += frameBP.BStartEnd(frameWM);
}
LogicalSize logicalSize = aFrame->GetLogicalSize(frameWM);
nsSize size = aFrame->GetSize(); // The RecomputePosition hint is not used if any offset changed between auto // and non-auto. If computedSize.height == NS_UNCONSTRAINEDSIZE then the new // element height will be its intrinsic height, and since 'top' and 'bottom''s // auto-ness hasn't changed, the old height must also be its intrinsic // height, which we can assume hasn't changed (or reflow would have // been triggered). if (computedISize == logicalSize.ISize(frameWM) &&
(computedBSize == NS_UNCONSTRAINEDSIZE ||
computedBSize == logicalSize.BSize(frameWM))) { // If we're solving for 'left' or 'top', then compute it here, in order to // match the reflow code path. // // TODO(emilio): It'd be nice if this did logical math instead, but it seems // to me the math should work out on vertical writing modes as well. See Bug // 1675861 for some hints. const nsMargin offset = reflowInput.ComputedPhysicalOffsets(); const nsMargin margin = reflowInput.ComputedPhysicalMargin();
nscoord left = offset.left; if (left == NS_AUTOOFFSET) {
left =
cbSize.width - offset.right - margin.right - size.width - margin.left;
}
nscoord top = offset.top; if (top == NS_AUTOOFFSET) {
top = cbSize.height - offset.bottom - margin.bottom - size.height -
margin.top;
}
// Move the frame
nsPoint pos(parentBorder.left + left + margin.left,
parentBorder.top + top + margin.top);
aFrame->SetPosition(pos);
/** * Return true if aFrame's subtree has placeholders for out-of-flow content * that would be affected due to the change to * `aPossiblyChangingContainingBlock` (and thus would need to get reframed). * * In particular, this function returns true if there are placeholders whose OOF * frames may need to be reparented (via reframing) as a result of whatever * change actually happened. * * The `aIs{Abs,Fixed}PosContainingBlock` params represent whether * `aPossiblyChangingContainingBlock` is a containing block for abs pos / fixed * pos stuff, respectively, for the _new_ style that the frame already has, not * the old one.
*/ staticbool ContainingBlockChangeAffectsDescendants(
nsIFrame* aPossiblyChangingContainingBlock, nsIFrame* aFrame, bool aIsAbsPosContainingBlock, bool aIsFixedPosContainingBlock) { // All fixed-pos containing blocks should also be abs-pos containing blocks.
MOZ_ASSERT_IF(aIsFixedPosContainingBlock, aIsAbsPosContainingBlock);
for (constauto& childList : aFrame->ChildLists()) { for (nsIFrame* f : childList.mList) { if (f->IsPlaceholderFrame()) {
nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); // If SVG text frames could appear here, they could confuse us since // they ignore their position style ... but they can't.
NS_ASSERTION(!outOfFlow->IsInSVGTextSubtree(), "SVG text frames can't be out of flow"); // Top-layer frames don't change containing block based on direct // ancestors. auto* display = outOfFlow->StyleDisplay(); if (display->IsAbsolutelyPositionedStyle() &&
display->mTopLayer == StyleTopLayer::None) { constbool isContainingBlock =
aIsFixedPosContainingBlock ||
(aIsAbsPosContainingBlock &&
display->mPosition == StylePositionProperty::Absolute); // NOTE(emilio): aPossiblyChangingContainingBlock is guaranteed to be // a first continuation, see the assertion in the caller.
nsIFrame* parent = outOfFlow->GetParent()->FirstContinuation(); if (isContainingBlock) { // If we are becoming a containing block, we only need to reframe if // this oof's current containing block is an ancestor of the new // frame. if (parent != aPossiblyChangingContainingBlock &&
nsLayoutUtils::IsProperAncestorFrame(
parent, aPossiblyChangingContainingBlock)) { returntrue;
}
} else { // If we are not a containing block anymore, we only need to reframe // if we are the current containing block of the oof frame. if (parent == aPossiblyChangingContainingBlock) { returntrue;
}
}
}
} // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or // f->IsFixedPosContainingBlock() here. However, that would only // be testing the *new* style of the frame, which might exclude // descendants that currently have this frame as an abs-pos // containing block. Taking the codepath where we don't reframe // could lead to an unsafe call to // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed // the descendant and taken it off the absolute list. if (ContainingBlockChangeAffectsDescendants(
aPossiblyChangingContainingBlock, f, aIsAbsPosContainingBlock,
aIsFixedPosContainingBlock)) { returntrue;
}
}
} returnfalse;
}
// Returns the frame that would serve as the containing block for aFrame's // positioned descendants, if aFrame had styles to make it a CB for such // descendants. (Typically this is just aFrame itself, or its insertion frame). // // Returns nullptr if this frame can't be easily determined. static nsIFrame* ContainingBlockForFrame(nsIFrame* aFrame) { if (aFrame->IsFieldSetFrame()) { // FIXME: This should be easily implementable. return nullptr;
}
nsIFrame* insertionFrame = aFrame->GetContentInsertionFrame(); if (insertionFrame == aFrame) { return insertionFrame;
} // Generally frames with a different insertion frame are hard to deal with, // but scrollframes are easy because the containing block is just the // insertion frame. if (aFrame->IsScrollContainerFrame()) { return insertionFrame;
} // Combobox frames are easy as well because they can't have positioned // children anyways. // Button and table cell frames are also easy because the containing block is // the frame itself. if (aFrame->IsComboboxControlFrame() || aFrame->IsHTMLButtonControlFrame() ||
aFrame->IsTableCellFrame()) { return aFrame;
} return nullptr;
}
staticbool NeedToReframeToUpdateContainingBlock(nsIFrame* aFrame,
nsIFrame* aMaybeChangingCB) { // NOTE: This looks at the new style. constbool isFixedContainingBlock = aFrame->IsFixedPosContainingBlock();
MOZ_ASSERT_IF(isFixedContainingBlock, aFrame->IsAbsPosContainingBlock());
for (nsIFrame* f = aFrame; f;
f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { if (ContainingBlockChangeAffectsDescendants(aMaybeChangingCB, f,
isAbsPosContainingBlock,
isFixedContainingBlock)) { returntrue;
}
} returnfalse;
}
staticvoid DoApplyRenderingChangeToTree(nsIFrame* aFrame,
nsChangeHint aChange) {
MOZ_ASSERT(gInApplyRenderingChangeToTree, "should only be called within ApplyRenderingChangeToTree");
for (; aFrame;
aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) { // Invalidate and sync views on all descendant frames, following // placeholders. We don't need to update transforms in // SyncViewsAndInvalidateDescendants, because there can't be any // out-of-flows or popups that need to be transformed; all out-of-flow // descendants of the transformed element must also be descendants of the // transformed frame.
SyncViewsAndInvalidateDescendants(
aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
nsChangeHint_UpdateOpacityLayer |
nsChangeHint_SchedulePaint))); // This must be set to true if the rendering change needs to // invalidate content. If it's false, a composite-only paint // (empty transaction) will be scheduled. bool needInvalidatingPaint = false;
// if frame has view, will already be invalidated if (aChange & nsChangeHint_RepaintFrame) { // Note that this whole block will be skipped when painting is suppressed // (due to our caller ApplyRendingChangeToTree() discarding the // nsChangeHint_RepaintFrame hint). If you add handling for any other // hints within this block, be sure that they too should be ignored when // painting is suppressed.
needInvalidatingPaint = true;
aFrame->InvalidateFrameSubtree(); if ((aChange & nsChangeHint_UpdateEffects) &&
aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { // Need to update our overflow rects:
SVGUtils::ScheduleReflowSVG(aFrame);
}
ActiveLayerTracker::NotifyNeedsRepaint(aFrame);
} if (aChange & nsChangeHint_UpdateOpacityLayer) { // FIXME/bug 796697: we can get away with empty transactions for // opacity updates in many cases.
needInvalidatingPaint = true;
ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity); if (SVGIntegrationUtils::UsingEffectsForFrame(aFrame)) { // SVG effects paints the opacity without using // nsDisplayOpacity. We need to invalidate manually.
aFrame->InvalidateFrameSubtree();
}
} if ((aChange & nsChangeHint_UpdateTransformLayer) &&
aFrame->IsTransformed()) { // Note: All the transform-like properties should map to the same // layer activity index, so does the restyle count. Therefore, using // eCSSProperty_transform should be fine.
ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
needInvalidatingPaint = true;
} if (aChange & nsChangeHint_ChildrenOnlyTransform) {
needInvalidatingPaint = true;
nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame)
->PrincipalChildList()
.FirstChild(); for (; childFrame; childFrame = childFrame->GetNextSibling()) { // Note: All the transform-like properties should map to the same // layer activity index, so does the restyle count. Therefore, using // eCSSProperty_transform should be fine.
ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
}
} if (aChange & nsChangeHint_SchedulePaint) {
needInvalidatingPaint = true;
}
aFrame->SchedulePaint(needInvalidatingPaint
? nsIFrame::PAINT_DEFAULT
: nsIFrame::PAINT_COMPOSITE_ONLY);
}
}
staticvoid SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
nsChangeHint aChange) {
MOZ_ASSERT(gInApplyRenderingChangeToTree, "should only be called within ApplyRenderingChangeToTree");
for (constauto& [list, listID] : aFrame->ChildLists()) { for (nsIFrame* child : list) { if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { // only do frames that don't have placeholders if (child->IsPlaceholderFrame()) { // do the out-of-flow frame and its continuations
nsIFrame* outOfFlowFrame =
nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
} else { // regular frame
SyncViewsAndInvalidateDescendants(child, aChange);
}
}
}
}
}
staticvoid ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame,
nsChangeHint aChange) { // We check StyleDisplay()->HasTransformStyle() in addition to checking // IsTransformed() since we can get here for some frames that don't support // CSS transforms, and table frames, which are their own odd-ball, since the // transform is handled by their wrapper, which _also_ gets a separate hint.
NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
aFrame->IsTransformed() ||
aFrame->StyleDisplay()->HasTransformStyle(), "Unexpected UpdateTransformLayer hint");
if (aPresShell->IsPaintingSuppressed()) { // Don't allow synchronous rendering changes when painting is turned off.
aChange &= ~nsChangeHint_RepaintFrame; if (!aChange) { return;
}
}
// Trigger rendering updates by damaging this frame and any // continuations of this frame. #ifdef DEBUG
gInApplyRenderingChangeToTree = true; #endif if (aChange & nsChangeHint_RepaintFrame) { // If the frame is the primary frame of either the body element or // the html element, we propagate the repaint change hint to the // viewport. This is necessary for background and scrollbar colors // propagation. if (aFrame->ShouldPropagateRepaintsToRoot()) {
nsIFrame* rootFrame = aPresShell->GetRootFrame();
MOZ_ASSERT(rootFrame, "No root frame?");
DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame);
aChange &= ~nsChangeHint_RepaintFrame; if (!aChange) { return;
}
}
}
DoApplyRenderingChangeToTree(aFrame, aChange); #ifdef DEBUG
gInApplyRenderingChangeToTree = false; #endif
}
staticvoid AddSubtreeToOverflowTracker(
nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) { if (aFrame->FrameMaintainsOverflow()) {
aOverflowChangedTracker.AddFrame(aFrame,
OverflowChangedTracker::CHILDREN_CHANGED);
} for (constauto& childList : aFrame->ChildLists()) { for (nsIFrame* child : childList.mList) {
AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
}
}
}
// If we're not going to clear any intrinsic sizes on the frames, and // there are no dirty bits to set, then there's nothing to do. if (dirtyType == IntrinsicDirty::None && !dirtyBits) { return;
}
do {
aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
rootHandling);
aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
} while (aFrame);
}
// Get the next sibling which might have a frame. This only considers siblings // that stylo post-traversal looks at, so only elements and text. In // particular, it ignores comments. static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) { for (nsIContent* next = aContent->GetNextSibling(); next;
next = next->GetNextSibling()) { if (next->IsElement() || next->IsText()) { return next;
}
}
return nullptr;
}
// If |aFrame| is dirty or has dirty children, then we can skip updating // overflows since that will happen when it's reflowed. staticinlinebool CanSkipOverflowUpdates(const nsIFrame* aFrame) { return aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY |
NS_FRAME_HAS_DIRTY_CHILDREN);
}
// Only bother with this if we're the root or the body element, since: // (a) It'd be *expensive* to reframe these particular nodes. They're // at the root, so reframing would mean rebuilding the world. // (b) It's often *unnecessary* to reframe for "overflow" changes on // these particular nodes. In general, the only reason we reframe // for "overflow" changes is so we can construct (or destroy) a // scrollframe & scrollbars -- and the html/body nodes often don't // need their own scrollframe/scrollbars because they coopt the ones // on the viewport (which always exist). So depending on whether // that's happening, we can skip the reframe for these nodes. if (isRoot || aContent->IsHTMLElement(nsGkAtoms::body)) { // If the restyled element provided/provides the scrollbar styles for // the viewport before and/or after this restyle, AND it's not coopting // that responsibility from some other element (which would need // reconstruction to make its own scrollframe now), THEN: we don't need // to reconstruct - we can just reflow, because no scrollframe is being // added/removed.
Element* prevOverride = aPc->GetViewportScrollStylesOverrideElement();
Element* newOverride = aPc->UpdateViewportScrollStylesOverride();
if (ProvidesScrollbarStyles(prevOverride) ||
ProvidesScrollbarStyles(newOverride)) { // If we get here, the restyled element provided the scrollbar styles // for viewport before this restyle, OR it will provide them after. if (!prevOverride || !newOverride || prevOverride == newOverride) { // If we get here, the restyled element is NOT replacing (or being // replaced by) some other element as the viewport's // scrollbar-styles provider. (If it were, we'd potentially need to // reframe to create a dedicated scrollframe for whichever element // is being booted from providing viewport scrollbar styles.) // // Under these conditions, we're OK to assume that this "overflow" // change only impacts the root viewport's scrollframe, which // already exists, so we can simply reflow instead of reframing. if (ScrollContainerFrame* sf = do_QueryFrame(aFrame)) {
sf->MarkScrollbarsDirtyForReflow();
} elseif (ScrollContainerFrame* sf =
aPc->PresShell()->GetRootScrollContainerFrame()) {
sf->MarkScrollbarsDirtyForReflow();
}
aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
} else { // If we changed the override element, we need to reconstruct as the old // override element might start / stop being scrollable.
aHint |= nsChangeHint_ReconstructFrame;
} return;
}
}
constbool scrollable = aFrame->StyleDisplay()->IsScrollableOverflow(); if (ScrollContainerFrame* sf = do_QueryFrame(aFrame)) { if (scrollable && sf->HasAllNeededScrollbars()) {
sf->MarkScrollbarsDirtyForReflow(); // Once we've created scrollbars for a frame, don't bother reconstructing // it just to remove them if we still need a scroll frame.
aHint |= nsChangeHint_ReflowHintsForScrollbarChange; return;
}
} elseif (aFrame->IsTextInputFrame()) { // input / textarea for the most part don't honor overflow themselves, the // editor root will deal with the change if needed. // However the textarea intrinsic size relies on GetDesiredScrollbarSizes(), // so we need to reflow the textarea itself, not just the inner control.
aHint |= nsChangeHint_ReflowHintsForScrollbarChange; return;
} elseif (!scrollable) { // Something changed, but we don't have nor will have a scroll frame, // there's nothing to do here. return;
}
// Oh well, we couldn't optimize it out, just reconstruct frames for the // subtree.
aHint |= nsChangeHint_ReconstructFrame;
}
staticvoid TryToHandleContainingBlockChange(nsChangeHint& aHint,
nsIFrame* aFrame) { if (!(aHint & nsChangeHint_UpdateContainingBlock)) { return;
} if (aHint & nsChangeHint_ReconstructFrame) { return;
}
MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
nsIFrame* containingBlock = ContainingBlockForFrame(aFrame); if (!containingBlock ||
NeedToReframeToUpdateContainingBlock(aFrame, containingBlock)) { // The frame has positioned children that need to be reparented, or it can't // easily be converted to/from being an abs-pos container correctly.
aHint |= nsChangeHint_ReconstructFrame; return;
} constbool isCb = aFrame->IsAbsPosContainingBlock();
// The absolute container should be containingBlock. for (nsIFrame* cont = containingBlock; cont;
cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { // Normally frame construction would set state bits as needed, // but we're not going to reconstruct the frame so we need to set // them. It's because we need to set this state on each affected frame // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up // to ancestors (i.e. it can't be an change hint that is handled for // descendants). if (isCb) { if (!cont->IsAbsoluteContainer() &&
cont->HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
cont->MarkAsAbsoluteContainingBlock();
}
} elseif (cont->IsAbsoluteContainer()) { if (cont->HasAbsolutelyPositionedChildren()) { // If |cont| still has absolutely positioned children, // we can't call MarkAsNotAbsoluteContainingBlock. This // will remove a frame list that still has children in // it that we need to keep track of. // The optimization of removing it isn't particularly // important, although it does mean we skip some tests.
NS_WARNING("skipping removal of absolute containing block");
} else {
cont->MarkAsNotAbsoluteContainingBlock();
}
}
}
}
void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), "Someone forgot a script blocker");
// See bug 1378219 comment 9: // Recursive calls here are a bit worrying, but apparently do happen in the // wild (although not currently in any of our automated tests). Try to get a // stack from Nightly/Dev channel to figure out what's going on and whether // it's OK.
MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion");
if (aChangeList.IsEmpty()) { return;
}
// If mDestroyedFrames is null, we want to create a new hashtable here // and destroy it on exit; but if it is already non-null (because we're in // a recursive call), we will continue to use the existing table to // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit. // We use a MaybeClearDestroyedFrames helper to conditionally reset the // mDestroyedFrames pointer when this method returns. typedef decltype(mDestroyedFrames) DestroyedFramesT; class MOZ_RAII MaybeClearDestroyedFrames { private:
DestroyedFramesT& mDestroyedFramesRef; // ref to caller's mDestroyedFrames constbool mResetOnDestruction;
public: explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget)
: mDestroyedFramesRef(aTarget),
mResetOnDestruction(!aTarget) // reset only if target starts out null
{}
~MaybeClearDestroyedFrames() { if (mResetOnDestruction) {
mDestroyedFramesRef.reset(nullptr);
}
}
};
MaybeClearDestroyedFrames maybeClear(mDestroyedFrames); if (!mDestroyedFrames) {
mDestroyedFrames = MakeUnique<nsTHashSet<const nsIFrame*>>();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.