michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim:cindent:tabstop=2:expandtab:shiftwidth=2: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* representation of a CSS style sheet */ michael@0: michael@0: #include "nsCSSStyleSheet.h" michael@0: michael@0: #include "nsIAtom.h" michael@0: #include "nsCSSRuleProcessor.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/MediaListBinding.h" michael@0: #include "mozilla/css/NameSpaceRule.h" michael@0: #include "mozilla/css/GroupRule.h" michael@0: #include "mozilla/css/ImportRule.h" michael@0: #include "nsIMediaList.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsString.h" michael@0: #include "nsTArray.h" michael@0: #include "nsIDOMCSSStyleSheet.h" michael@0: #include "nsICSSRuleList.h" michael@0: #include "nsIDOMMediaList.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsError.h" michael@0: #include "nsCSSParser.h" michael@0: #include "mozilla/css/Loader.h" michael@0: #include "nsICSSLoaderObserver.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsXMLNameSpaceMap.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "mozAutoDocUpdate.h" michael@0: #include "nsRuleNode.h" michael@0: #include "nsMediaFeatures.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/dom/CSSStyleSheetBinding.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: michael@0: // ------------------------------- michael@0: // Style Rule List for the DOM michael@0: // michael@0: class CSSRuleListImpl : public nsICSSRuleList michael@0: { michael@0: public: michael@0: CSSRuleListImpl(nsCSSStyleSheet *aStyleSheet); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: virtual nsIDOMCSSRule* michael@0: IndexedGetter(uint32_t aIndex, bool& aFound) MOZ_OVERRIDE; michael@0: virtual uint32_t michael@0: Length() MOZ_OVERRIDE; michael@0: michael@0: void DropReference() { mStyleSheet = nullptr; } michael@0: michael@0: protected: michael@0: virtual ~CSSRuleListImpl(); michael@0: michael@0: nsCSSStyleSheet* mStyleSheet; michael@0: }; michael@0: michael@0: CSSRuleListImpl::CSSRuleListImpl(nsCSSStyleSheet *aStyleSheet) michael@0: { michael@0: // Not reference counted to avoid circular references. michael@0: // The style sheet will tell us when its going away. michael@0: mStyleSheet = aStyleSheet; michael@0: } michael@0: michael@0: CSSRuleListImpl::~CSSRuleListImpl() michael@0: { michael@0: } michael@0: michael@0: DOMCI_DATA(CSSRuleList, CSSRuleListImpl) michael@0: michael@0: // QueryInterface implementation for CSSRuleList michael@0: NS_INTERFACE_MAP_BEGIN(CSSRuleListImpl) michael@0: NS_INTERFACE_MAP_ENTRY(nsICSSRuleList) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRuleList) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSRuleList) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: NS_IMPL_ADDREF(CSSRuleListImpl) michael@0: NS_IMPL_RELEASE(CSSRuleListImpl) michael@0: michael@0: michael@0: uint32_t michael@0: CSSRuleListImpl::Length() michael@0: { michael@0: if (!mStyleSheet) { michael@0: return 0; michael@0: } michael@0: michael@0: return SafeCast(mStyleSheet->StyleRuleCount()); michael@0: } michael@0: michael@0: nsIDOMCSSRule* michael@0: CSSRuleListImpl::IndexedGetter(uint32_t aIndex, bool& aFound) michael@0: { michael@0: aFound = false; michael@0: michael@0: if (mStyleSheet) { michael@0: // ensure rules have correct parent michael@0: mStyleSheet->EnsureUniqueInner(); michael@0: css::Rule* rule = mStyleSheet->GetStyleRuleAt(aIndex); michael@0: if (rule) { michael@0: aFound = true; michael@0: return rule->GetDOMRule(); michael@0: } michael@0: } michael@0: michael@0: // Per spec: "Return Value ... null if ... not a valid index." michael@0: return nullptr; michael@0: } michael@0: michael@0: template michael@0: int32_t DoCompare(Numeric a, Numeric b) michael@0: { michael@0: if (a == b) michael@0: return 0; michael@0: if (a < b) michael@0: return -1; michael@0: return 1; michael@0: } michael@0: michael@0: bool michael@0: nsMediaExpression::Matches(nsPresContext *aPresContext, michael@0: const nsCSSValue& aActualValue) const michael@0: { michael@0: const nsCSSValue& actual = aActualValue; michael@0: const nsCSSValue& required = mValue; michael@0: michael@0: // If we don't have the feature, the match fails. michael@0: if (actual.GetUnit() == eCSSUnit_Null) { michael@0: return false; michael@0: } michael@0: michael@0: // If the expression had no value to match, the match succeeds, michael@0: // unless the value is an integer 0 or a zero length. michael@0: if (required.GetUnit() == eCSSUnit_Null) { michael@0: if (actual.GetUnit() == eCSSUnit_Integer) michael@0: return actual.GetIntValue() != 0; michael@0: if (actual.IsLengthUnit()) michael@0: return actual.GetFloatValue() != 0; michael@0: return true; michael@0: } michael@0: michael@0: NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxAllowed || michael@0: mRange == nsMediaExpression::eEqual, "yikes"); michael@0: int32_t cmp; // -1 (actual < required) michael@0: // 0 (actual == required) michael@0: // 1 (actual > required) michael@0: switch (mFeature->mValueType) { michael@0: case nsMediaFeature::eLength: michael@0: { michael@0: NS_ASSERTION(actual.IsLengthUnit(), "bad actual value"); michael@0: NS_ASSERTION(required.IsLengthUnit(), "bad required value"); michael@0: nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont( michael@0: aPresContext, actual); michael@0: nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont( michael@0: aPresContext, required); michael@0: cmp = DoCompare(actualCoord, requiredCoord); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eInteger: michael@0: case nsMediaFeature::eBoolInteger: michael@0: { michael@0: NS_ASSERTION(actual.GetUnit() == eCSSUnit_Integer, michael@0: "bad actual value"); michael@0: NS_ASSERTION(required.GetUnit() == eCSSUnit_Integer, michael@0: "bad required value"); michael@0: NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger || michael@0: actual.GetIntValue() == 0 || actual.GetIntValue() == 1, michael@0: "bad actual bool integer value"); michael@0: NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger || michael@0: required.GetIntValue() == 0 || required.GetIntValue() == 1, michael@0: "bad required bool integer value"); michael@0: cmp = DoCompare(actual.GetIntValue(), required.GetIntValue()); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eFloat: michael@0: { michael@0: NS_ASSERTION(actual.GetUnit() == eCSSUnit_Number, michael@0: "bad actual value"); michael@0: NS_ASSERTION(required.GetUnit() == eCSSUnit_Number, michael@0: "bad required value"); michael@0: cmp = DoCompare(actual.GetFloatValue(), required.GetFloatValue()); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eIntRatio: michael@0: { michael@0: NS_ASSERTION(actual.GetUnit() == eCSSUnit_Array && michael@0: actual.GetArrayValue()->Count() == 2 && michael@0: actual.GetArrayValue()->Item(0).GetUnit() == michael@0: eCSSUnit_Integer && michael@0: actual.GetArrayValue()->Item(1).GetUnit() == michael@0: eCSSUnit_Integer, michael@0: "bad actual value"); michael@0: NS_ASSERTION(required.GetUnit() == eCSSUnit_Array && michael@0: required.GetArrayValue()->Count() == 2 && michael@0: required.GetArrayValue()->Item(0).GetUnit() == michael@0: eCSSUnit_Integer && michael@0: required.GetArrayValue()->Item(1).GetUnit() == michael@0: eCSSUnit_Integer, michael@0: "bad required value"); michael@0: // Convert to int64_t so we can multiply without worry. Note michael@0: // that while the spec requires that both halves of |required| michael@0: // be positive, the numerator or denominator of |actual| might michael@0: // be zero (e.g., when testing 'aspect-ratio' on a 0-width or michael@0: // 0-height iframe). michael@0: int64_t actualNum = actual.GetArrayValue()->Item(0).GetIntValue(), michael@0: actualDen = actual.GetArrayValue()->Item(1).GetIntValue(), michael@0: requiredNum = required.GetArrayValue()->Item(0).GetIntValue(), michael@0: requiredDen = required.GetArrayValue()->Item(1).GetIntValue(); michael@0: cmp = DoCompare(actualNum * requiredDen, requiredNum * actualDen); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eResolution: michael@0: { michael@0: NS_ASSERTION(actual.GetUnit() == eCSSUnit_Inch || michael@0: actual.GetUnit() == eCSSUnit_Pixel || michael@0: actual.GetUnit() == eCSSUnit_Centimeter, michael@0: "bad actual value"); michael@0: NS_ASSERTION(required.GetUnit() == eCSSUnit_Inch || michael@0: required.GetUnit() == eCSSUnit_Pixel || michael@0: required.GetUnit() == eCSSUnit_Centimeter, michael@0: "bad required value"); michael@0: float actualDPI = actual.GetFloatValue(); michael@0: if (actual.GetUnit() == eCSSUnit_Centimeter) { michael@0: actualDPI = actualDPI * 2.54f; michael@0: } else if (actual.GetUnit() == eCSSUnit_Pixel) { michael@0: actualDPI = actualDPI * 96.0f; michael@0: } michael@0: float requiredDPI = required.GetFloatValue(); michael@0: if (required.GetUnit() == eCSSUnit_Centimeter) { michael@0: requiredDPI = requiredDPI * 2.54f; michael@0: } else if (required.GetUnit() == eCSSUnit_Pixel) { michael@0: requiredDPI = requiredDPI * 96.0f; michael@0: } michael@0: cmp = DoCompare(actualDPI, requiredDPI); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eEnumerated: michael@0: { michael@0: NS_ASSERTION(actual.GetUnit() == eCSSUnit_Enumerated, michael@0: "bad actual value"); michael@0: NS_ASSERTION(required.GetUnit() == eCSSUnit_Enumerated, michael@0: "bad required value"); michael@0: NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed, michael@0: "bad range"); // we asserted above about mRange michael@0: // We don't really need DoCompare, but it doesn't hurt (and michael@0: // maybe the compiler will condense this case with eInteger). michael@0: cmp = DoCompare(actual.GetIntValue(), required.GetIntValue()); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eIdent: michael@0: { michael@0: NS_ASSERTION(actual.GetUnit() == eCSSUnit_Ident, michael@0: "bad actual value"); michael@0: NS_ASSERTION(required.GetUnit() == eCSSUnit_Ident, michael@0: "bad required value"); michael@0: NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed, michael@0: "bad range"); michael@0: cmp = !(actual == required); // string comparison michael@0: } michael@0: break; michael@0: } michael@0: switch (mRange) { michael@0: case nsMediaExpression::eMin: michael@0: return cmp != -1; michael@0: case nsMediaExpression::eMax: michael@0: return cmp != 1; michael@0: case nsMediaExpression::eEqual: michael@0: return cmp == 0; michael@0: } michael@0: NS_NOTREACHED("unexpected mRange"); michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsMediaQueryResultCacheKey::AddExpression(const nsMediaExpression* aExpression, michael@0: bool aExpressionMatches) michael@0: { michael@0: const nsMediaFeature *feature = aExpression->mFeature; michael@0: FeatureEntry *entry = nullptr; michael@0: for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) { michael@0: if (mFeatureCache[i].mFeature == feature) { michael@0: entry = &mFeatureCache[i]; michael@0: break; michael@0: } michael@0: } michael@0: if (!entry) { michael@0: entry = mFeatureCache.AppendElement(); michael@0: if (!entry) { michael@0: return; /* out of memory */ michael@0: } michael@0: entry->mFeature = feature; michael@0: } michael@0: michael@0: ExpressionEntry eentry = { *aExpression, aExpressionMatches }; michael@0: entry->mExpressions.AppendElement(eentry); michael@0: } michael@0: michael@0: bool michael@0: nsMediaQueryResultCacheKey::Matches(nsPresContext* aPresContext) const michael@0: { michael@0: if (aPresContext->Medium() != mMedium) { michael@0: return false; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) { michael@0: const FeatureEntry *entry = &mFeatureCache[i]; michael@0: nsCSSValue actual; michael@0: nsresult rv = michael@0: (entry->mFeature->mGetter)(aPresContext, entry->mFeature, actual); michael@0: NS_ENSURE_SUCCESS(rv, false); // any better ideas? michael@0: michael@0: for (uint32_t j = 0; j < entry->mExpressions.Length(); ++j) { michael@0: const ExpressionEntry &eentry = entry->mExpressions[j]; michael@0: if (eentry.mExpression.Matches(aPresContext, actual) != michael@0: eentry.mExpressionMatches) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsMediaQuery::AppendToString(nsAString& aString) const michael@0: { michael@0: if (mHadUnknownExpression) { michael@0: aString.AppendLiteral("not all"); michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(!mNegated || !mHasOnly, "can't have not and only"); michael@0: NS_ASSERTION(!mTypeOmitted || (!mNegated && !mHasOnly), michael@0: "can't have not or only when type is omitted"); michael@0: if (!mTypeOmitted) { michael@0: if (mNegated) { michael@0: aString.AppendLiteral("not "); michael@0: } else if (mHasOnly) { michael@0: aString.AppendLiteral("only "); michael@0: } michael@0: aString.Append(nsDependentAtomString(mMediaType)); michael@0: } michael@0: michael@0: for (uint32_t i = 0, i_end = mExpressions.Length(); i < i_end; ++i) { michael@0: if (i > 0 || !mTypeOmitted) michael@0: aString.AppendLiteral(" and "); michael@0: aString.AppendLiteral("("); michael@0: michael@0: const nsMediaExpression &expr = mExpressions[i]; michael@0: if (expr.mRange == nsMediaExpression::eMin) { michael@0: aString.AppendLiteral("min-"); michael@0: } else if (expr.mRange == nsMediaExpression::eMax) { michael@0: aString.AppendLiteral("max-"); michael@0: } michael@0: michael@0: const nsMediaFeature *feature = expr.mFeature; michael@0: aString.Append(nsDependentAtomString(*feature->mName)); michael@0: michael@0: if (expr.mValue.GetUnit() != eCSSUnit_Null) { michael@0: aString.AppendLiteral(": "); michael@0: switch (feature->mValueType) { michael@0: case nsMediaFeature::eLength: michael@0: NS_ASSERTION(expr.mValue.IsLengthUnit(), "bad unit"); michael@0: // Use 'width' as a property that takes length values michael@0: // written in the normal way. michael@0: expr.mValue.AppendToString(eCSSProperty_width, aString, michael@0: nsCSSValue::eNormalized); michael@0: break; michael@0: case nsMediaFeature::eInteger: michael@0: case nsMediaFeature::eBoolInteger: michael@0: NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Integer, michael@0: "bad unit"); michael@0: // Use 'z-index' as a property that takes integer values michael@0: // written without anything extra. michael@0: expr.mValue.AppendToString(eCSSProperty_z_index, aString, michael@0: nsCSSValue::eNormalized); michael@0: break; michael@0: case nsMediaFeature::eFloat: michael@0: { michael@0: NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Number, michael@0: "bad unit"); michael@0: // Use 'line-height' as a property that takes float values michael@0: // written in the normal way. michael@0: expr.mValue.AppendToString(eCSSProperty_line_height, aString, michael@0: nsCSSValue::eNormalized); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eIntRatio: michael@0: { michael@0: NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Array, michael@0: "bad unit"); michael@0: nsCSSValue::Array *array = expr.mValue.GetArrayValue(); michael@0: NS_ASSERTION(array->Count() == 2, "unexpected length"); michael@0: NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer, michael@0: "bad unit"); michael@0: NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Integer, michael@0: "bad unit"); michael@0: array->Item(0).AppendToString(eCSSProperty_z_index, aString, michael@0: nsCSSValue::eNormalized); michael@0: aString.AppendLiteral("/"); michael@0: array->Item(1).AppendToString(eCSSProperty_z_index, aString, michael@0: nsCSSValue::eNormalized); michael@0: } michael@0: break; michael@0: case nsMediaFeature::eResolution: michael@0: { michael@0: aString.AppendFloat(expr.mValue.GetFloatValue()); michael@0: if (expr.mValue.GetUnit() == eCSSUnit_Inch) { michael@0: aString.AppendLiteral("dpi"); michael@0: } else if (expr.mValue.GetUnit() == eCSSUnit_Pixel) { michael@0: aString.AppendLiteral("dppx"); michael@0: } else { michael@0: NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Centimeter, michael@0: "bad unit"); michael@0: aString.AppendLiteral("dpcm"); michael@0: } michael@0: } michael@0: break; michael@0: case nsMediaFeature::eEnumerated: michael@0: NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Enumerated, michael@0: "bad unit"); michael@0: AppendASCIItoUTF16( michael@0: nsCSSProps::ValueToKeyword(expr.mValue.GetIntValue(), michael@0: feature->mData.mKeywordTable), michael@0: aString); michael@0: break; michael@0: case nsMediaFeature::eIdent: michael@0: NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Ident, michael@0: "bad unit"); michael@0: aString.Append(expr.mValue.GetStringBufferValue()); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: aString.AppendLiteral(")"); michael@0: } michael@0: } michael@0: michael@0: nsMediaQuery* michael@0: nsMediaQuery::Clone() const michael@0: { michael@0: return new nsMediaQuery(*this); michael@0: } michael@0: michael@0: bool michael@0: nsMediaQuery::Matches(nsPresContext* aPresContext, michael@0: nsMediaQueryResultCacheKey* aKey) const michael@0: { michael@0: if (mHadUnknownExpression) michael@0: return false; michael@0: michael@0: bool match = michael@0: mMediaType == aPresContext->Medium() || mMediaType == nsGkAtoms::all; michael@0: for (uint32_t i = 0, i_end = mExpressions.Length(); match && i < i_end; ++i) { michael@0: const nsMediaExpression &expr = mExpressions[i]; michael@0: nsCSSValue actual; michael@0: nsresult rv = michael@0: (expr.mFeature->mGetter)(aPresContext, expr.mFeature, actual); michael@0: NS_ENSURE_SUCCESS(rv, false); // any better ideas? michael@0: michael@0: match = expr.Matches(aPresContext, actual); michael@0: if (aKey) { michael@0: aKey->AddExpression(&expr, match); michael@0: } michael@0: } michael@0: michael@0: return match == !mNegated; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMediaList) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMMediaList) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMediaList) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMediaList) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsMediaList) michael@0: michael@0: nsMediaList::nsMediaList() michael@0: : mStyleSheet(nullptr) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsMediaList::~nsMediaList() michael@0: { michael@0: } michael@0: michael@0: /* virtual */ JSObject* michael@0: nsMediaList::WrapObject(JSContext* aCx) michael@0: { michael@0: return MediaListBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: nsMediaList::GetText(nsAString& aMediaText) michael@0: { michael@0: aMediaText.Truncate(); michael@0: michael@0: for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) { michael@0: nsMediaQuery* query = mArray[i]; michael@0: michael@0: query->AppendToString(aMediaText); michael@0: michael@0: if (i + 1 < i_end) { michael@0: aMediaText.AppendLiteral(", "); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // XXXbz this is so ill-defined in the spec, it's not clear quite what michael@0: // it should be doing.... michael@0: void michael@0: nsMediaList::SetText(const nsAString& aMediaText) michael@0: { michael@0: nsCSSParser parser; michael@0: michael@0: bool htmlMode = mStyleSheet && mStyleSheet->GetOwnerNode(); michael@0: michael@0: parser.ParseMediaList(aMediaText, nullptr, 0, this, htmlMode); michael@0: } michael@0: michael@0: bool michael@0: nsMediaList::Matches(nsPresContext* aPresContext, michael@0: nsMediaQueryResultCacheKey* aKey) michael@0: { michael@0: for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) { michael@0: if (mArray[i]->Matches(aPresContext, aKey)) { michael@0: return true; michael@0: } michael@0: } michael@0: return mArray.IsEmpty(); michael@0: } michael@0: michael@0: nsresult michael@0: nsMediaList::SetStyleSheet(nsCSSStyleSheet *aSheet) michael@0: { michael@0: NS_ASSERTION(aSheet == mStyleSheet || !aSheet || !mStyleSheet, michael@0: "multiple style sheets competing for one media list"); michael@0: mStyleSheet = aSheet; michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsMediaList::Clone() michael@0: { michael@0: nsRefPtr result = new nsMediaList(); michael@0: result->mArray.AppendElements(mArray.Length()); michael@0: for (uint32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) { michael@0: result->mArray[i] = mArray[i]->Clone(); michael@0: MOZ_ASSERT(result->mArray[i]); michael@0: } michael@0: return result.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaList::GetMediaText(nsAString& aMediaText) michael@0: { michael@0: GetText(aMediaText); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // "sheet" should be an nsCSSStyleSheet and "doc" should be an michael@0: // nsCOMPtr michael@0: #define BEGIN_MEDIA_CHANGE(sheet, doc) \ michael@0: if (sheet) { \ michael@0: doc = sheet->GetOwningDocument(); \ michael@0: } \ michael@0: mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, true); \ michael@0: if (sheet) { \ michael@0: sheet->WillDirty(); \ michael@0: } michael@0: michael@0: #define END_MEDIA_CHANGE(sheet, doc) \ michael@0: if (sheet) { \ michael@0: sheet->DidDirty(); \ michael@0: } \ michael@0: /* XXXldb Pass something meaningful? */ \ michael@0: if (doc) { \ michael@0: doc->StyleRuleChanged(sheet, nullptr, nullptr); \ michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaList::SetMediaText(const nsAString& aMediaText) michael@0: { michael@0: nsCOMPtr doc; michael@0: michael@0: BEGIN_MEDIA_CHANGE(mStyleSheet, doc) michael@0: michael@0: SetText(aMediaText); michael@0: michael@0: END_MEDIA_CHANGE(mStyleSheet, doc) michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaList::GetLength(uint32_t* aLength) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aLength); michael@0: michael@0: *aLength = Length(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaList::Item(uint32_t aIndex, nsAString& aReturn) michael@0: { michael@0: bool dummy; michael@0: IndexedGetter(aIndex, dummy, aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsMediaList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aReturn) michael@0: { michael@0: if (aIndex < Length()) { michael@0: aFound = true; michael@0: aReturn.Truncate(); michael@0: mArray[aIndex]->AppendToString(aReturn); michael@0: } else { michael@0: aFound = false; michael@0: SetDOMStringToNull(aReturn); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaList::DeleteMedium(const nsAString& aOldMedium) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr doc; michael@0: michael@0: BEGIN_MEDIA_CHANGE(mStyleSheet, doc) michael@0: michael@0: rv = Delete(aOldMedium); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: END_MEDIA_CHANGE(mStyleSheet, doc) michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaList::AppendMedium(const nsAString& aNewMedium) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr doc; michael@0: michael@0: BEGIN_MEDIA_CHANGE(mStyleSheet, doc) michael@0: michael@0: rv = Append(aNewMedium); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: END_MEDIA_CHANGE(mStyleSheet, doc) michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsMediaList::Delete(const nsAString& aOldMedium) michael@0: { michael@0: if (aOldMedium.IsEmpty()) michael@0: return NS_ERROR_DOM_NOT_FOUND_ERR; michael@0: michael@0: for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) { michael@0: nsMediaQuery* query = mArray[i]; michael@0: michael@0: nsAutoString buf; michael@0: query->AppendToString(buf); michael@0: if (buf == aOldMedium) { michael@0: mArray.RemoveElementAt(i); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_DOM_NOT_FOUND_ERR; michael@0: } michael@0: michael@0: nsresult michael@0: nsMediaList::Append(const nsAString& aNewMedium) michael@0: { michael@0: if (aNewMedium.IsEmpty()) michael@0: return NS_ERROR_DOM_NOT_FOUND_ERR; michael@0: michael@0: Delete(aNewMedium); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsTArray > buf; michael@0: mArray.SwapElements(buf); michael@0: SetText(aNewMedium); michael@0: if (mArray.Length() == 1) { michael@0: nsMediaQuery *query = mArray[0].forget(); michael@0: if (!buf.AppendElement(query)) { michael@0: delete query; michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: michael@0: mArray.SwapElements(buf); michael@0: return rv; michael@0: } michael@0: michael@0: // ------------------------------- michael@0: // CSS Style Sheet Inner Data Container michael@0: // michael@0: michael@0: michael@0: nsCSSStyleSheetInner::nsCSSStyleSheetInner(nsCSSStyleSheet* aPrimarySheet, michael@0: CORSMode aCORSMode) michael@0: : mSheets(), michael@0: mCORSMode(aCORSMode), michael@0: mComplete(false) michael@0: #ifdef DEBUG michael@0: , mPrincipalSet(false) michael@0: #endif michael@0: { michael@0: MOZ_COUNT_CTOR(nsCSSStyleSheetInner); michael@0: mSheets.AppendElement(aPrimarySheet); michael@0: michael@0: mPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1"); michael@0: if (!mPrincipal) { michael@0: NS_RUNTIMEABORT("OOM"); michael@0: } michael@0: } michael@0: michael@0: static bool SetStyleSheetReference(css::Rule* aRule, void* aSheet) michael@0: { michael@0: if (aRule) { michael@0: aRule->SetStyleSheet(static_cast(aSheet)); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: struct ChildSheetListBuilder { michael@0: nsRefPtr* sheetSlot; michael@0: nsCSSStyleSheet* parent; michael@0: michael@0: void SetParentLinks(nsCSSStyleSheet* aSheet) { michael@0: aSheet->mParent = parent; michael@0: aSheet->SetOwningDocument(parent->mDocument); michael@0: } michael@0: michael@0: static void ReparentChildList(nsCSSStyleSheet* aPrimarySheet, michael@0: nsCSSStyleSheet* aFirstChild) michael@0: { michael@0: for (nsCSSStyleSheet *child = aFirstChild; child; child = child->mNext) { michael@0: child->mParent = aPrimarySheet; michael@0: child->SetOwningDocument(aPrimarySheet->mDocument); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: bool michael@0: nsCSSStyleSheet::RebuildChildList(css::Rule* aRule, void* aBuilder) michael@0: { michael@0: int32_t type = aRule->GetType(); michael@0: if (type < css::Rule::IMPORT_RULE) { michael@0: // Keep going till we get to the import rules. michael@0: return true; michael@0: } michael@0: michael@0: if (type != css::Rule::IMPORT_RULE) { michael@0: // We're past all the import rules; stop the enumeration. michael@0: return false; michael@0: } michael@0: michael@0: ChildSheetListBuilder* builder = michael@0: static_cast(aBuilder); michael@0: michael@0: // XXXbz We really need to decomtaminate all this stuff. Is there a reason michael@0: // that I can't just QI to ImportRule and get an nsCSSStyleSheet michael@0: // directly from it? michael@0: nsCOMPtr importRule(do_QueryInterface(aRule)); michael@0: NS_ASSERTION(importRule, "GetType lied"); michael@0: michael@0: nsCOMPtr childSheet; michael@0: importRule->GetStyleSheet(getter_AddRefs(childSheet)); michael@0: michael@0: // Have to do this QI to be safe, since XPConnect can fake michael@0: // nsIDOMCSSStyleSheets michael@0: nsRefPtr cssSheet = do_QueryObject(childSheet); michael@0: if (!cssSheet) { michael@0: return true; michael@0: } michael@0: michael@0: (*builder->sheetSlot) = cssSheet; michael@0: builder->SetParentLinks(*builder->sheetSlot); michael@0: builder->sheetSlot = &(*builder->sheetSlot)->mNext; michael@0: return true; michael@0: } michael@0: michael@0: size_t michael@0: nsCSSStyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = 0; michael@0: const nsCSSStyleSheet* s = this; michael@0: while (s) { michael@0: n += aMallocSizeOf(s); michael@0: michael@0: // Each inner can be shared by multiple sheets. So we only count the inner michael@0: // if this sheet is the first one in the list of those sharing it. As a michael@0: // result, the first such sheet takes all the blame for the memory michael@0: // consumption of the inner, which isn't ideal but it's better than michael@0: // double-counting the inner. michael@0: if (s->mInner->mSheets[0] == s) { michael@0: n += s->mInner->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: // Measurement of the following members may be added later if DMD finds it michael@0: // is worthwhile: michael@0: // - s->mTitle michael@0: // - s->mMedia michael@0: // - s->mRuleCollection michael@0: // - s->mRuleProcessors michael@0: // michael@0: // The following members are not measured: michael@0: // - s->mOwnerRule, because it's non-owning michael@0: michael@0: s = s->mNext; michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: nsCSSStyleSheetInner::nsCSSStyleSheetInner(nsCSSStyleSheetInner& aCopy, michael@0: nsCSSStyleSheet* aPrimarySheet) michael@0: : mSheets(), michael@0: mSheetURI(aCopy.mSheetURI), michael@0: mOriginalSheetURI(aCopy.mOriginalSheetURI), michael@0: mBaseURI(aCopy.mBaseURI), michael@0: mPrincipal(aCopy.mPrincipal), michael@0: mCORSMode(aCopy.mCORSMode), michael@0: mComplete(aCopy.mComplete) michael@0: #ifdef DEBUG michael@0: , mPrincipalSet(aCopy.mPrincipalSet) michael@0: #endif michael@0: { michael@0: MOZ_COUNT_CTOR(nsCSSStyleSheetInner); michael@0: AddSheet(aPrimarySheet); michael@0: aCopy.mOrderedRules.EnumerateForwards(css::GroupRule::CloneRuleInto, &mOrderedRules); michael@0: mOrderedRules.EnumerateForwards(SetStyleSheetReference, aPrimarySheet); michael@0: michael@0: ChildSheetListBuilder builder = { &mFirstChild, aPrimarySheet }; michael@0: mOrderedRules.EnumerateForwards(nsCSSStyleSheet::RebuildChildList, &builder); michael@0: michael@0: RebuildNameSpaces(); michael@0: } michael@0: michael@0: nsCSSStyleSheetInner::~nsCSSStyleSheetInner() michael@0: { michael@0: MOZ_COUNT_DTOR(nsCSSStyleSheetInner); michael@0: mOrderedRules.EnumerateForwards(SetStyleSheetReference, nullptr); michael@0: } michael@0: michael@0: nsCSSStyleSheetInner* michael@0: nsCSSStyleSheetInner::CloneFor(nsCSSStyleSheet* aPrimarySheet) michael@0: { michael@0: return new nsCSSStyleSheetInner(*this, aPrimarySheet); michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheetInner::AddSheet(nsCSSStyleSheet* aSheet) michael@0: { michael@0: mSheets.AppendElement(aSheet); michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheetInner::RemoveSheet(nsCSSStyleSheet* aSheet) michael@0: { michael@0: if (1 == mSheets.Length()) { michael@0: NS_ASSERTION(aSheet == mSheets.ElementAt(0), "bad parent"); michael@0: delete this; michael@0: return; michael@0: } michael@0: if (aSheet == mSheets.ElementAt(0)) { michael@0: mSheets.RemoveElementAt(0); michael@0: NS_ASSERTION(mSheets.Length(), "no parents"); michael@0: mOrderedRules.EnumerateForwards(SetStyleSheetReference, michael@0: mSheets.ElementAt(0)); michael@0: michael@0: ChildSheetListBuilder::ReparentChildList(mSheets[0], mFirstChild); michael@0: } michael@0: else { michael@0: mSheets.RemoveElement(aSheet); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: AddNamespaceRuleToMap(css::Rule* aRule, nsXMLNameSpaceMap* aMap) michael@0: { michael@0: NS_ASSERTION(aRule->GetType() == css::Rule::NAMESPACE_RULE, "Bogus rule type"); michael@0: michael@0: nsRefPtr nameSpaceRule = do_QueryObject(aRule); michael@0: michael@0: nsAutoString urlSpec; michael@0: nameSpaceRule->GetURLSpec(urlSpec); michael@0: michael@0: aMap->AddPrefix(nameSpaceRule->GetPrefix(), urlSpec); michael@0: } michael@0: michael@0: static bool michael@0: CreateNameSpace(css::Rule* aRule, void* aNameSpacePtr) michael@0: { michael@0: int32_t type = aRule->GetType(); michael@0: if (css::Rule::NAMESPACE_RULE == type) { michael@0: AddNamespaceRuleToMap(aRule, michael@0: static_cast(aNameSpacePtr)); michael@0: return true; michael@0: } michael@0: // stop if not namespace, import or charset because namespace can't follow michael@0: // anything else michael@0: return (css::Rule::CHARSET_RULE == type || css::Rule::IMPORT_RULE == type); michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheetInner::RebuildNameSpaces() michael@0: { michael@0: // Just nuke our existing namespace map, if any michael@0: if (NS_SUCCEEDED(CreateNamespaceMap())) { michael@0: mOrderedRules.EnumerateForwards(CreateNameSpace, mNameSpaceMap); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheetInner::CreateNamespaceMap() michael@0: { michael@0: mNameSpaceMap = nsXMLNameSpaceMap::Create(false); michael@0: NS_ENSURE_TRUE(mNameSpaceMap, NS_ERROR_OUT_OF_MEMORY); michael@0: // Override the default namespace map behavior for the null prefix to michael@0: // return the wildcard namespace instead of the null namespace. michael@0: mNameSpaceMap->AddPrefix(nullptr, kNameSpaceID_Unknown); michael@0: return NS_OK; michael@0: } michael@0: michael@0: size_t michael@0: nsCSSStyleSheetInner::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = aMallocSizeOf(this); michael@0: n += mOrderedRules.SizeOfExcludingThis(css::Rule::SizeOfCOMArrayElementIncludingThis, michael@0: aMallocSizeOf); michael@0: n += mFirstChild ? mFirstChild->SizeOfIncludingThis(aMallocSizeOf) : 0; michael@0: michael@0: // Measurement of the following members may be added later if DMD finds it is michael@0: // worthwhile: michael@0: // - mSheetURI michael@0: // - mOriginalSheetURI michael@0: // - mBaseURI michael@0: // - mPrincipal michael@0: // - mNameSpaceMap michael@0: // michael@0: // The following members are not measured: michael@0: // - mSheets, because it's non-owning michael@0: michael@0: return n; michael@0: } michael@0: michael@0: // ------------------------------- michael@0: // CSS Style Sheet michael@0: // michael@0: michael@0: nsCSSStyleSheet::nsCSSStyleSheet(CORSMode aCORSMode) michael@0: : mTitle(), michael@0: mParent(nullptr), michael@0: mOwnerRule(nullptr), michael@0: mDocument(nullptr), michael@0: mOwningNode(nullptr), michael@0: mDisabled(false), michael@0: mDirty(false), michael@0: mScopeElement(nullptr), michael@0: mRuleProcessors(nullptr) michael@0: { michael@0: mInner = new nsCSSStyleSheetInner(this, aCORSMode); michael@0: michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsCSSStyleSheet::nsCSSStyleSheet(const nsCSSStyleSheet& aCopy, michael@0: nsCSSStyleSheet* aParentToUse, michael@0: css::ImportRule* aOwnerRuleToUse, michael@0: nsIDocument* aDocumentToUse, michael@0: nsINode* aOwningNodeToUse) michael@0: : mTitle(aCopy.mTitle), michael@0: mParent(aParentToUse), michael@0: mOwnerRule(aOwnerRuleToUse), michael@0: mDocument(aDocumentToUse), michael@0: mOwningNode(aOwningNodeToUse), michael@0: mDisabled(aCopy.mDisabled), michael@0: mDirty(aCopy.mDirty), michael@0: mScopeElement(nullptr), michael@0: mInner(aCopy.mInner), michael@0: mRuleProcessors(nullptr) michael@0: { michael@0: michael@0: mInner->AddSheet(this); michael@0: michael@0: if (mDirty) { // CSSOM's been there, force full copy now michael@0: NS_ASSERTION(mInner->mComplete, "Why have rules been accessed on an incomplete sheet?"); michael@0: // FIXME: handle failure? michael@0: EnsureUniqueInner(); michael@0: } michael@0: michael@0: if (aCopy.mMedia) { michael@0: // XXX This is wrong; we should be keeping @import rules and michael@0: // sheets in sync! michael@0: mMedia = aCopy.mMedia->Clone(); michael@0: } michael@0: michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsCSSStyleSheet::~nsCSSStyleSheet() michael@0: { michael@0: for (nsCSSStyleSheet* child = mInner->mFirstChild; michael@0: child; michael@0: child = child->mNext) { michael@0: // XXXbz this is a little bogus; see the XXX comment where we michael@0: // declare mFirstChild. michael@0: if (child->mParent == this) { michael@0: child->mParent = nullptr; michael@0: child->mDocument = nullptr; michael@0: } michael@0: } michael@0: DropRuleCollection(); michael@0: DropMedia(); michael@0: mInner->RemoveSheet(this); michael@0: // XXX The document reference is not reference counted and should michael@0: // not be released. The document will let us know when it is going michael@0: // away. michael@0: if (mRuleProcessors) { michael@0: NS_ASSERTION(mRuleProcessors->Length() == 0, "destructing sheet with rule processor reference"); michael@0: delete mRuleProcessors; // weak refs, should be empty here anyway michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::DropRuleCollection() michael@0: { michael@0: if (mRuleCollection) { michael@0: mRuleCollection->DropReference(); michael@0: mRuleCollection = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::DropMedia() michael@0: { michael@0: if (mMedia) { michael@0: mMedia->SetStyleSheet(nullptr); michael@0: mMedia = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::UnlinkInner() michael@0: { michael@0: // We can only have a cycle through our inner if we have a unique inner, michael@0: // because otherwise there are no JS wrappers for anything in the inner. michael@0: if (mInner->mSheets.Length() != 1) { michael@0: return; michael@0: } michael@0: michael@0: mInner->mOrderedRules.EnumerateForwards(SetStyleSheetReference, nullptr); michael@0: mInner->mOrderedRules.Clear(); michael@0: michael@0: // Have to be a bit careful with child sheets, because we want to michael@0: // drop their mNext pointers and null out their mParent and michael@0: // mDocument, but don't want to work with deleted objects. And we michael@0: // don't want to do any addrefing in the process, just to make sure michael@0: // we don't confuse the cycle collector (though on the face of it, michael@0: // addref/release pairs during unlink should probably be ok). michael@0: nsRefPtr child; michael@0: child.swap(mInner->mFirstChild); michael@0: while (child) { michael@0: MOZ_ASSERT(child->mParent == this, "We have a unique inner!"); michael@0: child->mParent = nullptr; michael@0: child->mDocument = nullptr; michael@0: nsRefPtr next; michael@0: // Null out child->mNext, but don't let it die yet michael@0: next.swap(child->mNext); michael@0: // Switch to looking at the old value of child->mNext next iteration michael@0: child.swap(next); michael@0: // "next" is now our previous value of child; it'll get released michael@0: // as we loop around. michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::TraverseInner(nsCycleCollectionTraversalCallback &cb) michael@0: { michael@0: // We can only have a cycle through our inner if we have a unique inner, michael@0: // because otherwise there are no JS wrappers for anything in the inner. michael@0: if (mInner->mSheets.Length() != 1) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr* childSheetSlot = &mInner->mFirstChild; michael@0: while (*childSheetSlot) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "child sheet"); michael@0: cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIStyleSheet*, childSheetSlot->get())); michael@0: childSheetSlot = &(*childSheetSlot)->mNext; michael@0: } michael@0: michael@0: const nsCOMArray& rules = mInner->mOrderedRules; michael@0: for (int32_t i = 0, count = rules.Count(); i < count; ++i) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOrderedRules[i]"); michael@0: cb.NoteXPCOMChild(rules[i]->GetExistingDOMRule()); michael@0: } michael@0: } michael@0: michael@0: DOMCI_DATA(CSSStyleSheet, nsCSSStyleSheet) michael@0: michael@0: // QueryInterface implementation for nsCSSStyleSheet michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSStyleSheet) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsIStyleSheet) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMStyleSheet) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMCSSStyleSheet) michael@0: NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStyleSheet) michael@0: NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSStyleSheet) michael@0: if (aIID.Equals(NS_GET_IID(nsCSSStyleSheet))) michael@0: foundInterface = reinterpret_cast(this); michael@0: else michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSStyleSheet) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSStyleSheet) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsCSSStyleSheet) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCSSStyleSheet) michael@0: tmp->DropMedia(); michael@0: // We do not unlink mNext; our parent will handle that. If we michael@0: // unlinked it here, our parent would not be able to walk its list michael@0: // of child sheets and null out the back-references to it, if we got michael@0: // unlinked before it does. michael@0: tmp->DropRuleCollection(); michael@0: tmp->UnlinkInner(); michael@0: tmp->mScopeElement = nullptr; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCSSStyleSheet) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMedia) michael@0: // We do not traverse mNext; our parent will handle that. See michael@0: // comments in Unlink for why. michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleCollection) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScopeElement) michael@0: tmp->TraverseInner(cb); michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsCSSStyleSheet) michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::AddRuleProcessor(nsCSSRuleProcessor* aProcessor) michael@0: { michael@0: if (! mRuleProcessors) { michael@0: mRuleProcessors = new nsAutoTArray(); michael@0: if (!mRuleProcessors) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: NS_ASSERTION(mRuleProcessors->NoIndex == mRuleProcessors->IndexOf(aProcessor), michael@0: "processor already registered"); michael@0: mRuleProcessors->AppendElement(aProcessor); // weak ref michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::DropRuleProcessor(nsCSSRuleProcessor* aProcessor) michael@0: { michael@0: if (!mRuleProcessors) michael@0: return NS_ERROR_FAILURE; michael@0: return mRuleProcessors->RemoveElement(aProcessor) michael@0: ? NS_OK michael@0: : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCSSStyleSheet::SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI, michael@0: nsIURI* aBaseURI) michael@0: { michael@0: NS_PRECONDITION(aSheetURI && aBaseURI, "null ptr"); michael@0: michael@0: NS_ASSERTION(mInner->mOrderedRules.Count() == 0 && !mInner->mComplete, michael@0: "Can't call SetURL on sheets that are complete or have rules"); michael@0: michael@0: mInner->mSheetURI = aSheetURI; michael@0: mInner->mOriginalSheetURI = aOriginalSheetURI; michael@0: mInner->mBaseURI = aBaseURI; michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::SetPrincipal(nsIPrincipal* aPrincipal) michael@0: { michael@0: NS_PRECONDITION(!mInner->mPrincipalSet, michael@0: "Should have an inner whose principal has not yet been set"); michael@0: if (aPrincipal) { michael@0: mInner->mPrincipal = aPrincipal; michael@0: #ifdef DEBUG michael@0: mInner->mPrincipalSet = true; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: /* virtual */ nsIURI* michael@0: nsCSSStyleSheet::GetSheetURI() const michael@0: { michael@0: return mInner->mSheetURI; michael@0: } michael@0: michael@0: /* virtual */ nsIURI* michael@0: nsCSSStyleSheet::GetBaseURI() const michael@0: { michael@0: return mInner->mBaseURI; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsCSSStyleSheet::GetType(nsString& aType) const michael@0: { michael@0: aType.AssignLiteral("text/css"); michael@0: } michael@0: michael@0: bool michael@0: nsCSSStyleSheet::UseForPresentation(nsPresContext* aPresContext, michael@0: nsMediaQueryResultCacheKey& aKey) const michael@0: { michael@0: if (mMedia) { michael@0: return mMedia->Matches(aPresContext, &aKey); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCSSStyleSheet::SetMedia(nsMediaList* aMedia) michael@0: { michael@0: mMedia = aMedia; michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsCSSStyleSheet::HasRules() const michael@0: { michael@0: return StyleRuleCount() != 0; michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsCSSStyleSheet::IsApplicable() const michael@0: { michael@0: return !mDisabled && mInner->mComplete; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsCSSStyleSheet::SetEnabled(bool aEnabled) michael@0: { michael@0: // Internal method, so callers must handle BeginUpdate/EndUpdate michael@0: bool oldDisabled = mDisabled; michael@0: mDisabled = !aEnabled; michael@0: michael@0: if (mInner->mComplete && oldDisabled != mDisabled) { michael@0: ClearRuleCascades(); michael@0: michael@0: if (mDocument) { michael@0: mDocument->SetStyleSheetApplicableState(this, !mDisabled); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsCSSStyleSheet::IsComplete() const michael@0: { michael@0: return mInner->mComplete; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsCSSStyleSheet::SetComplete() michael@0: { michael@0: NS_ASSERTION(!mDirty, "Can't set a dirty sheet complete!"); michael@0: mInner->mComplete = true; michael@0: if (mDocument && !mDisabled) { michael@0: // Let the document know michael@0: mDocument->BeginUpdate(UPDATE_STYLE); michael@0: mDocument->SetStyleSheetApplicableState(this, true); michael@0: mDocument->EndUpdate(UPDATE_STYLE); michael@0: } michael@0: } michael@0: michael@0: /* virtual */ nsIStyleSheet* michael@0: nsCSSStyleSheet::GetParentSheet() const michael@0: { michael@0: return mParent; michael@0: } michael@0: michael@0: /* virtual */ nsIDocument* michael@0: nsCSSStyleSheet::GetOwningDocument() const michael@0: { michael@0: return mDocument; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsCSSStyleSheet::SetOwningDocument(nsIDocument* aDocument) michael@0: { // not ref counted michael@0: mDocument = aDocument; michael@0: // Now set the same document on all our child sheets.... michael@0: // XXXbz this is a little bogus; see the XXX comment where we michael@0: // declare mFirstChild. michael@0: for (nsCSSStyleSheet* child = mInner->mFirstChild; michael@0: child; child = child->mNext) { michael@0: if (child->mParent == this) { michael@0: child->SetOwningDocument(aDocument); michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint64_t michael@0: nsCSSStyleSheet::FindOwningWindowInnerID() const michael@0: { michael@0: uint64_t windowID = 0; michael@0: if (mDocument) { michael@0: windowID = mDocument->InnerWindowID(); michael@0: } michael@0: michael@0: if (windowID == 0 && mOwningNode) { michael@0: windowID = mOwningNode->OwnerDoc()->InnerWindowID(); michael@0: } michael@0: michael@0: if (windowID == 0 && mOwnerRule) { michael@0: nsCOMPtr sheet = static_cast(mOwnerRule)->GetStyleSheet(); michael@0: if (sheet) { michael@0: nsRefPtr cssSheet = do_QueryObject(sheet); michael@0: if (cssSheet) { michael@0: windowID = cssSheet->FindOwningWindowInnerID(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (windowID == 0 && mParent) { michael@0: windowID = mParent->FindOwningWindowInnerID(); michael@0: } michael@0: michael@0: return windowID; michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::AppendStyleSheet(nsCSSStyleSheet* aSheet) michael@0: { michael@0: NS_PRECONDITION(nullptr != aSheet, "null arg"); michael@0: michael@0: WillDirty(); michael@0: nsRefPtr* tail = &mInner->mFirstChild; michael@0: while (*tail) { michael@0: tail = &(*tail)->mNext; michael@0: } michael@0: *tail = aSheet; michael@0: michael@0: // This is not reference counted. Our parent tells us when michael@0: // it's going away. michael@0: aSheet->mParent = this; michael@0: aSheet->mDocument = mDocument; michael@0: DidDirty(); michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::InsertStyleSheetAt(nsCSSStyleSheet* aSheet, int32_t aIndex) michael@0: { michael@0: NS_PRECONDITION(nullptr != aSheet, "null arg"); michael@0: michael@0: WillDirty(); michael@0: nsRefPtr* tail = &mInner->mFirstChild; michael@0: while (*tail && aIndex) { michael@0: --aIndex; michael@0: tail = &(*tail)->mNext; michael@0: } michael@0: aSheet->mNext = *tail; michael@0: *tail = aSheet; michael@0: michael@0: // This is not reference counted. Our parent tells us when michael@0: // it's going away. michael@0: aSheet->mParent = this; michael@0: aSheet->mDocument = mDocument; michael@0: DidDirty(); michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::PrependStyleRule(css::Rule* aRule) michael@0: { michael@0: NS_PRECONDITION(nullptr != aRule, "null arg"); michael@0: michael@0: WillDirty(); michael@0: mInner->mOrderedRules.InsertObjectAt(aRule, 0); michael@0: aRule->SetStyleSheet(this); michael@0: DidDirty(); michael@0: michael@0: if (css::Rule::NAMESPACE_RULE == aRule->GetType()) { michael@0: // no api to prepend a namespace (ugh), release old ones and re-create them all michael@0: mInner->RebuildNameSpaces(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::AppendStyleRule(css::Rule* aRule) michael@0: { michael@0: NS_PRECONDITION(nullptr != aRule, "null arg"); michael@0: michael@0: WillDirty(); michael@0: mInner->mOrderedRules.AppendObject(aRule); michael@0: aRule->SetStyleSheet(this); michael@0: DidDirty(); michael@0: michael@0: if (css::Rule::NAMESPACE_RULE == aRule->GetType()) { michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: RegisterNamespaceRule(aRule); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), michael@0: "RegisterNamespaceRule returned error"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::ReplaceStyleRule(css::Rule* aOld, css::Rule* aNew) michael@0: { michael@0: NS_PRECONDITION(mInner->mOrderedRules.Count() != 0, "can't have old rule"); michael@0: NS_PRECONDITION(mInner->mComplete, "No replacing in an incomplete sheet!"); michael@0: michael@0: WillDirty(); michael@0: int32_t index = mInner->mOrderedRules.IndexOf(aOld); michael@0: if (MOZ_UNLIKELY(index == -1)) { michael@0: NS_NOTREACHED("Couldn't find old rule"); michael@0: return; michael@0: } michael@0: mInner->mOrderedRules.ReplaceObjectAt(aNew, index); michael@0: michael@0: aNew->SetStyleSheet(this); michael@0: aOld->SetStyleSheet(nullptr); michael@0: DidDirty(); michael@0: NS_ASSERTION(css::Rule::NAMESPACE_RULE != aNew->GetType(), "not yet implemented"); michael@0: NS_ASSERTION(css::Rule::NAMESPACE_RULE != aOld->GetType(), "not yet implemented"); michael@0: } michael@0: michael@0: int32_t michael@0: nsCSSStyleSheet::StyleRuleCount() const michael@0: { michael@0: return mInner->mOrderedRules.Count(); michael@0: } michael@0: michael@0: css::Rule* michael@0: nsCSSStyleSheet::GetStyleRuleAt(int32_t aIndex) const michael@0: { michael@0: // Important: If this function is ever made scriptable, we must add michael@0: // a security check here. See GetCssRules below for an example. michael@0: return mInner->mOrderedRules.SafeObjectAt(aIndex); michael@0: } michael@0: michael@0: int32_t michael@0: nsCSSStyleSheet::StyleSheetCount() const michael@0: { michael@0: // XXX Far from an ideal way to do this, but the hope is that michael@0: // it won't be done too often. If it is, we might want to michael@0: // consider storing the children in an array. michael@0: int32_t count = 0; michael@0: michael@0: const nsCSSStyleSheet* child = mInner->mFirstChild; michael@0: while (child) { michael@0: count++; michael@0: child = child->mNext; michael@0: } michael@0: michael@0: return count; michael@0: } michael@0: michael@0: nsCSSStyleSheet::EnsureUniqueInnerResult michael@0: nsCSSStyleSheet::EnsureUniqueInner() michael@0: { michael@0: mDirty = true; michael@0: michael@0: NS_ABORT_IF_FALSE(mInner->mSheets.Length() != 0, michael@0: "unexpected number of outers"); michael@0: if (mInner->mSheets.Length() == 1) { michael@0: return eUniqueInner_AlreadyUnique; michael@0: } michael@0: nsCSSStyleSheetInner* clone = mInner->CloneFor(this); michael@0: MOZ_ASSERT(clone); michael@0: mInner->RemoveSheet(this); michael@0: mInner = clone; michael@0: michael@0: // otherwise the rule processor has pointers to the old rules michael@0: ClearRuleCascades(); michael@0: michael@0: return eUniqueInner_ClonedInner; michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::AppendAllChildSheets(nsTArray& aArray) michael@0: { michael@0: for (nsCSSStyleSheet* child = mInner->mFirstChild; child; michael@0: child = child->mNext) { michael@0: aArray.AppendElement(child); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsCSSStyleSheet::Clone(nsCSSStyleSheet* aCloneParent, michael@0: css::ImportRule* aCloneOwnerRule, michael@0: nsIDocument* aCloneDocument, michael@0: nsINode* aCloneOwningNode) const michael@0: { michael@0: nsRefPtr clone = new nsCSSStyleSheet(*this, michael@0: aCloneParent, michael@0: aCloneOwnerRule, michael@0: aCloneDocument, michael@0: aCloneOwningNode); michael@0: return clone.forget(); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static void michael@0: ListRules(const nsCOMArray& aRules, FILE* aOut, int32_t aIndent) michael@0: { michael@0: for (int32_t index = aRules.Count() - 1; index >= 0; --index) { michael@0: aRules.ObjectAt(index)->List(aOut, aIndent); michael@0: } michael@0: } michael@0: michael@0: struct ListEnumData { michael@0: ListEnumData(FILE* aOut, int32_t aIndent) michael@0: : mOut(aOut), michael@0: mIndent(aIndent) michael@0: { michael@0: } michael@0: FILE* mOut; michael@0: int32_t mIndent; michael@0: }; michael@0: michael@0: /* virtual */ void michael@0: nsCSSStyleSheet::List(FILE* out, int32_t aIndent) const michael@0: { michael@0: michael@0: int32_t index; michael@0: michael@0: // Indent michael@0: for (index = aIndent; --index >= 0; ) fputs(" ", out); michael@0: michael@0: fputs("CSS Style Sheet: ", out); michael@0: nsAutoCString urlSpec; michael@0: nsresult rv = mInner->mSheetURI->GetSpec(urlSpec); michael@0: if (NS_SUCCEEDED(rv) && !urlSpec.IsEmpty()) { michael@0: fputs(urlSpec.get(), out); michael@0: } michael@0: michael@0: if (mMedia) { michael@0: fputs(" media: ", out); michael@0: nsAutoString buffer; michael@0: mMedia->GetText(buffer); michael@0: fputs(NS_ConvertUTF16toUTF8(buffer).get(), out); michael@0: } michael@0: fputs("\n", out); michael@0: michael@0: for (const nsCSSStyleSheet* child = mInner->mFirstChild; michael@0: child; michael@0: child = child->mNext) { michael@0: child->List(out, aIndent + 1); michael@0: } michael@0: michael@0: fputs("Rules in source order:\n", out); michael@0: ListRules(mInner->mOrderedRules, out, aIndent); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsCSSStyleSheet::ClearRuleCascades() michael@0: { michael@0: if (mRuleProcessors) { michael@0: nsCSSRuleProcessor **iter = mRuleProcessors->Elements(), michael@0: **end = iter + mRuleProcessors->Length(); michael@0: for(; iter != end; ++iter) { michael@0: (*iter)->ClearRuleCascades(); michael@0: } michael@0: } michael@0: if (mParent) { michael@0: nsCSSStyleSheet* parent = (nsCSSStyleSheet*)mParent; michael@0: parent->ClearRuleCascades(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::WillDirty() michael@0: { michael@0: if (mInner->mComplete) { michael@0: EnsureUniqueInner(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCSSStyleSheet::DidDirty() michael@0: { michael@0: NS_ABORT_IF_FALSE(!mInner->mComplete || mDirty, michael@0: "caller must have called WillDirty()"); michael@0: ClearRuleCascades(); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::SubjectSubsumesInnerPrincipal() michael@0: { michael@0: // Get the security manager and do the subsumes check michael@0: nsIScriptSecurityManager *securityManager = michael@0: nsContentUtils::GetSecurityManager(); michael@0: michael@0: nsCOMPtr subjectPrincipal; michael@0: nsresult rv = securityManager->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!subjectPrincipal) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: bool subsumes; michael@0: rv = subjectPrincipal->Subsumes(mInner->mPrincipal, &subsumes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (subsumes) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!nsContentUtils::IsCallerChrome()) { michael@0: // Allow access only if CORS mode is not NONE michael@0: if (GetCORSMode() == CORS_NONE) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: // Now make sure we set the principal of our inner to the michael@0: // subjectPrincipal. That means we need a unique inner, of michael@0: // course. But we don't want to do that if we're not complete michael@0: // yet. Luckily, all the callers of this method throw anyway if michael@0: // not complete, so we can just do that here too. michael@0: if (!mInner->mComplete) { michael@0: return NS_ERROR_DOM_INVALID_ACCESS_ERR; michael@0: } michael@0: michael@0: WillDirty(); michael@0: michael@0: mInner->mPrincipal = subjectPrincipal; michael@0: michael@0: DidDirty(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::RegisterNamespaceRule(css::Rule* aRule) michael@0: { michael@0: if (!mInner->mNameSpaceMap) { michael@0: nsresult rv = mInner->CreateNamespaceMap(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: AddNamespaceRuleToMap(aRule, mInner->mNameSpaceMap); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIDOMStyleSheet interface michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetType(nsAString& aType) michael@0: { michael@0: aType.AssignLiteral("text/css"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetDisabled(bool* aDisabled) michael@0: { michael@0: *aDisabled = Disabled(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::SetDisabled(bool aDisabled) michael@0: { michael@0: // DOM method, so handle BeginUpdate/EndUpdate michael@0: MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_STYLE, true); michael@0: nsCSSStyleSheet::SetEnabled(!aDisabled); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetOwnerNode(nsIDOMNode** aOwnerNode) michael@0: { michael@0: nsCOMPtr ownerNode = do_QueryInterface(GetOwnerNode()); michael@0: ownerNode.forget(aOwnerNode); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetParentStyleSheet(nsIDOMStyleSheet** aParentStyleSheet) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aParentStyleSheet); michael@0: michael@0: NS_IF_ADDREF(*aParentStyleSheet = mParent); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetHref(nsAString& aHref) michael@0: { michael@0: if (mInner->mOriginalSheetURI) { michael@0: nsAutoCString str; michael@0: mInner->mOriginalSheetURI->GetSpec(str); michael@0: CopyUTF8toUTF16(str, aHref); michael@0: } else { michael@0: SetDOMStringToNull(aHref); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsCSSStyleSheet::GetTitle(nsString& aTitle) const michael@0: { michael@0: aTitle = mTitle; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetTitle(nsAString& aTitle) michael@0: { michael@0: aTitle.Assign(mTitle); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetMedia(nsIDOMMediaList** aMedia) michael@0: { michael@0: NS_ADDREF(*aMedia = Media()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsMediaList* michael@0: nsCSSStyleSheet::Media() michael@0: { michael@0: if (!mMedia) { michael@0: mMedia = new nsMediaList(); michael@0: mMedia->SetStyleSheet(this); michael@0: } michael@0: michael@0: return mMedia; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetOwnerRule(nsIDOMCSSRule** aOwnerRule) michael@0: { michael@0: NS_IF_ADDREF(*aOwnerRule = GetOwnerRule()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIDOMCSSRule* michael@0: nsCSSStyleSheet::GetDOMOwnerRule() const michael@0: { michael@0: return mOwnerRule ? mOwnerRule->GetDOMRule() : nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::GetCssRules(nsIDOMCSSRuleList** aCssRules) michael@0: { michael@0: ErrorResult rv; michael@0: nsCOMPtr rules = GetCssRules(rv); michael@0: rules.forget(aCssRules); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: nsIDOMCSSRuleList* michael@0: nsCSSStyleSheet::GetCssRules(ErrorResult& aRv) michael@0: { michael@0: // No doing this on incomplete sheets! michael@0: if (!mInner->mComplete) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: //-- Security check: Only scripts whose principal subsumes that of the michael@0: // style sheet can access rule collections. michael@0: nsresult rv = SubjectSubsumesInnerPrincipal(); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return nullptr; michael@0: } michael@0: michael@0: // OK, security check passed, so get the rule collection michael@0: if (!mRuleCollection) { michael@0: mRuleCollection = new CSSRuleListImpl(this); michael@0: } michael@0: michael@0: return mRuleCollection; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::InsertRule(const nsAString& aRule, michael@0: uint32_t aIndex, michael@0: uint32_t* aReturn) michael@0: { michael@0: //-- Security check: Only scripts whose principal subsumes that of the michael@0: // style sheet can modify rule collections. michael@0: nsresult rv = SubjectSubsumesInnerPrincipal(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return InsertRuleInternal(aRule, aIndex, aReturn); michael@0: } michael@0: michael@0: static bool michael@0: RuleHasPendingChildSheet(css::Rule *cssRule) michael@0: { michael@0: nsCOMPtr importRule(do_QueryInterface(cssRule)); michael@0: NS_ASSERTION(importRule, "Rule which has type IMPORT_RULE and does not implement nsIDOMCSSImportRule!"); michael@0: nsCOMPtr childSheet; michael@0: importRule->GetStyleSheet(getter_AddRefs(childSheet)); michael@0: nsRefPtr cssSheet = do_QueryObject(childSheet); michael@0: return cssSheet != nullptr && !cssSheet->IsComplete(); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::InsertRuleInternal(const nsAString& aRule, michael@0: uint32_t aIndex, michael@0: uint32_t* aReturn) michael@0: { michael@0: // No doing this if the sheet is not complete! michael@0: if (!mInner->mComplete) { michael@0: return NS_ERROR_DOM_INVALID_ACCESS_ERR; michael@0: } michael@0: michael@0: WillDirty(); michael@0: michael@0: if (aIndex > uint32_t(mInner->mOrderedRules.Count())) michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: michael@0: NS_ASSERTION(uint32_t(mInner->mOrderedRules.Count()) <= INT32_MAX, michael@0: "Too many style rules!"); michael@0: michael@0: // Hold strong ref to the CSSLoader in case the document update michael@0: // kills the document michael@0: nsRefPtr loader; michael@0: if (mDocument) { michael@0: loader = mDocument->CSSLoader(); michael@0: NS_ASSERTION(loader, "Document with no CSS loader!"); michael@0: } michael@0: michael@0: nsCSSParser css(loader, this); michael@0: michael@0: mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true); michael@0: michael@0: nsRefPtr rule; michael@0: nsresult result = css.ParseRule(aRule, mInner->mSheetURI, mInner->mBaseURI, michael@0: mInner->mPrincipal, getter_AddRefs(rule)); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: // Hierarchy checking. michael@0: int32_t newType = rule->GetType(); michael@0: michael@0: // check that we're not inserting before a charset rule michael@0: css::Rule* nextRule = mInner->mOrderedRules.SafeObjectAt(aIndex); michael@0: if (nextRule) { michael@0: int32_t nextType = nextRule->GetType(); michael@0: if (nextType == css::Rule::CHARSET_RULE) { michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: michael@0: if (nextType == css::Rule::IMPORT_RULE && michael@0: newType != css::Rule::CHARSET_RULE && michael@0: newType != css::Rule::IMPORT_RULE) { michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: michael@0: if (nextType == css::Rule::NAMESPACE_RULE && michael@0: newType != css::Rule::CHARSET_RULE && michael@0: newType != css::Rule::IMPORT_RULE && michael@0: newType != css::Rule::NAMESPACE_RULE) { michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: } michael@0: michael@0: if (aIndex != 0) { michael@0: // no inserting charset at nonzero position michael@0: if (newType == css::Rule::CHARSET_RULE) { michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: michael@0: css::Rule* prevRule = mInner->mOrderedRules.SafeObjectAt(aIndex - 1); michael@0: int32_t prevType = prevRule->GetType(); michael@0: michael@0: if (newType == css::Rule::IMPORT_RULE && michael@0: prevType != css::Rule::CHARSET_RULE && michael@0: prevType != css::Rule::IMPORT_RULE) { michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: michael@0: if (newType == css::Rule::NAMESPACE_RULE && michael@0: prevType != css::Rule::CHARSET_RULE && michael@0: prevType != css::Rule::IMPORT_RULE && michael@0: prevType != css::Rule::NAMESPACE_RULE) { michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: } michael@0: michael@0: bool insertResult = mInner->mOrderedRules.InsertObjectAt(rule, aIndex); michael@0: NS_ENSURE_TRUE(insertResult, NS_ERROR_OUT_OF_MEMORY); michael@0: DidDirty(); michael@0: michael@0: rule->SetStyleSheet(this); michael@0: michael@0: int32_t type = rule->GetType(); michael@0: if (type == css::Rule::NAMESPACE_RULE) { michael@0: // XXXbz does this screw up when inserting a namespace rule before michael@0: // another namespace rule that binds the same prefix to a different michael@0: // namespace? michael@0: result = RegisterNamespaceRule(rule); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: // We don't notify immediately for @import rules, but rather when michael@0: // the sheet the rule is importing is loaded (see StyleSheetLoaded) michael@0: if ((type != css::Rule::IMPORT_RULE || !RuleHasPendingChildSheet(rule)) && michael@0: mDocument) { michael@0: mDocument->StyleRuleAdded(this, rule); michael@0: } michael@0: michael@0: *aReturn = aIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::DeleteRule(uint32_t aIndex) michael@0: { michael@0: // No doing this if the sheet is not complete! michael@0: if (!mInner->mComplete) { michael@0: return NS_ERROR_DOM_INVALID_ACCESS_ERR; michael@0: } michael@0: michael@0: //-- Security check: Only scripts whose principal subsumes that of the michael@0: // style sheet can modify rule collections. michael@0: nsresult rv = SubjectSubsumesInnerPrincipal(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // XXX TBI: handle @rule types michael@0: mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true); michael@0: michael@0: WillDirty(); michael@0: michael@0: if (aIndex >= uint32_t(mInner->mOrderedRules.Count())) michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: michael@0: NS_ASSERTION(uint32_t(mInner->mOrderedRules.Count()) <= INT32_MAX, michael@0: "Too many style rules!"); michael@0: michael@0: // Hold a strong ref to the rule so it doesn't die when we RemoveObjectAt michael@0: nsRefPtr rule = mInner->mOrderedRules.ObjectAt(aIndex); michael@0: if (rule) { michael@0: mInner->mOrderedRules.RemoveObjectAt(aIndex); michael@0: if (mDocument && mDocument->StyleSheetChangeEventsEnabled()) { michael@0: // Force creation of the DOM rule, so that it can be put on the michael@0: // StyleRuleRemoved event object. michael@0: rule->GetDOMRule(); michael@0: } michael@0: rule->SetStyleSheet(nullptr); michael@0: DidDirty(); michael@0: michael@0: if (mDocument) { michael@0: mDocument->StyleRuleRemoved(this, rule); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::DeleteRuleFromGroup(css::GroupRule* aGroup, uint32_t aIndex) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aGroup); michael@0: NS_ASSERTION(mInner->mComplete, "No deleting from an incomplete sheet!"); michael@0: nsRefPtr rule = aGroup->GetStyleRuleAt(aIndex); michael@0: NS_ENSURE_TRUE(rule, NS_ERROR_ILLEGAL_VALUE); michael@0: michael@0: // check that the rule actually belongs to this sheet! michael@0: if (this != rule->GetStyleSheet()) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true); michael@0: michael@0: WillDirty(); michael@0: michael@0: nsresult result = aGroup->DeleteStyleRuleAt(aIndex); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: rule->SetStyleSheet(nullptr); michael@0: michael@0: DidDirty(); michael@0: michael@0: if (mDocument) { michael@0: mDocument->StyleRuleRemoved(this, rule); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::InsertRuleIntoGroup(const nsAString & aRule, michael@0: css::GroupRule* aGroup, michael@0: uint32_t aIndex, michael@0: uint32_t* _retval) michael@0: { michael@0: NS_ASSERTION(mInner->mComplete, "No inserting into an incomplete sheet!"); michael@0: // check that the group actually belongs to this sheet! michael@0: if (this != aGroup->GetStyleSheet()) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // Hold strong ref to the CSSLoader in case the document update michael@0: // kills the document michael@0: nsRefPtr loader; michael@0: if (mDocument) { michael@0: loader = mDocument->CSSLoader(); michael@0: NS_ASSERTION(loader, "Document with no CSS loader!"); michael@0: } michael@0: michael@0: nsCSSParser css(loader, this); michael@0: michael@0: // parse and grab the rule michael@0: mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true); michael@0: michael@0: WillDirty(); michael@0: michael@0: nsRefPtr rule; michael@0: nsresult result = css.ParseRule(aRule, mInner->mSheetURI, mInner->mBaseURI, michael@0: mInner->mPrincipal, getter_AddRefs(rule)); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: switch (rule->GetType()) { michael@0: case css::Rule::STYLE_RULE: michael@0: case css::Rule::MEDIA_RULE: michael@0: case css::Rule::FONT_FACE_RULE: michael@0: case css::Rule::PAGE_RULE: michael@0: case css::Rule::KEYFRAMES_RULE: michael@0: case css::Rule::DOCUMENT_RULE: michael@0: case css::Rule::SUPPORTS_RULE: michael@0: // these types are OK to insert into a group michael@0: break; michael@0: case css::Rule::CHARSET_RULE: michael@0: case css::Rule::IMPORT_RULE: michael@0: case css::Rule::NAMESPACE_RULE: michael@0: // these aren't michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: default: michael@0: NS_NOTREACHED("unexpected rule type"); michael@0: return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; michael@0: } michael@0: michael@0: result = aGroup->InsertStyleRuleAt(aIndex, rule); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: DidDirty(); michael@0: michael@0: if (mDocument) { michael@0: mDocument->StyleRuleAdded(this, rule); michael@0: } michael@0: michael@0: *_retval = aIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::ReplaceRuleInGroup(css::GroupRule* aGroup, michael@0: css::Rule* aOld, css::Rule* aNew) michael@0: { michael@0: NS_PRECONDITION(mInner->mComplete, "No replacing in an incomplete sheet!"); michael@0: NS_ASSERTION(this == aGroup->GetStyleSheet(), "group doesn't belong to this sheet"); michael@0: michael@0: WillDirty(); michael@0: michael@0: nsresult result = aGroup->ReplaceStyleRule(aOld, aNew); michael@0: DidDirty(); michael@0: return result; michael@0: } michael@0: michael@0: // nsICSSLoaderObserver implementation michael@0: NS_IMETHODIMP michael@0: nsCSSStyleSheet::StyleSheetLoaded(nsCSSStyleSheet* aSheet, michael@0: bool aWasAlternate, michael@0: nsresult aStatus) michael@0: { michael@0: if (aSheet->GetParentSheet() == nullptr) { michael@0: return NS_OK; // ignore if sheet has been detached already (see parseSheet) michael@0: } michael@0: NS_ASSERTION(this == aSheet->GetParentSheet(), michael@0: "We are being notified of a sheet load for a sheet that is not our child!"); michael@0: michael@0: if (mDocument && NS_SUCCEEDED(aStatus)) { michael@0: mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true); michael@0: michael@0: // XXXldb @import rules shouldn't even implement nsIStyleRule (but michael@0: // they do)! michael@0: mDocument->StyleRuleAdded(this, aSheet->GetOwnerRule()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSStyleSheet::ParseSheet(const nsAString& aInput) michael@0: { michael@0: // Not doing this if the sheet is not complete! michael@0: if (!mInner->mComplete) { michael@0: return NS_ERROR_DOM_INVALID_ACCESS_ERR; michael@0: } michael@0: michael@0: // Hold strong ref to the CSSLoader in case the document update michael@0: // kills the document michael@0: nsRefPtr loader; michael@0: if (mDocument) { michael@0: loader = mDocument->CSSLoader(); michael@0: NS_ASSERTION(loader, "Document with no CSS loader!"); michael@0: } else { michael@0: loader = new css::Loader(); michael@0: } michael@0: michael@0: mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true); michael@0: michael@0: WillDirty(); michael@0: michael@0: // detach existing rules (including child sheets via import rules) michael@0: int ruleCount; michael@0: while ((ruleCount = mInner->mOrderedRules.Count()) != 0) { michael@0: nsRefPtr rule = mInner->mOrderedRules.ObjectAt(ruleCount - 1); michael@0: mInner->mOrderedRules.RemoveObjectAt(ruleCount - 1); michael@0: rule->SetStyleSheet(nullptr); michael@0: if (mDocument) { michael@0: mDocument->StyleRuleRemoved(this, rule); michael@0: } michael@0: } michael@0: michael@0: // nuke child sheets list and current namespace map michael@0: for (nsCSSStyleSheet* child = mInner->mFirstChild; child; child = child->mNext) { michael@0: NS_ASSERTION(child->mParent == this, "Child sheet is not parented to this!"); michael@0: child->mParent = nullptr; michael@0: child->mDocument = nullptr; michael@0: } michael@0: mInner->mFirstChild = nullptr; michael@0: mInner->mNameSpaceMap = nullptr; michael@0: michael@0: // allow unsafe rules if the style sheet's principal is the system principal michael@0: bool allowUnsafeRules = nsContentUtils::IsSystemPrincipal(mInner->mPrincipal); michael@0: michael@0: nsCSSParser parser(loader, this); michael@0: nsresult rv = parser.ParseSheet(aInput, mInner->mSheetURI, mInner->mBaseURI, michael@0: mInner->mPrincipal, 1, allowUnsafeRules); michael@0: DidDirty(); // we are always 'dirty' here since we always remove rules first michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // notify document of all new rules michael@0: if (mDocument) { michael@0: for (int32_t index = 0; index < mInner->mOrderedRules.Count(); ++index) { michael@0: nsRefPtr rule = mInner->mOrderedRules.ObjectAt(index); michael@0: if (rule->GetType() == css::Rule::IMPORT_RULE && michael@0: RuleHasPendingChildSheet(rule)) { michael@0: continue; // notify when loaded (see StyleSheetLoaded) michael@0: } michael@0: mDocument->StyleRuleAdded(this, rule); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ nsIURI* michael@0: nsCSSStyleSheet::GetOriginalURI() const michael@0: { michael@0: return mInner->mOriginalSheetURI; michael@0: } michael@0: michael@0: /* virtual */ michael@0: JSObject* michael@0: nsCSSStyleSheet::WrapObject(JSContext* aCx) michael@0: { michael@0: return CSSStyleSheetBinding::Wrap(aCx, this); michael@0: }