/* -*- 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/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/MediaList.h" #include "mozilla/dom/Promise.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 "mozAutoDocUpdate.h" #include "SheetLoadData.h" namespace mozilla { using namespace dom; StyleSheet::StyleSheet(css::SheetParsingMode aParsingMode, CORSMode aCORSMode, const dom::SRIMetadata& aIntegrity) : mParent(nullptr), mConstructorDocument(nullptr), mDocumentOrShadowRoot(nullptr), mOwningNode(nullptr), mOwnerRule(nullptr), mParsingMode(aParsingMode), mState(static_cast(0)), mAssociationMode(NotOwnedByDocumentOrShadowRoot), mInner(new StyleSheetInfo(aCORSMode, aIntegrity, aParsingMode)) { mInner->AddSheet(this); } StyleSheet::StyleSheet(const StyleSheet& aCopy, StyleSheet* aParentToUse, dom::CSSImportRule* aOwnerRuleToUse, dom::DocumentOrShadowRoot* aDocumentOrShadowRoot, nsINode* aOwningNodeToUse) : mParent(aParentToUse), mConstructorDocument(aCopy.mConstructorDocument), 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); // 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.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR, "CSSStyleSheet constructor not supported when there " "is no document"); return nullptr; } Document* constructorDocument = window->GetExtantDoc(); if (!constructorDocument) { aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR, "CSSStyleSheet constructor not supported when there " "is no document"); return nullptr; } // 1. Construct a sheet and set its properties (see spec). // TODO(nordzilla): Various issues with the spec's handling of aOptions: // * https://github.com/WICG/construct-stylesheets/issues/113 // * https://github.com/WICG/construct-stylesheets/issues/105 auto sheet = MakeRefPtr(css::SheetParsingMode::eAuthorSheetFeatures, CORSMode::CORS_NONE, dom::SRIMetadata()); nsIURI* baseURI = constructorDocument->GetBaseURI(); nsIURI* sheetURI = constructorDocument->GetDocumentURI(); nsIURI* originalURI = nullptr; sheet->SetURIs(sheetURI, originalURI, baseURI); sheet->SetTitle(aOptions.mTitle); sheet->SetPrincipal(constructorDocument->NodePrincipal()); sheet->SetReferrerInfo(constructorDocument->GetReferrerInfo()); sheet->mConstructorDocument = constructorDocument; // 2. Set the sheet's media according to aOptions. if (aOptions.mMedia.IsString()) { sheet->SetMedia(MediaList::Create(aOptions.mMedia.GetAsString())); } else { sheet->SetMedia(aOptions.mMedia.GetAsMediaList()->Clone()); } // 3. Set the sheet's disabled flag according to aOptions. sheet->SetDisabled(aOptions.mDisabled); sheet->SetComplete(); // 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 { return mDocumentOrShadowRoot ? mDocumentOrShadowRoot->AsNode().OwnerDoc() : nullptr; } Document* 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; } for (StyleSheet* child : ChildSheets()) { 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 doesn'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(); } Inner().mChildren.Clear(); } 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; } for (StyleSheet* child : ChildSheets()) { 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_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(mConstructorDocument) 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(mConstructorDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(StyleSheet) 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() { 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 (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()); } 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()) #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), mReferrerInfo(aCopy.mReferrerInfo), mIntegrity(aCopy.mIntegrity), // 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[0] && mSheets.Length() > 1) { StyleSheet* newParent = mSheets[1]; for (StyleSheet* child : mChildren) { child->mParent = newParent; child->SetAssociatedDocumentOrShadowRoot(newParent->mDocumentOrShadowRoot, newParent->mAssociationMode); } } 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 { \ if (auto* shadow = GetContainingShadow()) { \ shadow->function_ args_; \ } \ if (auto* doc = GetComposedDoc()) { \ doc->function_ args_; \ } \ StyleSheet* current = this; \ do { \ for (ServoStyleSet * set : current->mStyleSets) { \ set->function_ args_; \ } \ current = current->mParent; \ } 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. BuildChildListAfterInnerClone(); // 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(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 (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) { return 0; } return InsertRuleInternal(aRule, aIndex, aRv); } void StyleSheet::DeleteRule(uint32_t aIndex, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) { return; } return DeleteRuleInternal(aIndex, aRv); } int32_t StyleSheet::AddRule(const nsAString& aSelector, const nsAString& aBlock, const Optional& aIndex, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (IsReadOnly() || !AreRulesAvailable(aSubjectPrincipal, aRv)) { return -1; } nsAutoString 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; } // https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-replace already_AddRefed StyleSheet::Replace(const nsAString& aText, ErrorResult& aRv) { // TODO(nordzilla) This is a stub to land the Constructable Stylesheets // API under a preference (Bug 1604296). Functionality will be added later. // Step 1 and 4 are variable declarations // 2.1 Check if sheet is constructed, else throw. if (!mConstructorDocument) { aRv.ThrowDOMException( NS_ERROR_DOM_NOT_ALLOWED_ERR, "The replace() method can only be called on constructed style sheets"); return nullptr; } // 2.2 Check if sheet is modifiable, else throw. // 3. Disallow modifications until finished. nsIGlobalObject* globalObject = mConstructorDocument->GetScopeObject(); RefPtr promise = dom::Promise::Create(globalObject, aRv); if (!promise) { return nullptr; } // 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. promise->MaybeResolve(this); // 6. Return the promise return promise.forget(); } // https://wicg.github.io/construct-stylesheets/#dom-cssstylesheet-replacesync void StyleSheet::ReplaceSync(const nsAString& aText, ErrorResult& aRv) { // TODO(nordzilla) This is a stub to land the Constructable Stylesheets // API under a preference (Bug 1604296). Functionality will be added later. // Step 1 is a variable declaration // 2.1 Check if sheet is constructed, else throw. if (!mConstructorDocument) { aRv.ThrowDOMException(NS_ERROR_DOM_NOT_ALLOWED_ERR, "The replaceSync() method can only be called on " "constructed style sheets"); return; } // 2.2 Check if sheet is modifiable, else throw. // 3. Parse aText into rules. // 4. If rules contain @imports, throw NotAllowedError // 5. Set sheet's rules to rules. } 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; } ShadowRoot* StyleSheet::GetContainingShadow() const { auto* docOrShadow = GetAssociatedDocumentOrShadowRoot(); if (!docOrShadow) { return nullptr; } return ShadowRoot::FromNode(docOrShadow->AsNode()); } 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) { SetModifiedRules(); NOTIFY(RuleChanged, (*this, aRule)); } // 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 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; } if (IsReadOnly()) { return NS_OK; } 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 && mParent) { windowID = mParent->FindOwningWindowInnerID(); } return windowID; } void StyleSheet::RemoveFromParent() { if (!mParent) { return; } MOZ_ASSERT(mParent->ChildSheets().Contains(this)); mParent->Inner().mChildren.RemoveElement(this); mParent = nullptr; ClearAssociatedDocumentOrShadowRoot(); } void StyleSheet::UnparentChildren() { // XXXbz this is a little bogus; see the XXX comment where we // declare mFirstChild in StyleSheetInfo. for (StyleSheet* child : ChildSheets()) { 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; } 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 : ChildSheets()) { if (child->mParent == this) { child->SetAssociatedDocumentOrShadowRoot(aDocOrShadowRoot, aAssociationMode); } } } 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.mParent = this; aSheet.SetAssociatedDocumentOrShadowRoot(mDocumentOrShadowRoot, mAssociationMode); } 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; } #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 : ChildSheets()) { child->List(out, aIndent + 1); } } #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(nsString()); mMedia->SetStyleSheet(this); } return mMedia; } // nsWrapperCache JSObject* StyleSheet::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return dom::CSSStyleSheet_Binding::Wrap(aCx, this, aGivenProto); } void StyleSheet::BuildChildListAfterInnerClone() { MOZ_ASSERT(Inner().mSheets.Length() == 1, "Should've just cloned"); MOZ_ASSERT(Inner().mSheets[0] == this); MOZ_ASSERT(Inner().mChildren.IsEmpty()); 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); 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(); } // 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) { // If the browser is recording CSS errors, we need to use the sequential path // because the parallel path doesn't support that. Document* 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(mParsePromise.IsEmpty()); RefPtr p = mParsePromise.Ensure(__func__); SetURLExtraData(); 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, StyleSanitizationKind::None, /* sanitized_output = */ nullptr) .Consume(); FinishAsyncParse(contents.forget()); } else { auto holder = MakeRefPtr(__func__, &aLoadData); Servo_StyleSheet_FromUTF8BytesAsync( holder, 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; SetURLExtraData(); Inner().mContents = Servo_StyleSheet_FromUTF8Bytes(aLoader, this, aLoadData, &aBytes, mParsingMode, Inner().mURLData, aLineNumber, compatMode, aReusableSheets, useCounters, StyleSanitizationKind::None, /* sanitized_output = */ nullptr) .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 nsACString& aInput) { if (!IsComplete()) { return NS_ERROR_DOM_INVALID_ACCESS_ERR; } // 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 NS_OK; } // 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->mParent = nullptr; child->ClearAssociatedDocumentOrShadowRoot(); } Inner().mChildren.Clear(); 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); RuleRemoved(*rule); } } DropRuleList(); ParseSheetSync(loader, 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); RuleAdded(*rule); } } // Our rules are no longer considered modified for devtools. mState &= ~State::ModifiedRulesForDevtools; 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) { MOZ_ASSERT(!IsReadOnly()); // 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()); // Ensure mRuleList is constructed. GetCssRulesInternal(); if (aIndex >= mRuleList->Length()) { aRv.ThrowDOMException( NS_ERROR_DOM_INDEX_SIZE_ERR, 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); 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) { MOZ_ASSERT(!IsReadOnly()); auto rules = static_cast(aGroup->CssRules()); MOZ_ASSERT(rules->GetParentRule() == aGroup); return rules->InsertRule(aRule, aIndex); } StyleOrigin StyleSheet::GetOrigin() const { return Servo_StyleSheet_GetOrigin(Inner().mContents); } void StyleSheet::SetSharedContents(const ServoCssRules* aSharedRules) { MOZ_ASSERT(!IsComplete()); SetURLExtraData(); Inner().mContents = Servo_StyleSheet_FromSharedData(Inner().mURLData, aSharedRules).Consume(); // Don't call FinishParse(), since that tries to set source map URLs, // which we don't have. } const ServoCssRules* StyleSheet::ToShared( RawServoSharedMemoryBuilder* aBuilder) { // 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()); return Servo_SharedMemoryBuilder_AddStylesheet(aBuilder, Inner().mContents); } bool StyleSheet::IsReadOnly() const { return IsComplete() && GetOrigin() == StyleOrigin::UserAgent; } } // namespace mozilla