diff options
Diffstat (limited to 'layout/style/nsStyleSet.cpp')
-rw-r--r-- | layout/style/nsStyleSet.cpp | 2537 |
1 files changed, 2537 insertions, 0 deletions
diff --git a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp new file mode 100644 index 000000000..414eee4d4 --- /dev/null +++ b/layout/style/nsStyleSet.cpp @@ -0,0 +1,2537 @@ +/* -*- 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 container for the style sheets that apply to a presentation, and + * the internal API that the style system exposes for creating (and + * potentially re-creating) style contexts + */ + +#include "nsStyleSet.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EnumeratedRange.h" +#include "mozilla/EventStates.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RuleProcessorCache.h" +#include "mozilla/StyleSheetInlines.h" +#include "nsIDocumentInlines.h" +#include "nsRuleWalker.h" +#include "nsStyleContext.h" +#include "mozilla/css/StyleRule.h" +#include "nsCSSAnonBoxes.h" +#include "nsCSSPseudoElements.h" +#include "nsCSSRuleProcessor.h" +#include "nsDataHashtable.h" +#include "nsIContent.h" +#include "nsRuleData.h" +#include "nsRuleProcessorData.h" +#include "nsAnimationManager.h" +#include "nsStyleSheetService.h" +#include "mozilla/dom/Element.h" +#include "GeckoProfiler.h" +#include "nsHTMLCSSStyleSheet.h" +#include "nsHTMLStyleSheet.h" +#include "SVGAttrAnimationRuleProcessor.h" +#include "nsCSSRules.h" +#include "nsPrintfCString.h" +#include "nsIFrame.h" +#include "mozilla/RestyleManagerHandle.h" +#include "mozilla/RestyleManagerHandleInlines.h" +#include "nsQueryObject.h" + +#include <inttypes.h> + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(nsEmptyStyleRule, nsIStyleRule) + +/* virtual */ void +nsEmptyStyleRule::MapRuleInfoInto(nsRuleData* aRuleData) +{ +} + +/* virtual */ bool +nsEmptyStyleRule::MightMapInheritedStyleData() +{ + return false; +} + +/* virtual */ bool +nsEmptyStyleRule::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, + nsCSSValue* aValue) +{ + MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet"); + return false; +} + +#ifdef DEBUG +/* virtual */ void +nsEmptyStyleRule::List(FILE* out, int32_t aIndent) const +{ + nsAutoCString indentStr; + for (int32_t index = aIndent; --index >= 0; ) { + indentStr.AppendLiteral(" "); + } + fprintf_stderr(out, "%s[empty style rule] {}\n", indentStr.get()); +} +#endif + +NS_IMPL_ISUPPORTS(nsInitialStyleRule, nsIStyleRule) + +/* virtual */ void +nsInitialStyleRule::MapRuleInfoInto(nsRuleData* aRuleData) +{ + // Iterate over the property groups + for (nsStyleStructID sid = nsStyleStructID(0); + sid < nsStyleStructID_Length; sid = nsStyleStructID(sid + 1)) { + if (aRuleData->mSIDs & (1 << sid)) { + // Iterate over nsCSSValues within the property group + nsCSSValue * const value_start = + aRuleData->mValueStorage + aRuleData->mValueOffsets[sid]; + for (nsCSSValue *value = value_start, + *value_end = value + nsCSSProps::PropertyCountInStruct(sid); + value != value_end; ++value) { + // If MathML is disabled take care not to set MathML properties (or we + // will trigger assertions in nsRuleNode) + if (sid == eStyleStruct_Font && + !aRuleData->mPresContext->Document()->GetMathMLEnabled()) { + size_t index = value - value_start; + if (index == nsCSSProps::PropertyIndexInStruct( + eCSSProperty_script_level) || + index == nsCSSProps::PropertyIndexInStruct( + eCSSProperty_script_size_multiplier) || + index == nsCSSProps::PropertyIndexInStruct( + eCSSProperty_script_min_size) || + index == nsCSSProps::PropertyIndexInStruct( + eCSSProperty_math_variant) || + index == nsCSSProps::PropertyIndexInStruct( + eCSSProperty_math_display)) { + continue; + } + } + if (value->GetUnit() == eCSSUnit_Null) { + value->SetInitialValue(); + } + } + } + } +} + +/* virtual */ bool +nsInitialStyleRule::MightMapInheritedStyleData() +{ + return true; +} + +/* virtual */ bool +nsInitialStyleRule::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, + nsCSSValue* aValue) +{ + MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet"); + return false; +} + +#ifdef DEBUG +/* virtual */ void +nsInitialStyleRule::List(FILE* out, int32_t aIndent) const +{ + nsAutoCString indentStr; + for (int32_t index = aIndent; --index >= 0; ) { + indentStr.AppendLiteral(" "); + } + fprintf_stderr(out, "%s[initial style rule] {}\n", indentStr.get()); +} +#endif + +NS_IMPL_ISUPPORTS(nsDisableTextZoomStyleRule, nsIStyleRule) + +/* virtual */ void +nsDisableTextZoomStyleRule::MapRuleInfoInto(nsRuleData* aRuleData) +{ + if (!(aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Font))) + return; + + nsCSSValue* value = aRuleData->ValueForTextZoom(); + if (value->GetUnit() == eCSSUnit_Null) + value->SetNoneValue(); +} + +/* virtual */ bool +nsDisableTextZoomStyleRule::MightMapInheritedStyleData() +{ + return true; +} + +/* virtual */ bool +nsDisableTextZoomStyleRule:: +GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue) +{ + MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet"); + return false; +} + +#ifdef DEBUG +/* virtual */ void +nsDisableTextZoomStyleRule::List(FILE* out, int32_t aIndent) const +{ + nsAutoCString indentStr; + for (int32_t index = aIndent; --index >= 0; ) { + indentStr.AppendLiteral(" "); + } + fprintf_stderr(out, "%s[disable text zoom style rule] {}\n", indentStr.get()); +} +#endif + +static const SheetType gCSSSheetTypes[] = { + // From lowest to highest in cascading order. + SheetType::Agent, + SheetType::User, + SheetType::Doc, + SheetType::ScopedDoc, + SheetType::Override +}; + +/* static */ bool +nsStyleSet::IsCSSSheetType(SheetType aSheetType) +{ + for (SheetType type : gCSSSheetTypes) { + if (type == aSheetType) { + return true; + } + } + return false; +} + +nsStyleSet::nsStyleSet() + : mRuleTree(nullptr), + mBatching(0), + mInShutdown(false), + mInGC(false), + mAuthorStyleDisabled(false), + mInReconstruct(false), + mInitFontFeatureValuesLookup(true), + mNeedsRestyleAfterEnsureUniqueInner(false), + mDirty(0), + mRootStyleContextCount(0), +#ifdef DEBUG + mOldRootNode(nullptr), +#endif + mUnusedRuleNodeCount(0) +{ +} + +nsStyleSet::~nsStyleSet() +{ + for (SheetType type : gCSSSheetTypes) { + for (CSSStyleSheet* sheet : mSheets[type]) { + sheet->DropStyleSet(this); + } + } + + // drop reference to cached rule processors + nsCSSRuleProcessor* rp; + rp = static_cast<nsCSSRuleProcessor*>(mRuleProcessors[SheetType::Agent].get()); + if (rp) { + MOZ_ASSERT(rp->IsShared()); + rp->ReleaseStyleSetRef(); + } + rp = static_cast<nsCSSRuleProcessor*>(mRuleProcessors[SheetType::User].get()); + if (rp) { + MOZ_ASSERT(rp->IsShared()); + rp->ReleaseStyleSetRef(); + } +} + +size_t +nsStyleSet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + + for (SheetType type : MakeEnumeratedRange(SheetType::Count)) { + if (mRuleProcessors[type]) { + bool shared = false; + if (type == SheetType::Agent || type == SheetType::User) { + // The only two origins we consider caching rule processors for. + nsCSSRuleProcessor* rp = + static_cast<nsCSSRuleProcessor*>(mRuleProcessors[type].get()); + shared = rp->IsShared(); + } + if (!shared) { + n += mRuleProcessors[type]->SizeOfIncludingThis(aMallocSizeOf); + } + } + // We don't own the sheets (either the nsLayoutStyleSheetCache singleton + // or our document owns them). + n += mSheets[type].ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) { + n += mScopedDocSheetRuleProcessors[i]->SizeOfIncludingThis(aMallocSizeOf); + } + n += mScopedDocSheetRuleProcessors.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return n; +} + +void +nsStyleSet::Init(nsPresContext *aPresContext) +{ + mFirstLineRule = new nsEmptyStyleRule; + mFirstLetterRule = new nsEmptyStyleRule; + mPlaceholderRule = new nsEmptyStyleRule; + mDisableTextZoomStyleRule = new nsDisableTextZoomStyleRule; + + mRuleTree = nsRuleNode::CreateRootNode(aPresContext); + + // Make an explicit GatherRuleProcessors call for the levels that + // don't have style sheets. The other levels will have their calls + // triggered by DirtyRuleProcessors. + GatherRuleProcessors(SheetType::PresHint); + GatherRuleProcessors(SheetType::SVGAttrAnimation); + GatherRuleProcessors(SheetType::StyleAttr); + GatherRuleProcessors(SheetType::Animation); + GatherRuleProcessors(SheetType::Transition); +} + +nsresult +nsStyleSet::BeginReconstruct() +{ + NS_ASSERTION(!mInReconstruct, "Unmatched begin/end?"); + NS_ASSERTION(mRuleTree, "Reconstructing before first construction?"); + mInReconstruct = true; + + // Clear any ArenaRefPtr-managed style contexts, as we don't want them + // held on to after the rule tree has been reconstructed. + PresContext()->PresShell()->ClearArenaRefPtrs(eArenaObjectID_nsStyleContext); + +#ifdef DEBUG + MOZ_ASSERT(!mOldRootNode); + mOldRootNode = mRuleTree; +#endif + + // Create a new rule tree root, dropping the reference to our old rule tree. + // After reconstruction, we will re-enable GC, and allow everything to be + // collected. + mRuleTree = nsRuleNode::CreateRootNode(mRuleTree->PresContext()); + + return NS_OK; +} + +void +nsStyleSet::EndReconstruct() +{ + NS_ASSERTION(mInReconstruct, "Unmatched begin/end?"); + mInReconstruct = false; + GCRuleTrees(); +} + +typedef nsDataHashtable<nsPtrHashKey<nsINode>, uint32_t> ScopeDepthCache; + +// Returns the depth of a style scope element, with 1 being the depth of +// a style scope element that has no ancestor style scope elements. The +// depth does not count intervening non-scope elements. +static uint32_t +GetScopeDepth(nsINode* aScopeElement, ScopeDepthCache& aCache) +{ + nsINode* parent = aScopeElement->GetParent(); + if (!parent || !parent->IsElementInStyleScope()) { + return 1; + } + + uint32_t depth = aCache.Get(aScopeElement); + if (!depth) { + for (nsINode* n = parent; n; n = n->GetParent()) { + if (n->IsScopedStyleRoot()) { + depth = GetScopeDepth(n, aCache) + 1; + aCache.Put(aScopeElement, depth); + break; + } + } + } + return depth; +} + +struct ScopedSheetOrder +{ + CSSStyleSheet* mSheet; + uint32_t mDepth; + uint32_t mOrder; + + bool operator==(const ScopedSheetOrder& aRHS) const + { + return mDepth == aRHS.mDepth && + mOrder == aRHS.mOrder; + } + + bool operator<(const ScopedSheetOrder& aRHS) const + { + if (mDepth != aRHS.mDepth) { + return mDepth < aRHS.mDepth; + } + return mOrder < aRHS.mOrder; + } +}; + +// Sorts aSheets such that style sheets for ancestor scopes come +// before those for descendant scopes, and with sheets for a single +// scope in document order. +static void +SortStyleSheetsByScope(nsTArray<CSSStyleSheet*>& aSheets) +{ + uint32_t n = aSheets.Length(); + if (n == 1) { + return; + } + + ScopeDepthCache cache; + + nsTArray<ScopedSheetOrder> sheets; + sheets.SetLength(n); + + // For each sheet, record the depth of its scope element and its original + // document order. + for (uint32_t i = 0; i < n; i++) { + sheets[i].mSheet = aSheets[i]; + sheets[i].mDepth = GetScopeDepth(aSheets[i]->GetScopeElement(), cache); + sheets[i].mOrder = i; + } + + // Sort by depth first, then document order. + sheets.Sort(); + + for (uint32_t i = 0; i < n; i++) { + aSheets[i] = sheets[i].mSheet; + } +} + +nsresult +nsStyleSet::GatherRuleProcessors(SheetType aType) +{ + NS_ENSURE_FALSE(mInShutdown, NS_ERROR_FAILURE); + + // We might be in GatherRuleProcessors because we are dropping a sheet, + // resulting in an nsCSSSelector being destroyed. Tell the + // RestyleManager for each document we're used in so that they can + // drop any nsCSSSelector pointers (used for eRestyle_SomeDescendants) + // in their mPendingRestyles. + if (IsCSSSheetType(aType)) { + ClearSelectors(); + } + nsCOMPtr<nsIStyleRuleProcessor> oldRuleProcessor(mRuleProcessors[aType]); + nsTArray<nsCOMPtr<nsIStyleRuleProcessor>> oldScopedDocRuleProcessors; + if (aType == SheetType::Agent || aType == SheetType::User) { + // drop reference to cached rule processor + nsCSSRuleProcessor* rp = + static_cast<nsCSSRuleProcessor*>(mRuleProcessors[aType].get()); + if (rp) { + MOZ_ASSERT(rp->IsShared()); + rp->ReleaseStyleSetRef(); + } + } + mRuleProcessors[aType] = nullptr; + if (aType == SheetType::ScopedDoc) { + for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) { + nsIStyleRuleProcessor* processor = mScopedDocSheetRuleProcessors[i].get(); + Element* scope = + static_cast<nsCSSRuleProcessor*>(processor)->GetScopeElement(); + scope->ClearIsScopedStyleRoot(); + } + + // Clear mScopedDocSheetRuleProcessors, but save it. + oldScopedDocRuleProcessors.SwapElements(mScopedDocSheetRuleProcessors); + } + if (mAuthorStyleDisabled && (aType == SheetType::Doc || + aType == SheetType::ScopedDoc || + aType == SheetType::StyleAttr)) { + // Don't regather if this level is disabled. Note that we gather + // preshint sheets no matter what, but then skip them for some + // elements later if mAuthorStyleDisabled. + return NS_OK; + } + switch (aType) { + // levels that do not contain CSS style sheets + case SheetType::Animation: + MOZ_ASSERT(mSheets[aType].IsEmpty()); + mRuleProcessors[aType] = PresContext()->EffectCompositor()-> + RuleProcessor(EffectCompositor::CascadeLevel::Animations); + return NS_OK; + case SheetType::Transition: + MOZ_ASSERT(mSheets[aType].IsEmpty()); + mRuleProcessors[aType] = PresContext()->EffectCompositor()-> + RuleProcessor(EffectCompositor::CascadeLevel::Transitions); + return NS_OK; + case SheetType::StyleAttr: + MOZ_ASSERT(mSheets[aType].IsEmpty()); + mRuleProcessors[aType] = PresContext()->Document()->GetInlineStyleSheet(); + return NS_OK; + case SheetType::PresHint: + MOZ_ASSERT(mSheets[aType].IsEmpty()); + mRuleProcessors[aType] = + PresContext()->Document()->GetAttributeStyleSheet(); + return NS_OK; + case SheetType::SVGAttrAnimation: + MOZ_ASSERT(mSheets[aType].IsEmpty()); + mRuleProcessors[aType] = + PresContext()->Document()->GetSVGAttrAnimationRuleProcessor(); + return NS_OK; + default: + // keep going + break; + } + MOZ_ASSERT(IsCSSSheetType(aType)); + if (aType == SheetType::ScopedDoc) { + // Create a rule processor for each scope. + uint32_t count = mSheets[SheetType::ScopedDoc].Length(); + if (count) { + // Gather the scoped style sheets into an array as + // CSSStyleSheets, and mark all of their scope elements + // as scoped style roots. + nsTArray<CSSStyleSheet*> sheets(count); + for (CSSStyleSheet* sheet : mSheets[SheetType::ScopedDoc]) { + sheets.AppendElement(sheet); + + Element* scope = sheet->GetScopeElement(); + scope->SetIsScopedStyleRoot(); + } + + // Sort the scoped style sheets so that those for the same scope are + // adjacent and that ancestor scopes come before descendent scopes. + SortStyleSheetsByScope(sheets); + + // Put the old scoped rule processors in a hashtable so that we + // can retrieve them efficiently, even in edge cases like the + // simultaneous removal and addition of a large number of elements + // with scoped sheets. + nsDataHashtable<nsPtrHashKey<Element>, + nsCSSRuleProcessor*> oldScopedRuleProcessorHash; + for (size_t i = oldScopedDocRuleProcessors.Length(); i-- != 0; ) { + nsCSSRuleProcessor* oldRP = + static_cast<nsCSSRuleProcessor*>(oldScopedDocRuleProcessors[i].get()); + Element* scope = oldRP->GetScopeElement(); + MOZ_ASSERT(!oldScopedRuleProcessorHash.Get(scope), + "duplicate rule processors for same scope element?"); + oldScopedRuleProcessorHash.Put(scope, oldRP); + } + + uint32_t start = 0, end; + do { + // Find the range of style sheets with the same scope. + Element* scope = sheets[start]->GetScopeElement(); + end = start + 1; + while (end < count && sheets[end]->GetScopeElement() == scope) { + end++; + } + + scope->SetIsScopedStyleRoot(); + + // Create a rule processor for the scope. + nsTArray<RefPtr<CSSStyleSheet>> sheetsForScope; + sheetsForScope.AppendElements(sheets.Elements() + start, end - start); + nsCSSRuleProcessor* oldRP = oldScopedRuleProcessorHash.Get(scope); + mScopedDocSheetRuleProcessors.AppendElement + (new nsCSSRuleProcessor(Move(sheetsForScope), aType, scope, oldRP)); + + start = end; + } while (start < count); + } + return NS_OK; + } + if (!mSheets[aType].IsEmpty()) { + switch (aType) { + case SheetType::Agent: + case SheetType::User: { + // levels containing non-scoped CSS style sheets whose rule processors + // we want to re-use + nsTArray<CSSStyleSheet*> sheets(mSheets[aType].Length()); + for (CSSStyleSheet* sheet : mSheets[aType]) { + sheets.AppendElement(sheet); + } + nsCSSRuleProcessor* rp = + RuleProcessorCache::GetRuleProcessor(sheets, PresContext()); + if (!rp) { + rp = new nsCSSRuleProcessor(mSheets[aType], aType, nullptr, + static_cast<nsCSSRuleProcessor*>( + oldRuleProcessor.get()), + true /* aIsShared */); + nsTArray<css::DocumentRule*> documentRules; + nsDocumentRuleResultCacheKey cacheKey; + rp->TakeDocumentRulesAndCacheKey(PresContext(), + documentRules, cacheKey); + RuleProcessorCache::PutRuleProcessor(sheets, + Move(documentRules), + cacheKey, rp); + } + mRuleProcessors[aType] = rp; + rp->AddStyleSetRef(); + break; + } + case SheetType::Doc: + case SheetType::Override: { + // levels containing non-scoped CSS stylesheets whose rule processors + // we don't want to re-use + mRuleProcessors[aType] = + new nsCSSRuleProcessor(mSheets[aType], aType, nullptr, + static_cast<nsCSSRuleProcessor*>( + oldRuleProcessor.get())); + } break; + + default: + MOZ_ASSERT_UNREACHABLE("non-CSS sheet types should be handled above"); + break; + } + } + + return NS_OK; +} + +nsresult +nsStyleSet::AppendStyleSheet(SheetType aType, CSSStyleSheet* aSheet) +{ + NS_PRECONDITION(aSheet, "null arg"); + NS_ASSERTION(aSheet->IsApplicable(), + "Inapplicable sheet being placed in style set"); + bool present = mSheets[aType].RemoveElement(aSheet); + mSheets[aType].AppendElement(aSheet); + + if (!present && IsCSSSheetType(aType)) { + aSheet->AddStyleSet(this); + } + + return DirtyRuleProcessors(aType); +} + +nsresult +nsStyleSet::PrependStyleSheet(SheetType aType, CSSStyleSheet* aSheet) +{ + NS_PRECONDITION(aSheet, "null arg"); + NS_ASSERTION(aSheet->IsApplicable(), + "Inapplicable sheet being placed in style set"); + bool present = mSheets[aType].RemoveElement(aSheet); + mSheets[aType].InsertElementAt(0, aSheet); + + if (!present && IsCSSSheetType(aType)) { + aSheet->AddStyleSet(this); + } + + return DirtyRuleProcessors(aType); +} + +nsresult +nsStyleSet::RemoveStyleSheet(SheetType aType, CSSStyleSheet* aSheet) +{ + NS_PRECONDITION(aSheet, "null arg"); + NS_ASSERTION(aSheet->IsComplete(), + "Incomplete sheet being removed from style set"); + if (mSheets[aType].RemoveElement(aSheet)) { + if (IsCSSSheetType(aType)) { + aSheet->DropStyleSet(this); + } + } + + return DirtyRuleProcessors(aType); +} + +nsresult +nsStyleSet::ReplaceSheets(SheetType aType, + const nsTArray<RefPtr<CSSStyleSheet>>& aNewSheets) +{ + bool cssSheetType = IsCSSSheetType(aType); + if (cssSheetType) { + for (CSSStyleSheet* sheet : mSheets[aType]) { + sheet->DropStyleSet(this); + } + } + + mSheets[aType].Clear(); + mSheets[aType].AppendElements(aNewSheets); + + if (cssSheetType) { + for (CSSStyleSheet* sheet : mSheets[aType]) { + sheet->AddStyleSet(this); + } + } + + return DirtyRuleProcessors(aType); +} + +nsresult +nsStyleSet::InsertStyleSheetBefore(SheetType aType, CSSStyleSheet* aNewSheet, + CSSStyleSheet* aReferenceSheet) +{ + NS_PRECONDITION(aNewSheet && aReferenceSheet, "null arg"); + NS_ASSERTION(aNewSheet->IsApplicable(), + "Inapplicable sheet being placed in style set"); + + bool present = mSheets[aType].RemoveElement(aNewSheet); + int32_t idx = mSheets[aType].IndexOf(aReferenceSheet); + if (idx < 0) + return NS_ERROR_INVALID_ARG; + + mSheets[aType].InsertElementAt(idx, aNewSheet); + + if (!present && IsCSSSheetType(aType)) { + aNewSheet->AddStyleSet(this); + } + + return DirtyRuleProcessors(aType); +} + +static inline uint32_t +DirtyBit(SheetType aType) +{ + return 1 << uint32_t(aType); +} + +nsresult +nsStyleSet::DirtyRuleProcessors(SheetType aType) +{ + if (!mBatching) + return GatherRuleProcessors(aType); + + mDirty |= DirtyBit(aType); + return NS_OK; +} + +bool +nsStyleSet::GetAuthorStyleDisabled() const +{ + return mAuthorStyleDisabled; +} + +nsresult +nsStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled) +{ + if (aStyleDisabled == !mAuthorStyleDisabled) { + mAuthorStyleDisabled = aStyleDisabled; + BeginUpdate(); + mDirty |= DirtyBit(SheetType::Doc) | + DirtyBit(SheetType::ScopedDoc) | + DirtyBit(SheetType::StyleAttr); + return EndUpdate(); + } + return NS_OK; +} + +// -------- Doc Sheets + +nsresult +nsStyleSet::AddDocStyleSheet(CSSStyleSheet* aSheet, nsIDocument* aDocument) +{ + NS_PRECONDITION(aSheet && aDocument, "null arg"); + NS_ASSERTION(aSheet->IsApplicable(), + "Inapplicable sheet being placed in style set"); + + SheetType type = aSheet->GetScopeElement() ? + SheetType::ScopedDoc : + SheetType::Doc; + nsTArray<RefPtr<CSSStyleSheet>>& sheets = mSheets[type]; + + bool present = sheets.RemoveElement(aSheet); + + size_t index = aDocument->FindDocStyleSheetInsertionPoint(sheets, aSheet); + sheets.InsertElementAt(index, aSheet); + + if (!present) { + aSheet->AddStyleSet(this); + } + + return DirtyRuleProcessors(type); +} + +void +nsStyleSet::AppendAllXBLStyleSheets(nsTArray<mozilla::CSSStyleSheet*>& aArray) const +{ + if (mBindingManager) { + // XXXheycam stylo: AppendAllSheets will need to be able to return either + // CSSStyleSheets or ServoStyleSheets, on request (and then here requesting + // CSSStyleSheets). + AutoTArray<StyleSheet*, 32> sheets; + mBindingManager->AppendAllSheets(sheets); + for (StyleSheet* handle : sheets) { + MOZ_ASSERT(handle->IsGecko(), "stylo: AppendAllSheets shouldn't give us " + "ServoStyleSheets yet"); + aArray.AppendElement(handle->AsGecko()); + } + } +} + +nsresult +nsStyleSet::RemoveDocStyleSheet(CSSStyleSheet* aSheet) +{ + bool isScoped = aSheet->GetScopeElement(); + return RemoveStyleSheet(isScoped ? SheetType::ScopedDoc : SheetType::Doc, + aSheet); +} + +// Batching +void +nsStyleSet::BeginUpdate() +{ + ++mBatching; +} + +nsresult +nsStyleSet::EndUpdate() +{ + NS_ASSERTION(mBatching > 0, "Unbalanced EndUpdate"); + if (--mBatching) { + // We're not completely done yet. + return NS_OK; + } + + for (SheetType type : MakeEnumeratedRange(SheetType::Count)) { + if (mDirty & DirtyBit(type)) { + nsresult rv = GatherRuleProcessors(type); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + mDirty = 0; + return NS_OK; +} + +template<class T> +static bool +EnumRulesMatching(nsIStyleRuleProcessor* aProcessor, void* aData) +{ + T* data = static_cast<T*>(aData); + aProcessor->RulesMatching(data); + return true; +} + +static inline bool +IsMoreSpecificThanAnimation(nsRuleNode *aRuleNode) +{ + return !aRuleNode->IsRoot() && + (aRuleNode->GetLevel() == SheetType::Transition || + aRuleNode->IsImportantRule()); +} + +static nsIStyleRule* +GetAnimationRule(nsRuleNode *aRuleNode) +{ + nsRuleNode *n = aRuleNode; + while (IsMoreSpecificThanAnimation(n)) { + n = n->GetParent(); + } + + if (n->IsRoot() || n->GetLevel() != SheetType::Animation) { + return nullptr; + } + + return n->GetRule(); +} + +static nsRuleNode* +ReplaceAnimationRule(nsRuleNode *aOldRuleNode, + nsIStyleRule *aOldAnimRule, + nsIStyleRule *aNewAnimRule) +{ + nsTArray<nsRuleNode*> moreSpecificNodes; + + nsRuleNode *n = aOldRuleNode; + while (IsMoreSpecificThanAnimation(n)) { + moreSpecificNodes.AppendElement(n); + n = n->GetParent(); + } + + if (aOldAnimRule) { + MOZ_ASSERT(n->GetRule() == aOldAnimRule, "wrong rule"); + MOZ_ASSERT(n->GetLevel() == SheetType::Animation, + "wrong level"); + n = n->GetParent(); + } + + MOZ_ASSERT(!IsMoreSpecificThanAnimation(n) && + (n->IsRoot() || n->GetLevel() != SheetType::Animation), + "wrong level"); + + if (aNewAnimRule) { + n = n->Transition(aNewAnimRule, SheetType::Animation, false); + n->SetIsAnimationRule(); + } + + for (uint32_t i = moreSpecificNodes.Length(); i-- != 0; ) { + nsRuleNode *oldNode = moreSpecificNodes[i]; + n = n->Transition(oldNode->GetRule(), oldNode->GetLevel(), + oldNode->IsImportantRule()); + } + + return n; +} + +/** + * |GetContext| implements sharing of style contexts (not just the data + * on the rule nodes) between siblings and cousins of the same + * generation. (It works for cousins of the same generation since + * |aParentContext| could itself be a shared context.) + */ +already_AddRefed<nsStyleContext> +nsStyleSet::GetContext(nsStyleContext* aParentContext, + nsRuleNode* aRuleNode, + // aVisitedRuleNode may be null; if it is null + // it means that we don't need to force creation + // of a StyleIfVisited. (But if we make one + // because aParentContext has one, then aRuleNode + // should be used.) + nsRuleNode* aVisitedRuleNode, + nsIAtom* aPseudoTag, + CSSPseudoElementType aPseudoType, + Element* aElementForAnimation, + uint32_t aFlags) +{ + NS_PRECONDITION((!aPseudoTag && + aPseudoType == + CSSPseudoElementType::NotPseudo) || + (aPseudoTag && + nsCSSPseudoElements::GetPseudoType( + aPseudoTag, CSSEnabledState::eIgnoreEnabledState) == + aPseudoType), + "Pseudo mismatch"); + + if (aVisitedRuleNode == aRuleNode) { + // No need to force creation of a visited style in this case. + aVisitedRuleNode = nullptr; + } + + // Ensure |aVisitedRuleNode != nullptr| corresponds to the need to + // create an if-visited style context, and that in that case, we have + // parentIfVisited set correctly. + nsStyleContext *parentIfVisited = + aParentContext ? aParentContext->GetStyleIfVisited() : nullptr; + if (parentIfVisited) { + if (!aVisitedRuleNode) { + aVisitedRuleNode = aRuleNode; + } + } else { + if (aVisitedRuleNode) { + parentIfVisited = aParentContext; + } + } + + if (aFlags & eIsLink) { + // If this node is a link, we want its visited's style context's + // parent to be the regular style context of its parent, because + // only the visitedness of the relevant link should influence style. + parentIfVisited = aParentContext; + } + + bool relevantLinkVisited = (aFlags & eIsLink) ? + (aFlags & eIsVisitedLink) : + (aParentContext && aParentContext->RelevantLinkVisited()); + + RefPtr<nsStyleContext> result; + if (aParentContext) + result = aParentContext->FindChildWithRules(aPseudoTag, aRuleNode, + aVisitedRuleNode, + relevantLinkVisited); + + if (!result) { + // |aVisitedRuleNode| may have a ref-count of zero since we are yet + // to create the style context that will hold an owning reference to it. + // As a result, we need to make sure it stays alive until that point + // in case something in the first call to NS_NewStyleContext triggers a + // GC sweep of rule nodes. + RefPtr<nsRuleNode> kungFuDeathGrip{aVisitedRuleNode}; + + result = NS_NewStyleContext(aParentContext, aPseudoTag, aPseudoType, + aRuleNode, + aFlags & eSkipParentDisplayBasedStyleFixup); + if (aVisitedRuleNode) { + RefPtr<nsStyleContext> resultIfVisited = + NS_NewStyleContext(parentIfVisited, aPseudoTag, aPseudoType, + aVisitedRuleNode, + aFlags & eSkipParentDisplayBasedStyleFixup); + resultIfVisited->SetIsStyleIfVisited(); + result->SetStyleIfVisited(resultIfVisited.forget()); + + if (relevantLinkVisited) { + result->AddStyleBit(NS_STYLE_RELEVANT_LINK_VISITED); + } + } + } + else { + NS_ASSERTION(result->GetPseudoType() == aPseudoType, "Unexpected type"); + NS_ASSERTION(result->GetPseudo() == aPseudoTag, "Unexpected pseudo"); + } + + if (aFlags & eDoAnimation) { + + nsIStyleRule *oldAnimRule = GetAnimationRule(aRuleNode); + nsIStyleRule *animRule = nullptr; + + // Ignore animations for print or print preview, and for elements + // that are not attached to the document tree. + if (PresContext()->IsDynamic() && + aElementForAnimation->IsInComposedDoc()) { + // Update CSS animations in case the animation-name has just changed. + PresContext()->AnimationManager()->UpdateAnimations(result, + aElementForAnimation); + PresContext()->EffectCompositor()->UpdateEffectProperties( + result, aElementForAnimation, result->GetPseudoType()); + + animRule = PresContext()->EffectCompositor()-> + GetAnimationRule(aElementForAnimation, + result->GetPseudoType(), + EffectCompositor::CascadeLevel::Animations, + result); + } + + MOZ_ASSERT(result->RuleNode() == aRuleNode, + "unexpected rule node"); + MOZ_ASSERT(!result->GetStyleIfVisited() == !aVisitedRuleNode, + "unexpected visited rule node"); + MOZ_ASSERT(!aVisitedRuleNode || + result->GetStyleIfVisited()->RuleNode() == aVisitedRuleNode, + "unexpected visited rule node"); + MOZ_ASSERT(!aVisitedRuleNode || + oldAnimRule == GetAnimationRule(aVisitedRuleNode), + "animation rule mismatch between rule nodes"); + if (oldAnimRule != animRule) { + nsRuleNode *ruleNode = + ReplaceAnimationRule(aRuleNode, oldAnimRule, animRule); + nsRuleNode *visitedRuleNode = aVisitedRuleNode + ? ReplaceAnimationRule(aVisitedRuleNode, oldAnimRule, animRule) + : nullptr; + MOZ_ASSERT(!visitedRuleNode || + GetAnimationRule(ruleNode) == + GetAnimationRule(visitedRuleNode), + "animation rule mismatch between rule nodes"); + result = GetContext(aParentContext, ruleNode, visitedRuleNode, + aPseudoTag, aPseudoType, nullptr, + aFlags & ~eDoAnimation); + } + } + + if (aElementForAnimation && + aElementForAnimation->IsHTMLElement(nsGkAtoms::body) && + aPseudoType == CSSPseudoElementType::NotPseudo && + PresContext()->CompatibilityMode() == eCompatibility_NavQuirks) { + nsIDocument* doc = aElementForAnimation->GetUncomposedDoc(); + if (doc && doc->GetBodyElement() == aElementForAnimation) { + // Update the prescontext's body color + PresContext()->SetBodyTextColor(result->StyleColor()->mColor); + } + } + + return result.forget(); +} + +void +nsStyleSet::AddImportantRules(nsRuleNode* aCurrLevelNode, + nsRuleNode* aLastPrevLevelNode, + nsRuleWalker* aRuleWalker) +{ + NS_ASSERTION(aCurrLevelNode && + aCurrLevelNode != aLastPrevLevelNode, "How did we get here?"); + + AutoTArray<nsIStyleRule*, 16> importantRules; + for (nsRuleNode *node = aCurrLevelNode; node != aLastPrevLevelNode; + node = node->GetParent()) { + // We guarantee that we never walk the root node here, so no need + // to null-check GetRule(). Furthermore, it must be a CSS rule. + NS_ASSERTION(RefPtr<css::Declaration>(do_QueryObject(node->GetRule())), + "Unexpected non-CSS rule"); + + nsIStyleRule* impRule = + static_cast<css::Declaration*>(node->GetRule())->GetImportantStyleData(); + if (impRule) + importantRules.AppendElement(impRule); + } + + NS_ASSERTION(importantRules.Length() != 0, + "Why did we think there were important rules?"); + + for (uint32_t i = importantRules.Length(); i-- != 0; ) { + aRuleWalker->Forward(importantRules[i]); + } +} + +#ifdef DEBUG +void +nsStyleSet::AssertNoImportantRules(nsRuleNode* aCurrLevelNode, + nsRuleNode* aLastPrevLevelNode) +{ + if (!aCurrLevelNode) + return; + + for (nsRuleNode *node = aCurrLevelNode; node != aLastPrevLevelNode; + node = node->GetParent()) { + RefPtr<css::Declaration> declaration(do_QueryObject(node->GetRule())); + NS_ASSERTION(declaration, "Unexpected non-CSS rule"); + + NS_ASSERTION(!declaration->GetImportantStyleData(), + "Unexpected important style source"); + } +} + +void +nsStyleSet::AssertNoCSSRules(nsRuleNode* aCurrLevelNode, + nsRuleNode* aLastPrevLevelNode) +{ + if (!aCurrLevelNode) + return; + + for (nsRuleNode *node = aCurrLevelNode; node != aLastPrevLevelNode; + node = node->GetParent()) { + nsIStyleRule *rule = node->GetRule(); + RefPtr<css::Declaration> declaration(do_QueryObject(rule)); + if (declaration) { + RefPtr<css::StyleRule> cssRule = + do_QueryObject(declaration->GetOwningRule()); + NS_ASSERTION(!cssRule || !cssRule->Selector(), + "Unexpected CSS rule"); + } + } +} +#endif + +// Enumerate the rules in a way that cares about the order of the rules. +void +nsStyleSet::FileRules(nsIStyleRuleProcessor::EnumFunc aCollectorFunc, + RuleProcessorData* aData, Element* aElement, + nsRuleWalker* aRuleWalker) +{ + PROFILER_LABEL("nsStyleSet", "FileRules", + js::ProfileEntry::Category::CSS); + + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + // Cascading order: + // [least important] + // - UA normal rules = Agent normal + // - User normal rules = User normal + // - Presentation hints = PresHint normal + // - SVG Animation (highest pres hint) = SVGAttrAnimation normal + // - Author normal rules = Document normal + // - Override normal rules = Override normal + // - animation rules = Animation normal + // - Author !important rules = Document !important + // - Override !important rules = Override !important + // - User !important rules = User !important + // - UA !important rules = Agent !important + // - transition rules = Transition normal + // [most important] + + // Save off the last rule before we start walking our agent sheets; + // this will be either the root or one of the restriction rules. + nsRuleNode* lastRestrictionRN = aRuleWalker->CurrentNode(); + + aRuleWalker->SetLevel(SheetType::Agent, false, true); + if (mRuleProcessors[SheetType::Agent]) + (*aCollectorFunc)(mRuleProcessors[SheetType::Agent], aData); + nsRuleNode* lastAgentRN = aRuleWalker->CurrentNode(); + bool haveImportantUARules = !aRuleWalker->GetCheckForImportantRules(); + + aRuleWalker->SetLevel(SheetType::User, false, true); + bool skipUserStyles = + aElement && aElement->IsInNativeAnonymousSubtree(); + if (!skipUserStyles && mRuleProcessors[SheetType::User]) // NOTE: different + (*aCollectorFunc)(mRuleProcessors[SheetType::User], aData); + nsRuleNode* lastUserRN = aRuleWalker->CurrentNode(); + bool haveImportantUserRules = !aRuleWalker->GetCheckForImportantRules(); + + aRuleWalker->SetLevel(SheetType::PresHint, false, false); + if (mRuleProcessors[SheetType::PresHint]) + (*aCollectorFunc)(mRuleProcessors[SheetType::PresHint], aData); + + aRuleWalker->SetLevel(SheetType::SVGAttrAnimation, false, false); + if (mRuleProcessors[SheetType::SVGAttrAnimation]) + (*aCollectorFunc)(mRuleProcessors[SheetType::SVGAttrAnimation], aData); + nsRuleNode* lastSVGAttrAnimationRN = aRuleWalker->CurrentNode(); + + aRuleWalker->SetLevel(SheetType::Doc, false, true); + bool cutOffInheritance = false; + if (mBindingManager && aElement) { + // We can supply additional document-level sheets that should be walked. + mBindingManager->WalkRules(aCollectorFunc, + static_cast<ElementDependentRuleProcessorData*>(aData), + &cutOffInheritance); + } + if (!skipUserStyles && !cutOffInheritance && // NOTE: different + mRuleProcessors[SheetType::Doc]) + (*aCollectorFunc)(mRuleProcessors[SheetType::Doc], aData); + nsRuleNode* lastDocRN = aRuleWalker->CurrentNode(); + bool haveImportantDocRules = !aRuleWalker->GetCheckForImportantRules(); + nsTArray<nsRuleNode*> lastScopedRNs; + nsTArray<bool> haveImportantScopedRules; + bool haveAnyImportantScopedRules = false; + if (!skipUserStyles && !cutOffInheritance && + aElement && aElement->IsElementInStyleScope()) { + lastScopedRNs.SetLength(mScopedDocSheetRuleProcessors.Length()); + haveImportantScopedRules.SetLength(mScopedDocSheetRuleProcessors.Length()); + for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) { + aRuleWalker->SetLevel(SheetType::ScopedDoc, false, true); + nsCSSRuleProcessor* processor = + static_cast<nsCSSRuleProcessor*>(mScopedDocSheetRuleProcessors[i].get()); + aData->mScope = processor->GetScopeElement(); + (*aCollectorFunc)(mScopedDocSheetRuleProcessors[i], aData); + lastScopedRNs[i] = aRuleWalker->CurrentNode(); + haveImportantScopedRules[i] = !aRuleWalker->GetCheckForImportantRules(); + haveAnyImportantScopedRules = haveAnyImportantScopedRules || haveImportantScopedRules[i]; + } + aData->mScope = nullptr; + } + nsRuleNode* lastScopedRN = aRuleWalker->CurrentNode(); + aRuleWalker->SetLevel(SheetType::StyleAttr, false, true); + if (mRuleProcessors[SheetType::StyleAttr]) + (*aCollectorFunc)(mRuleProcessors[SheetType::StyleAttr], aData); + nsRuleNode* lastStyleAttrRN = aRuleWalker->CurrentNode(); + bool haveImportantStyleAttrRules = !aRuleWalker->GetCheckForImportantRules(); + + aRuleWalker->SetLevel(SheetType::Override, false, true); + if (mRuleProcessors[SheetType::Override]) + (*aCollectorFunc)(mRuleProcessors[SheetType::Override], aData); + nsRuleNode* lastOvrRN = aRuleWalker->CurrentNode(); + bool haveImportantOverrideRules = !aRuleWalker->GetCheckForImportantRules(); + + // This needs to match IsMoreSpecificThanAnimation() above. + aRuleWalker->SetLevel(SheetType::Animation, false, false); + (*aCollectorFunc)(mRuleProcessors[SheetType::Animation], aData); + + if (haveAnyImportantScopedRules) { + for (uint32_t i = lastScopedRNs.Length(); i-- != 0; ) { + aRuleWalker->SetLevel(SheetType::ScopedDoc, true, false); + nsRuleNode* startRN = lastScopedRNs[i]; + nsRuleNode* endRN = i == 0 ? lastDocRN : lastScopedRNs[i - 1]; + if (haveImportantScopedRules[i]) { + AddImportantRules(startRN, endRN, aRuleWalker); // scoped + } +#ifdef DEBUG + else { + AssertNoImportantRules(startRN, endRN); + } +#endif + } + } +#ifdef DEBUG + else { + AssertNoImportantRules(lastScopedRN, lastDocRN); + } +#endif + + if (haveImportantDocRules) { + aRuleWalker->SetLevel(SheetType::Doc, true, false); + AddImportantRules(lastDocRN, lastSVGAttrAnimationRN, aRuleWalker); // doc + } +#ifdef DEBUG + else { + AssertNoImportantRules(lastDocRN, lastSVGAttrAnimationRN); + } +#endif + + if (haveImportantStyleAttrRules) { + aRuleWalker->SetLevel(SheetType::StyleAttr, true, false); + AddImportantRules(lastStyleAttrRN, lastScopedRN, aRuleWalker); // style attr + } +#ifdef DEBUG + else { + AssertNoImportantRules(lastStyleAttrRN, lastScopedRN); + } +#endif + + if (haveImportantOverrideRules) { + aRuleWalker->SetLevel(SheetType::Override, true, false); + AddImportantRules(lastOvrRN, lastStyleAttrRN, aRuleWalker); // override + } +#ifdef DEBUG + else { + AssertNoImportantRules(lastOvrRN, lastStyleAttrRN); + } +#endif + +#ifdef DEBUG + AssertNoCSSRules(lastSVGAttrAnimationRN, lastUserRN); +#endif + + if (haveImportantUserRules) { + aRuleWalker->SetLevel(SheetType::User, true, false); + AddImportantRules(lastUserRN, lastAgentRN, aRuleWalker); //user + } +#ifdef DEBUG + else { + AssertNoImportantRules(lastUserRN, lastAgentRN); + } +#endif + + if (haveImportantUARules) { + aRuleWalker->SetLevel(SheetType::Agent, true, false); + AddImportantRules(lastAgentRN, lastRestrictionRN, aRuleWalker); //agent + } +#ifdef DEBUG + else { + AssertNoImportantRules(lastAgentRN, lastRestrictionRN); + } +#endif + +#ifdef DEBUG + AssertNoCSSRules(lastRestrictionRN, mRuleTree); +#endif + +#ifdef DEBUG + nsRuleNode *lastImportantRN = aRuleWalker->CurrentNode(); +#endif + aRuleWalker->SetLevel(SheetType::Transition, false, false); + (*aCollectorFunc)(mRuleProcessors[SheetType::Transition], aData); +#ifdef DEBUG + AssertNoCSSRules(aRuleWalker->CurrentNode(), lastImportantRN); +#endif + +} + +// Enumerate all the rules in a way that doesn't care about the order +// of the rules and doesn't walk !important-rules. +void +nsStyleSet::WalkRuleProcessors(nsIStyleRuleProcessor::EnumFunc aFunc, + ElementDependentRuleProcessorData* aData, + bool aWalkAllXBLStylesheets) +{ + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + if (mRuleProcessors[SheetType::Agent]) + (*aFunc)(mRuleProcessors[SheetType::Agent], aData); + + bool skipUserStyles = aData->mElement->IsInNativeAnonymousSubtree(); + if (!skipUserStyles && mRuleProcessors[SheetType::User]) // NOTE: different + (*aFunc)(mRuleProcessors[SheetType::User], aData); + + if (mRuleProcessors[SheetType::PresHint]) + (*aFunc)(mRuleProcessors[SheetType::PresHint], aData); + + if (mRuleProcessors[SheetType::SVGAttrAnimation]) + (*aFunc)(mRuleProcessors[SheetType::SVGAttrAnimation], aData); + + bool cutOffInheritance = false; + if (mBindingManager) { + // We can supply additional document-level sheets that should be walked. + if (aWalkAllXBLStylesheets) { + mBindingManager->WalkAllRules(aFunc, aData); + } else { + mBindingManager->WalkRules(aFunc, aData, &cutOffInheritance); + } + } + if (!skipUserStyles && !cutOffInheritance) { + if (mRuleProcessors[SheetType::Doc]) // NOTE: different + (*aFunc)(mRuleProcessors[SheetType::Doc], aData); + if (aData->mElement->IsElementInStyleScope()) { + for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) + (*aFunc)(mScopedDocSheetRuleProcessors[i], aData); + } + } + if (mRuleProcessors[SheetType::StyleAttr]) + (*aFunc)(mRuleProcessors[SheetType::StyleAttr], aData); + if (mRuleProcessors[SheetType::Override]) + (*aFunc)(mRuleProcessors[SheetType::Override], aData); + (*aFunc)(mRuleProcessors[SheetType::Animation], aData); + (*aFunc)(mRuleProcessors[SheetType::Transition], aData); +} + +static void +InitStyleScopes(TreeMatchContext& aTreeContext, Element* aElement) +{ + if (aElement->IsElementInStyleScope()) { + aTreeContext.InitStyleScopes(aElement->GetParentElementCrossingShadowRoot()); + } +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleFor(Element* aElement, + nsStyleContext* aParentContext) +{ + TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited, + aElement->OwnerDoc()); + InitStyleScopes(treeContext, aElement); + return ResolveStyleFor(aElement, aParentContext, treeContext); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleFor(Element* aElement, + nsStyleContext* aParentContext, + TreeMatchContext& aTreeMatchContext) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + NS_ASSERTION(aElement, "aElement must not be null"); + + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + aTreeMatchContext.ResetForUnvisitedMatching(); + ElementRuleProcessorData data(PresContext(), aElement, &ruleWalker, + aTreeMatchContext); + WalkDisableTextZoomRule(aElement, &ruleWalker); + FileRules(EnumRulesMatching<ElementRuleProcessorData>, &data, aElement, + &ruleWalker); + + nsRuleNode *ruleNode = ruleWalker.CurrentNode(); + nsRuleNode *visitedRuleNode = nullptr; + + if (aTreeMatchContext.HaveRelevantLink()) { + aTreeMatchContext.ResetForVisitedMatching(); + ruleWalker.Reset(); + FileRules(EnumRulesMatching<ElementRuleProcessorData>, &data, aElement, + &ruleWalker); + visitedRuleNode = ruleWalker.CurrentNode(); + } + + uint32_t flags = eDoAnimation; + if (nsCSSRuleProcessor::IsLink(aElement)) { + flags |= eIsLink; + } + if (nsCSSRuleProcessor::GetContentState(aElement, aTreeMatchContext). + HasState(NS_EVENT_STATE_VISITED)) { + flags |= eIsVisitedLink; + } + if (aTreeMatchContext.mSkippingParentDisplayBasedStyleFixup) { + flags |= eSkipParentDisplayBasedStyleFixup; + } + + return GetContext(aParentContext, ruleNode, visitedRuleNode, + nullptr, CSSPseudoElementType::NotPseudo, + aElement, flags); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext, + const nsTArray< nsCOMPtr<nsIStyleRule> > &aRules) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + // FIXME: Perhaps this should be passed in, but it probably doesn't + // matter. + ruleWalker.SetLevel(SheetType::Doc, false, false); + for (uint32_t i = 0; i < aRules.Length(); i++) { + ruleWalker.ForwardOnPossiblyCSSRule(aRules.ElementAt(i)); + } + + return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr, + nullptr, CSSPseudoElementType::NotPseudo, + nullptr, eNoFlags); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleByAddingRules(nsStyleContext* aBaseContext, + const nsCOMArray<nsIStyleRule> &aRules) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + ruleWalker.SetCurrentNode(aBaseContext->RuleNode()); + // This needs to be the transition sheet because that is the highest + // level of the cascade, and thus the only thing that makes sense if + // we are ever going to call ResolveStyleWithReplacement on the + // resulting context. It's also the right thing for the one case (the + // transition manager's cover rule) where we put the result of this + // function in the style context tree. + ruleWalker.SetLevel(SheetType::Transition, false, false); + for (int32_t i = 0; i < aRules.Count(); i++) { + ruleWalker.ForwardOnPossiblyCSSRule(aRules.ObjectAt(i)); + } + + nsRuleNode *ruleNode = ruleWalker.CurrentNode(); + nsRuleNode *visitedRuleNode = nullptr; + + if (aBaseContext->GetStyleIfVisited()) { + ruleWalker.SetCurrentNode(aBaseContext->GetStyleIfVisited()->RuleNode()); + for (int32_t i = 0; i < aRules.Count(); i++) { + ruleWalker.ForwardOnPossiblyCSSRule(aRules.ObjectAt(i)); + } + visitedRuleNode = ruleWalker.CurrentNode(); + } + + uint32_t flags = eNoFlags; + if (aBaseContext->IsLinkContext()) { + flags |= eIsLink; + + // GetContext handles propagating RelevantLinkVisited state from the + // parent in non-link cases; all we need to pass in is if this link + // is visited. + if (aBaseContext->RelevantLinkVisited()) { + flags |= eIsVisitedLink; + } + } + return GetContext(aBaseContext->GetParent(), ruleNode, visitedRuleNode, + aBaseContext->GetPseudo(), + aBaseContext->GetPseudoType(), + nullptr, flags); +} + +struct RuleNodeInfo { + nsIStyleRule* mRule; + SheetType mLevel; + bool mIsImportant; + bool mIsAnimationRule; +}; + +struct CascadeLevel { + SheetType mLevel; + bool mIsImportant; + bool mCheckForImportantRules; + nsRestyleHint mLevelReplacementHint; +}; + +static const CascadeLevel gCascadeLevels[] = { + { SheetType::Agent, false, false, nsRestyleHint(0) }, + { SheetType::User, false, false, nsRestyleHint(0) }, + { SheetType::PresHint, false, false, nsRestyleHint(0) }, + { SheetType::SVGAttrAnimation, false, false, eRestyle_SVGAttrAnimations }, + { SheetType::Doc, false, false, nsRestyleHint(0) }, + { SheetType::ScopedDoc, false, false, nsRestyleHint(0) }, + { SheetType::StyleAttr, false, true, eRestyle_StyleAttribute | + eRestyle_StyleAttribute_Animations }, + { SheetType::Override, false, false, nsRestyleHint(0) }, + { SheetType::Animation, false, false, eRestyle_CSSAnimations }, + { SheetType::ScopedDoc, true, false, nsRestyleHint(0) }, + { SheetType::Doc, true, false, nsRestyleHint(0) }, + { SheetType::StyleAttr, true, false, eRestyle_StyleAttribute | + eRestyle_StyleAttribute_Animations }, + { SheetType::Override, true, false, nsRestyleHint(0) }, + { SheetType::User, true, false, nsRestyleHint(0) }, + { SheetType::Agent, true, false, nsRestyleHint(0) }, + { SheetType::Transition, false, false, eRestyle_CSSTransitions }, +}; + +nsRuleNode* +nsStyleSet::RuleNodeWithReplacement(Element* aElement, + Element* aPseudoElement, + nsRuleNode* aOldRuleNode, + CSSPseudoElementType aPseudoType, + nsRestyleHint aReplacements) +{ + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + MOZ_ASSERT(!aPseudoElement == + (aPseudoType >= CSSPseudoElementType::Count || + !(nsCSSPseudoElements::PseudoElementSupportsStyleAttribute(aPseudoType) || + nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudoType))), + "should have aPseudoElement only for certain pseudo elements"); + + MOZ_ASSERT(!(aReplacements & ~(eRestyle_CSSTransitions | + eRestyle_CSSAnimations | + eRestyle_SVGAttrAnimations | + eRestyle_StyleAttribute | + eRestyle_StyleAttribute_Animations | + eRestyle_Force | + eRestyle_ForceDescendants)), + "unexpected replacement bits"); + + // FIXME (perf): This should probably not rebuild the whole path, but + // only the path from the last change in the rule tree, like + // ReplaceAnimationRule in nsStyleSet.cpp does. (That could then + // perhaps share this code, too?) + // But if we do that, we'll need to pass whether we are rebuilding the + // rule tree from ElementRestyler::RestyleSelf to avoid taking that + // path when we're rebuilding the rule tree. + + // This array can be hot and often grows to ~20 elements, so inline storage + // is best. + AutoTArray<RuleNodeInfo, 30> rules; + for (nsRuleNode* ruleNode = aOldRuleNode; !ruleNode->IsRoot(); + ruleNode = ruleNode->GetParent()) { + RuleNodeInfo* curRule = rules.AppendElement(); + curRule->mRule = ruleNode->GetRule(); + curRule->mLevel = ruleNode->GetLevel(); + curRule->mIsImportant = ruleNode->IsImportantRule(); + curRule->mIsAnimationRule = ruleNode->IsAnimationRule(); + } + + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + auto rulesIndex = rules.Length(); + + // We need to transfer this information between the non-!important and + // !important phases for the style attribute level. + nsRuleNode* lastScopedRN = nullptr; + nsRuleNode* lastStyleAttrRN = nullptr; + bool haveImportantStyleAttrRules = false; + + for (const CascadeLevel *level = gCascadeLevels, + *levelEnd = ArrayEnd(gCascadeLevels); + level != levelEnd; ++level) { + + bool doReplace = level->mLevelReplacementHint & aReplacements; + + ruleWalker.SetLevel(level->mLevel, level->mIsImportant, + level->mCheckForImportantRules && doReplace); + + if (doReplace) { + switch (level->mLevel) { + case SheetType::Animation: { + if (aPseudoType == CSSPseudoElementType::NotPseudo || + aPseudoType == CSSPseudoElementType::before || + aPseudoType == CSSPseudoElementType::after) { + nsIStyleRule* rule = PresContext()->EffectCompositor()-> + GetAnimationRule(aElement, aPseudoType, + EffectCompositor::CascadeLevel::Animations, + nullptr); + if (rule) { + ruleWalker.ForwardOnPossiblyCSSRule(rule); + ruleWalker.CurrentNode()->SetIsAnimationRule(); + } + } + break; + } + case SheetType::Transition: { + if (aPseudoType == CSSPseudoElementType::NotPseudo || + aPseudoType == CSSPseudoElementType::before || + aPseudoType == CSSPseudoElementType::after) { + nsIStyleRule* rule = PresContext()->EffectCompositor()-> + GetAnimationRule(aElement, aPseudoType, + EffectCompositor::CascadeLevel::Transitions, + nullptr); + if (rule) { + ruleWalker.ForwardOnPossiblyCSSRule(rule); + ruleWalker.CurrentNode()->SetIsAnimationRule(); + } + } + break; + } + case SheetType::SVGAttrAnimation: { + SVGAttrAnimationRuleProcessor* ruleProcessor = + static_cast<SVGAttrAnimationRuleProcessor*>( + mRuleProcessors[SheetType::SVGAttrAnimation].get()); + if (ruleProcessor && + aPseudoType == CSSPseudoElementType::NotPseudo) { + ruleProcessor->ElementRulesMatching(aElement, &ruleWalker); + } + break; + } + case SheetType::StyleAttr: { + if (!level->mIsImportant) { + // First time through, we handle the non-!important rule. + nsHTMLCSSStyleSheet* ruleProcessor = + static_cast<nsHTMLCSSStyleSheet*>( + mRuleProcessors[SheetType::StyleAttr].get()); + if (ruleProcessor) { + lastScopedRN = ruleWalker.CurrentNode(); + if (aPseudoType == + CSSPseudoElementType::NotPseudo) { + ruleProcessor->ElementRulesMatching(PresContext(), + aElement, + &ruleWalker); + } else if (aPseudoType < + CSSPseudoElementType::Count && + nsCSSPseudoElements:: + PseudoElementSupportsStyleAttribute(aPseudoType)) { + ruleProcessor->PseudoElementRulesMatching(aPseudoElement, + aPseudoType, + &ruleWalker); + } + lastStyleAttrRN = ruleWalker.CurrentNode(); + haveImportantStyleAttrRules = + !ruleWalker.GetCheckForImportantRules(); + } + } else { + // Second time through, we handle the !important rule(s). + if (haveImportantStyleAttrRules) { + AddImportantRules(lastStyleAttrRN, lastScopedRN, &ruleWalker); + } + } + break; + } + default: + MOZ_ASSERT(false, "unexpected result from gCascadeLevels lookup"); + break; + } + } + + while (rulesIndex != 0) { + --rulesIndex; + const RuleNodeInfo& ruleInfo = rules[rulesIndex]; + + if (ruleInfo.mLevel != level->mLevel || + ruleInfo.mIsImportant != level->mIsImportant) { + ++rulesIndex; + break; + } + + if (!doReplace) { + ruleWalker.ForwardOnPossiblyCSSRule(ruleInfo.mRule); + if (ruleInfo.mIsAnimationRule) { + ruleWalker.CurrentNode()->SetIsAnimationRule(); + } + } + } + } + + NS_ASSERTION(rulesIndex == 0, + "rules are in incorrect cascading order, " + "which means we replaced them incorrectly"); + + return ruleWalker.CurrentNode(); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleWithReplacement(Element* aElement, + Element* aPseudoElement, + nsStyleContext* aNewParentContext, + nsStyleContext* aOldStyleContext, + nsRestyleHint aReplacements, + uint32_t aFlags) +{ + nsRuleNode* ruleNode = + RuleNodeWithReplacement(aElement, aPseudoElement, + aOldStyleContext->RuleNode(), + aOldStyleContext->GetPseudoType(), aReplacements); + + nsRuleNode* visitedRuleNode = nullptr; + nsStyleContext* oldStyleIfVisited = aOldStyleContext->GetStyleIfVisited(); + if (oldStyleIfVisited) { + if (oldStyleIfVisited->RuleNode() == aOldStyleContext->RuleNode()) { + visitedRuleNode = ruleNode; + } else { + visitedRuleNode = + RuleNodeWithReplacement(aElement, aPseudoElement, + oldStyleIfVisited->RuleNode(), + oldStyleIfVisited->GetPseudoType(), + aReplacements); + } + } + + uint32_t flags = eNoFlags; + if (aOldStyleContext->IsLinkContext()) { + flags |= eIsLink; + + // GetContext handles propagating RelevantLinkVisited state from the + // parent in non-link cases; all we need to pass in is if this link + // is visited. + if (aOldStyleContext->RelevantLinkVisited()) { + flags |= eIsVisitedLink; + } + } + + CSSPseudoElementType pseudoType = aOldStyleContext->GetPseudoType(); + Element* elementForAnimation = nullptr; + if (!(aFlags & eSkipStartingAnimations) && + (pseudoType == CSSPseudoElementType::NotPseudo || + pseudoType == CSSPseudoElementType::before || + pseudoType == CSSPseudoElementType::after)) { + // We want to compute a correct elementForAnimation to pass in + // because at this point the parameter is more than just the element + // for animation; it's also used for the SetBodyTextColor call when + // it's the body element. + // However, we only want to set the flag to call CheckAnimationRule + // if we're dealing with a replacement (such as style attribute + // replacement) that could lead to the animation property changing, + // and we explicitly do NOT want to call CheckAnimationRule when + // we're trying to do an animation-only update. + if (aReplacements & ~(eRestyle_CSSTransitions | eRestyle_CSSAnimations)) { + flags |= eDoAnimation; + } + elementForAnimation = aElement; +#ifdef DEBUG + { + nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(elementForAnimation); + NS_ASSERTION(pseudoType == CSSPseudoElementType::NotPseudo || + !styleFrame || + styleFrame->StyleContext()->GetPseudoType() == + CSSPseudoElementType::NotPseudo, + "aElement should be the element and not the pseudo-element"); + } +#endif + } + + if (aElement && aElement->IsRootOfAnonymousSubtree()) { + // For anonymous subtree roots, don't tweak "display" value based on whether + // or not the parent is styled as a flex/grid container. (If the parent + // has anonymous-subtree kids, then we know it's not actually going to get + // a flex/grid container frame, anyway.) + flags |= eSkipParentDisplayBasedStyleFixup; + } + + return GetContext(aNewParentContext, ruleNode, visitedRuleNode, + aOldStyleContext->GetPseudo(), pseudoType, + elementForAnimation, flags); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleWithoutAnimation(dom::Element* aTarget, + nsStyleContext* aStyleContext, + nsRestyleHint aWhichToRemove) +{ +#ifdef DEBUG + CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType(); +#endif + MOZ_ASSERT(pseudoType == CSSPseudoElementType::NotPseudo || + pseudoType == CSSPseudoElementType::before || + pseudoType == CSSPseudoElementType::after, + "unexpected type for animations"); + MOZ_ASSERT(PresContext()->RestyleManager()->IsGecko(), + "stylo: the style set and restyle manager must have the same " + "StyleBackendType"); + RestyleManager* restyleManager = PresContext()->RestyleManager()->AsGecko(); + + bool oldSkipAnimationRules = restyleManager->SkipAnimationRules(); + restyleManager->SetSkipAnimationRules(true); + + RefPtr<nsStyleContext> result = + ResolveStyleWithReplacement(aTarget, nullptr, aStyleContext->GetParent(), + aStyleContext, aWhichToRemove, + eSkipStartingAnimations); + + restyleManager->SetSkipAnimationRules(oldSkipAnimationRules); + + return result.forget(); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleForText(nsIContent* aTextNode, + nsStyleContext* aParentContext) +{ + MOZ_ASSERT(aTextNode && aTextNode->IsNodeOfType(nsINode::eTEXT)); + return GetContext(aParentContext, mRuleTree, nullptr, + nsCSSAnonBoxes::mozText, + CSSPseudoElementType::AnonBox, nullptr, eNoFlags); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext) +{ + return GetContext(aParentContext, mRuleTree, nullptr, + nsCSSAnonBoxes::mozOtherNonElement, + CSSPseudoElementType::AnonBox, nullptr, eNoFlags); +} + +void +nsStyleSet::WalkRestrictionRule(CSSPseudoElementType aPseudoType, + nsRuleWalker* aRuleWalker) +{ + // This needs to match GetPseudoRestriction in nsRuleNode.cpp. + aRuleWalker->SetLevel(SheetType::Agent, false, false); + if (aPseudoType == CSSPseudoElementType::firstLetter) + aRuleWalker->Forward(mFirstLetterRule); + else if (aPseudoType == CSSPseudoElementType::firstLine) + aRuleWalker->Forward(mFirstLineRule); + else if (aPseudoType == CSSPseudoElementType::placeholder) + aRuleWalker->Forward(mPlaceholderRule); +} + +void +nsStyleSet::WalkDisableTextZoomRule(Element* aElement, nsRuleWalker* aRuleWalker) +{ + aRuleWalker->SetLevel(SheetType::Agent, false, false); + if (aElement->IsSVGElement(nsGkAtoms::text)) + aRuleWalker->Forward(mDisableTextZoomStyleRule); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolvePseudoElementStyle(Element* aParentElement, + CSSPseudoElementType aType, + nsStyleContext* aParentContext, + Element* aPseudoElement) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + + NS_ASSERTION(aType < CSSPseudoElementType::Count, + "must have pseudo element type"); + NS_ASSERTION(aParentElement, "Must have parent element"); + + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited, + aParentElement->OwnerDoc()); + InitStyleScopes(treeContext, aParentElement); + PseudoElementRuleProcessorData data(PresContext(), aParentElement, + &ruleWalker, aType, treeContext, + aPseudoElement); + WalkRestrictionRule(aType, &ruleWalker); + FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data, + aParentElement, &ruleWalker); + + nsRuleNode *ruleNode = ruleWalker.CurrentNode(); + nsRuleNode *visitedRuleNode = nullptr; + + if (treeContext.HaveRelevantLink()) { + treeContext.ResetForVisitedMatching(); + ruleWalker.Reset(); + WalkRestrictionRule(aType, &ruleWalker); + FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data, + aParentElement, &ruleWalker); + visitedRuleNode = ruleWalker.CurrentNode(); + } + + // For pseudos, |data.IsLink()| being true means that + // our parent node is a link. + uint32_t flags = eNoFlags; + if (aType == CSSPseudoElementType::before || + aType == CSSPseudoElementType::after) { + flags |= eDoAnimation; + } else { + // Flex and grid containers don't expect to have any pseudo-element children + // aside from ::before and ::after. So if we have such a child, we're not + // actually in a flex/grid container, and we should skip flex/grid item + // style fixup. + flags |= eSkipParentDisplayBasedStyleFixup; + } + + return GetContext(aParentContext, ruleNode, visitedRuleNode, + nsCSSPseudoElements::GetPseudoAtom(aType), aType, + aParentElement, flags); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ProbePseudoElementStyle(Element* aParentElement, + CSSPseudoElementType aType, + nsStyleContext* aParentContext) +{ + TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited, + aParentElement->OwnerDoc()); + InitStyleScopes(treeContext, aParentElement); + return ProbePseudoElementStyle(aParentElement, aType, aParentContext, + treeContext); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ProbePseudoElementStyle(Element* aParentElement, + CSSPseudoElementType aType, + nsStyleContext* aParentContext, + TreeMatchContext& aTreeMatchContext, + Element* aPseudoElement) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + + NS_ASSERTION(aType < CSSPseudoElementType::Count, + "must have pseudo element type"); + NS_ASSERTION(aParentElement, "aParentElement must not be null"); + + nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType); + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + aTreeMatchContext.ResetForUnvisitedMatching(); + PseudoElementRuleProcessorData data(PresContext(), aParentElement, + &ruleWalker, aType, aTreeMatchContext, + aPseudoElement); + WalkRestrictionRule(aType, &ruleWalker); + // not the root if there was a restriction rule + nsRuleNode *adjustedRoot = ruleWalker.CurrentNode(); + FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data, + aParentElement, &ruleWalker); + + nsRuleNode *ruleNode = ruleWalker.CurrentNode(); + if (ruleNode == adjustedRoot) { + return nullptr; + } + + nsRuleNode *visitedRuleNode = nullptr; + + if (aTreeMatchContext.HaveRelevantLink()) { + aTreeMatchContext.ResetForVisitedMatching(); + ruleWalker.Reset(); + WalkRestrictionRule(aType, &ruleWalker); + FileRules(EnumRulesMatching<PseudoElementRuleProcessorData>, &data, + aParentElement, &ruleWalker); + visitedRuleNode = ruleWalker.CurrentNode(); + } + + // For pseudos, |data.IsLink()| being true means that + // our parent node is a link. + uint32_t flags = eNoFlags; + if (aType == CSSPseudoElementType::before || + aType == CSSPseudoElementType::after) { + flags |= eDoAnimation; + } else { + // Flex and grid containers don't expect to have any pseudo-element children + // aside from ::before and ::after. So if we have such a child, we're not + // actually in a flex/grid container, and we should skip flex/grid item + // style fixup. + flags |= eSkipParentDisplayBasedStyleFixup; + } + + RefPtr<nsStyleContext> result = + GetContext(aParentContext, ruleNode, visitedRuleNode, + pseudoTag, aType, + aParentElement, flags); + + // For :before and :after pseudo-elements, having display: none or no + // 'content' property is equivalent to not having the pseudo-element + // at all. + if (result && + (pseudoTag == nsCSSPseudoElements::before || + pseudoTag == nsCSSPseudoElements::after)) { + const nsStyleDisplay *display = result->StyleDisplay(); + const nsStyleContent *content = result->StyleContent(); + // XXXldb What is contentCount for |content: ""|? + if (display->mDisplay == StyleDisplay::None || + content->ContentCount() == 0) { + result = nullptr; + } + } + + return result.forget(); +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag, + nsStyleContext* aParentContext, + uint32_t aFlags) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + +#ifdef DEBUG + bool isAnonBox = nsCSSAnonBoxes::IsAnonBox(aPseudoTag) +#ifdef MOZ_XUL + && !nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag) +#endif + ; + NS_PRECONDITION(isAnonBox, "Unexpected pseudo"); +#endif + + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + AnonBoxRuleProcessorData data(PresContext(), aPseudoTag, &ruleWalker); + FileRules(EnumRulesMatching<AnonBoxRuleProcessorData>, &data, nullptr, + &ruleWalker); + + if (aPseudoTag == nsCSSAnonBoxes::pageContent) { + // Add any @page rules that are specified. + nsTArray<nsCSSPageRule*> rules; + nsTArray<css::ImportantStyleData*> importantRules; + AppendPageRules(rules); + for (uint32_t i = 0, i_end = rules.Length(); i != i_end; ++i) { + css::Declaration* declaration = rules[i]->Declaration(); + declaration->SetImmutable(); + ruleWalker.Forward(declaration); + css::ImportantStyleData* importantRule = + declaration->GetImportantStyleData(); + if (importantRule) { + importantRules.AppendElement(importantRule); + } + } + for (uint32_t i = 0, i_end = importantRules.Length(); i != i_end; ++i) { + ruleWalker.Forward(importantRules[i]); + } + } + + return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr, + aPseudoTag, CSSPseudoElementType::AnonBox, + nullptr, aFlags); +} + +#ifdef MOZ_XUL +already_AddRefed<nsStyleContext> +nsStyleSet::ResolveXULTreePseudoStyle(Element* aParentElement, + nsIAtom* aPseudoTag, + nsStyleContext* aParentContext, + nsICSSPseudoComparator* aComparator) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + + NS_ASSERTION(aPseudoTag, "must have pseudo tag"); + NS_ASSERTION(nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag), + "Unexpected pseudo"); + + nsRuleWalker ruleWalker(mRuleTree, mAuthorStyleDisabled); + TreeMatchContext treeContext(true, nsRuleWalker::eRelevantLinkUnvisited, + aParentElement->OwnerDoc()); + InitStyleScopes(treeContext, aParentElement); + XULTreeRuleProcessorData data(PresContext(), aParentElement, &ruleWalker, + aPseudoTag, aComparator, treeContext); + FileRules(EnumRulesMatching<XULTreeRuleProcessorData>, &data, aParentElement, + &ruleWalker); + + nsRuleNode *ruleNode = ruleWalker.CurrentNode(); + nsRuleNode *visitedRuleNode = nullptr; + + if (treeContext.HaveRelevantLink()) { + treeContext.ResetForVisitedMatching(); + ruleWalker.Reset(); + FileRules(EnumRulesMatching<XULTreeRuleProcessorData>, &data, + aParentElement, &ruleWalker); + visitedRuleNode = ruleWalker.CurrentNode(); + } + + return GetContext(aParentContext, ruleNode, visitedRuleNode, + // For pseudos, |data.IsLink()| being true means that + // our parent node is a link. + aPseudoTag, CSSPseudoElementType::XULTree, + nullptr, eNoFlags); +} +#endif + +bool +nsStyleSet::AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray) +{ + NS_ENSURE_FALSE(mInShutdown, false); + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + nsPresContext* presContext = PresContext(); + for (uint32_t i = 0; i < ArrayLength(gCSSSheetTypes); ++i) { + if (gCSSSheetTypes[i] == SheetType::ScopedDoc) + continue; + nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*> + (mRuleProcessors[gCSSSheetTypes[i]].get()); + if (ruleProc && !ruleProc->AppendFontFaceRules(presContext, aArray)) + return false; + } + return true; +} + +nsCSSKeyframesRule* +nsStyleSet::KeyframesRuleForName(const nsString& aName) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + nsPresContext* presContext = PresContext(); + for (uint32_t i = ArrayLength(gCSSSheetTypes); i-- != 0; ) { + if (gCSSSheetTypes[i] == SheetType::ScopedDoc) + continue; + nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*> + (mRuleProcessors[gCSSSheetTypes[i]].get()); + if (!ruleProc) + continue; + nsCSSKeyframesRule* result = + ruleProc->KeyframesRuleForName(presContext, aName); + if (result) + return result; + } + return nullptr; +} + +nsCSSCounterStyleRule* +nsStyleSet::CounterStyleRuleForName(const nsAString& aName) +{ + NS_ENSURE_FALSE(mInShutdown, nullptr); + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + nsPresContext* presContext = PresContext(); + for (uint32_t i = ArrayLength(gCSSSheetTypes); i-- != 0; ) { + if (gCSSSheetTypes[i] == SheetType::ScopedDoc) + continue; + nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*> + (mRuleProcessors[gCSSSheetTypes[i]].get()); + if (!ruleProc) + continue; + nsCSSCounterStyleRule *result = + ruleProc->CounterStyleRuleForName(presContext, aName); + if (result) + return result; + } + return nullptr; +} + +bool +nsStyleSet::AppendFontFeatureValuesRules( + nsTArray<nsCSSFontFeatureValuesRule*>& aArray) +{ + NS_ENSURE_FALSE(mInShutdown, false); + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + nsPresContext* presContext = PresContext(); + for (uint32_t i = 0; i < ArrayLength(gCSSSheetTypes); ++i) { + nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*> + (mRuleProcessors[gCSSSheetTypes[i]].get()); + if (ruleProc && + !ruleProc->AppendFontFeatureValuesRules(presContext, aArray)) + { + return false; + } + } + return true; +} + +already_AddRefed<gfxFontFeatureValueSet> +nsStyleSet::GetFontFeatureValuesLookup() +{ + if (mInitFontFeatureValuesLookup) { + mInitFontFeatureValuesLookup = false; + + nsTArray<nsCSSFontFeatureValuesRule*> rules; + AppendFontFeatureValuesRules(rules); + + mFontFeatureValuesLookup = new gfxFontFeatureValueSet(); + + uint32_t i, numRules = rules.Length(); + for (i = 0; i < numRules; i++) { + nsCSSFontFeatureValuesRule *rule = rules[i]; + + const nsTArray<FontFamilyName>& familyList = rule->GetFamilyList().GetFontlist(); + const nsTArray<gfxFontFeatureValueSet::FeatureValues>& + featureValues = rule->GetFeatureValues(); + + // for each family + size_t f, numFam; + + numFam = familyList.Length(); + for (f = 0; f < numFam; f++) { + mFontFeatureValuesLookup->AddFontFeatureValues(familyList[f].mName, + featureValues); + } + } + } + + RefPtr<gfxFontFeatureValueSet> lookup = mFontFeatureValuesLookup; + return lookup.forget(); +} + +bool +nsStyleSet::AppendPageRules(nsTArray<nsCSSPageRule*>& aArray) +{ + NS_ENSURE_FALSE(mInShutdown, false); + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + nsPresContext* presContext = PresContext(); + for (uint32_t i = 0; i < ArrayLength(gCSSSheetTypes); ++i) { + if (gCSSSheetTypes[i] == SheetType::ScopedDoc) + continue; + nsCSSRuleProcessor* ruleProc = static_cast<nsCSSRuleProcessor*> + (mRuleProcessors[gCSSSheetTypes[i]].get()); + if (ruleProc && !ruleProc->AppendPageRules(presContext, aArray)) + return false; + } + return true; +} + +void +nsStyleSet::BeginShutdown() +{ + mInShutdown = 1; +} + +void +nsStyleSet::Shutdown() +{ + mRuleTree = nullptr; + GCRuleTrees(); + MOZ_ASSERT(mUnusedRuleNodeList.isEmpty()); + MOZ_ASSERT(mUnusedRuleNodeCount == 0); +} + + +void +nsStyleSet::GCRuleTrees() +{ + MOZ_ASSERT(!mInReconstruct); + MOZ_ASSERT(!mInGC); + mInGC = true; + + while (!mUnusedRuleNodeList.isEmpty()) { + nsRuleNode* node = mUnusedRuleNodeList.popFirst(); +#ifdef DEBUG + if (node == mOldRootNode) { + // Flag that we've GCed the old root, if any. + mOldRootNode = nullptr; + } +#endif + node->Destroy(); + } + +#ifdef DEBUG + NS_ASSERTION(!mOldRootNode, "Should have GCed old root node"); + mOldRootNode = nullptr; +#endif + mUnusedRuleNodeCount = 0; + mInGC = false; +} + +already_AddRefed<nsStyleContext> +nsStyleSet::ReparentStyleContext(nsStyleContext* aStyleContext, + nsStyleContext* aNewParentContext, + Element* aElement) +{ + MOZ_ASSERT(aStyleContext, "aStyleContext must not be null"); + + // This short-circuit is OK because we don't call TryInitatingTransition + // during style reresolution if the style context pointer hasn't changed. + if (aStyleContext->GetParent() == aNewParentContext) { + RefPtr<nsStyleContext> ret = aStyleContext; + return ret.forget(); + } + + nsIAtom* pseudoTag = aStyleContext->GetPseudo(); + CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType(); + nsRuleNode* ruleNode = aStyleContext->RuleNode(); + + MOZ_ASSERT(PresContext()->RestyleManager()->IsGecko(), + "stylo: the style set and restyle manager must have the same " + "StyleBackendType"); + NS_ASSERTION(!PresContext()->RestyleManager()->AsGecko()->SkipAnimationRules(), + "we no longer handle SkipAnimationRules()"); + + nsRuleNode* visitedRuleNode = nullptr; + nsStyleContext* visitedContext = aStyleContext->GetStyleIfVisited(); + // Reparenting a style context just changes where we inherit from, + // not what rules we match or what our DOM looks like. In + // particular, it doesn't change whether this is a style context for + // a link. + if (visitedContext) { + visitedRuleNode = visitedContext->RuleNode(); + } + + uint32_t flags = eNoFlags; + if (aStyleContext->IsLinkContext()) { + flags |= eIsLink; + + // GetContext handles propagating RelevantLinkVisited state from the + // parent in non-link cases; all we need to pass in is if this link + // is visited. + if (aStyleContext->RelevantLinkVisited()) { + flags |= eIsVisitedLink; + } + } + + if (pseudoType == CSSPseudoElementType::NotPseudo || + pseudoType == CSSPseudoElementType::before || + pseudoType == CSSPseudoElementType::after) { + flags |= eDoAnimation; + } + + if (aElement && aElement->IsRootOfAnonymousSubtree()) { + // For anonymous subtree roots, don't tweak "display" value based on whether + // or not the parent is styled as a flex/grid container. (If the parent + // has anonymous-subtree kids, then we know it's not actually going to get + // a flex/grid container frame, anyway.) + flags |= eSkipParentDisplayBasedStyleFixup; + } + + return GetContext(aNewParentContext, ruleNode, visitedRuleNode, + pseudoTag, pseudoType, + aElement, flags); +} + +struct MOZ_STACK_CLASS StatefulData : public StateRuleProcessorData { + StatefulData(nsPresContext* aPresContext, Element* aElement, + EventStates aStateMask, TreeMatchContext& aTreeMatchContext) + : StateRuleProcessorData(aPresContext, aElement, aStateMask, + aTreeMatchContext), + mHint(nsRestyleHint(0)) + {} + nsRestyleHint mHint; +}; + +struct MOZ_STACK_CLASS StatefulPseudoElementData : public PseudoElementStateRuleProcessorData { + StatefulPseudoElementData(nsPresContext* aPresContext, Element* aElement, + EventStates aStateMask, CSSPseudoElementType aPseudoType, + TreeMatchContext& aTreeMatchContext, Element* aPseudoElement) + : PseudoElementStateRuleProcessorData(aPresContext, aElement, aStateMask, + aPseudoType, aTreeMatchContext, + aPseudoElement), + mHint(nsRestyleHint(0)) + {} + nsRestyleHint mHint; +}; + +static bool SheetHasDocumentStateStyle(nsIStyleRuleProcessor* aProcessor, + void *aData) +{ + StatefulData* data = (StatefulData*)aData; + if (aProcessor->HasDocumentStateDependentStyle(data)) { + data->mHint = eRestyle_Self; + return false; // don't continue + } + return true; // continue +} + +// Test if style is dependent on a document state. +bool +nsStyleSet::HasDocumentStateDependentStyle(nsIContent* aContent, + EventStates aStateMask) +{ + if (!aContent || !aContent->IsElement()) + return false; + + TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited, + aContent->OwnerDoc()); + InitStyleScopes(treeContext, aContent->AsElement()); + StatefulData data(PresContext(), aContent->AsElement(), aStateMask, + treeContext); + WalkRuleProcessors(SheetHasDocumentStateStyle, &data, true); + return data.mHint != 0; +} + +static bool SheetHasStatefulStyle(nsIStyleRuleProcessor* aProcessor, + void *aData) +{ + StatefulData* data = (StatefulData*)aData; + nsRestyleHint hint = aProcessor->HasStateDependentStyle(data); + data->mHint = nsRestyleHint(data->mHint | hint); + return true; // continue +} + +static bool SheetHasStatefulPseudoElementStyle(nsIStyleRuleProcessor* aProcessor, + void *aData) +{ + StatefulPseudoElementData* data = (StatefulPseudoElementData*)aData; + nsRestyleHint hint = aProcessor->HasStateDependentStyle(data); + data->mHint = nsRestyleHint(data->mHint | hint); + return true; // continue +} + +// Test if style is dependent on content state +nsRestyleHint +nsStyleSet::HasStateDependentStyle(Element* aElement, + EventStates aStateMask) +{ + TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited, + aElement->OwnerDoc()); + InitStyleScopes(treeContext, aElement); + StatefulData data(PresContext(), aElement, aStateMask, treeContext); + WalkRuleProcessors(SheetHasStatefulStyle, &data, false); + return data.mHint; +} + +nsRestyleHint +nsStyleSet::HasStateDependentStyle(Element* aElement, + CSSPseudoElementType aPseudoType, + Element* aPseudoElement, + EventStates aStateMask) +{ + TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited, + aElement->OwnerDoc()); + InitStyleScopes(treeContext, aElement); + StatefulPseudoElementData data(PresContext(), aElement, aStateMask, + aPseudoType, treeContext, aPseudoElement); + WalkRuleProcessors(SheetHasStatefulPseudoElementStyle, &data, false); + return data.mHint; +} + +struct MOZ_STACK_CLASS AttributeData : public AttributeRuleProcessorData { + AttributeData(nsPresContext* aPresContext, Element* aElement, + int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, + bool aAttrHasChanged, const nsAttrValue* aOtherValue, + TreeMatchContext& aTreeMatchContext) + : AttributeRuleProcessorData(aPresContext, aElement, aNameSpaceID, + aAttribute, aModType, aAttrHasChanged, + aOtherValue, aTreeMatchContext), + mHint(nsRestyleHint(0)) + {} + nsRestyleHint mHint; + RestyleHintData mHintData; +}; + +static bool +SheetHasAttributeStyle(nsIStyleRuleProcessor* aProcessor, void *aData) +{ + AttributeData* data = (AttributeData*)aData; + nsRestyleHint hint = + aProcessor->HasAttributeDependentStyle(data, data->mHintData); + data->mHint = nsRestyleHint(data->mHint | hint); + return true; // continue +} + +// Test if style is dependent on content state +nsRestyleHint +nsStyleSet::HasAttributeDependentStyle(Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + bool aAttrHasChanged, + const nsAttrValue* aOtherValue, + mozilla::RestyleHintData& + aRestyleHintDataResult) +{ + TreeMatchContext treeContext(false, nsRuleWalker::eLinksVisitedOrUnvisited, + aElement->OwnerDoc()); + InitStyleScopes(treeContext, aElement); + AttributeData data(PresContext(), aElement, aNameSpaceID, aAttribute, + aModType, aAttrHasChanged, aOtherValue, treeContext); + WalkRuleProcessors(SheetHasAttributeStyle, &data, false); + if (!(data.mHint & eRestyle_Subtree)) { + // No point keeping the list of selectors around if we are going to + // restyle the whole subtree unconditionally. + aRestyleHintDataResult = Move(data.mHintData); + } + return data.mHint; +} + +bool +nsStyleSet::MediumFeaturesChanged() +{ + NS_ASSERTION(mBatching == 0, "rule processors out of date"); + + // We can't use WalkRuleProcessors without a content node. + nsPresContext* presContext = PresContext(); + bool stylesChanged = false; + for (nsIStyleRuleProcessor* processor : mRuleProcessors) { + if (!processor) { + continue; + } + bool thisChanged = processor->MediumFeaturesChanged(presContext); + stylesChanged = stylesChanged || thisChanged; + } + for (nsIStyleRuleProcessor* processor : mScopedDocSheetRuleProcessors) { + bool thisChanged = processor->MediumFeaturesChanged(presContext); + stylesChanged = stylesChanged || thisChanged; + } + + if (mBindingManager) { + bool thisChanged = false; + mBindingManager->MediumFeaturesChanged(presContext, &thisChanged); + stylesChanged = stylesChanged || thisChanged; + } + + return stylesChanged; +} + +bool +nsStyleSet::EnsureUniqueInnerOnCSSSheets() +{ + AutoTArray<CSSStyleSheet*, 32> queue; + for (SheetType type : gCSSSheetTypes) { + for (CSSStyleSheet* sheet : mSheets[type]) { + queue.AppendElement(sheet); + } + } + + if (mBindingManager) { + AutoTArray<StyleSheet*, 32> sheets; + // XXXheycam stylo: AppendAllSheets will need to be able to return either + // CSSStyleSheets or ServoStyleSheets, on request (and then here requesting + // CSSStyleSheets). + mBindingManager->AppendAllSheets(sheets); + for (StyleSheet* sheet : sheets) { + MOZ_ASSERT(sheet->IsGecko(), "stylo: AppendAllSheets shouldn't give us " + "ServoStyleSheets yet"); + queue.AppendElement(sheet->AsGecko()); + } + } + + while (!queue.IsEmpty()) { + uint32_t idx = queue.Length() - 1; + CSSStyleSheet* sheet = queue[idx]; + queue.RemoveElementAt(idx); + + sheet->EnsureUniqueInner(); + + // Enqueue all the sheet's children. + sheet->AppendAllChildSheets(queue); + } + + bool res = mNeedsRestyleAfterEnsureUniqueInner; + mNeedsRestyleAfterEnsureUniqueInner = false; + return res; +} + +nsIStyleRule* +nsStyleSet::InitialStyleRule() +{ + if (!mInitialStyleRule) { + mInitialStyleRule = new nsInitialStyleRule; + } + return mInitialStyleRule; +} + +bool +nsStyleSet::HasRuleProcessorUsedByMultipleStyleSets(SheetType aSheetType) +{ + MOZ_ASSERT(size_t(aSheetType) < ArrayLength(mRuleProcessors)); + if (!IsCSSSheetType(aSheetType) || !mRuleProcessors[aSheetType]) { + return false; + } + nsCSSRuleProcessor* rp = + static_cast<nsCSSRuleProcessor*>(mRuleProcessors[aSheetType].get()); + return rp->IsUsedByMultipleStyleSets(); +} + +void +nsStyleSet::ClearSelectors() +{ + // We might be called before we've done our first rule tree construction. + if (!mRuleTree) { + return; + } + MOZ_ASSERT(PresContext()->RestyleManager()->IsGecko(), + "stylo: the style set and restyle manager must have the same " + "StyleBackendType"); + PresContext()->RestyleManager()->AsGecko()->ClearSelectors(); +} |