/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ /* the interface (to internal code) for retrieving computed style data */ #include "CSSVariableImageTable.h" #include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" #include "nsCSSAnonBoxes.h" #include "nsCSSPseudoElements.h" #include "nsStyleConsts.h" #include "nsStyleStruct.h" #include "nsStyleStructInlines.h" #include "nsString.h" #include "nsPresContext.h" #include "nsIStyleRule.h" #include "nsCOMPtr.h" #include "nsStyleSet.h" #include "nsIPresShell.h" #include "nsRuleNode.h" #include "nsStyleContext.h" #include "mozilla/StyleAnimationValue.h" #include "GeckoProfiler.h" #include "nsIDocument.h" #include "nsPrintfCString.h" #include "RubyUtils.h" #include "mozilla/Preferences.h" #include "mozilla/ArenaObjectID.h" #include "mozilla/StyleSetHandle.h" #include "mozilla/StyleSetHandleInlines.h" #include "mozilla/ReflowInput.h" #include "nsLayoutUtils.h" #include "nsCoord.h" #include "nsFontMetrics.h" // Ensure the binding function declarations in nsStyleContext.h matches // those in ServoBindings.h. #include "mozilla/ServoBindings.h" using namespace mozilla; //---------------------------------------------------------------------- #ifdef DEBUG // Check that the style struct IDs are in the same order as they are // in nsStyleStructList.h, since when we set up the IDs, we include // the inherited and reset structs spearately from nsStyleStructList.h enum DebugStyleStruct { #define STYLE_STRUCT(name, checkdata_cb) eDebugStyleStruct_##name, #include "nsStyleStructList.h" #undef STYLE_STRUCT }; #define STYLE_STRUCT(name, checkdata_cb) \ static_assert(static_cast<int>(eDebugStyleStruct_##name) == \ static_cast<int>(eStyleStruct_##name), \ "Style struct IDs are not declared in order?"); #include "nsStyleStructList.h" #undef STYLE_STRUCT const uint32_t nsStyleContext::sDependencyTable[] = { #define STYLE_STRUCT(name, checkdata_cb) #define STYLE_STRUCT_DEP(dep) NS_STYLE_INHERIT_BIT(dep) | #define STYLE_STRUCT_END() 0, #include "nsStyleStructList.h" #undef STYLE_STRUCT #undef STYLE_STRUCT_DEP #undef STYLE_STRUCT_END }; // Whether to perform expensive assertions in the nsStyleContext destructor. static bool sExpensiveStyleStructAssertionsEnabled; #endif nsStyleContext::nsStyleContext(nsStyleContext* aParent, OwningStyleContextSource&& aSource, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType) : mParent(aParent) , mChild(nullptr) , mEmptyChild(nullptr) , mPseudoTag(aPseudoTag) , mSource(Move(aSource)) #ifdef MOZ_STYLO , mPresContext(nullptr) #endif , mCachedResetData(nullptr) , mBits(((uint64_t)aPseudoType) << NS_STYLE_CONTEXT_TYPE_SHIFT) , mRefCnt(0) #ifdef MOZ_STYLO , mStoredChangeHint(nsChangeHint(0)) #ifdef DEBUG , mConsumedChangeHint(false) #endif #endif #ifdef DEBUG , mFrameRefCnt(0) , mComputingStruct(nsStyleStructID_None) #endif { MOZ_COUNT_CTOR(nsStyleContext); } nsStyleContext::nsStyleContext(nsStyleContext* aParent, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType, already_AddRefed<nsRuleNode> aRuleNode, bool aSkipParentDisplayBasedStyleFixup) : nsStyleContext(aParent, OwningStyleContextSource(Move(aRuleNode)), aPseudoTag, aPseudoType) { #ifdef MOZ_STYLO mPresContext = mSource.AsGeckoRuleNode()->PresContext(); #endif if (aParent) { #ifdef DEBUG nsRuleNode *r1 = mParent->RuleNode(), *r2 = mSource.AsGeckoRuleNode(); while (r1->GetParent()) r1 = r1->GetParent(); while (r2->GetParent()) r2 = r2->GetParent(); NS_ASSERTION(r1 == r2, "must be in the same rule tree as parent"); #endif } else { PresContext()->PresShell()->StyleSet()->RootStyleContextAdded(); } mSource.AsGeckoRuleNode()->SetUsedDirectly(); // before ApplyStyleFixups()! FinishConstruction(aSkipParentDisplayBasedStyleFixup); } nsStyleContext::nsStyleContext(nsStyleContext* aParent, nsPresContext* aPresContext, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType, already_AddRefed<ServoComputedValues> aComputedValues, bool aSkipParentDisplayBasedStyleFixup) : nsStyleContext(aParent, OwningStyleContextSource(Move(aComputedValues)), aPseudoTag, aPseudoType) { #ifdef MOZ_STYLO mPresContext = aPresContext; #endif FinishConstruction(aSkipParentDisplayBasedStyleFixup); } void nsStyleContext::FinishConstruction(bool aSkipParentDisplayBasedStyleFixup) { // This check has to be done "backward", because if it were written the // more natural way it wouldn't fail even when it needed to. static_assert((UINT64_MAX >> NS_STYLE_CONTEXT_TYPE_SHIFT) >= static_cast<CSSPseudoElementTypeBase>( CSSPseudoElementType::MAX), "pseudo element bits no longer fit in a uint64_t"); MOZ_ASSERT(!mSource.IsNull()); #ifdef DEBUG static_assert(MOZ_ARRAY_LENGTH(nsStyleContext::sDependencyTable) == nsStyleStructID_Length, "Number of items in dependency table doesn't match IDs"); #endif mNextSibling = this; mPrevSibling = this; if (mParent) { mParent->AddChild(this); } SetStyleBits(); if (!mSource.IsServoComputedValues()) { ApplyStyleFixups(aSkipParentDisplayBasedStyleFixup); } #define eStyleStruct_LastItem (nsStyleStructID_Length - 1) NS_ASSERTION(NS_STYLE_INHERIT_MASK & NS_STYLE_INHERIT_BIT(LastItem), "NS_STYLE_INHERIT_MASK must be bigger, and other bits shifted"); #undef eStyleStruct_LastItem } nsStyleContext::~nsStyleContext() { MOZ_COUNT_DTOR(nsStyleContext); NS_ASSERTION((nullptr == mChild) && (nullptr == mEmptyChild), "destructing context with children"); #ifdef DEBUG if (sExpensiveStyleStructAssertionsEnabled) { // Assert that the style structs we are about to destroy are not referenced // anywhere else in the style context tree. These checks are expensive, // which is why they are not enabled by default. nsStyleContext* root = this; while (root->mParent) { root = root->mParent; } root->AssertStructsNotUsedElsewhere(this, std::numeric_limits<int32_t>::max()); } else { // In DEBUG builds when the pref is not enabled, we perform a more limited // check just of the children of this style context. AssertStructsNotUsedElsewhere(this, 2); } #endif nsPresContext *presContext = PresContext(); DebugOnly<nsStyleSet*> geckoStyleSet = presContext->PresShell()->StyleSet()->GetAsGecko(); NS_ASSERTION(!geckoStyleSet || geckoStyleSet->GetRuleTree() == mSource.AsGeckoRuleNode()->RuleTree() || geckoStyleSet->IsInRuleTreeReconstruct(), "destroying style context from old rule tree too late"); if (mParent) { mParent->RemoveChild(this); } else { presContext->StyleSet()->RootStyleContextRemoved(); } // Free up our data structs. mCachedInheritedData.DestroyStructs(mBits, presContext); if (mCachedResetData) { mCachedResetData->Destroy(mBits, presContext); } // Free any ImageValues we were holding on to for CSS variable values. CSSVariableImageTable::RemoveAll(this); } #ifdef DEBUG void nsStyleContext::AssertStructsNotUsedElsewhere( nsStyleContext* aDestroyingContext, int32_t aLevels) const { if (aLevels == 0) { return; } void* data; if (mBits & NS_STYLE_IS_GOING_AWAY) { return; } if (this != aDestroyingContext) { nsInheritedStyleData& destroyingInheritedData = aDestroyingContext->mCachedInheritedData; #define STYLE_STRUCT_INHERITED(name_, checkdata_cb) \ data = destroyingInheritedData.mStyleStructs[eStyleStruct_##name_]; \ if (data && \ !(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) && \ (mCachedInheritedData.mStyleStructs[eStyleStruct_##name_] == data)) { \ printf_stderr("style struct %p found on style context %p\n", data, this);\ nsString url; \ nsresult rv = PresContext()->Document()->GetURL(url); \ if (NS_SUCCEEDED(rv)) { \ printf_stderr(" in %s\n", NS_ConvertUTF16toUTF8(url).get()); \ } \ MOZ_ASSERT(false, "destroying " #name_ " style struct still present " \ "in style context tree"); \ } #define STYLE_STRUCT_RESET(name_, checkdata_cb) #include "nsStyleStructList.h" #undef STYLE_STRUCT_INHERITED #undef STYLE_STRUCT_RESET if (mCachedResetData) { nsResetStyleData* destroyingResetData = aDestroyingContext->mCachedResetData; if (destroyingResetData) { #define STYLE_STRUCT_INHERITED(name_, checkdata_cb_) #define STYLE_STRUCT_RESET(name_, checkdata_cb) \ data = destroyingResetData->mStyleStructs[eStyleStruct_##name_]; \ if (data && \ !(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) && \ (mCachedResetData->mStyleStructs[eStyleStruct_##name_] == data)) { \ printf_stderr("style struct %p found on style context %p\n", data, \ this); \ nsString url; \ nsresult rv = PresContext()->Document()->GetURL(url); \ if (NS_SUCCEEDED(rv)) { \ printf_stderr(" in %s\n", NS_ConvertUTF16toUTF8(url).get()); \ } \ MOZ_ASSERT(false, "destroying " #name_ " style struct still present "\ "in style context tree"); \ } #include "nsStyleStructList.h" #undef STYLE_STRUCT_INHERITED #undef STYLE_STRUCT_RESET } } } if (mChild) { const nsStyleContext* child = mChild; do { child->AssertStructsNotUsedElsewhere(aDestroyingContext, aLevels - 1); child = child->mNextSibling; } while (child != mChild); } if (mEmptyChild) { const nsStyleContext* child = mEmptyChild; do { child->AssertStructsNotUsedElsewhere(aDestroyingContext, aLevels - 1); child = child->mNextSibling; } while (child != mEmptyChild); } } #endif void nsStyleContext::AddChild(nsStyleContext* aChild) { NS_ASSERTION(aChild->mPrevSibling == aChild && aChild->mNextSibling == aChild, "child already in a child list"); nsStyleContext **listPtr = aChild->mSource.MatchesNoRules() ? &mEmptyChild : &mChild; // Explicitly dereference listPtr so that compiler doesn't have to know that mNextSibling // etc. don't alias with what ever listPtr points at. nsStyleContext *list = *listPtr; // Insert at the beginning of the list. See also FindChildWithRules. if (list) { // Link into existing elements, if there are any. aChild->mNextSibling = list; aChild->mPrevSibling = list->mPrevSibling; list->mPrevSibling->mNextSibling = aChild; list->mPrevSibling = aChild; } (*listPtr) = aChild; } void nsStyleContext::RemoveChild(nsStyleContext* aChild) { NS_PRECONDITION(nullptr != aChild && this == aChild->mParent, "bad argument"); nsStyleContext **list = aChild->mSource.MatchesNoRules() ? &mEmptyChild : &mChild; if (aChild->mPrevSibling != aChild) { // has siblings if ((*list) == aChild) { (*list) = (*list)->mNextSibling; } } else { NS_ASSERTION((*list) == aChild, "bad sibling pointers"); (*list) = nullptr; } aChild->mPrevSibling->mNextSibling = aChild->mNextSibling; aChild->mNextSibling->mPrevSibling = aChild->mPrevSibling; aChild->mNextSibling = aChild; aChild->mPrevSibling = aChild; } void nsStyleContext::MoveTo(nsStyleContext* aNewParent) { MOZ_ASSERT(aNewParent != mParent); // This function shouldn't be getting called if the parents have different // values for some flags in mBits (unless the flag is also set on this style // context) because if that were the case we would need to recompute those // bits for |this|. #define CHECK_FLAG(bit_) \ MOZ_ASSERT((mParent->mBits & (bit_)) == (aNewParent->mBits & (bit_)) || \ (mBits & (bit_)), \ "MoveTo cannot be called if " #bit_ " value on old and new " \ "style context parents do not match, unless the flag is set " \ "on this style context"); CHECK_FLAG(NS_STYLE_HAS_PSEUDO_ELEMENT_DATA) CHECK_FLAG(NS_STYLE_IN_DISPLAY_NONE_SUBTREE) CHECK_FLAG(NS_STYLE_HAS_TEXT_DECORATION_LINES) CHECK_FLAG(NS_STYLE_RELEVANT_LINK_VISITED) #undef CHECK_FLAG // Assertions checking for visited style are just to avoid some tricky // cases we can't be bothered handling at the moment. MOZ_ASSERT(!IsStyleIfVisited()); MOZ_ASSERT(!mParent->IsStyleIfVisited()); MOZ_ASSERT(!aNewParent->IsStyleIfVisited()); MOZ_ASSERT(!mStyleIfVisited || mStyleIfVisited->mParent == mParent); if (mParent->HasChildThatUsesResetStyle()) { aNewParent->AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE); } mParent->RemoveChild(this); mParent = aNewParent; mParent->AddChild(this); if (mStyleIfVisited) { mStyleIfVisited->mParent->RemoveChild(mStyleIfVisited); mStyleIfVisited->mParent = aNewParent; mStyleIfVisited->mParent->AddChild(mStyleIfVisited); } } already_AddRefed<nsStyleContext> nsStyleContext::FindChildWithRules(const nsIAtom* aPseudoTag, NonOwningStyleContextSource aSource, NonOwningStyleContextSource aSourceIfVisited, bool aRelevantLinkVisited) { uint32_t threshold = 10; // The # of siblings we're willing to examine // before just giving this whole thing up. RefPtr<nsStyleContext> result; nsStyleContext *list = aSource.MatchesNoRules() ? mEmptyChild : mChild; if (list) { nsStyleContext *child = list; do { if (child->mSource.AsRaw() == aSource && child->mPseudoTag == aPseudoTag && !child->IsStyleIfVisited() && child->RelevantLinkVisited() == aRelevantLinkVisited) { bool match = false; if (!aSourceIfVisited.IsNull()) { match = child->GetStyleIfVisited() && child->GetStyleIfVisited()->mSource.AsRaw() == aSourceIfVisited; } else { match = !child->GetStyleIfVisited(); } if (match && !(child->mBits & NS_STYLE_INELIGIBLE_FOR_SHARING)) { result = child; break; } } child = child->mNextSibling; threshold--; if (threshold == 0) break; } while (child != list); } if (result) { if (result != list) { // Move result to the front of the list. RemoveChild(result); AddChild(result); } result->mBits |= NS_STYLE_IS_SHARED; } return result.forget(); } const void* nsStyleContext::StyleData(nsStyleStructID aSID) { const void* cachedData = GetCachedStyleData(aSID); if (cachedData) return cachedData; // We have computed data stored on this node in the context tree. // Our style source will take care of it for us. const void* newData; if (mSource.IsGeckoRuleNode()) { newData = mSource.AsGeckoRuleNode()->GetStyleData(aSID, this, true); if (!nsCachedStyleData::IsReset(aSID)) { // always cache inherited data on the style context; the rule // node set the bit in mBits for us if needed. mCachedInheritedData.mStyleStructs[aSID] = const_cast<void*>(newData); } } else { newData = StyleStructFromServoComputedValues(aSID); // perform any remaining main thread work on the struct switch (aSID) { #define STYLE_STRUCT(name_, checkdata_cb_) \ case eStyleStruct_##name_: { \ auto data = static_cast<const nsStyle##name_*>(newData); \ const_cast<nsStyle##name_*>(data)->FinishStyle(PresContext()); \ break; \ } #include "nsStyleStructList.h" #undef STYLE_STRUCT default: MOZ_ASSERT_UNREACHABLE("unexpected nsStyleStructID value"); break; } // The Servo-backed StyleContextSource owns the struct. AddStyleBit(nsCachedStyleData::GetBitForSID(aSID)); // XXXbholley: Unconditionally caching reset structs here defeats the memory // optimization where we lazily allocate mCachedResetData, so that we can avoid // performing an FFI call each time we want to get the style structs. We should // measure the tradeoffs at some point. If the FFI overhead is low and the memory // win significant, we should consider _always_ grabbing the struct over FFI, and // potentially giving mCachedInheritedData the same treatment. // // Note that there is a similar comment in the struct getters in nsStyleContext.h. SetStyle(aSID, const_cast<void*>(newData)); } return newData; } // This is an evil evil function, since it forces you to alloc your own separate copy of // style data! Do not use this function unless you absolutely have to! You should avoid // this at all costs! -dwh void* nsStyleContext::GetUniqueStyleData(const nsStyleStructID& aSID) { MOZ_ASSERT(!mSource.IsServoComputedValues(), "Can't COW-mutate servo values from Gecko!"); // If we already own the struct and no kids could depend on it, then // just return it. (We leak in this case if there are kids -- and this // function really shouldn't be called for style contexts that could // have kids depending on the data. ClearStyleData would be OK, but // this test for no mChild or mEmptyChild doesn't catch that case.) const void *current = StyleData(aSID); if (!mChild && !mEmptyChild && !(mBits & nsCachedStyleData::GetBitForSID(aSID)) && GetCachedStyleData(aSID)) return const_cast<void*>(current); void* result; nsPresContext *presContext = PresContext(); switch (aSID) { #define UNIQUE_CASE(c_) \ case eStyleStruct_##c_: \ result = new (presContext) nsStyle##c_( \ * static_cast<const nsStyle##c_ *>(current)); \ break; UNIQUE_CASE(Font) UNIQUE_CASE(Display) UNIQUE_CASE(Text) UNIQUE_CASE(TextReset) UNIQUE_CASE(Visibility) #undef UNIQUE_CASE default: NS_ERROR("Struct type not supported. Please find another way to do this if you can!"); return nullptr; } SetStyle(aSID, result); mBits &= ~static_cast<uint64_t>(nsCachedStyleData::GetBitForSID(aSID)); return result; } // This is an evil function, but less evil than GetUniqueStyleData. It // creates an empty style struct for this nsStyleContext. void* nsStyleContext::CreateEmptyStyleData(const nsStyleStructID& aSID) { MOZ_ASSERT(!mChild && !mEmptyChild && !(mBits & nsCachedStyleData::GetBitForSID(aSID)) && !GetCachedStyleData(aSID), "This style should not have been computed"); void* result; nsPresContext* presContext = PresContext(); switch (aSID) { #define UNIQUE_CASE(c_) \ case eStyleStruct_##c_: \ result = new (presContext) nsStyle##c_(presContext); \ break; UNIQUE_CASE(Border) UNIQUE_CASE(Padding) #undef UNIQUE_CASE default: NS_ERROR("Struct type not supported."); return nullptr; } // The new struct is owned by this style context, but that we don't // need to clear the bit in mBits because we've asserted that at the // top of this function. SetStyle(aSID, result); return result; } void nsStyleContext::SetStyle(nsStyleStructID aSID, void* aStruct) { // This method should only be called from nsRuleNode! It is not a public // method! NS_ASSERTION(aSID >= 0 && aSID < nsStyleStructID_Length, "out of bounds"); // NOTE: nsCachedStyleData::GetStyleData works roughly the same way. // See the comments there (in nsRuleNode.h) for more details about // what this is doing and why. void** dataSlot; if (nsCachedStyleData::IsReset(aSID)) { if (!mCachedResetData) { mCachedResetData = new (PresContext()) nsResetStyleData; } dataSlot = &mCachedResetData->mStyleStructs[aSID]; } else { dataSlot = &mCachedInheritedData.mStyleStructs[aSID]; } NS_ASSERTION(!*dataSlot || (mBits & nsCachedStyleData::GetBitForSID(aSID)), "Going to leak style data"); *dataSlot = aStruct; } static bool ShouldSuppressLineBreak(const nsStyleContext* aContext, const nsStyleDisplay* aDisplay, const nsStyleContext* aParentContext, const nsStyleDisplay* aParentDisplay) { // The display change should only occur for "in-flow" children if (aDisplay->IsOutOfFlowStyle()) { return false; } // Display value of any anonymous box should not be touched. In most // cases, anonymous boxes are actually not in ruby frame, but instead, // some other frame with a ruby display value. Non-element pseudos // which represents text frames, as well as ruby pseudos are excluded // because we still want to set the flag for them. if (aContext->GetPseudoType() == CSSPseudoElementType::AnonBox && !nsCSSAnonBoxes::IsNonElement(aContext->GetPseudo()) && !RubyUtils::IsRubyPseudo(aContext->GetPseudo())) { return false; } if (aParentContext->ShouldSuppressLineBreak()) { // Line break suppressing bit is propagated to any children of // line participants, which include inline, contents, and inline // ruby boxes. if (aParentDisplay->mDisplay == mozilla::StyleDisplay::Inline || aParentDisplay->mDisplay == mozilla::StyleDisplay::Contents || aParentDisplay->mDisplay == mozilla::StyleDisplay::Ruby || aParentDisplay->mDisplay == mozilla::StyleDisplay::RubyBaseContainer) { return true; } } // Any descendant of ruby level containers is non-breakable, but // the level containers themselves are breakable. We have to check // the container display type against all ruby display type here // because any of the ruby boxes could be anonymous. // Note that, when certain HTML tags, e.g. form controls, have ruby // level container display type, they could also escape from this flag // while they shouldn't. However, it is generally fine since they // won't usually break the assertion that there is no line break // inside ruby, because: // 1. their display types, the ruby level container types, are inline- // outside, which means they won't cause any forced line break; and // 2. they never start an inline span, which means their children, if // any, won't be able to break the line its ruby ancestor lays; and // 3. their parent frame is always a ruby content frame (due to // anonymous ruby box generation), which makes line layout suppress // any optional line break around this frame. // However, there is one special case which is BR tag, because it // directly affects the line layout. This case is handled by the BR // frame which checks the flag of its parent frame instead of itself. if ((aParentDisplay->IsRubyDisplayType() && aDisplay->mDisplay != mozilla::StyleDisplay::RubyBaseContainer && aDisplay->mDisplay != mozilla::StyleDisplay::RubyTextContainer) || // Since ruby base and ruby text may exist themselves without any // non-anonymous frame outside, we should also check them. aDisplay->mDisplay == mozilla::StyleDisplay::RubyBase || aDisplay->mDisplay == mozilla::StyleDisplay::RubyText) { return true; } return false; } // Flex & grid containers blockify their children. // "The display value of a flex item is blockified" // https://drafts.csswg.org/css-flexbox-1/#flex-items // "The display value of a grid item is blockified" // https://drafts.csswg.org/css-grid/#grid-items static bool ShouldBlockifyChildren(const nsStyleDisplay* aStyleDisp) { auto displayVal = aStyleDisp->mDisplay; return mozilla::StyleDisplay::Flex == displayVal || mozilla::StyleDisplay::InlineFlex == displayVal || mozilla::StyleDisplay::Grid == displayVal || mozilla::StyleDisplay::InlineGrid == displayVal; } void nsStyleContext::SetStyleBits() { // XXXbholley: We should get this information directly from the // ServoComputedValues rather than computing it here. This setup for // ServoComputedValues-backed nsStyleContexts is probably not something // we should ship. // // For example, NS_STYLE_IS_TEXT_COMBINED is still set in ApplyStyleFixups, // which isn't called for ServoComputedValues. // See if we have any text decorations. // First see if our parent has text decorations. If our parent does, then we inherit the bit. if (mParent && mParent->HasTextDecorationLines()) { mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES; } else { // We might have defined a decoration. if (StyleTextReset()->HasTextDecorationLines()) { mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES; } } if ((mParent && mParent->HasPseudoElementData()) || IsPseudoElement()) { mBits |= NS_STYLE_HAS_PSEUDO_ELEMENT_DATA; } // Set the NS_STYLE_IN_DISPLAY_NONE_SUBTREE bit const nsStyleDisplay* disp = StyleDisplay(); if ((mParent && mParent->IsInDisplayNoneSubtree()) || disp->mDisplay == mozilla::StyleDisplay::None) { mBits |= NS_STYLE_IN_DISPLAY_NONE_SUBTREE; } } void nsStyleContext::ApplyStyleFixups(bool aSkipParentDisplayBasedStyleFixup) { MOZ_ASSERT(!mSource.IsServoComputedValues(), "Can't do Gecko style fixups on Servo values"); #define GET_UNIQUE_STYLE_DATA(name_) \ static_cast<nsStyle##name_*>(GetUniqueStyleData(eStyleStruct_##name_)) // CSS Inline Layout Level 3 - 3.5 Sizing Initial Letters: // For an N-line drop initial in a Western script, the cap-height of the // letter needs to be (N – 1) times the line-height, plus the cap-height // of the surrounding text. if (mPseudoTag == nsCSSPseudoElements::firstLetter) { const nsStyleTextReset* textReset = StyleTextReset(); if (textReset->mInitialLetterSize != 0.0f) { nsStyleContext* containerSC = mParent; const nsStyleDisplay* containerDisp = containerSC->StyleDisplay(); while (containerDisp->mDisplay == mozilla::StyleDisplay::Contents) { if (!containerSC->GetParent()) { break; } containerSC = containerSC->GetParent(); containerDisp = containerSC->StyleDisplay(); } nscoord containerLH = ReflowInput::CalcLineHeight(nullptr, containerSC, NS_AUTOHEIGHT, 1.0f); RefPtr<nsFontMetrics> containerFM = nsLayoutUtils::GetFontMetricsForStyleContext(containerSC); MOZ_ASSERT(containerFM, "Should have fontMetrics!!"); nscoord containerCH = containerFM->CapHeight(); RefPtr<nsFontMetrics> firstLetterFM = nsLayoutUtils::GetFontMetricsForStyleContext(this); MOZ_ASSERT(firstLetterFM, "Should have fontMetrics!!"); nscoord firstLetterCH = firstLetterFM->CapHeight(); nsStyleFont* mutableStyleFont = GET_UNIQUE_STYLE_DATA(Font); float invCapHeightRatio = mutableStyleFont->mFont.size / NSCoordToFloat(firstLetterCH); mutableStyleFont->mFont.size = NSToCoordRound(((textReset->mInitialLetterSize - 1) * containerLH + containerCH) * invCapHeightRatio); } } // Change writing mode of text frame for text-combine-upright. We use // style structs of the parent to avoid triggering computation before // we change the writing mode. // It is safe to look at the parent's style because we are looking at // inherited properties, and ::-moz-text never matches any rules. if (mPseudoTag == nsCSSAnonBoxes::mozText && mParent && mParent->StyleVisibility()->mWritingMode != NS_STYLE_WRITING_MODE_HORIZONTAL_TB && mParent->StyleText()->mTextCombineUpright == NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) { MOZ_ASSERT(!PeekStyleVisibility(), "If StyleVisibility was already " "computed, some properties may have been computed " "incorrectly based on the old writing mode value"); nsStyleVisibility* mutableVis = GET_UNIQUE_STYLE_DATA(Visibility); mutableVis->mWritingMode = NS_STYLE_WRITING_MODE_HORIZONTAL_TB; AddStyleBit(NS_STYLE_IS_TEXT_COMBINED); } // CSS 2.1 10.1: Propagate the root element's 'direction' to the ICB. // (PageContentFrame/CanvasFrame etc will inherit 'direction') if (mPseudoTag == nsCSSAnonBoxes::viewport) { nsPresContext* presContext = PresContext(); mozilla::dom::Element* docElement = presContext->Document()->GetRootElement(); if (docElement) { RefPtr<nsStyleContext> rootStyle = presContext->StyleSet()->ResolveStyleFor(docElement, nullptr); auto dir = rootStyle->StyleVisibility()->mDirection; if (dir != StyleVisibility()->mDirection) { nsStyleVisibility* uniqueVisibility = GET_UNIQUE_STYLE_DATA(Visibility); uniqueVisibility->mDirection = dir; } } } // Correct tables. const nsStyleDisplay* disp = StyleDisplay(); if (disp->mDisplay == mozilla::StyleDisplay::Table) { // -moz-center and -moz-right are used for HTML's alignment // This is covering the <div align="right"><table>...</table></div> case. // In this case, we don't want to inherit the text alignment into the table. const nsStyleText* text = StyleText(); if (text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_LEFT || text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_CENTER || text->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_RIGHT) { nsStyleText* uniqueText = GET_UNIQUE_STYLE_DATA(Text); uniqueText->mTextAlign = NS_STYLE_TEXT_ALIGN_START; } } // CSS2.1 section 9.2.4 specifies fixups for the 'display' property of // the root element. We can't implement them in nsRuleNode because we // don't want to store all display structs that aren't 'block', // 'inline', or 'table' in the style context tree on the off chance // that the root element has its style reresolved later. So do them // here if needed, by changing the style data, so that other code // doesn't get confused by looking at the style data. if (!mParent) { auto displayVal = disp->mDisplay; if (displayVal != mozilla::StyleDisplay::Contents) { nsRuleNode::EnsureBlockDisplay(displayVal, true); } else { // http://dev.w3.org/csswg/css-display/#transformations // "... a display-outside of 'contents' computes to block-level // on the root element." displayVal = mozilla::StyleDisplay::Block; } if (displayVal != disp->mDisplay) { nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display); disp = mutable_display; // If we're in this code, then mOriginalDisplay doesn't matter // for purposes of the cascade (because this nsStyleDisplay // isn't living in the ruletree anyway), and for determining // hypothetical boxes it's better to have mOriginalDisplay // matching mDisplay here. mutable_display->mOriginalDisplay = mutable_display->mDisplay = displayVal; } } // Adjust the "display" values of flex and grid items (but not for raw text // or placeholders). CSS3 Flexbox section 4 says: // # The computed 'display' of a flex item is determined // # by applying the table in CSS 2.1 Chapter 9.7. // ...which converts inline-level elements to their block-level equivalents. // Any block-level element directly contained by elements with ruby display // values are converted to their inline-level equivalents. if (!aSkipParentDisplayBasedStyleFixup && mParent) { // Skip display:contents ancestors to reach the potential container. // (If there are only display:contents ancestors between this node and // a flex/grid container ancestor, then this node is a flex/grid item, since // its parent *in the frame tree* will be the flex/grid container. So we treat // it like a flex/grid item here.) nsStyleContext* containerContext = mParent; const nsStyleDisplay* containerDisp = containerContext->StyleDisplay(); while (containerDisp->mDisplay == mozilla::StyleDisplay::Contents) { if (!containerContext->GetParent()) { break; } containerContext = containerContext->GetParent(); containerDisp = containerContext->StyleDisplay(); } if (ShouldBlockifyChildren(containerDisp) && !nsCSSAnonBoxes::IsNonElement(GetPseudo())) { // NOTE: Technically, we shouldn't modify the 'display' value of // positioned elements, since they aren't flex/grid items. However, // we don't need to worry about checking for that, because if we're // positioned, we'll have already been through a call to // EnsureBlockDisplay() in nsRuleNode, so this call here won't change // anything. So we're OK. auto displayVal = disp->mDisplay; nsRuleNode::EnsureBlockDisplay(displayVal); if (displayVal != disp->mDisplay) { NS_ASSERTION(!disp->IsAbsolutelyPositionedStyle(), "We shouldn't be changing the display value of " "positioned content (and we should have already " "converted its display value to be block-level...)"); nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display); disp = mutable_display; mutable_display->mDisplay = displayVal; } } } // Note: This must come after the blockification above, otherwise we fail // the grid-item-blockifying-001.html reftest. if (mParent && ::ShouldSuppressLineBreak(this, disp, mParent, mParent->StyleDisplay())) { mBits |= NS_STYLE_SUPPRESS_LINEBREAK; auto displayVal = disp->mDisplay; nsRuleNode::EnsureInlineDisplay(displayVal); if (displayVal != disp->mDisplay) { nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display); disp = mutable_display; mutable_display->mDisplay = displayVal; } } // Suppress border/padding of ruby level containers if (disp->mDisplay == mozilla::StyleDisplay::RubyBaseContainer || disp->mDisplay == mozilla::StyleDisplay::RubyTextContainer) { CreateEmptyStyleData(eStyleStruct_Border); CreateEmptyStyleData(eStyleStruct_Padding); } if (disp->IsRubyDisplayType()) { // Per CSS Ruby spec section Bidi Reordering, for all ruby boxes, // the 'normal' and 'embed' values of 'unicode-bidi' should compute to // 'isolate', and 'bidi-override' should compute to 'isolate-override'. const nsStyleTextReset* textReset = StyleTextReset(); uint8_t unicodeBidi = textReset->mUnicodeBidi; if (unicodeBidi == NS_STYLE_UNICODE_BIDI_NORMAL || unicodeBidi == NS_STYLE_UNICODE_BIDI_EMBED) { unicodeBidi = NS_STYLE_UNICODE_BIDI_ISOLATE; } else if (unicodeBidi == NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) { unicodeBidi = NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE; } if (unicodeBidi != textReset->mUnicodeBidi) { nsStyleTextReset* mutableTextReset = GET_UNIQUE_STYLE_DATA(TextReset); mutableTextReset->mUnicodeBidi = unicodeBidi; } } /* * According to https://drafts.csswg.org/css-writing-modes-3/#block-flow: * * If a box has a different block flow direction than its containing block: * * If the box has a specified display of inline, its display computes * to inline-block. [CSS21] * ...etc. */ if (disp->mDisplay == mozilla::StyleDisplay::Inline && !nsCSSAnonBoxes::IsNonElement(mPseudoTag) && mParent) { auto cbContext = mParent; while (cbContext->StyleDisplay()->mDisplay == mozilla::StyleDisplay::Contents) { cbContext = cbContext->mParent; } MOZ_ASSERT(cbContext, "the root context can't have display:contents"); // We don't need the full mozilla::WritingMode value (incorporating dir // and text-orientation) here; just the writing-mode property is enough. if (StyleVisibility()->mWritingMode != cbContext->StyleVisibility()->mWritingMode) { nsStyleDisplay* mutable_display = GET_UNIQUE_STYLE_DATA(Display); disp = mutable_display; mutable_display->mOriginalDisplay = mutable_display->mDisplay = mozilla::StyleDisplay::InlineBlock; } } // Compute User Interface style, to trigger loads of cursors StyleUserInterface(); #undef GET_UNIQUE_STYLE_DATA } template<class StyleContextLike> nsChangeHint nsStyleContext::CalcStyleDifferenceInternal(StyleContextLike* aNewContext, nsChangeHint aParentHintsNotHandledForDescendants, uint32_t* aEqualStructs, uint32_t* aSamePointerStructs) { PROFILER_LABEL("nsStyleContext", "CalcStyleDifference", js::ProfileEntry::Category::CSS); MOZ_ASSERT(NS_IsHintSubset(aParentHintsNotHandledForDescendants, nsChangeHint_Hints_NotHandledForDescendants), "caller is passing inherited hints, but shouldn't be"); static_assert(nsStyleStructID_Length <= 32, "aEqualStructs is not big enough"); *aEqualStructs = 0; nsChangeHint hint = nsChangeHint(0); NS_ENSURE_TRUE(aNewContext, hint); // We must always ensure that we populate the structs on the new style // context that are filled in on the old context, so that if we get // two style changes in succession, the second of which causes a real // style change, the PeekStyleData doesn't return null (implying that // nobody ever looked at that struct's data). In other words, we // can't skip later structs if we get a big change up front, because // we could later get a small change in one of those structs that we // don't want to miss. // If our sources are the same, then any differences in style data // are already accounted for by differences on ancestors. We know // this because CalcStyleDifference is always called on two style // contexts that point to the same element, so we know that our // position in the style context tree is the same and our position in // the rule node tree (if applicable) is also the same. // However, if there were noninherited style change hints on the // parent, we might produce these same noninherited hints on this // style context's frame due to 'inherit' values, so we do need to // compare. // (Things like 'em' units are handled by the change hint produced // by font-size changing, so we don't need to worry about them like // we worry about 'inherit' values.) bool compare = StyleSource() != aNewContext->StyleSource(); DebugOnly<uint32_t> structsFound = 0; // If we had any change in variable values, then we'll need to examine // all of the other style structs too, even if the new style context has // the same source as the old one. const nsStyleVariables* thisVariables = PeekStyleVariables(); if (thisVariables) { structsFound |= NS_STYLE_INHERIT_BIT(Variables); const nsStyleVariables* otherVariables = aNewContext->StyleVariables(); if (thisVariables->mVariables == otherVariables->mVariables) { *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables); } else { compare = true; } } else { *aEqualStructs |= NS_STYLE_INHERIT_BIT(Variables); } DebugOnly<int> styleStructCount = 1; // count Variables already #define DO_STRUCT_DIFFERENCE(struct_) \ PR_BEGIN_MACRO \ const nsStyle##struct_* this##struct_ = PeekStyle##struct_(); \ if (this##struct_) { \ structsFound |= NS_STYLE_INHERIT_BIT(struct_); \ const nsStyle##struct_* other##struct_ = aNewContext->Style##struct_(); \ nsChangeHint maxDifference = nsStyle##struct_::MaxDifference(); \ nsChangeHint differenceAlwaysHandledForDescendants = \ nsStyle##struct_::DifferenceAlwaysHandledForDescendants(); \ if (this##struct_ == other##struct_) { \ /* The very same struct, so we know that there will be no */ \ /* differences. */ \ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \ } else if (compare || \ ((maxDifference & ~differenceAlwaysHandledForDescendants) & \ aParentHintsNotHandledForDescendants)) { \ nsChangeHint difference = \ this##struct_->CalcDifference(*other##struct_ EXTRA_DIFF_ARGS); \ NS_ASSERTION(NS_IsHintSubset(difference, maxDifference), \ "CalcDifference() returned bigger hint than " \ "MaxDifference()"); \ hint |= difference; \ if (!difference) { \ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \ } \ } else { \ /* We still must call CalcDifference to see if there were any */ \ /* changes so that we can set *aEqualStructs appropriately. */ \ nsChangeHint difference = \ this##struct_->CalcDifference(*other##struct_ EXTRA_DIFF_ARGS); \ NS_ASSERTION(NS_IsHintSubset(difference, maxDifference), \ "CalcDifference() returned bigger hint than " \ "MaxDifference()"); \ if (!difference) { \ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \ } \ } \ } else { \ *aEqualStructs |= NS_STYLE_INHERIT_BIT(struct_); \ } \ styleStructCount++; \ PR_END_MACRO // In general, we want to examine structs starting with those that can // cause the largest style change, down to those that can cause the // smallest. This lets us skip later ones if we already have a hint // that subsumes their MaxDifference. (As the hints get // finer-grained, this optimization is becoming less useful, though.) #define EXTRA_DIFF_ARGS /* nothing */ DO_STRUCT_DIFFERENCE(Display); DO_STRUCT_DIFFERENCE(XUL); DO_STRUCT_DIFFERENCE(Column); DO_STRUCT_DIFFERENCE(Content); DO_STRUCT_DIFFERENCE(UserInterface); DO_STRUCT_DIFFERENCE(Visibility); DO_STRUCT_DIFFERENCE(Outline); DO_STRUCT_DIFFERENCE(TableBorder); DO_STRUCT_DIFFERENCE(Table); DO_STRUCT_DIFFERENCE(UIReset); DO_STRUCT_DIFFERENCE(Text); DO_STRUCT_DIFFERENCE(List); DO_STRUCT_DIFFERENCE(SVGReset); DO_STRUCT_DIFFERENCE(SVG); #undef EXTRA_DIFF_ARGS #define EXTRA_DIFF_ARGS , PeekStyleVisibility() DO_STRUCT_DIFFERENCE(Position); #undef EXTRA_DIFF_ARGS #define EXTRA_DIFF_ARGS /* nothing */ DO_STRUCT_DIFFERENCE(Font); DO_STRUCT_DIFFERENCE(Margin); DO_STRUCT_DIFFERENCE(Padding); DO_STRUCT_DIFFERENCE(Border); DO_STRUCT_DIFFERENCE(TextReset); DO_STRUCT_DIFFERENCE(Effects); DO_STRUCT_DIFFERENCE(Background); DO_STRUCT_DIFFERENCE(Color); #undef EXTRA_DIFF_ARGS #undef DO_STRUCT_DIFFERENCE MOZ_ASSERT(styleStructCount == nsStyleStructID_Length, "missing a call to DO_STRUCT_DIFFERENCE"); #ifdef DEBUG #define STYLE_STRUCT(name_, callback_) \ MOZ_ASSERT(!!(structsFound & NS_STYLE_INHERIT_BIT(name_)) == \ !!PeekStyle##name_(), \ "PeekStyleData results must not change in the middle of " \ "difference calculation."); #include "nsStyleStructList.h" #undef STYLE_STRUCT #endif // We check for struct pointer equality here rather than as part of the // DO_STRUCT_DIFFERENCE calls, since those calls can result in structs // we previously examined and found to be null on this style context // getting computed by later DO_STRUCT_DIFFERENCE calls (which can // happen when the nsRuleNode::ComputeXXXData method looks up another // struct.) This is important for callers in RestyleManager that // need to know the equality or not of the final set of cached struct // pointers. *aSamePointerStructs = 0; #define STYLE_STRUCT(name_, callback_) \ { \ const nsStyle##name_* data = PeekStyle##name_(); \ if (!data || data == aNewContext->Style##name_()) { \ *aSamePointerStructs |= NS_STYLE_INHERIT_BIT(name_); \ } \ } #include "nsStyleStructList.h" #undef STYLE_STRUCT // Note that we do not check whether this->RelevantLinkVisited() != // aNewContext->RelevantLinkVisited(); we don't need to since // nsCSSFrameConstructor::DoContentStateChanged always adds // nsChangeHint_RepaintFrame for NS_EVENT_STATE_VISITED changes (and // needs to, since HasStateDependentStyle probably doesn't work right // for NS_EVENT_STATE_VISITED). Hopefully this doesn't actually // expose whether links are visited to performance tests since all // link coloring happens asynchronously at a time when it's hard for // the page to measure. // However, we do need to compute the larger of the changes that can // happen depending on whether the link is visited or unvisited, since // doing only the one that's currently appropriate would expose which // links are in history to easy performance measurement. Therefore, // here, we add nsChangeHint_RepaintFrame hints (the maximum for // things that can depend on :visited) for the properties on which we // call GetVisitedDependentColor. nsStyleContext *thisVis = GetStyleIfVisited(), *otherVis = aNewContext->GetStyleIfVisited(); if (!thisVis != !otherVis) { // One style context has a style-if-visited and the other doesn't. // Presume a difference. hint |= nsChangeHint_RepaintFrame; } else if (thisVis && !NS_IsHintSubset(nsChangeHint_RepaintFrame, hint)) { // Both style contexts have a style-if-visited. bool change = false; // NB: Calling Peek on |this|, not |thisVis|, since callers may look // at a struct on |this| without looking at the same struct on // |thisVis| (including this function if we skip one of these checks // due to change being true already or due to the old style context // not having a style-if-visited), but not the other way around. if (PeekStyleColor()) { if (thisVis->StyleColor()->mColor != otherVis->StyleColor()->mColor) { change = true; } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleBackground()) { if (thisVis->StyleBackground()->mBackgroundColor != otherVis->StyleBackground()->mBackgroundColor) { change = true; } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleBorder()) { const nsStyleBorder *thisVisBorder = thisVis->StyleBorder(); const nsStyleBorder *otherVisBorder = otherVis->StyleBorder(); NS_FOR_CSS_SIDES(side) { if (thisVisBorder->mBorderColor[side] != otherVisBorder->mBorderColor[side]) { change = true; break; } } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleOutline()) { const nsStyleOutline *thisVisOutline = thisVis->StyleOutline(); const nsStyleOutline *otherVisOutline = otherVis->StyleOutline(); if (thisVisOutline->mOutlineColor != otherVisOutline->mOutlineColor) { change = true; } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleColumn()) { const nsStyleColumn *thisVisColumn = thisVis->StyleColumn(); const nsStyleColumn *otherVisColumn = otherVis->StyleColumn(); if (thisVisColumn->mColumnRuleColor != otherVisColumn->mColumnRuleColor) { change = true; } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleText()) { const nsStyleText* thisVisText = thisVis->StyleText(); const nsStyleText* otherVisText = otherVis->StyleText(); if (thisVisText->mTextEmphasisColor != otherVisText->mTextEmphasisColor || thisVisText->mWebkitTextFillColor != otherVisText->mWebkitTextFillColor || thisVisText->mWebkitTextStrokeColor != otherVisText->mWebkitTextStrokeColor) { change = true; } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleTextReset()) { const nsStyleTextReset *thisVisTextReset = thisVis->StyleTextReset(); const nsStyleTextReset *otherVisTextReset = otherVis->StyleTextReset(); if (thisVisTextReset->mTextDecorationColor != otherVisTextReset->mTextDecorationColor) { change = true; } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleSVG()) { const nsStyleSVG *thisVisSVG = thisVis->StyleSVG(); const nsStyleSVG *otherVisSVG = otherVis->StyleSVG(); if (thisVisSVG->mFill != otherVisSVG->mFill || thisVisSVG->mStroke != otherVisSVG->mStroke) { change = true; } } // NB: Calling Peek on |this|, not |thisVis| (see above). if (!change && PeekStyleUserInterface()) { const nsStyleUserInterface *thisVisUserInterface = thisVis->StyleUserInterface(); const nsStyleUserInterface *otherVisUserInterface = otherVis->StyleUserInterface(); if (thisVisUserInterface->mCaretColor != otherVisUserInterface->mCaretColor) { change = true; } } if (change) { hint |= nsChangeHint_RepaintFrame; } } if (hint & nsChangeHint_UpdateContainingBlock) { // If a struct returned nsChangeHint_UpdateContainingBlock, that // means that one property's influence on whether we're a containing // block for abs-pos or fixed-pos elements has changed. However, we // only need to return the hint if the overall computation of // whether we establish a containing block has changed. // This depends on data in nsStyleDisplay and nsStyleEffects, so we // do it here. // Note that it's perhaps good for this test to be last because it // doesn't use Peek* functions to get the structs on the old // context. But this isn't a big concern because these struct // getters should be called during frame construction anyway. if (StyleDisplay()->IsAbsPosContainingBlockForAppropriateFrame(this) == aNewContext->StyleDisplay()-> IsAbsPosContainingBlockForAppropriateFrame(aNewContext) && StyleDisplay()->IsFixedPosContainingBlockForAppropriateFrame(this) == aNewContext->StyleDisplay()-> IsFixedPosContainingBlockForAppropriateFrame(aNewContext)) { // While some styles that cause the frame to be a containing block // has changed, the overall result hasn't. hint &= ~nsChangeHint_UpdateContainingBlock; } } MOZ_ASSERT(NS_IsHintSubset(hint, nsChangeHint_AllHints), "Added a new hint without bumping AllHints?"); return hint & ~nsChangeHint_NeutralChange; } nsChangeHint nsStyleContext::CalcStyleDifference(nsStyleContext* aNewContext, nsChangeHint aParentHintsNotHandledForDescendants, uint32_t* aEqualStructs, uint32_t* aSamePointerStructs) { return CalcStyleDifferenceInternal(aNewContext, aParentHintsNotHandledForDescendants, aEqualStructs, aSamePointerStructs); } class MOZ_STACK_CLASS FakeStyleContext { public: explicit FakeStyleContext(const ServoComputedValues* aComputedValues) : mComputedValues(aComputedValues) {} mozilla::NonOwningStyleContextSource StyleSource() const { return mozilla::NonOwningStyleContextSource(mComputedValues); } nsStyleContext* GetStyleIfVisited() { // XXXbholley: This is wrong. Need to implement to get visited handling // corrrect! return nullptr; } #define STYLE_STRUCT(name_, checkdata_cb_) \ const nsStyle##name_ * Style##name_() { \ return Servo_GetStyle##name_(mComputedValues); \ } #include "nsStyleStructList.h" #undef STYLE_STRUCT private: const ServoComputedValues* MOZ_NON_OWNING_REF mComputedValues; }; nsChangeHint nsStyleContext::CalcStyleDifference(const ServoComputedValues* aNewComputedValues, nsChangeHint aParentHintsNotHandledForDescendants, uint32_t* aEqualStructs, uint32_t* aSamePointerStructs) { FakeStyleContext newContext(aNewComputedValues); return CalcStyleDifferenceInternal(&newContext, aParentHintsNotHandledForDescendants, aEqualStructs, aSamePointerStructs); } #ifdef DEBUG void nsStyleContext::List(FILE* out, int32_t aIndent, bool aListDescendants) { nsAutoCString str; // Indent int32_t ix; for (ix = aIndent; --ix >= 0; ) { str.AppendLiteral(" "); } str.Append(nsPrintfCString("%p(%d) parent=%p ", (void*)this, mRefCnt, (void *)mParent)); if (mPseudoTag) { nsAutoString buffer; mPseudoTag->ToString(buffer); AppendUTF16toUTF8(buffer, str); str.Append(' '); } if (mSource.IsServoComputedValues()) { fprintf_stderr(out, "%s{ServoComputedValues}\n", str.get()); } else if (mSource.IsGeckoRuleNode()) { fprintf_stderr(out, "%s{\n", str.get()); str.Truncate(); nsRuleNode* ruleNode = mSource.AsGeckoRuleNode(); while (ruleNode) { nsIStyleRule *styleRule = ruleNode->GetRule(); if (styleRule) { styleRule->List(out, aIndent + 1); } ruleNode = ruleNode->GetParent(); } for (ix = aIndent; --ix >= 0; ) { str.AppendLiteral(" "); } fprintf_stderr(out, "%s}\n", str.get()); } else { fprintf_stderr(out, "%s{}\n", str.get()); } if (aListDescendants) { if (nullptr != mChild) { nsStyleContext* child = mChild; do { child->List(out, aIndent + 1, aListDescendants); child = child->mNextSibling; } while (mChild != child); } if (nullptr != mEmptyChild) { nsStyleContext* child = mEmptyChild; do { child->List(out, aIndent + 1, aListDescendants); child = child->mNextSibling; } while (mEmptyChild != child); } } } #endif // Overloaded new operator. Initializes the memory to 0 and relies on an arena // (which comes from the presShell) to perform the allocation. void* nsStyleContext::operator new(size_t sz, nsPresContext* aPresContext) { // Check the recycle list first. return aPresContext->PresShell()-> AllocateByObjectID(eArenaObjectID_nsStyleContext, sz); } // Overridden to prevent the global delete from being called, since the memory // came out of an nsIArena instead of the global delete operator's heap. void nsStyleContext::Destroy() { // Get the pres context. RefPtr<nsPresContext> presContext = PresContext(); // Call our destructor. this->~nsStyleContext(); // Don't let the memory be freed, since it will be recycled // instead. Don't call the global operator delete. presContext->PresShell()-> FreeByObjectID(eArenaObjectID_nsStyleContext, this); } already_AddRefed<nsStyleContext> NS_NewStyleContext(nsStyleContext* aParentContext, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType, nsRuleNode* aRuleNode, bool aSkipParentDisplayBasedStyleFixup) { RefPtr<nsRuleNode> node = aRuleNode; RefPtr<nsStyleContext> context = new (aRuleNode->PresContext()) nsStyleContext(aParentContext, aPseudoTag, aPseudoType, node.forget(), aSkipParentDisplayBasedStyleFixup); return context.forget(); } already_AddRefed<nsStyleContext> NS_NewStyleContext(nsStyleContext* aParentContext, nsPresContext* aPresContext, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType, already_AddRefed<ServoComputedValues> aComputedValues, bool aSkipParentDisplayBasedStyleFixup) { RefPtr<nsStyleContext> context = new (aPresContext) nsStyleContext(aParentContext, aPresContext, aPseudoTag, aPseudoType, Move(aComputedValues), aSkipParentDisplayBasedStyleFixup); return context.forget(); } nsIPresShell* nsStyleContext::Arena() { return PresContext()->PresShell(); } static inline void ExtractAnimationValue(nsCSSPropertyID aProperty, nsStyleContext* aStyleContext, StyleAnimationValue& aResult) { DebugOnly<bool> success = StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext, aResult); MOZ_ASSERT(success, "aProperty must be extractable by StyleAnimationValue"); } static Maybe<nscolor> ExtractColor(nsCSSPropertyID aProperty, nsStyleContext *aStyleContext) { StyleAnimationValue val; ExtractAnimationValue(aProperty, aStyleContext, val); switch (val.GetUnit()) { case StyleAnimationValue::eUnit_Color: return Some(val.GetCSSValueValue()->GetColorValue()); case StyleAnimationValue::eUnit_CurrentColor: return Some(aStyleContext->StyleColor()->mColor); case StyleAnimationValue::eUnit_ComplexColor: return Some(aStyleContext->StyleColor()-> CalcComplexColor(val.GetStyleComplexColorValue())); case StyleAnimationValue::eUnit_Auto: return Some(aStyleContext->StyleColor()-> CalcComplexColor(StyleComplexColor::Auto())); default: return Nothing(); } } static nscolor ExtractColorLenient(nsCSSPropertyID aProperty, nsStyleContext *aStyleContext) { return ExtractColor(aProperty, aStyleContext).valueOr(NS_RGBA(0, 0, 0, 0)); } struct ColorIndexSet { uint8_t colorIndex, alphaIndex; }; static const ColorIndexSet gVisitedIndices[2] = { { 0, 0 }, { 1, 0 } }; nscolor nsStyleContext::GetVisitedDependentColor(nsCSSPropertyID aProperty) { NS_ASSERTION(aProperty == eCSSProperty_caret_color || aProperty == eCSSProperty_color || aProperty == eCSSProperty_background_color || aProperty == eCSSProperty_border_top_color || aProperty == eCSSProperty_border_right_color || aProperty == eCSSProperty_border_bottom_color || aProperty == eCSSProperty_border_left_color || aProperty == eCSSProperty_outline_color || aProperty == eCSSProperty_column_rule_color || aProperty == eCSSProperty_text_decoration_color || aProperty == eCSSProperty_text_emphasis_color || aProperty == eCSSProperty__webkit_text_fill_color || aProperty == eCSSProperty__webkit_text_stroke_color || aProperty == eCSSProperty_fill || aProperty == eCSSProperty_stroke, "we need to add to nsStyleContext::CalcStyleDifference"); bool isPaintProperty = aProperty == eCSSProperty_fill || aProperty == eCSSProperty_stroke; nscolor colors[2]; colors[0] = isPaintProperty ? ExtractColorLenient(aProperty, this) : ExtractColor(aProperty, this).value(); nsStyleContext *visitedStyle = this->GetStyleIfVisited(); if (!visitedStyle) { return colors[0]; } colors[1] = isPaintProperty ? ExtractColorLenient(aProperty, visitedStyle) : ExtractColor(aProperty, visitedStyle).value(); return nsStyleContext::CombineVisitedColors(colors, this->RelevantLinkVisited()); } /* static */ nscolor nsStyleContext::CombineVisitedColors(nscolor *aColors, bool aLinkIsVisited) { if (NS_GET_A(aColors[1]) == 0) { // If the style-if-visited is transparent, then just use the // unvisited style rather than using the (meaningless) color // components of the visited style along with a potentially // non-transparent alpha value. aLinkIsVisited = false; } // NOTE: We want this code to have as little timing dependence as // possible on whether this->RelevantLinkVisited() is true. const ColorIndexSet &set = gVisitedIndices[aLinkIsVisited ? 1 : 0]; nscolor colorColor = aColors[set.colorIndex]; nscolor alphaColor = aColors[set.alphaIndex]; return NS_RGBA(NS_GET_R(colorColor), NS_GET_G(colorColor), NS_GET_B(colorColor), NS_GET_A(alphaColor)); } #ifdef DEBUG /* static */ void nsStyleContext::AssertStyleStructMaxDifferenceValid() { #define STYLE_STRUCT(name, checkdata_cb) \ MOZ_ASSERT(NS_IsHintSubset(nsStyle##name::DifferenceAlwaysHandledForDescendants(), \ nsStyle##name::MaxDifference())); #include "nsStyleStructList.h" #undef STYLE_STRUCT } /* static */ const char* nsStyleContext::StructName(nsStyleStructID aSID) { switch (aSID) { #define STYLE_STRUCT(name_, checkdata_cb) \ case eStyleStruct_##name_: \ return #name_; #include "nsStyleStructList.h" #undef STYLE_STRUCT default: return "Unknown"; } } /* static */ bool nsStyleContext::LookupStruct(const nsACString& aName, nsStyleStructID& aResult) { if (false) ; #define STYLE_STRUCT(name_, checkdata_cb_) \ else if (aName.EqualsLiteral(#name_)) \ aResult = eStyleStruct_##name_; #include "nsStyleStructList.h" #undef STYLE_STRUCT else return false; return true; } #endif void nsStyleContext::SwapStyleData(nsStyleContext* aNewContext, uint32_t aStructs) { static_assert(nsStyleStructID_Length <= 32, "aStructs is not big enough"); for (nsStyleStructID i = nsStyleStructID_Inherited_Start; i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count; i = nsStyleStructID(i + 1)) { uint32_t bit = nsCachedStyleData::GetBitForSID(i); if (!(aStructs & bit)) { continue; } void*& thisData = mCachedInheritedData.mStyleStructs[i]; void*& otherData = aNewContext->mCachedInheritedData.mStyleStructs[i]; if (mBits & bit) { if (thisData == otherData) { thisData = nullptr; } } else if (!(aNewContext->mBits & bit) && thisData && otherData) { std::swap(thisData, otherData); } } for (nsStyleStructID i = nsStyleStructID_Reset_Start; i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count; i = nsStyleStructID(i + 1)) { uint32_t bit = nsCachedStyleData::GetBitForSID(i); if (!(aStructs & bit)) { continue; } if (!mCachedResetData) { mCachedResetData = new (PresContext()) nsResetStyleData; } if (!aNewContext->mCachedResetData) { aNewContext->mCachedResetData = new (PresContext()) nsResetStyleData; } void*& thisData = mCachedResetData->mStyleStructs[i]; void*& otherData = aNewContext->mCachedResetData->mStyleStructs[i]; if (mBits & bit) { if (thisData == otherData) { thisData = nullptr; } } else if (!(aNewContext->mBits & bit) && thisData && otherData) { std::swap(thisData, otherData); } } } void nsStyleContext::ClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs) { if (mChild) { nsStyleContext* child = mChild; do { child->DoClearCachedInheritedStyleDataOnDescendants(aStructs); child = child->mNextSibling; } while (mChild != child); } if (mEmptyChild) { nsStyleContext* child = mEmptyChild; do { child->DoClearCachedInheritedStyleDataOnDescendants(aStructs); child = child->mNextSibling; } while (mEmptyChild != child); } } void nsStyleContext::DoClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs) { NS_ASSERTION(mFrameRefCnt == 0, "frame still referencing style context"); for (nsStyleStructID i = nsStyleStructID_Inherited_Start; i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count; i = nsStyleStructID(i + 1)) { uint32_t bit = nsCachedStyleData::GetBitForSID(i); if (aStructs & bit) { if (!(mBits & bit) && mCachedInheritedData.mStyleStructs[i]) { aStructs &= ~bit; } else { mCachedInheritedData.mStyleStructs[i] = nullptr; } } } if (mCachedResetData) { for (nsStyleStructID i = nsStyleStructID_Reset_Start; i < nsStyleStructID_Reset_Start + nsStyleStructID_Reset_Count; i = nsStyleStructID(i + 1)) { uint32_t bit = nsCachedStyleData::GetBitForSID(i); if (aStructs & bit) { if (!(mBits & bit) && mCachedResetData->mStyleStructs[i]) { aStructs &= ~bit; } else { mCachedResetData->mStyleStructs[i] = nullptr; } } } } if (aStructs == 0) { return; } ClearCachedInheritedStyleDataOnDescendants(aStructs); } void nsStyleContext::SetIneligibleForSharing() { if (mBits & NS_STYLE_INELIGIBLE_FOR_SHARING) { return; } mBits |= NS_STYLE_INELIGIBLE_FOR_SHARING; if (mChild) { nsStyleContext* child = mChild; do { child->SetIneligibleForSharing(); child = child->mNextSibling; } while (mChild != child); } if (mEmptyChild) { nsStyleContext* child = mEmptyChild; do { child->SetIneligibleForSharing(); child = child->mNextSibling; } while (mEmptyChild != child); } } #ifdef RESTYLE_LOGGING nsCString nsStyleContext::GetCachedStyleDataAsString(uint32_t aStructs) { nsCString structs; for (nsStyleStructID i = nsStyleStructID(0); i < nsStyleStructID_Length; i = nsStyleStructID(i + 1)) { if (aStructs & nsCachedStyleData::GetBitForSID(i)) { const void* data = GetCachedStyleData(i); if (!structs.IsEmpty()) { structs.Append(' '); } structs.AppendPrintf("%s=%p", StructName(i), data); if (HasCachedDependentStyleData(i)) { structs.AppendLiteral("(dependent)"); } else { structs.AppendLiteral("(owned)"); } } } return structs; } int32_t& nsStyleContext::LoggingDepth() { static int32_t depth = 0; return depth; } void nsStyleContext::LogStyleContextTree(int32_t aLoggingDepth, uint32_t aStructs) { LoggingDepth() = aLoggingDepth; LogStyleContextTree(true, aStructs); } void nsStyleContext::LogStyleContextTree(bool aFirst, uint32_t aStructs) { nsCString structs = GetCachedStyleDataAsString(aStructs); if (!structs.IsEmpty()) { structs.Append(' '); } nsCString pseudo; if (mPseudoTag) { nsAutoString pseudoTag; mPseudoTag->ToString(pseudoTag); AppendUTF16toUTF8(pseudoTag, pseudo); pseudo.Append(' '); } nsCString flags; if (IsStyleIfVisited()) { flags.AppendLiteral("IS_STYLE_IF_VISITED "); } if (HasChildThatUsesGrandancestorStyle()) { flags.AppendLiteral("CHILD_USES_GRANDANCESTOR_STYLE "); } if (IsShared()) { flags.AppendLiteral("IS_SHARED "); } nsCString parent; if (aFirst) { parent.AppendPrintf("parent=%p ", mParent.get()); } LOG_RESTYLE("%p(%d) %s%s%s%s", this, mRefCnt, structs.get(), pseudo.get(), flags.get(), parent.get()); LOG_RESTYLE_INDENT(); if (nullptr != mChild) { nsStyleContext* child = mChild; do { child->LogStyleContextTree(false, aStructs); child = child->mNextSibling; } while (mChild != child); } if (nullptr != mEmptyChild) { nsStyleContext* child = mEmptyChild; do { child->LogStyleContextTree(false, aStructs); child = child->mNextSibling; } while (mEmptyChild != child); } } #endif #ifdef DEBUG /* static */ void nsStyleContext::Initialize() { Preferences::AddBoolVarCache( &sExpensiveStyleStructAssertionsEnabled, "layout.css.expensive-style-struct-assertions.enabled"); } #endif