diff options
Diffstat (limited to 'layout/style/nsStyleContext.cpp')
-rw-r--r-- | layout/style/nsStyleContext.cpp | 1836 |
1 files changed, 1836 insertions, 0 deletions
diff --git a/layout/style/nsStyleContext.cpp b/layout/style/nsStyleContext.cpp new file mode 100644 index 000000000..7ad260f1b --- /dev/null +++ b/layout/style/nsStyleContext.cpp @@ -0,0 +1,1836 @@ +/* -*- 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 "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" + +// 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; + } + } + + 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())); + 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_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 |