/* -*- 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/Assertions.h" #include "mozilla/BasePrincipal.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/FetchPriority.h" #include "mozilla/dom/MediaList.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ReferrerInfo.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_layout.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/css/SheetLoadData.h" #include "mozAutoDocUpdate.h" namespace mozilla { using namespace dom; StyleSheet::StyleSheet(css::SheetParsingMode aParsingMode, CORSMode aCORSMode, const dom::SRIMetadata& aIntegrity) : mParentSheet(nullptr), mConstructorDocument(nullptr), mDocumentOrShadowRoot(nullptr), mParsingMode(aParsingMode), mState(static_cast(0)), mInner(new StyleSheetInfo(aCORSMode, aIntegrity, aParsingMode)) { mInner->AddSheet(this); } StyleSheet::StyleSheet(const StyleSheet& aCopy, StyleSheet* aParentSheetToUse, dom::DocumentOrShadowRoot* aDocOrShadowRootToUse, dom::Document* aConstructorDocToUse) : mParentSheet(aParentSheetToUse), mConstructorDocument(aConstructorDocToUse), mTitle(aCopy.mTitle), mDocumentOrShadowRoot(aDocOrShadowRootToUse), mParsingMode(aCopy.mParsingMode), mState(aCopy.mState), // Shallow copy, but concrete subclasses will fix up. mInner(aCopy.mInner) { MOZ_ASSERT(!aConstructorDocToUse || aCopy.IsConstructed()); MOZ_ASSERT(!aConstructorDocToUse || !aDocOrShadowRootToUse, "Should never have both of these together."); MOZ_ASSERT(mInner, "Should only copy StyleSheets with an mInner."); mInner->AddSheet(this); // CSSOM's been there, force full copy now. if (HasForcedUniqueInner()) { MOZ_ASSERT(IsComplete(), "Why have rules been accessed on an incomplete sheet?"); EnsureUniqueInner(); // But CSSOM hasn't been on _this_ stylesheet yet, so no need to clone // ourselves. mState &= ~(State::ForcedUniqueInner | State::ModifiedRules | State::ModifiedRulesForDevtools); } if (aCopy.mMedia) { // XXX This is wrong; we should be keeping @import rules and // sheets in sync! mMedia = aCopy.mMedia->Clone(); } } /* static */ // https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-cssstylesheet already_AddRefed StyleSheet::Constructor( const dom::GlobalObject& aGlobal, const dom::CSSStyleSheetInit& aOptions, ErrorResult& aRv) { nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); if (!window) { aRv.ThrowNotSupportedError("Not supported when there is no document"); return nullptr; } Document* constructorDocument = window->GetExtantDoc(); if (!constructorDocument) { aRv.ThrowNotSupportedError("Not supported when there is no document"); return nullptr; } // 1. Construct a sheet and set its properties (see spec). auto sheet = MakeRefPtr(css::SheetParsingMode::eAuthorSheetFeatures, CORSMode::CORS_NONE, dom::SRIMetadata()); // baseURL not yet in the spec. Implemented based on the following discussion: // https://github.com/WICG/construct-stylesheets/issues/95#issuecomment-594217180 RefPtr baseURI; if (!aOptions.mBaseURL.WasPassed()) { baseURI = constructorDocument->GetBaseURI(); } else { nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aOptions.mBaseURL.Value(), nullptr, constructorDocument->GetBaseURI()); if (NS_FAILED(rv)) { aRv.ThrowNotAllowedError( "Constructed style sheets must have a valid base URL"); return nullptr; } } nsIURI* sheetURI = constructorDocument->GetDocumentURI(); nsIURI* originalURI = nullptr; sheet->SetURIs(sheetURI, originalURI, baseURI); sheet->SetPrincipal(constructorDocument->NodePrincipal()); auto referrerInfo = MakeRefPtr(*constructorDocument); sheet->SetReferrerInfo(referrerInfo); sheet->mConstructorDocument = constructorDocument; // 2. Set the sheet's media according to aOptions. if (aOptions.mMedia.IsUTF8String()) { sheet->SetMedia(MediaList::Create(aOptions.mMedia.GetAsUTF8String())); } else { sheet->SetMedia(aOptions.mMedia.GetAsMediaList()->Clone()); } // 3. Set the sheet's disabled flag according to aOptions. sheet->SetDisabled(aOptions.mDisabled); sheet->SetURLExtraData(); sheet->SetComplete(); sheet->ReplaceSync(""_ns, aRv); MOZ_ASSERT(!aRv.Failed()); // 4. Return sheet. return sheet.forget(); } StyleSheet::~StyleSheet() { MOZ_ASSERT(!mInner, "Inner should have been dropped in LastRelease"); } bool StyleSheet::HasRules() const { return Servo_StyleSheet_HasRules(Inner().mContents); } Document* StyleSheet::GetAssociatedDocument() const { auto* associated = GetAssociatedDocumentOrShadowRoot(); return associated ? associated->AsNode().OwnerDoc() : nullptr; } dom::DocumentOrShadowRoot* StyleSheet::GetAssociatedDocumentOrShadowRoot() const { const StyleSheet& outer = OutermostSheet(); if (outer.mDocumentOrShadowRoot) { return outer.mDocumentOrShadowRoot; } if (outer.IsConstructed()) { return outer.mConstructorDocument; } return nullptr; } void StyleSheet::UpdateRelevantGlobal() { if (mRelevantGlobal || !IsComplete()) { return; } if (Document* doc = GetAssociatedDocument()) { mRelevantGlobal = doc->GetScopeObject(); } } Document* StyleSheet::GetKeptAliveByDocument() const { const StyleSheet& outer = OutermostSheet(); if (outer.mDocumentOrShadowRoot) { return outer.mDocumentOrShadowRoot->AsNode().GetComposedDoc(); } if (outer.IsConstructed()) { for (DocumentOrShadowRoot* adopter : outer.mAdopters) { MOZ_ASSERT(adopter->AsNode().OwnerDoc() == outer.mConstructorDocument); if (adopter->AsNode().IsInComposedDoc()) { return outer.mConstructorDocument.get(); } } } return nullptr; } void StyleSheet::LastRelease() { MOZ_DIAGNOSTIC_ASSERT(mAdopters.IsEmpty(), "Should have no adopters at time of destruction."); if (mInner) { MOZ_ASSERT(mInner->mSheets.Contains(this), "Our mInner should include us."); mInner->RemoveSheet(this); mInner = nullptr; } DropMedia(); DropRuleList(); } void StyleSheet::UnlinkInner() { if (!mInner) { return; } // 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) { mInner->RemoveSheet(this); mInner = nullptr; return; } for (StyleSheet* child : ChildSheets()) { MOZ_ASSERT(child->mParentSheet == this, "We have a unique inner!"); child->mParentSheet = nullptr; } Inner().mChildren.Clear(); } void StyleSheet::TraverseInner(nsCycleCollectionTraversalCallback& cb) { if (!mInner) { return; } for (StyleSheet* child : ChildSheets()) { if (child->mParentSheet == this) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "child sheet"); cb.NoteXPCOMChild(child); } } } // 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_WRAPPERCACHE_CLASS(StyleSheet) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StyleSheet) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelevantGlobal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConstructorDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReplacePromise) 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(mRelevantGlobal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mConstructorDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReplacePromise) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END dom::CSSStyleSheetParsingMode StyleSheet::ParsingModeDOM() { #define CHECK_MODE(X, Y) \ static_assert( \ static_cast(X) == static_cast(Y), \ "mozilla::dom::CSSStyleSheetParsingMode and " \ "mozilla::css::SheetParsingMode should have identical values"); CHECK_MODE(dom::CSSStyleSheetParsingMode::Agent, css::eAgentSheetFeatures); CHECK_MODE(dom::CSSStyleSheetParsingMode::User, css::eUserSheetFeatures); CHECK_MODE(dom::CSSStyleSheetParsingMode::Author, css::eAuthorSheetFeatures); #undef CHECK_MODE return static_cast(mParsingMode); } void StyleSheet::SetComplete() { // HasForcedUniqueInner() is okay if the sheet is constructed, because // constructed sheets are always unique and they may be set to complete // multiple times if their rules are replaced via Replace() MOZ_ASSERT(IsConstructed() || !HasForcedUniqueInner(), "Can't complete a sheet that's already been forced unique."); MOZ_ASSERT(!IsComplete(), "Already complete?"); mState |= State::Complete; UpdateRelevantGlobal(); if (!Disabled()) { ApplicableStateChanged(true); } MaybeResolveReplacePromise(); } void StyleSheet::ApplicableStateChanged(bool aApplicable) { MOZ_ASSERT(aApplicable == IsApplicable()); Document* docToPostEvent = nullptr; auto Notify = [&](DocumentOrShadowRoot& target) { nsINode& node = target.AsNode(); if (ShadowRoot* shadow = ShadowRoot::FromNode(node)) { shadow->StyleSheetApplicableStateChanged(*this); MOZ_ASSERT(!docToPostEvent || !shadow->IsInComposedDoc() || docToPostEvent == shadow->GetComposedDoc()); if (!docToPostEvent) { docToPostEvent = shadow->GetComposedDoc(); } } else { Document* doc = node.AsDocument(); MOZ_ASSERT(!docToPostEvent || docToPostEvent == doc); doc->StyleSheetApplicableStateChanged(*this); docToPostEvent = doc; } }; const StyleSheet& sheet = OutermostSheet(); if (sheet.mDocumentOrShadowRoot) { Notify(*sheet.mDocumentOrShadowRoot); } if (sheet.mConstructorDocument) { Notify(*sheet.mConstructorDocument); } for (DocumentOrShadowRoot* adopter : sheet.mAdopters) { MOZ_ASSERT(adopter, "adopters should never be null"); if (adopter != sheet.mConstructorDocument) { Notify(*adopter); } } if (docToPostEvent) { docToPostEvent->PostStyleSheetApplicableStateChangeEvent(*this); } } void StyleSheet::SetDisabled(bool aDisabled) { if (IsReadOnly()) { return; } if (aDisabled == Disabled()) { return; } if (aDisabled) { mState |= State::Disabled; } else { mState &= ~State::Disabled; } if (IsComplete()) { ApplicableStateChanged(!aDisabled); } } void StyleSheet::SetURLExtraData() { Inner().mURLData = new URLExtraData(GetBaseURI(), GetReferrerInfo(), Principal()); } nsISupports* StyleSheet::GetRelevantGlobal() const { const StyleSheet& outer = OutermostSheet(); return outer.mRelevantGlobal; } StyleSheetInfo::StyleSheetInfo(CORSMode aCORSMode, const SRIMetadata& aIntegrity, css::SheetParsingMode aParsingMode) : mPrincipal(NullPrincipal::CreateWithoutOriginAttributes()), mCORSMode(aCORSMode), mReferrerInfo(new ReferrerInfo(nullptr)), mIntegrity(aIntegrity), mContents(Servo_StyleSheet_Empty(aParsingMode).Consume()), mURLData(URLExtraData::Dummy()) { 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), mReferrerInfo(aCopy.mReferrerInfo), mIntegrity(aCopy.mIntegrity), // We don't rebuild the child because we're making a copy without // children. mSourceMapURL(aCopy.mSourceMapURL), 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) { // Fix up the parent pointer in children lists. StyleSheet* newParent = aSheet == mSheets[0] ? mSheets.SafeElementAt(1) : mSheets[0]; for (StyleSheet* child : mChildren) { MOZ_ASSERT(child->mParentSheet); MOZ_ASSERT(child->mParentSheet->mInner == this); if (child->mParentSheet == aSheet) { child->mParentSheet = newParent; } } if (1 == mSheets.Length()) { NS_ASSERTION(aSheet == mSheets.ElementAt(0), "bad parent"); delete this; return; } mSheets.RemoveElement(aSheet); } 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() { MOZ_ASSERT(!IsReadOnly()); if (IsComplete()) { EnsureUniqueInner(); } } void StyleSheet::AddStyleSet(ServoStyleSet* aStyleSet) { MOZ_DIAGNOSTIC_ASSERT(!mStyleSets.Contains(aStyleSet), "style set already registered"); mStyleSets.AppendElement(aStyleSet); } void StyleSheet::DropStyleSet(ServoStyleSet* aStyleSet) { bool found = mStyleSets.RemoveElement(aStyleSet); MOZ_DIAGNOSTIC_ASSERT(found, "didn't find style set"); #ifndef MOZ_DIAGNOSTIC_ASSERT_ENABLED Unused << found; #endif } // NOTE(emilio): Composed doc and containing shadow root are set in child sheets // too, so no need to do it for each ancestor. #define NOTIFY(function_, args_) \ do { \ StyleSheet* current = this; \ do { \ for (ServoStyleSet * set : current->mStyleSets) { \ set->function_ args_; \ } \ if (auto* docOrShadow = current->mDocumentOrShadowRoot) { \ if (auto* shadow = ShadowRoot::FromNode(docOrShadow->AsNode())) { \ shadow->function_ args_; \ } else { \ docOrShadow->AsNode().AsDocument()->function_ args_; \ } \ } \ for (auto* adopter : mAdopters) { \ if (auto* shadow = ShadowRoot::FromNode(adopter->AsNode())) { \ shadow->function_ args_; \ } else { \ adopter->AsNode().AsDocument()->function_ args_; \ } \ } \ current = current->mParentSheet; \ } while (current); \ } while (0) void StyleSheet::EnsureUniqueInner() { MOZ_ASSERT(mInner->mSheets.Length() != 0, "unexpected number of outers"); if (IsReadOnly()) { // Sheets that can't be modified don't need a unique inner. return; } 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. FixUpAfterInnerClone(); // let our containing style sets know that if we call // nsPresContext::EnsureSafeToHandOutCSSRules we will need to restyle the // document NOTIFY(SheetCloned, (*this)); } // WebIDL CSSStyleSheet API dom::CSSRuleList* StyleSheet::GetCssRules(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!AreRulesAvailable(aSubjectPrincipal, aRv)) { return nullptr; } return GetCssRulesInternal(); } void StyleSheet::GetSourceMapURL(nsACString& aSourceMapURL) { if (!mInner->mSourceMapURL.IsEmpty()) { aSourceMapURL = mInner->mSourceMapURL; return; } Servo_StyleSheet_GetSourceMapURL(mInner->mContents, &aSourceMapURL); } void StyleSheet::SetSourceMapURL(nsCString&& aSourceMapURL) { mInner->mSourceMapURL = std::move(aSourceMapURL); } void StyleSheet::GetSourceURL(nsACString& aSourceURL) { Servo_StyleSheet_GetSourceURL(mInner->mContents, &aSourceURL); } css::Rule* StyleSheet::GetDOMOwnerRule() const { return GetOwnerRule(); } // https://drafts.csswg.org/cssom/#dom-cssstylesheet-insertrule // https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-insertrule uint32_t StyleSheet::InsertRule(const nsACString& aRule, uint32_t aIndex, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) { return 0; } if (ModificationDisallowed()) { aRv.ThrowNotAllowedError( "This method can only be called on " "modifiable style sheets"); return 0; } return InsertRuleInternal(aRule, aIndex, aRv); } // https://drafts.csswg.org/cssom/#dom-cssstylesheet-deleterule // https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-deleterule void StyleSheet::DeleteRule(uint32_t aIndex, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) { return; } if (ModificationDisallowed()) { return aRv.ThrowNotAllowedError( "This method can only be called on " "modifiable style sheets"); } return DeleteRuleInternal(aIndex, aRv); } int32_t StyleSheet::AddRule(const nsACString& aSelector, const nsACString& aBlock, const Optional& aIndex, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) { return -1; } nsAutoCString rule; rule.Append(aSelector); rule.AppendLiteral(" { "); if (!aBlock.IsEmpty()) { rule.Append(aBlock); rule.Append(' '); } rule.Append('}'); auto index = aIndex.WasPassed() ? aIndex.Value() : GetCssRulesInternal()->Length(); InsertRuleInternal(rule, index, aRv); // Always return -1. return -1; } void StyleSheet::MaybeResolveReplacePromise() { MOZ_ASSERT(!!mReplacePromise == ModificationDisallowed()); if (!mReplacePromise) { return; } SetModificationDisallowed(false); mReplacePromise->MaybeResolve(this); mReplacePromise = nullptr; } void StyleSheet::MaybeRejectReplacePromise() { MOZ_ASSERT(!!mReplacePromise == ModificationDisallowed()); if (!mReplacePromise) { return; } SetModificationDisallowed(false); mReplacePromise->MaybeRejectWithNetworkError( "@import style sheet load failed"); mReplacePromise = nullptr; } // https://drafts.csswg.org/cssom/#dom-cssstylesheet-replace already_AddRefed StyleSheet::Replace(const nsACString& aText, ErrorResult& aRv) { nsIGlobalObject* globalObject = nullptr; const StyleSheet& outer = OutermostSheet(); if (outer.mRelevantGlobal) { globalObject = outer.mRelevantGlobal; } else if (Document* doc = outer.GetAssociatedDocument()) { globalObject = doc->GetScopeObject(); } RefPtr promise = dom::Promise::Create(globalObject, aRv); if (!promise) { return nullptr; } // Step 1 and 4 are variable declarations // 2.1 Check if sheet is constructed, else reject promise. if (!IsConstructed()) { promise->MaybeRejectWithNotAllowedError( "This method can only be called on " "constructed style sheets"); return promise.forget(); } // 2.2 Check if sheet is modifiable, else throw. if (ModificationDisallowed()) { promise->MaybeRejectWithNotAllowedError( "This method can only be called on " "modifiable style sheets"); return promise.forget(); } // 3. Disallow modifications until finished. SetModificationDisallowed(true); // TODO(emilio, 1642227): Should constructable stylesheets notify global // observers (i.e., set mMustNotify to true)? auto* loader = mConstructorDocument->CSSLoader(); auto loadData = MakeRefPtr( loader, /* aURI = */ nullptr, this, css::SyncLoad::No, css::Loader::UseSystemPrincipal::No, css::StylePreloadKind::None, /* aPreloadEncoding */ nullptr, /* aObserver */ nullptr, mConstructorDocument->NodePrincipal(), GetReferrerInfo(), /* aNonce */ u""_ns, FetchPriority::Auto); // In parallel // 5.1 Parse aText into rules. // 5.2 Load import rules, throw NetworkError if failed. // 5.3 Set sheet's rules to new rules. nsISerialEventTarget* target = GetMainThreadSerialEventTarget(); loadData->mIsBeingParsed = true; MOZ_ASSERT(!mReplacePromise); mReplacePromise = promise; RefPtr holder( new css::SheetLoadDataHolder(__func__, loadData, false)); ParseSheet(*loader, aText, holder) ->Then( target, __func__, [loadData] { loadData->SheetFinishedParsingAsync(); }, [] { MOZ_CRASH("This MozPromise should never be rejected."); }); // 6. Return the promise return promise.forget(); } // https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-replacesync void StyleSheet::ReplaceSync(const nsACString& aText, ErrorResult& aRv) { // Step 1 is a variable declaration // 2.1 Check if sheet is constructed, else throw. if (!IsConstructed()) { return aRv.ThrowNotAllowedError( "Can only be called on constructed style sheets"); } // 2.2 Check if sheet is modifiable, else throw. if (ModificationDisallowed()) { return aRv.ThrowNotAllowedError( "Can only be called on modifiable style sheets"); } // 3. Parse aText into rules. // 4. If rules contain @imports, skip them and continue parsing. auto* loader = mConstructorDocument->CSSLoader(); RefPtr rawContent = Servo_StyleSheet_FromUTF8Bytes( loader, this, /* load_data = */ nullptr, &aText, mParsingMode, URLData(), mConstructorDocument->GetCompatibilityMode(), /* reusable_sheets = */ nullptr, mConstructorDocument->GetStyleUseCounters(), StyleAllowImportRules::No, StyleSanitizationKind::None, /* sanitized_output = */ nullptr) .Consume(); // 5. Set sheet's rules to the new rules. Inner().mContents = std::move(rawContent); FixUpRuleListAfterContentsChangeIfNeeded(); RuleChanged(nullptr, StyleRuleChangeKind::Generic); } 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; } if (IsReadOnly()) { return NS_OK; } WillDirty(); nsresult result = aGroup->DeleteStyleRuleAt(aIndex); NS_ENSURE_SUCCESS(result, result); rule->DropReferences(); RuleRemoved(*rule); return NS_OK; } void StyleSheet::RuleAdded(css::Rule& aRule) { SetModifiedRules(); NOTIFY(RuleAdded, (*this, aRule)); } void StyleSheet::RuleRemoved(css::Rule& aRule) { SetModifiedRules(); NOTIFY(RuleRemoved, (*this, aRule)); } void StyleSheet::RuleChanged(css::Rule* aRule, StyleRuleChangeKind aKind) { MOZ_ASSERT(!aRule || HasUniqueInner(), "Shouldn't have mutated a shared sheet"); SetModifiedRules(); NOTIFY(RuleChanged, (*this, aRule, aKind)); } // nsICSSLoaderObserver implementation NS_IMETHODIMP StyleSheet::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred, nsresult aStatus) { if (!aSheet->GetParentSheet()) { return NS_OK; // ignore if sheet has been detached already } MOZ_ASSERT(this == aSheet->GetParentSheet(), "We are being notified of a sheet load for a sheet that is not " "our child!"); if (NS_FAILED(aStatus)) { return NS_OK; } MOZ_ASSERT(aSheet->GetOwnerRule()); NOTIFY(ImportRuleLoaded, (*aSheet->GetOwnerRule(), *aSheet)); return NS_OK; } #undef NOTIFY nsresult StyleSheet::InsertRuleIntoGroup(const nsACString& 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; } if (IsReadOnly()) { return NS_OK; } if (ModificationDisallowed()) { return NS_ERROR_DOM_NOT_ALLOWED_ERR; } 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 (Document* 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 && mParentSheet) { windowID = mParentSheet->FindOwningWindowInnerID(); } return windowID; } void StyleSheet::RemoveFromParent() { if (!mParentSheet) { return; } MOZ_ASSERT(mParentSheet->ChildSheets().Contains(this)); mParentSheet->Inner().mChildren.RemoveElement(this); mParentSheet = 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.ThrowSecurityError("Not allowed to access cross-origin stylesheet"); 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.ThrowInvalidAccessError( "Not allowed to access still-loading stylesheet"); return; } WillDirty(); info.mPrincipal = &aSubjectPrincipal; } bool StyleSheet::AreRulesAvailable(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { // Rules are not available on incomplete sheets. if (!IsComplete()) { aRv.ThrowInvalidAccessError( "Can't access rules of still-loading style sheet"); 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; } void StyleSheet::SetAssociatedDocumentOrShadowRoot( DocumentOrShadowRoot* aDocOrShadowRoot) { MOZ_ASSERT(!IsConstructed()); MOZ_ASSERT(!mParentSheet || !aDocOrShadowRoot, "Shouldn't be set on child sheets"); // not ref counted mDocumentOrShadowRoot = aDocOrShadowRoot; UpdateRelevantGlobal(); } void StyleSheet::AppendStyleSheet(StyleSheet& aSheet) { WillDirty(); AppendStyleSheetSilently(aSheet); } void StyleSheet::AppendStyleSheetSilently(StyleSheet& aSheet) { MOZ_ASSERT(!IsReadOnly()); Inner().mChildren.AppendElement(&aSheet); // This is not reference counted. Our parent tells us when // it's going away. aSheet.mParentSheet = this; } size_t StyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { size_t n = 0; n += aMallocSizeOf(this); // We want to measure the inner with only one of the children, and it makes // sense for it to be the latest as it is the most likely to be reachable. if (Inner().mSheets.LastElement() == this) { n += Inner().SizeOfIncludingThis(aMallocSizeOf); } // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - mTitle // - mMedia // - mStyleSets // - mRuleList return n; } #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER) void StyleSheet::List(FILE* aOut, int32_t aIndent) { for (StyleSheet* child : ChildSheets()) { child->List(aOut, aIndent); } nsCString line; for (int i = 0; i < aIndent; ++i) { line.AppendLiteral(" "); } line.AppendLiteral("/* "); nsCString url; GetSheetURI()->GetSpec(url); if (url.IsEmpty()) { line.AppendLiteral("(no URL)"); } else { line.Append(url); } line.AppendLiteral(" ("); switch (GetOrigin()) { case StyleOrigin::UserAgent: line.AppendLiteral("User Agent"); break; case StyleOrigin::User: line.AppendLiteral("User"); break; case StyleOrigin::Author: line.AppendLiteral("Author"); break; } if (mMedia) { nsAutoCString buffer; mMedia->GetText(buffer); if (!buffer.IsEmpty()) { line.AppendLiteral(", "); line.Append(buffer); } } line.AppendLiteral(") */"); fprintf_stderr(aOut, "%s\n\n", line.get()); nsCString newlineIndent; newlineIndent.Append('\n'); for (int i = 0; i < aIndent; ++i) { newlineIndent.AppendLiteral(" "); } ServoCSSRuleList* ruleList = GetCssRulesInternal(); for (uint32_t i = 0, len = ruleList->Length(); i < len; ++i) { css::Rule* rule = ruleList->GetRule(i); nsAutoCString cssText; rule->GetCssText(cssText); cssText.ReplaceSubstring("\n"_ns, newlineIndent); fprintf_stderr(aOut, "%s\n", cssText.get()); } if (ruleList->Length() != 0) { fprintf_stderr(aOut, "\n"); } } #endif void StyleSheet::SetMedia(already_AddRefed aMedia) { mMedia = aMedia; if (mMedia) { mMedia->SetStyleSheet(this); } } void StyleSheet::DropMedia() { if (mMedia) { mMedia->SetStyleSheet(nullptr); mMedia = nullptr; } } dom::MediaList* StyleSheet::Media() { if (!mMedia) { mMedia = dom::MediaList::Create(EmptyCString()); mMedia->SetStyleSheet(this); } return mMedia; } // nsWrapperCache JSObject* StyleSheet::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return dom::CSSStyleSheet_Binding::Wrap(aCx, this, aGivenProto); } void StyleSheet::FixUpRuleListAfterContentsChangeIfNeeded(bool aFromClone) { if (!mRuleList) { return; } RefPtr rules = Servo_StyleSheet_GetRules(Inner().mContents.get()).Consume(); mRuleList->SetRawContents(std::move(rules), aFromClone); } void StyleSheet::FixUpAfterInnerClone() { MOZ_ASSERT(Inner().mSheets.Length() == 1, "Should've just cloned"); MOZ_ASSERT(Inner().mSheets[0] == this); MOZ_ASSERT(Inner().mChildren.IsEmpty()); FixUpRuleListAfterContentsChangeIfNeeded(/* aFromClone = */ true); RefPtr rules = Servo_StyleSheet_GetRules(Inner().mContents.get()).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); AppendStyleSheetSilently(*sheet); index++; } } already_AddRefed StyleSheet::CreateEmptyChildSheet( already_AddRefed aMediaList) const { RefPtr child = new StyleSheet(ParsingMode(), CORSMode::CORS_NONE, SRIMetadata()); child->mMedia = aMediaList; return child.forget(); } RefPtr StyleSheet::ParseSheet( css::Loader& aLoader, const nsACString& aBytes, const RefPtr& aLoadData) { MOZ_ASSERT(mParsePromise.IsEmpty()); MOZ_ASSERT_IF(NS_IsMainThread(), mAsyncParseBlockers == 0); RefPtr p = mParsePromise.Ensure(__func__); if (!aLoadData->get()->ShouldDefer()) { mParsePromise.SetTaskPriority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING, __func__); } BlockParsePromise(); SetURLExtraData(); // @import rules are disallowed due to this decision: // https://github.com/WICG/construct-stylesheets/issues/119#issuecomment-588352418 // We may allow @import rules again in the future. auto allowImportRules = SelfOrAncestorIsConstructed() ? StyleAllowImportRules::No : StyleAllowImportRules::Yes; URLExtraData* urlData = URLData(); const bool shouldRecordCounters = aLoader.GetDocument() && aLoader.GetDocument()->GetStyleUseCounters() && !urlData->ChromeRulesEnabled(); if (aLoadData->get()->mRecordErrors) { MOZ_ASSERT(NS_IsMainThread()); UniquePtr counters; if (shouldRecordCounters) { counters.reset(Servo_UseCounters_Create()); } RefPtr contents = Servo_StyleSheet_FromUTF8Bytes( &aLoader, this, aLoadData->get(), &aBytes, mParsingMode, urlData, aLoadData->get()->mCompatMode, /* reusable_sheets = */ nullptr, counters.get(), allowImportRules, StyleSanitizationKind::None, /* sanitized_output = */ nullptr) .Consume(); FinishAsyncParse(contents.forget(), std::move(counters)); } else { Servo_StyleSheet_FromUTF8BytesAsync( aLoadData, urlData, &aBytes, mParsingMode, aLoadData->get()->mCompatMode, shouldRecordCounters, allowImportRules); } return p; } void StyleSheet::FinishAsyncParse( already_AddRefed aSheetContents, UniquePtr aUseCounters) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mParsePromise.IsEmpty()); Inner().mContents = aSheetContents; Inner().mUseCounters = std::move(aUseCounters); FixUpRuleListAfterContentsChangeIfNeeded(); UnblockParsePromise(); } void StyleSheet::ParseSheetSync( css::Loader* aLoader, const nsACString& aBytes, css::SheetLoadData* aLoadData, css::LoaderReusableStyleSheets* aReusableSheets) { const nsCompatibility compatMode = [&] { if (aLoadData) { return aLoadData->mCompatMode; } if (aLoader) { return aLoader->CompatMode(css::StylePreloadKind::None); } return eCompatibility_FullStandards; }(); SetURLExtraData(); URLExtraData* urlData = URLData(); const StyleUseCounters* useCounters = aLoader && aLoader->GetDocument() && !urlData->ChromeRulesEnabled() ? aLoader->GetDocument()->GetStyleUseCounters() : nullptr; auto allowImportRules = SelfOrAncestorIsConstructed() ? StyleAllowImportRules::No : StyleAllowImportRules::Yes; Inner().mContents = Servo_StyleSheet_FromUTF8Bytes( aLoader, this, aLoadData, &aBytes, mParsingMode, urlData, compatMode, aReusableSheets, useCounters, allowImportRules, StyleSanitizationKind::None, /* sanitized_output = */ nullptr) .Consume(); } void StyleSheet::ReparseSheet(const nsACString& aInput, ErrorResult& aRv) { if (!IsComplete()) { return aRv.ThrowInvalidAccessError("Cannot reparse still-loading sheet"); } // Allowing to modify UA sheets is dangerous (in the sense that C++ code // relies on rules in those sheets), plus they're probably going to be shared // across processes in which case this is directly a no-go. if (IsReadOnly()) { return; } // Hold strong ref to the CSSLoader in case the document update // kills the document RefPtr loader; if (Document* 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 : ChildSheets()) { if (child->GetOriginalURI()) { reusableSheets.AddReusableSheet(child); } } // Clean up child sheets list. for (StyleSheet* child : ChildSheets()) { child->mParentSheet = nullptr; } Inner().mChildren.Clear(); // 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); RuleRemoved(*rule); } // We need to clear the rule list here (rather than after parsing) because // ParseSheetSync may reuse child sheets, which would cause us to end up // with a wrong mChilden array. ruleList->SetRawContents(nullptr, /* aFromClone = */ false); } ParseSheetSync(loader, aInput, /* aLoadData = */ nullptr, &reusableSheets); FixUpRuleListAfterContentsChangeIfNeeded(); // 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); RuleAdded(*rule); } } // Our rules are no longer considered modified for devtools. mState &= ~State::ModifiedRulesForDevtools; } void StyleSheet::DropRuleList() { if (mRuleList) { mRuleList->DropReferences(); mRuleList = nullptr; } } already_AddRefed StyleSheet::Clone( StyleSheet* aCloneParent, dom::DocumentOrShadowRoot* aCloneDocumentOrShadowRoot) const { MOZ_ASSERT(!IsConstructed(), "Cannot create a non-constructed sheet from a constructed sheet"); RefPtr clone = new StyleSheet(*this, aCloneParent, aCloneDocumentOrShadowRoot, /* aConstructorDocToUse */ nullptr); return clone.forget(); } already_AddRefed StyleSheet::CloneAdoptedSheet( Document& aConstructorDocument) const { MOZ_ASSERT(IsConstructed(), "Cannot create a constructed sheet from a non-constructed sheet"); MOZ_ASSERT(aConstructorDocument.IsStaticDocument(), "Should never clone adopted sheets for a non-static document"); RefPtr clone = new StyleSheet(*this, /* aParentSheetToUse */ nullptr, /* aDocOrShadowRootToUse */ nullptr, &aConstructorDocument); return clone.forget(); } ServoCSSRuleList* StyleSheet::GetCssRulesInternal() { if (!mRuleList) { // TODO(emilio): This should go away, but we need to fix the CC setup for // @import rules first, see bug 1719963. 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 nsACString& aRule, uint32_t aIndex, ErrorResult& aRv) { MOZ_ASSERT(!IsReadOnly()); MOZ_ASSERT(!ModificationDisallowed()); // 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); RuleAdded(*rule); return aIndex; } void StyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv) { MOZ_ASSERT(!IsReadOnly()); MOZ_ASSERT(!ModificationDisallowed()); // Ensure mRuleList is constructed. GetCssRulesInternal(); if (aIndex >= mRuleList->Length()) { aRv.ThrowIndexSizeError( nsPrintfCString("Cannot delete rule at index %u" " because the number of rules is only %u", aIndex, mRuleList->Length())); 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); if (!aRv.Failed()) { RuleRemoved(*rule); } } nsresult StyleSheet::InsertRuleIntoGroupInternal(const nsACString& aRule, css::GroupRule* aGroup, uint32_t aIndex) { MOZ_ASSERT(!IsReadOnly()); ServoCSSRuleList* rules = aGroup->CssRules(); MOZ_ASSERT(rules && rules->GetParentRule() == aGroup); return rules->InsertRule(aRule, aIndex); } StyleOrigin StyleSheet::GetOrigin() const { return Servo_StyleSheet_GetOrigin(Inner().mContents); } void StyleSheet::SetSharedContents(const StyleLockedCssRules* aSharedRules) { MOZ_ASSERT(!IsComplete()); SetURLExtraData(); Inner().mContents = Servo_StyleSheet_FromSharedData(URLData(), aSharedRules).Consume(); } const StyleLockedCssRules* StyleSheet::ToShared( StyleSharedMemoryBuilder* aBuilder, nsCString& aErrorMessage) { // Assert some things we assume when creating a StyleSheet using shared // memory. MOZ_ASSERT(GetReferrerInfo()->ReferrerPolicy() == ReferrerPolicy::_empty); MOZ_ASSERT(GetReferrerInfo()->GetSendReferrer()); MOZ_ASSERT(!nsCOMPtr(GetReferrerInfo()->GetComputedReferrer())); MOZ_ASSERT(GetCORSMode() == CORS_NONE); MOZ_ASSERT(Inner().mIntegrity.IsEmpty()); MOZ_ASSERT(Principal()->IsSystemPrincipal()); const StyleLockedCssRules* rules = Servo_SharedMemoryBuilder_AddStylesheet( aBuilder, Inner().mContents, &aErrorMessage); #ifdef DEBUG if (!rules) { // Print the ToShmem error message so that developers know what to fix. printf_stderr("%s\n", aErrorMessage.get()); MOZ_CRASH("UA style sheet contents failed shared memory requirements"); } #endif return rules; } bool StyleSheet::IsReadOnly() const { return IsComplete() && GetOrigin() == StyleOrigin::UserAgent; } } // namespace mozilla