/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/StyleSheet.h" #include "mozilla/ComputedStyleInlines.h" #include "mozilla/css/ErrorReporter.h" #include "mozilla/css/GroupRule.h" #include "mozilla/dom/CSSImportRule.h" #include "mozilla/dom/CSSRuleList.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/MediaList.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/ShadowRootBinding.h" #include "mozilla/NullPrincipal.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoCSSRuleList.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/StaticPrefs.h" #include "mozilla/StyleSheetInlines.h" #include "mozAutoDocUpdate.h" namespace mozilla { using namespace dom; StyleSheet::StyleSheet(css::SheetParsingMode aParsingMode, CORSMode aCORSMode, net::ReferrerPolicy aReferrerPolicy, const dom::SRIMetadata& aIntegrity) : mParent(nullptr), mDocumentOrShadowRoot(nullptr), mOwningNode(nullptr), mOwnerRule(nullptr), mParsingMode(aParsingMode), mState(static_cast(0)), mAssociationMode(NotOwnedByDocumentOrShadowRoot), mInner(new StyleSheetInfo(aCORSMode, aReferrerPolicy, aIntegrity, aParsingMode)) { mInner->AddSheet(this); } StyleSheet::StyleSheet(const StyleSheet& aCopy, StyleSheet* aParentToUse, dom::CSSImportRule* aOwnerRuleToUse, dom::DocumentOrShadowRoot* aDocumentOrShadowRoot, nsINode* aOwningNodeToUse) : mParent(aParentToUse), mTitle(aCopy.mTitle), mDocumentOrShadowRoot(aDocumentOrShadowRoot), mOwningNode(aOwningNodeToUse), mOwnerRule(aOwnerRuleToUse), mParsingMode(aCopy.mParsingMode), mState(aCopy.mState), // We only use this constructor during cloning. It's the cloner's // responsibility to notify us if we end up being owned by a document. mAssociationMode(NotOwnedByDocumentOrShadowRoot), // Shallow copy, but concrete subclasses will fix up. mInner(aCopy.mInner) { MOZ_ASSERT(mInner, "Should only copy StyleSheets with an mInner."); mInner->AddSheet(this); if (HasForcedUniqueInner()) { // CSSOM's been there, force full copy now MOZ_ASSERT(IsComplete(), "Why have rules been accessed on an incomplete sheet?"); // FIXME: handle failure? EnsureUniqueInner(); } if (aCopy.mMedia) { // XXX This is wrong; we should be keeping @import rules and // sheets in sync! mMedia = aCopy.mMedia->Clone(); } } StyleSheet::~StyleSheet() { MOZ_ASSERT(!mInner, "Inner should have been dropped in LastRelease"); } bool StyleSheet::HasRules() const { return Servo_StyleSheet_HasRules(Inner().mContents); } nsIDocument* StyleSheet::GetAssociatedDocument() const { return mDocumentOrShadowRoot ? mDocumentOrShadowRoot->AsNode().OwnerDoc() : nullptr; } nsIDocument* StyleSheet::GetComposedDoc() const { return mDocumentOrShadowRoot ? mDocumentOrShadowRoot->AsNode().GetComposedDoc() : nullptr; } bool StyleSheet::IsKeptAliveByDocument() const { if (mAssociationMode != OwnedByDocumentOrShadowRoot) { return false; } return !!GetComposedDoc(); } void StyleSheet::LastRelease() { MOZ_ASSERT(mInner, "Should have an mInner at time of destruction."); MOZ_ASSERT(mInner->mSheets.Contains(this), "Our mInner should include us."); UnparentChildren(); mInner->RemoveSheet(this); mInner = nullptr; DropMedia(); DropRuleList(); } void StyleSheet::UnlinkInner() { // We can only have a cycle through our inner if we have a unique inner, // because otherwise there are no JS wrappers for anything in the inner. if (mInner->mSheets.Length() != 1) { return; } // Have to be a bit careful with child sheets, because we want to // drop their mNext pointers and null out their mParent and // mDocument, but don't want to work with deleted objects. And we // don't want to do any addrefing in the process, just to make sure // we don't confuse the cycle collector (though on the face of it, // addref/release pairs during unlink should probably be ok). RefPtr child; child.swap(Inner().mFirstChild); while (child) { MOZ_ASSERT(child->mParent == this, "We have a unique inner!"); child->mParent = nullptr; // We (and child) might still think we're owned by a document, because // unlink order is non-deterministic, so the document's unlink, which would // tell us it does't own us anymore, may not have happened yet. But if // we're being unlinked, clearly we're not owned by a document anymore // conceptually! child->ClearAssociatedDocumentOrShadowRoot(); RefPtr next; // Null out child->mNext, but don't let it die yet next.swap(child->mNext); // Switch to looking at the old value of child->mNext next iteration child.swap(next); // "next" is now our previous value of child; it'll get released // as we loop around. } } void StyleSheet::TraverseInner(nsCycleCollectionTraversalCallback& cb) { // We can only have a cycle through our inner if we have a unique inner, // because otherwise there are no JS wrappers for anything in the inner. if (mInner->mSheets.Length() != 1) { return; } StyleSheet* childSheet = GetFirstChild(); while (childSheet) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "child sheet"); cb.NoteXPCOMChild(childSheet); childSheet = childSheet->mNext; } } // QueryInterface implementation for StyleSheet NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StyleSheet) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(StyleSheet) // We want to disconnect from our inner as soon as our refcount drops to zero, // without waiting for async deletion by the cycle collector. Otherwise we // might end up cloning the inner if someone mutates another sheet that shares // it with us, even though there is only one such sheet and we're about to go // away. This situation arises easily with sheet preloading. NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(StyleSheet, LastRelease()) NS_IMPL_CYCLE_COLLECTION_CLASS(StyleSheet) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StyleSheet) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleList) tmp->TraverseInner(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StyleSheet) tmp->DropMedia(); tmp->UnlinkInner(); tmp->DropRuleList(); NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(StyleSheet) mozilla::dom::CSSStyleSheetParsingMode StyleSheet::ParsingModeDOM() { #define CHECK(X, Y) \ static_assert( \ static_cast(X) == static_cast(Y), \ "mozilla::dom::CSSStyleSheetParsingMode and " \ "mozilla::css::SheetParsingMode should have identical values"); CHECK(mozilla::dom::CSSStyleSheetParsingMode::Agent, css::eAgentSheetFeatures); CHECK(mozilla::dom::CSSStyleSheetParsingMode::User, css::eUserSheetFeatures); CHECK(mozilla::dom::CSSStyleSheetParsingMode::Author, css::eAuthorSheetFeatures); #undef CHECK return static_cast(mParsingMode); } void StyleSheet::SetComplete() { MOZ_ASSERT(!HasForcedUniqueInner(), "Can't complete a sheet that's already been forced unique."); MOZ_ASSERT(!IsComplete(), "Already complete?"); mState |= State::Complete; if (!Disabled()) { ApplicableStateChanged(true); } } void StyleSheet::ApplicableStateChanged(bool aApplicable) { if (!mDocumentOrShadowRoot) { return; } nsINode& node = mDocumentOrShadowRoot->AsNode(); if (auto* shadow = ShadowRoot::FromNode(node)) { shadow->StyleSheetApplicableStateChanged(*this, aApplicable); } else { node.AsDocument()->SetStyleSheetApplicableState(this, aApplicable); } } void StyleSheet::SetDisabled(bool aDisabled) { if (aDisabled == Disabled()) { return; } if (aDisabled) { mState |= State::Disabled; } else { mState &= ~State::Disabled; } if (IsComplete()) { ApplicableStateChanged(!aDisabled); } } already_AddRefed StyleSheet::CreateURLExtraData() const { RefPtr data = new URLExtraData( GetBaseURI(), GetSheetURI(), Principal(), GetReferrerPolicy()); return data.forget(); } StyleSheetInfo::StyleSheetInfo(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy, const SRIMetadata& aIntegrity, css::SheetParsingMode aParsingMode) : mPrincipal(NullPrincipal::CreateWithoutOriginAttributes()), mCORSMode(aCORSMode), mReferrerPolicy(aReferrerPolicy), mIntegrity(aIntegrity), mContents(Servo_StyleSheet_Empty(aParsingMode).Consume()), mURLData(URLExtraData::Dummy()) #ifdef DEBUG , mPrincipalSet(false) #endif { if (!mPrincipal) { MOZ_CRASH("NullPrincipal::Init failed"); } MOZ_COUNT_CTOR(StyleSheetInfo); } StyleSheetInfo::StyleSheetInfo(StyleSheetInfo& aCopy, StyleSheet* aPrimarySheet) : mSheetURI(aCopy.mSheetURI), mOriginalSheetURI(aCopy.mOriginalSheetURI), mBaseURI(aCopy.mBaseURI), mPrincipal(aCopy.mPrincipal), mCORSMode(aCopy.mCORSMode), mReferrerPolicy(aCopy.mReferrerPolicy), mIntegrity(aCopy.mIntegrity), mFirstChild() // We don't rebuild the child because we're making a copy // without children. , mSourceMapURL(aCopy.mSourceMapURL), mSourceMapURLFromComment(aCopy.mSourceMapURLFromComment), mSourceURL(aCopy.mSourceURL), mContents(Servo_StyleSheet_Clone(aCopy.mContents.get(), aPrimarySheet) .Consume()), mURLData(aCopy.mURLData) #ifdef DEBUG , mPrincipalSet(aCopy.mPrincipalSet) #endif { AddSheet(aPrimarySheet); // Our child list is fixed up by our parent. MOZ_COUNT_CTOR(StyleSheetInfo); } StyleSheetInfo::~StyleSheetInfo() { MOZ_COUNT_DTOR(StyleSheetInfo); } StyleSheetInfo* StyleSheetInfo::CloneFor(StyleSheet* aPrimarySheet) { return new StyleSheetInfo(*this, aPrimarySheet); } MOZ_DEFINE_MALLOC_SIZE_OF(ServoStyleSheetMallocSizeOf) MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoStyleSheetMallocEnclosingSizeOf) size_t StyleSheetInfo::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); n += Servo_StyleSheet_SizeOfIncludingThis( ServoStyleSheetMallocSizeOf, ServoStyleSheetMallocEnclosingSizeOf, mContents); return n; } void StyleSheetInfo::AddSheet(StyleSheet* aSheet) { mSheets.AppendElement(aSheet); } void StyleSheetInfo::RemoveSheet(StyleSheet* aSheet) { if ((aSheet == mSheets.ElementAt(0)) && (mSheets.Length() > 1)) { StyleSheet::ChildSheetListBuilder::ReparentChildList(mSheets[1], mFirstChild); } if (1 == mSheets.Length()) { NS_ASSERTION(aSheet == mSheets.ElementAt(0), "bad parent"); delete this; return; } mSheets.RemoveElement(aSheet); } void StyleSheet::ChildSheetListBuilder::SetParentLinks(StyleSheet* aSheet) { aSheet->mParent = parent; aSheet->SetAssociatedDocumentOrShadowRoot(parent->mDocumentOrShadowRoot, parent->mAssociationMode); } void StyleSheet::ChildSheetListBuilder::ReparentChildList( StyleSheet* aPrimarySheet, StyleSheet* aFirstChild) { for (StyleSheet* child = aFirstChild; child; child = child->mNext) { child->mParent = aPrimarySheet; child->SetAssociatedDocumentOrShadowRoot( aPrimarySheet->mDocumentOrShadowRoot, aPrimarySheet->mAssociationMode); } } void StyleSheet::GetType(nsAString& aType) { aType.AssignLiteral("text/css"); } void StyleSheet::GetHref(nsAString& aHref, ErrorResult& aRv) { if (nsIURI* sheetURI = Inner().mOriginalSheetURI) { nsAutoCString str; nsresult rv = sheetURI->GetSpec(str); if (NS_FAILED(rv)) { aRv.Throw(rv); return; } CopyUTF8toUTF16(str, aHref); } else { SetDOMStringToNull(aHref); } } void StyleSheet::GetTitle(nsAString& aTitle) { // From https://drafts.csswg.org/cssom/#dom-stylesheet-title: // // The title attribute must return the title or null if title is the empty // string. // if (!mTitle.IsEmpty()) { aTitle.Assign(mTitle); } else { SetDOMStringToNull(aTitle); } } void StyleSheet::WillDirty() { if (IsComplete()) { EnsureUniqueInner(); } } void StyleSheet::AddStyleSet(ServoStyleSet* aStyleSet) { NS_ASSERTION(!mStyleSets.Contains(aStyleSet), "style set already registered"); mStyleSets.AppendElement(aStyleSet); } void StyleSheet::DropStyleSet(ServoStyleSet* aStyleSet) { DebugOnly found = mStyleSets.RemoveElement(aStyleSet); NS_ASSERTION(found, "didn't find style set"); } void StyleSheet::EnsureUniqueInner() { MOZ_ASSERT(mInner->mSheets.Length() != 0, "unexpected number of outers"); mState |= State::ForcedUniqueInner; if (HasUniqueInner()) { // already unique return; } StyleSheetInfo* clone = mInner->CloneFor(this); MOZ_ASSERT(clone); mInner->RemoveSheet(this); mInner = clone; // Fixup the child lists and parent links in the Servo sheet. This is done // here instead of in StyleSheetInner::CloneFor, because it's just more // convenient to do so instead. BuildChildListAfterInnerClone(); // let our containing style sets know that if we call // nsPresContext::EnsureSafeToHandOutCSSRules we will need to restyle the // document for (ServoStyleSet* setHandle : mStyleSets) { setHandle->SetNeedsRestyleAfterEnsureUniqueInner(); } } void StyleSheet::AppendAllChildSheets(nsTArray& aArray) { for (StyleSheet* child = GetFirstChild(); child; child = child->mNext) { aArray.AppendElement(child); } } // WebIDL CSSStyleSheet API dom::CSSRuleList* StyleSheet::GetCssRules(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!AreRulesAvailable(aSubjectPrincipal, aRv)) { return nullptr; } return GetCssRulesInternal(); } void StyleSheet::GetSourceMapURL(nsAString& aSourceMapURL) { if (mInner->mSourceMapURL.IsEmpty()) { aSourceMapURL = mInner->mSourceMapURLFromComment; } else { aSourceMapURL = mInner->mSourceMapURL; } } void StyleSheet::SetSourceMapURL(const nsAString& aSourceMapURL) { mInner->mSourceMapURL = aSourceMapURL; } void StyleSheet::SetSourceMapURLFromComment( const nsAString& aSourceMapURLFromComment) { mInner->mSourceMapURLFromComment = aSourceMapURLFromComment; } void StyleSheet::GetSourceURL(nsAString& aSourceURL) { aSourceURL = mInner->mSourceURL; } void StyleSheet::SetSourceURL(const nsAString& aSourceURL) { mInner->mSourceURL = aSourceURL; } css::Rule* StyleSheet::GetDOMOwnerRule() const { return mOwnerRule; } uint32_t StyleSheet::InsertRule(const nsAString& aRule, uint32_t aIndex, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!AreRulesAvailable(aSubjectPrincipal, aRv)) { return 0; } return InsertRuleInternal(aRule, aIndex, aRv); } void StyleSheet::DeleteRule(uint32_t aIndex, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!AreRulesAvailable(aSubjectPrincipal, aRv)) { return; } return DeleteRuleInternal(aIndex, aRv); } nsresult StyleSheet::DeleteRuleFromGroup(css::GroupRule* aGroup, uint32_t aIndex) { NS_ENSURE_ARG_POINTER(aGroup); NS_ASSERTION(IsComplete(), "No deleting from an incomplete sheet!"); RefPtr rule = aGroup->GetStyleRuleAt(aIndex); NS_ENSURE_TRUE(rule, NS_ERROR_ILLEGAL_VALUE); // check that the rule actually belongs to this sheet! if (this != rule->GetStyleSheet()) { return NS_ERROR_INVALID_ARG; } WillDirty(); nsresult result = aGroup->DeleteStyleRuleAt(aIndex); NS_ENSURE_SUCCESS(result, result); rule->DropReferences(); RuleRemoved(*rule); return NS_OK; } dom::ShadowRoot* StyleSheet::GetContainingShadow() const { if (!mOwningNode || !mOwningNode->IsContent()) { return nullptr; } return mOwningNode->AsContent()->GetContainingShadow(); } #define NOTIFY(function_, args_) \ do { \ StyleSheet* current = this; \ do { \ for (ServoStyleSet * handle : current->mStyleSets) { \ handle->function_ args_; \ } \ if (auto* shadow = current->GetContainingShadow()) { \ shadow->function_ args_; \ } \ current = current->mParent; \ } while (current); \ } while (0) void StyleSheet::RuleAdded(css::Rule& aRule) { mState |= State::ModifiedRules; NOTIFY(RuleAdded, (*this, aRule)); if (nsIDocument* doc = GetComposedDoc()) { doc->StyleRuleAdded(this, &aRule); } } void StyleSheet::RuleRemoved(css::Rule& aRule) { mState |= State::ModifiedRules; NOTIFY(RuleRemoved, (*this, aRule)); if (nsIDocument* doc = GetComposedDoc()) { doc->StyleRuleRemoved(this, &aRule); } } void StyleSheet::RuleChanged(css::Rule* aRule) { mState |= State::ModifiedRules; NOTIFY(RuleChanged, (*this, aRule)); if (nsIDocument* doc = GetComposedDoc()) { doc->StyleRuleChanged(this, aRule); } } #undef NOTIFY nsresult StyleSheet::InsertRuleIntoGroup(const nsAString& aRule, css::GroupRule* aGroup, uint32_t aIndex) { NS_ASSERTION(IsComplete(), "No inserting into an incomplete sheet!"); // check that the group actually belongs to this sheet! if (this != aGroup->GetStyleSheet()) { return NS_ERROR_INVALID_ARG; } WillDirty(); nsresult result = InsertRuleIntoGroupInternal(aRule, aGroup, aIndex); NS_ENSURE_SUCCESS(result, result); RuleAdded(*aGroup->GetStyleRuleAt(aIndex)); return NS_OK; } uint64_t StyleSheet::FindOwningWindowInnerID() const { uint64_t windowID = 0; if (nsIDocument* doc = GetAssociatedDocument()) { windowID = doc->InnerWindowID(); } if (windowID == 0 && mOwningNode) { windowID = mOwningNode->OwnerDoc()->InnerWindowID(); } RefPtr ownerRule; if (windowID == 0 && (ownerRule = GetDOMOwnerRule())) { RefPtr sheet = ownerRule->GetStyleSheet(); if (sheet) { windowID = sheet->FindOwningWindowInnerID(); } } if (windowID == 0 && mParent) { windowID = mParent->FindOwningWindowInnerID(); } return windowID; } void StyleSheet::UnparentChildren() { // XXXbz this is a little bogus; see the XXX comment where we // declare mFirstChild in StyleSheetInfo. for (StyleSheet* child = GetFirstChild(); child; child = child->mNext) { if (child->mParent == this) { child->mParent = nullptr; MOZ_ASSERT(child->mAssociationMode == NotOwnedByDocumentOrShadowRoot, "How did we get to the destructor, exactly, if we're owned " "by a document?"); child->mDocumentOrShadowRoot = nullptr; } } } void StyleSheet::SubjectSubsumesInnerPrincipal(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { StyleSheetInfo& info = Inner(); if (aSubjectPrincipal.Subsumes(info.mPrincipal)) { return; } // Allow access only if CORS mode is not NONE and the security flag // is not turned off. if (GetCORSMode() == CORS_NONE && !nsContentUtils::BypassCSSOMOriginCheck()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } // Now make sure we set the principal of our inner to the subjectPrincipal. // We do this because we're in a situation where the caller would not normally // be able to access the sheet, but the sheet has opted in to being read. // Unfortunately, that means it's also opted in to being _edited_, and if the // caller now makes edits to the sheet we want the resulting resource loads, // if any, to look as if they are coming from the caller's principal, not the // original sheet principal. // // That means we need a unique inner, of course. But we don't want to do that // if we're not complete yet. Luckily, all the callers of this method throw // anyway if not complete, so we can just do that here too. if (!IsComplete()) { aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); return; } WillDirty(); info.mPrincipal = &aSubjectPrincipal; } bool StyleSheet::AreRulesAvailable(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { // Rules are not available on incomplete sheets. if (!IsComplete()) { aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); return false; } //-- Security check: Only scripts whose principal subsumes that of the // style sheet can access rule collections. SubjectSubsumesInnerPrincipal(aSubjectPrincipal, aRv); if (NS_WARN_IF(aRv.Failed())) { return false; } return true; } StyleSheet* StyleSheet::GetFirstChild() const { return Inner().mFirstChild; } void StyleSheet::SetAssociatedDocumentOrShadowRoot( DocumentOrShadowRoot* aDocOrShadowRoot, AssociationMode aAssociationMode) { MOZ_ASSERT(aDocOrShadowRoot || aAssociationMode == NotOwnedByDocumentOrShadowRoot); // not ref counted mDocumentOrShadowRoot = aDocOrShadowRoot; mAssociationMode = aAssociationMode; // Now set the same document on all our child sheets.... // XXXbz this is a little bogus; see the XXX comment where we // declare mFirstChild. for (StyleSheet* child = GetFirstChild(); child; child = child->mNext) { if (child->mParent == this) { child->SetAssociatedDocumentOrShadowRoot(aDocOrShadowRoot, aAssociationMode); } } } void StyleSheet::PrependStyleSheet(StyleSheet* aSheet) { WillDirty(); PrependStyleSheetSilently(aSheet); } void StyleSheet::PrependStyleSheetSilently(StyleSheet* aSheet) { MOZ_ASSERT(aSheet); aSheet->mNext = Inner().mFirstChild; Inner().mFirstChild = aSheet; // This is not reference counted. Our parent tells us when // it's going away. aSheet->mParent = this; aSheet->SetAssociatedDocumentOrShadowRoot(mDocumentOrShadowRoot, mAssociationMode); } size_t StyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { size_t n = 0; const StyleSheet* s = this; while (s) { n += aMallocSizeOf(s); // See the comment in CSSStyleSheet::SizeOfIncludingThis() for an // explanation of this. // // FIXME(emilio): This comment is gone, someone should go find it. if (s->Inner().mSheets.LastElement() == s) { n += s->Inner().SizeOfIncludingThis(aMallocSizeOf); } // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - s->mTitle // - s->mMedia // - s->mStyleSets // - s->mRuleList s = s->mNext; } return n; } #ifdef DEBUG void StyleSheet::List(FILE* out, int32_t aIndent) const { int32_t index; // Indent nsAutoCString str; for (index = aIndent; --index >= 0;) { str.AppendLiteral(" "); } str.AppendLiteral("CSS Style Sheet: "); nsAutoCString urlSpec; nsresult rv = GetSheetURI()->GetSpec(urlSpec); if (NS_SUCCEEDED(rv) && !urlSpec.IsEmpty()) { str.Append(urlSpec); } if (mMedia) { str.AppendLiteral(" media: "); nsAutoString buffer; mMedia->GetText(buffer); AppendUTF16toUTF8(buffer, str); } str.Append('\n'); fprintf_stderr(out, "%s", str.get()); for (const StyleSheet* child = GetFirstChild(); child; child = child->mNext) { child->List(out, aIndent + 1); } } #endif void StyleSheet::SetMedia(dom::MediaList* aMedia) { if (aMedia) { aMedia->SetStyleSheet(this); } mMedia = aMedia; } void StyleSheet::SetReferrerPolicy(net::ReferrerPolicy aReferrerPolicy) { Inner().mReferrerPolicy = aReferrerPolicy; } void StyleSheet::DropMedia() { if (mMedia) { mMedia->SetStyleSheet(nullptr); mMedia = nullptr; } } dom::MediaList* StyleSheet::Media() { if (!mMedia) { mMedia = dom::MediaList::Create(nsString()); mMedia->SetStyleSheet(this); } return mMedia; } // nsWrapperCache JSObject* StyleSheet::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return dom::CSSStyleSheet_Binding::Wrap(aCx, this, aGivenProto); } /* static */ bool StyleSheet::RuleHasPendingChildSheet(css::Rule* aRule) { MOZ_ASSERT(aRule->Type() == dom::CSSRule_Binding::IMPORT_RULE); auto rule = static_cast(aRule); if (StyleSheet* childSheet = rule->GetStyleSheet()) { return !childSheet->IsComplete(); } return false; } void StyleSheet::BuildChildListAfterInnerClone() { MOZ_ASSERT(Inner().mSheets.Length() == 1, "Should've just cloned"); MOZ_ASSERT(Inner().mSheets[0] == this); MOZ_ASSERT(!Inner().mFirstChild); auto* contents = Inner().mContents.get(); RefPtr rules = Servo_StyleSheet_GetRules(contents).Consume(); uint32_t index = 0; while (true) { uint32_t line, column; // Actually unused. RefPtr import = Servo_CssRules_GetImportRuleAt(rules, index, &line, &column).Consume(); if (!import) { // Note that only @charset rules come before @import rules, and @charset // rules are parsed but skipped, so we can stop iterating as soon as we // find something that isn't an @import rule. break; } auto* sheet = const_cast(Servo_ImportRule_GetSheet(import)); MOZ_ASSERT(sheet); PrependStyleSheetSilently(sheet); index++; } } already_AddRefed StyleSheet::CreateEmptyChildSheet( already_AddRefed aMediaList) const { RefPtr child = new StyleSheet(ParsingMode(), CORSMode::CORS_NONE, GetReferrerPolicy(), SRIMetadata()); child->mMedia = aMediaList; return child.forget(); } // We disable parallel stylesheet parsing if any of the following three // conditions hold: // // (1) The pref is off. // (2) The browser is recording CSS errors (which parallel parsing can't // handle). // (3) The stylesheet is a chrome stylesheet, since those can use // -moz-bool-pref, which needs to access the pref service, which is not // threadsafe. static bool AllowParallelParse(css::Loader* aLoader, nsIURI* aSheetURI) { // Check the pref. if (!StaticPrefs::layout_css_parsing_parallel()) { return false; } // If the browser is recording CSS errors, we need to use the sequential path // because the parallel path doesn't support that. nsIDocument* doc = aLoader->GetDocument(); if (doc && css::ErrorReporter::ShouldReportErrors(*doc)) { return false; } // If this is a chrome stylesheet, it might use -moz-bool-pref, which needs to // access the pref service, which is not thread-safe. We could probably expose // the relevant booleans as thread-safe var caches if we needed to, but // parsing chrome stylesheets in parallel is unlikely to be a win anyway. // // Note that UA stylesheets can also use -moz-bool-pref, but those are always // parsed sync. if (dom::IsChromeURI(aSheetURI)) { return false; } return true; } RefPtr StyleSheet::ParseSheet( css::Loader* aLoader, const nsACString& aBytes, css::SheetLoadData* aLoadData) { MOZ_ASSERT(aLoader); MOZ_ASSERT(aLoadData); MOZ_ASSERT(mParsePromise.IsEmpty()); RefPtr p = mParsePromise.Ensure(__func__); Inner().mURLData = CreateURLExtraData(); // RefPtr const StyleUseCounters* useCounters = aLoader->GetDocument() ? aLoader->GetDocument()->GetStyleUseCounters() : nullptr; if (!AllowParallelParse(aLoader, GetSheetURI())) { RefPtr contents = Servo_StyleSheet_FromUTF8Bytes( aLoader, this, aLoadData, &aBytes, mParsingMode, Inner().mURLData, aLoadData->mLineNumber, aLoader->GetCompatibilityMode(), /* reusable_sheets = */ nullptr, useCounters) .Consume(); FinishAsyncParse(contents.forget()); } else { RefPtr loadDataHolder = new css::SheetLoadDataHolder(__func__, aLoadData); Servo_StyleSheet_FromUTF8BytesAsync( loadDataHolder, Inner().mURLData, &aBytes, mParsingMode, aLoadData->mLineNumber, aLoader->GetCompatibilityMode(), /* should_record_counters = */ !!useCounters); } return p; } void StyleSheet::FinishAsyncParse( already_AddRefed aSheetContents) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mParsePromise.IsEmpty()); Inner().mContents = aSheetContents; FinishParse(); mParsePromise.Resolve(true, __func__); } void StyleSheet::ParseSheetSync( css::Loader* aLoader, const nsACString& aBytes, css::SheetLoadData* aLoadData, uint32_t aLineNumber, css::LoaderReusableStyleSheets* aReusableSheets) { nsCompatibility compatMode = aLoader ? aLoader->GetCompatibilityMode() : eCompatibility_FullStandards; const StyleUseCounters* useCounters = aLoader && aLoader->GetDocument() ? aLoader->GetDocument()->GetStyleUseCounters() : nullptr; Inner().mURLData = CreateURLExtraData(); // RefPtr Inner().mContents = Servo_StyleSheet_FromUTF8Bytes( aLoader, this, aLoadData, &aBytes, mParsingMode, Inner().mURLData, aLineNumber, compatMode, aReusableSheets, useCounters) .Consume(); FinishParse(); } void StyleSheet::FinishParse() { nsString sourceMapURL; Servo_StyleSheet_GetSourceMapURL(Inner().mContents, &sourceMapURL); SetSourceMapURLFromComment(sourceMapURL); nsString sourceURL; Servo_StyleSheet_GetSourceURL(Inner().mContents, &sourceURL); SetSourceURL(sourceURL); } nsresult StyleSheet::ReparseSheet(const nsAString& aInput) { if (!IsComplete()) { return NS_ERROR_DOM_INVALID_ACCESS_ERR; } // Hold strong ref to the CSSLoader in case the document update // kills the document RefPtr loader; if (nsIDocument* doc = GetAssociatedDocument()) { loader = doc->CSSLoader(); NS_ASSERTION(loader, "Document with no CSS loader!"); } else { loader = new css::Loader; } WillDirty(); // cache child sheets to reuse css::LoaderReusableStyleSheets reusableSheets; for (StyleSheet* child = GetFirstChild(); child; child = child->mNext) { if (child->GetOriginalURI()) { reusableSheets.AddReusableSheet(child); } } // clean up child sheets list for (StyleSheet* child = GetFirstChild(); child;) { StyleSheet* next = child->mNext; child->mParent = nullptr; child->ClearAssociatedDocumentOrShadowRoot(); child->mNext = nullptr; child = next; } Inner().mFirstChild = nullptr; uint32_t lineNumber = 1; if (mOwningNode) { nsCOMPtr link = do_QueryInterface(mOwningNode); if (link) { lineNumber = link->GetLineNumber(); } } // Notify to the stylesets about the old rules going away. { ServoCSSRuleList* ruleList = GetCssRulesInternal(); MOZ_ASSERT(ruleList); uint32_t ruleCount = ruleList->Length(); for (uint32_t i = 0; i < ruleCount; ++i) { css::Rule* rule = ruleList->GetRule(i); MOZ_ASSERT(rule); if (rule->Type() == dom::CSSRule_Binding::IMPORT_RULE && RuleHasPendingChildSheet(rule)) { continue; // notify when loaded (see StyleSheetLoaded) } RuleRemoved(*rule); } } DropRuleList(); ParseSheetSync(loader, NS_ConvertUTF16toUTF8(aInput), /* aLoadData = */ nullptr, lineNumber, &reusableSheets); // Notify the stylesets about the new rules. { // Get the rule list (which will need to be regenerated after ParseSheet). ServoCSSRuleList* ruleList = GetCssRulesInternal(); MOZ_ASSERT(ruleList); uint32_t ruleCount = ruleList->Length(); for (uint32_t i = 0; i < ruleCount; ++i) { css::Rule* rule = ruleList->GetRule(i); MOZ_ASSERT(rule); if (rule->Type() == CSSRule_Binding::IMPORT_RULE && RuleHasPendingChildSheet(rule)) { continue; // notify when loaded (see StyleSheetLoaded) } RuleAdded(*rule); } } // Our rules are no longer considered modified. ClearModifiedRules(); return NS_OK; } // nsICSSLoaderObserver implementation NS_IMETHODIMP StyleSheet::StyleSheetLoaded(StyleSheet* aSheet, bool aWasAlternate, nsresult aStatus) { if (!aSheet->GetParentSheet()) { return NS_OK; // ignore if sheet has been detached already } NS_ASSERTION(this == aSheet->GetParentSheet(), "We are being notified of a sheet load for a sheet that is not " "our child!"); if (NS_SUCCEEDED(aStatus)) { RuleAdded(*aSheet->GetOwnerRule()); } return NS_OK; } void StyleSheet::DropRuleList() { if (mRuleList) { mRuleList->DropReferences(); mRuleList = nullptr; } } already_AddRefed StyleSheet::Clone( StyleSheet* aCloneParent, dom::CSSImportRule* aCloneOwnerRule, dom::DocumentOrShadowRoot* aCloneDocumentOrShadowRoot, nsINode* aCloneOwningNode) const { RefPtr clone = new StyleSheet(*this, aCloneParent, aCloneOwnerRule, aCloneDocumentOrShadowRoot, aCloneOwningNode); return clone.forget(); } ServoCSSRuleList* StyleSheet::GetCssRulesInternal() { if (!mRuleList) { EnsureUniqueInner(); RefPtr rawRules = Servo_StyleSheet_GetRules(Inner().mContents).Consume(); MOZ_ASSERT(rawRules); mRuleList = new ServoCSSRuleList(rawRules.forget(), this, nullptr); } return mRuleList; } uint32_t StyleSheet::InsertRuleInternal(const nsAString& aRule, uint32_t aIndex, ErrorResult& aRv) { // Ensure mRuleList is constructed. GetCssRulesInternal(); aRv = mRuleList->InsertRule(aRule, aIndex); if (aRv.Failed()) { return 0; } // XXX We may not want to get the rule when stylesheet change event // is not enabled. css::Rule* rule = mRuleList->GetRule(aIndex); if (rule->Type() != CSSRule_Binding::IMPORT_RULE || !RuleHasPendingChildSheet(rule)) { RuleAdded(*rule); } return aIndex; } void StyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv) { // Ensure mRuleList is constructed. GetCssRulesInternal(); if (aIndex >= mRuleList->Length()) { aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); return; } // Hold a strong ref to the rule so it doesn't die when we remove it // from the list. XXX We may not want to hold it if stylesheet change // event is not enabled. RefPtr rule = mRuleList->GetRule(aIndex); aRv = mRuleList->DeleteRule(aIndex); MOZ_ASSERT(!aRv.ErrorCodeIs(NS_ERROR_DOM_INDEX_SIZE_ERR), "IndexSizeError should have been handled earlier"); if (!aRv.Failed()) { RuleRemoved(*rule); } } nsresult StyleSheet::InsertRuleIntoGroupInternal(const nsAString& aRule, css::GroupRule* aGroup, uint32_t aIndex) { auto rules = static_cast(aGroup->CssRules()); MOZ_ASSERT(rules->GetParentRule() == aGroup); return rules->InsertRule(aRule, aIndex); } OriginFlags StyleSheet::GetOrigin() { return static_cast( Servo_StyleSheet_GetOrigin(Inner().mContents)); } } // namespace mozilla