diff options
Diffstat (limited to 'layout/style/nsCSSDataBlock.cpp')
-rw-r--r-- | layout/style/nsCSSDataBlock.cpp | 802 |
1 files changed, 802 insertions, 0 deletions
diff --git a/layout/style/nsCSSDataBlock.cpp b/layout/style/nsCSSDataBlock.cpp new file mode 100644 index 000000000..fe2dc621a --- /dev/null +++ b/layout/style/nsCSSDataBlock.cpp @@ -0,0 +1,802 @@ +/* -*- 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/. */ + +/* + * compact representation of the property-value pairs within a CSS + * declaration, and the code for expanding and compacting it + */ + +#include "nsCSSDataBlock.h" + +#include "CSSVariableImageTable.h" +#include "mozilla/css/Declaration.h" +#include "mozilla/css/ImageLoader.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/WritingModes.h" +#include "nsAutoPtr.h" +#include "nsIDocument.h" +#include "nsRuleData.h" +#include "nsStyleContext.h" +#include "nsStyleSet.h" + +using namespace mozilla; +using namespace mozilla::css; + +/** + * Does a fast move of aSource to aDest. The previous value in + * aDest is cleanly destroyed, and aSource is cleared. Returns + * true if, before the copy, the value at aSource compared unequal + * to the value at aDest; false otherwise. + */ +static bool +MoveValue(nsCSSValue* aSource, nsCSSValue* aDest) +{ + bool changed = (*aSource != *aDest); + aDest->~nsCSSValue(); + memcpy(aDest, aSource, sizeof(nsCSSValue)); + new (aSource) nsCSSValue(); + return changed; +} + +static bool +ShouldIgnoreColors(nsRuleData *aRuleData) +{ + return aRuleData->mLevel != SheetType::Agent && + aRuleData->mLevel != SheetType::User && + !aRuleData->mPresContext->UseDocumentColors(); +} + +/** + * Tries to call |nsCSSValue::StartImageLoad()| on an image source. + * Image sources are specified by |url()| or |-moz-image-rect()| function. + */ +static void +TryToStartImageLoadOnValue(const nsCSSValue& aValue, nsIDocument* aDocument, + nsStyleContext* aContext, nsCSSPropertyID aProperty, + bool aForTokenStream) +{ + MOZ_ASSERT(aDocument); + + if (aValue.GetUnit() == eCSSUnit_URL) { +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + // The 'mask-image' property accepts local reference URIs. + // For example, + // mask-image: url(#mask_id); // refer to a SVG mask element, whose id is + // // "mask_id", in the current document. + // For such 'mask-image' values (pointing to an in-document element), + // there is no need to trigger image download. + if (aProperty == eCSSProperty_mask_image) { + // Filter out all fragment URLs. + // Since nsCSSValue::GetURLStructValue runs much faster than + // nsIURI::EqualsExceptRef bellow, we get performance gain by this + // early return. + URLValue* urlValue = aValue.GetURLStructValue(); + if (urlValue->IsLocalRef()) { + return; + } + + // Even though urlValue is not a fragment URL, it might still refer to + // an internal resource. + // For example, aDocument base URL is "http://foo/index.html" and + // intentionally references a mask-image at + // url(http://foo/index.html#mask) which still refers to a resource in + // aDocument. + nsIURI* imageURI = aValue.GetURLValue(); + if (imageURI) { + nsIURI* docURI = aDocument->GetDocumentURI(); + bool isEqualExceptRef = false; + nsresult rv = imageURI->EqualsExceptRef(docURI, &isEqualExceptRef); + if (NS_SUCCEEDED(rv) && isEqualExceptRef) { + return; + } + } + } +#endif + aValue.StartImageLoad(aDocument); + if (aForTokenStream && aContext) { + CSSVariableImageTable::Add(aContext, aProperty, + aValue.GetImageStructValue()); + } + } + else if (aValue.GetUnit() == eCSSUnit_Image) { + // If we already have a request, see if this document needs to clone it. + imgIRequest* request = aValue.GetImageValue(nullptr); + + if (request) { + ImageValue* imageValue = aValue.GetImageStructValue(); + aDocument->StyleImageLoader()->MaybeRegisterCSSImage(imageValue); + if (aForTokenStream && aContext) { + CSSVariableImageTable::Add(aContext, aProperty, imageValue); + } + } + } + else if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) { + nsCSSValue::Array* arguments = aValue.GetArrayValue(); + MOZ_ASSERT(arguments->Count() == 6, "unexpected num of arguments"); + + const nsCSSValue& image = arguments->Item(1); + TryToStartImageLoadOnValue(image, aDocument, aContext, aProperty, + aForTokenStream); + } +} + +static void +TryToStartImageLoad(const nsCSSValue& aValue, nsIDocument* aDocument, + nsStyleContext* aContext, nsCSSPropertyID aProperty, + bool aForTokenStream) +{ + if (aValue.GetUnit() == eCSSUnit_List) { + for (const nsCSSValueList* l = aValue.GetListValue(); l; l = l->mNext) { + TryToStartImageLoad(l->mValue, aDocument, aContext, aProperty, + aForTokenStream); + } + } else if (nsCSSProps::PropHasFlags(aProperty, + CSS_PROPERTY_IMAGE_IS_IN_ARRAY_0)) { + if (aValue.GetUnit() == eCSSUnit_Array) { + TryToStartImageLoadOnValue(aValue.GetArrayValue()->Item(0), aDocument, + aContext, aProperty, aForTokenStream); + } + } else { + TryToStartImageLoadOnValue(aValue, aDocument, aContext, aProperty, + aForTokenStream); + } +} + +static inline bool +ShouldStartImageLoads(nsRuleData *aRuleData, nsCSSPropertyID aProperty) +{ + // Don't initiate image loads for if-visited styles. This is + // important because: + // (1) it's a waste of CPU and bandwidth + // (2) in some cases we'd start the image load on a style change + // where we wouldn't have started the load initially, which makes + // which links are visited detectable to Web pages (see bug + // 557287) + return !aRuleData->mStyleContext->IsStyleIfVisited() && + nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_START_IMAGE_LOADS); +} + +static void +MapSinglePropertyInto(nsCSSPropertyID aTargetProp, + const nsCSSValue* aSrcValue, + nsCSSValue* aTargetValue, + nsRuleData* aRuleData) +{ + MOZ_ASSERT(!nsCSSProps::PropHasFlags(aTargetProp, CSS_PROPERTY_LOGICAL), + "Can't map into a logical property"); + MOZ_ASSERT(aSrcValue->GetUnit() != eCSSUnit_Null, "oops"); + + // Although aTargetValue is the nsCSSValue we are going to write into, + // we also look at its value before writing into it. This is done + // when aTargetValue is a token stream value, which is the case when we + // have just re-parsed a property that had a variable reference (in + // nsCSSParser::ParsePropertyWithVariableReferences). TryToStartImageLoad + // then records any resulting ImageValue objects in the + // CSSVariableImageTable, to give them the appropriate lifetime. + MOZ_ASSERT(aTargetValue->GetUnit() == eCSSUnit_TokenStream || + aTargetValue->GetUnit() == eCSSUnit_Null, + "aTargetValue must only be a token stream (when re-parsing " + "properties with variable references) or null"); + + if (ShouldStartImageLoads(aRuleData, aTargetProp)) { + nsIDocument* doc = aRuleData->mPresContext->Document(); + TryToStartImageLoad(*aSrcValue, doc, aRuleData->mStyleContext, + aTargetProp, + aTargetValue->GetUnit() == eCSSUnit_TokenStream); + } + *aTargetValue = *aSrcValue; + if (nsCSSProps::PropHasFlags(aTargetProp, + CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED) && + ShouldIgnoreColors(aRuleData)) + { + if (aTargetProp == eCSSProperty_background_color) { + // Force non-'transparent' background + // colors to the user's default. + if (aTargetValue->IsNonTransparentColor()) { + aTargetValue->SetColorValue(aRuleData->mPresContext-> + DefaultBackgroundColor()); + } + } else { + // Ignore 'color', 'border-*-color', etc. + *aTargetValue = nsCSSValue(); + } + } +} + +/** + * If aProperty is a logical property, converts it to the equivalent physical + * property based on writing mode information obtained from aRuleData's + * style context. + */ +static inline void +EnsurePhysicalProperty(nsCSSPropertyID& aProperty, nsRuleData* aRuleData) +{ + bool isAxisProperty = + nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_AXIS); + bool isBlock = + nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_BLOCK_AXIS); + + int index; + + if (isAxisProperty) { + LogicalAxis logicalAxis = isBlock ? eLogicalAxisBlock : eLogicalAxisInline; + uint8_t wm = aRuleData->mStyleContext->StyleVisibility()->mWritingMode; + PhysicalAxis axis = + WritingMode::PhysicalAxisForLogicalAxis(wm, logicalAxis); + + // We rely on physical axis constants values matching the order of the + // physical properties in the logical group array. + static_assert(eAxisVertical == 0 && eAxisHorizontal == 1, + "unexpected axis constant values"); + index = axis; + } else { + bool isEnd = + nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_END_EDGE); + + LogicalEdge edge = isEnd ? eLogicalEdgeEnd : eLogicalEdgeStart; + + // We handle block axis logical properties separately to save a bit of + // work that the WritingMode constructor does that is unnecessary + // unless we have an inline axis property. + mozilla::css::Side side; + if (isBlock) { + uint8_t wm = aRuleData->mStyleContext->StyleVisibility()->mWritingMode; + side = WritingMode::PhysicalSideForBlockAxis(wm, edge); + } else { + WritingMode wm(aRuleData->mStyleContext); + side = wm.PhysicalSideForInlineAxis(edge); + } + + // We rely on the physical side constant values matching the order of + // the physical properties in the logical group array. + static_assert(NS_SIDE_TOP == 0 && NS_SIDE_RIGHT == 1 && + NS_SIDE_BOTTOM == 2 && NS_SIDE_LEFT == 3, + "unexpected side constant values"); + index = side; + } + + const nsCSSPropertyID* props = nsCSSProps::LogicalGroup(aProperty); + size_t len = isAxisProperty ? 2 : 4; +#ifdef DEBUG + for (size_t i = 0; i < len; i++) { + MOZ_ASSERT(props[i] != eCSSProperty_UNKNOWN, + "unexpected logical group length"); + } + MOZ_ASSERT(props[len] == eCSSProperty_UNKNOWN, + "unexpected logical group length"); +#endif + + for (size_t i = 0; i < len; i++) { + if (aRuleData->ValueFor(props[i])->GetUnit() == eCSSUnit_Null) { + // A declaration of one of the logical properties in this logical + // group (but maybe not aProperty) would be the winning + // declaration in the cascade. This means that it's reasonably + // likely that this logical property could be the winning + // declaration in the cascade for some values of writing-mode, + // direction, and text-orientation. (It doesn't mean that for + // sure, though. For example, if this is a block-start logical + // property, and all but the bottom physical property were set. + // But the common case we want to hit here is logical declarations + // that are completely overridden by a shorthand.) + // + // If this logical property could be the winning declaration in + // the cascade for some values of writing-mode, direction, and + // text-orientation, then we have to fault the resulting style + // struct out of the rule tree. We can't cache anything on the + // rule tree if it depends on data from the style context, since + // data cached in the rule tree could be used with a style context + // with a different value of the depended-upon data. + uint8_t wm = WritingMode(aRuleData->mStyleContext).GetBits(); + aRuleData->mConditions.SetWritingModeDependency(wm); + break; + } + } + + aProperty = props[index]; +} + +void +nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const +{ + // If we have no data for these structs, then return immediately. + // This optimization should make us return most of the time, so we + // have to worry much less (although still some) about the speed of + // the rest of the function. + if (!(aRuleData->mSIDs & mStyleBits)) + return; + + // We process these in reverse order so that we end up mapping the + // right property when one can be expressed using both logical and + // physical property names. + for (uint32_t i = mNumProps; i-- > 0; ) { + nsCSSPropertyID iProp = PropertyAtIndex(i); + if (nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[iProp]) & + aRuleData->mSIDs) { + if (nsCSSProps::PropHasFlags(iProp, CSS_PROPERTY_LOGICAL)) { + EnsurePhysicalProperty(iProp, aRuleData); + } + nsCSSValue* target = aRuleData->ValueFor(iProp); + if (target->GetUnit() == eCSSUnit_Null) { + const nsCSSValue *val = ValueAtIndex(i); + // In order for variable resolution to have the right information + // about the stylesheet level of a value, that level needs to be + // stored on the token stream. We can't do that at creation time + // because the CSS parser (which creates the object) has no idea + // about the stylesheet level, so we do it here instead, where + // the rule walking will have just updated aRuleData. + if (val->GetUnit() == eCSSUnit_TokenStream) { + val->GetTokenStreamValue()->mLevel = aRuleData->mLevel; + } + MapSinglePropertyInto(iProp, val, target, aRuleData); + } + } + } +} + +const nsCSSValue* +nsCSSCompressedDataBlock::ValueFor(nsCSSPropertyID aProperty) const +{ + MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), + "Don't call for shorthands"); + + // If we have no data for this struct, then return immediately. + // This optimization should make us return most of the time, so we + // have to worry much less (although still some) about the speed of + // the rest of the function. + if (!(nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[aProperty]) & + mStyleBits)) + return nullptr; + + for (uint32_t i = 0; i < mNumProps; i++) { + if (PropertyAtIndex(i) == aProperty) { + return ValueAtIndex(i); + } + } + + return nullptr; +} + +bool +nsCSSCompressedDataBlock::TryReplaceValue(nsCSSPropertyID aProperty, + nsCSSExpandedDataBlock& aFromBlock, + bool *aChanged) +{ + nsCSSValue* newValue = aFromBlock.PropertyAt(aProperty); + MOZ_ASSERT(newValue && newValue->GetUnit() != eCSSUnit_Null, + "cannot replace with empty value"); + + const nsCSSValue* oldValue = ValueFor(aProperty); + if (!oldValue) { + *aChanged = false; + return false; + } + + *aChanged = MoveValue(newValue, const_cast<nsCSSValue*>(oldValue)); + aFromBlock.ClearPropertyBit(aProperty); + return true; +} + +nsCSSCompressedDataBlock* +nsCSSCompressedDataBlock::Clone() const +{ + nsAutoPtr<nsCSSCompressedDataBlock> + result(new(mNumProps) nsCSSCompressedDataBlock(mNumProps)); + + result->mStyleBits = mStyleBits; + + for (uint32_t i = 0; i < mNumProps; i++) { + result->SetPropertyAtIndex(i, PropertyAtIndex(i)); + result->CopyValueToIndex(i, ValueAtIndex(i)); + } + + return result.forget(); +} + +nsCSSCompressedDataBlock::~nsCSSCompressedDataBlock() +{ + for (uint32_t i = 0; i < mNumProps; i++) { +#ifdef DEBUG + (void)PropertyAtIndex(i); // this checks the property is in range +#endif + const nsCSSValue* val = ValueAtIndex(i); + MOZ_ASSERT(val->GetUnit() != eCSSUnit_Null, "oops"); + val->~nsCSSValue(); + } +} + +/* static */ nsCSSCompressedDataBlock* +nsCSSCompressedDataBlock::CreateEmptyBlock() +{ + nsCSSCompressedDataBlock *result = new(0) nsCSSCompressedDataBlock(0); + return result; +} + +size_t +nsCSSCompressedDataBlock::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + for (uint32_t i = 0; i < mNumProps; i++) { + n += ValueAtIndex(i)->SizeOfExcludingThis(aMallocSizeOf); + } + return n; +} + +bool +nsCSSCompressedDataBlock::HasDefaultBorderImageSlice() const +{ + const nsCSSValueList *slice = + ValueFor(eCSSProperty_border_image_slice)->GetListValue(); + return !slice->mNext && + slice->mValue.GetRectValue().AllSidesEqualTo( + nsCSSValue(1.0f, eCSSUnit_Percent)); +} + +bool +nsCSSCompressedDataBlock::HasDefaultBorderImageWidth() const +{ + const nsCSSRect &width = + ValueFor(eCSSProperty_border_image_width)->GetRectValue(); + return width.AllSidesEqualTo(nsCSSValue(1.0f, eCSSUnit_Number)); +} + +bool +nsCSSCompressedDataBlock::HasDefaultBorderImageOutset() const +{ + const nsCSSRect &outset = + ValueFor(eCSSProperty_border_image_outset)->GetRectValue(); + return outset.AllSidesEqualTo(nsCSSValue(0.0f, eCSSUnit_Number)); +} + +bool +nsCSSCompressedDataBlock::HasDefaultBorderImageRepeat() const +{ + const nsCSSValuePair &repeat = + ValueFor(eCSSProperty_border_image_repeat)->GetPairValue(); + return repeat.BothValuesEqualTo( + nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH, eCSSUnit_Enumerated)); +} + +/*****************************************************************************/ + +nsCSSExpandedDataBlock::nsCSSExpandedDataBlock() +{ + AssertInitialState(); +} + +nsCSSExpandedDataBlock::~nsCSSExpandedDataBlock() +{ + AssertInitialState(); +} + +void +nsCSSExpandedDataBlock::DoExpand(nsCSSCompressedDataBlock *aBlock, + bool aImportant) +{ + /* + * Save needless copying and allocation by copying the memory + * corresponding to the stored data in the compressed block. + */ + for (uint32_t i = 0; i < aBlock->mNumProps; i++) { + nsCSSPropertyID iProp = aBlock->PropertyAtIndex(i); + MOZ_ASSERT(!nsCSSProps::IsShorthand(iProp), "out of range"); + MOZ_ASSERT(!HasPropertyBit(iProp), + "compressed block has property multiple times"); + SetPropertyBit(iProp); + if (aImportant) + SetImportantBit(iProp); + + const nsCSSValue* val = aBlock->ValueAtIndex(i); + nsCSSValue* dest = PropertyAt(iProp); + MOZ_ASSERT(val->GetUnit() != eCSSUnit_Null, "oops"); + MOZ_ASSERT(dest->GetUnit() == eCSSUnit_Null, + "expanding into non-empty block"); +#ifdef NS_BUILD_REFCNT_LOGGING + dest->~nsCSSValue(); +#endif + memcpy(dest, val, sizeof(nsCSSValue)); + } + + // Set the number of properties to zero so that we don't destroy the + // remnants of what we just copied. + aBlock->SetNumPropsToZero(); + delete aBlock; +} + +void +nsCSSExpandedDataBlock::Expand(nsCSSCompressedDataBlock *aNormalBlock, + nsCSSCompressedDataBlock *aImportantBlock) +{ + MOZ_ASSERT(aNormalBlock, "unexpected null block"); + AssertInitialState(); + + DoExpand(aNormalBlock, false); + if (aImportantBlock) { + DoExpand(aImportantBlock, true); + } +} + +void +nsCSSExpandedDataBlock::ComputeNumProps(uint32_t* aNumPropsNormal, + uint32_t* aNumPropsImportant) +{ + *aNumPropsNormal = *aNumPropsImportant = 0; + for (size_t iHigh = 0; iHigh < nsCSSPropertyIDSet::kChunkCount; ++iHigh) { + if (!mPropertiesSet.HasPropertyInChunk(iHigh)) + continue; + for (size_t iLow = 0; iLow < nsCSSPropertyIDSet::kBitsInChunk; ++iLow) { + if (!mPropertiesSet.HasPropertyAt(iHigh, iLow)) + continue; +#ifdef DEBUG + nsCSSPropertyID iProp = nsCSSPropertyIDSet::CSSPropertyAt(iHigh, iLow); +#endif + MOZ_ASSERT(!nsCSSProps::IsShorthand(iProp), "out of range"); + MOZ_ASSERT(PropertyAt(iProp)->GetUnit() != eCSSUnit_Null, + "null value while computing size"); + if (mPropertiesImportant.HasPropertyAt(iHigh, iLow)) + (*aNumPropsImportant)++; + else + (*aNumPropsNormal)++; + } + } +} + +void +nsCSSExpandedDataBlock::Compress(nsCSSCompressedDataBlock **aNormalBlock, + nsCSSCompressedDataBlock **aImportantBlock, + const nsTArray<uint32_t>& aOrder) +{ + nsAutoPtr<nsCSSCompressedDataBlock> result_normal, result_important; + uint32_t i_normal = 0, i_important = 0; + + uint32_t numPropsNormal, numPropsImportant; + ComputeNumProps(&numPropsNormal, &numPropsImportant); + + result_normal = + new(numPropsNormal) nsCSSCompressedDataBlock(numPropsNormal); + + if (numPropsImportant != 0) { + result_important = + new(numPropsImportant) nsCSSCompressedDataBlock(numPropsImportant); + } else { + result_important = nullptr; + } + + /* + * Save needless copying and allocation by copying the memory + * corresponding to the stored data in the expanded block, and then + * clearing the data in the expanded block. + */ + for (size_t i = 0; i < aOrder.Length(); i++) { + nsCSSPropertyID iProp = static_cast<nsCSSPropertyID>(aOrder[i]); + if (iProp >= eCSSProperty_COUNT) { + // a custom property + continue; + } + MOZ_ASSERT(mPropertiesSet.HasProperty(iProp), + "aOrder identifies a property not in the expanded " + "data block"); + MOZ_ASSERT(!nsCSSProps::IsShorthand(iProp), "out of range"); + bool important = mPropertiesImportant.HasProperty(iProp); + nsCSSCompressedDataBlock *result = + important ? result_important : result_normal; + uint32_t* ip = important ? &i_important : &i_normal; + nsCSSValue* val = PropertyAt(iProp); + MOZ_ASSERT(val->GetUnit() != eCSSUnit_Null, + "Null value while compressing"); + result->SetPropertyAtIndex(*ip, iProp); + result->RawCopyValueToIndex(*ip, val); + new (val) nsCSSValue(); + (*ip)++; + result->mStyleBits |= + nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[iProp]); + } + + MOZ_ASSERT(numPropsNormal == i_normal, "bad numProps"); + + if (result_important) { + MOZ_ASSERT(numPropsImportant == i_important, "bad numProps"); + } + +#ifdef DEBUG + { + // assert that we didn't have any other properties on this expanded data + // block that we didn't find in aOrder + uint32_t numPropsInSet = 0; + for (size_t iHigh = 0; iHigh < nsCSSPropertyIDSet::kChunkCount; iHigh++) { + if (!mPropertiesSet.HasPropertyInChunk(iHigh)) { + continue; + } + for (size_t iLow = 0; iLow < nsCSSPropertyIDSet::kBitsInChunk; iLow++) { + if (mPropertiesSet.HasPropertyAt(iHigh, iLow)) { + numPropsInSet++; + } + } + } + MOZ_ASSERT(numPropsNormal + numPropsImportant == numPropsInSet, + "aOrder missing properties from the expanded data block"); + } +#endif + + ClearSets(); + AssertInitialState(); + *aNormalBlock = result_normal.forget(); + *aImportantBlock = result_important.forget(); +} + +void +nsCSSExpandedDataBlock::AddLonghandProperty(nsCSSPropertyID aProperty, + const nsCSSValue& aValue) +{ + MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), + "property out of range"); + nsCSSValue& storage = *static_cast<nsCSSValue*>(PropertyAt(aProperty)); + storage = aValue; + SetPropertyBit(aProperty); +} + +void +nsCSSExpandedDataBlock::Clear() +{ + for (size_t iHigh = 0; iHigh < nsCSSPropertyIDSet::kChunkCount; ++iHigh) { + if (!mPropertiesSet.HasPropertyInChunk(iHigh)) + continue; + for (size_t iLow = 0; iLow < nsCSSPropertyIDSet::kBitsInChunk; ++iLow) { + if (!mPropertiesSet.HasPropertyAt(iHigh, iLow)) + continue; + nsCSSPropertyID iProp = nsCSSPropertyIDSet::CSSPropertyAt(iHigh, iLow); + ClearLonghandProperty(iProp); + } + } + + AssertInitialState(); +} + +void +nsCSSExpandedDataBlock::ClearProperty(nsCSSPropertyID aPropID) +{ + if (nsCSSProps::IsShorthand(aPropID)) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES( + p, aPropID, CSSEnabledState::eIgnoreEnabledState) { + ClearLonghandProperty(*p); + } + } else { + ClearLonghandProperty(aPropID); + } +} + +void +nsCSSExpandedDataBlock::ClearLonghandProperty(nsCSSPropertyID aPropID) +{ + MOZ_ASSERT(!nsCSSProps::IsShorthand(aPropID), "out of range"); + + ClearPropertyBit(aPropID); + ClearImportantBit(aPropID); + PropertyAt(aPropID)->Reset(); +} + +bool +nsCSSExpandedDataBlock::TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, + nsCSSPropertyID aPropID, + CSSEnabledState aEnabledState, + bool aIsImportant, + bool aOverrideImportant, + bool aMustCallValueAppended, + css::Declaration* aDeclaration, + nsIDocument* aSheetDocument) +{ + if (!nsCSSProps::IsShorthand(aPropID)) { + return DoTransferFromBlock(aFromBlock, aPropID, + aIsImportant, aOverrideImportant, + aMustCallValueAppended, aDeclaration, + aSheetDocument); + } + + // We can pass CSSEnabledState::eIgnore (here, and in ClearProperty + // above) rather than a value corresponding to whether we're parsing + // a UA style sheet or certified app because we assert in nsCSSProps:: + // AddRefTable that shorthand properties available in these contexts + // also have all of their subproperties available in these contexts. + bool changed = false; + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, aEnabledState) { + changed |= DoTransferFromBlock(aFromBlock, *p, + aIsImportant, aOverrideImportant, + aMustCallValueAppended, aDeclaration, + aSheetDocument); + } + return changed; +} + +bool +nsCSSExpandedDataBlock::DoTransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, + nsCSSPropertyID aPropID, + bool aIsImportant, + bool aOverrideImportant, + bool aMustCallValueAppended, + css::Declaration* aDeclaration, + nsIDocument* aSheetDocument) +{ + bool changed = false; + MOZ_ASSERT(aFromBlock.HasPropertyBit(aPropID), "oops"); + if (aIsImportant) { + if (!HasImportantBit(aPropID)) + changed = true; + SetImportantBit(aPropID); + } else { + if (HasImportantBit(aPropID)) { + // When parsing a declaration block, an !important declaration + // is not overwritten by an ordinary declaration of the same + // property later in the block. However, CSSOM manipulations + // come through here too, and in that case we do want to + // overwrite the property. + if (!aOverrideImportant) { + aFromBlock.ClearLonghandProperty(aPropID); + return false; + } + changed = true; + ClearImportantBit(aPropID); + } + } + + if (aMustCallValueAppended || !HasPropertyBit(aPropID)) { + aDeclaration->ValueAppended(aPropID); + } + + if (aSheetDocument) { + UseCounter useCounter = nsCSSProps::UseCounterFor(aPropID); + if (useCounter != eUseCounter_UNKNOWN) { + aSheetDocument->SetDocumentAndPageUseCounter(useCounter); + } + } + + SetPropertyBit(aPropID); + aFromBlock.ClearPropertyBit(aPropID); + + /* + * Save needless copying and allocation by calling the destructor in + * the destination, copying memory directly, and then using placement + * new. + */ + changed |= MoveValue(aFromBlock.PropertyAt(aPropID), PropertyAt(aPropID)); + return changed; +} + +void +nsCSSExpandedDataBlock::MapRuleInfoInto(nsCSSPropertyID aPropID, + nsRuleData* aRuleData) const +{ + MOZ_ASSERT(!nsCSSProps::IsShorthand(aPropID)); + + const nsCSSValue* src = PropertyAt(aPropID); + MOZ_ASSERT(src->GetUnit() != eCSSUnit_Null); + + nsCSSPropertyID physicalProp = aPropID; + if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_LOGICAL)) { + EnsurePhysicalProperty(physicalProp, aRuleData); + } + + nsCSSValue* dest = aRuleData->ValueFor(physicalProp); + MOZ_ASSERT(dest->GetUnit() == eCSSUnit_TokenStream && + dest->GetTokenStreamValue()->mPropertyID == aPropID); + + CSSVariableImageTable::ReplaceAll(aRuleData->mStyleContext, aPropID, [=] { + MapSinglePropertyInto(physicalProp, src, dest, aRuleData); + }); +} + +#ifdef DEBUG +void +nsCSSExpandedDataBlock::DoAssertInitialState() +{ + mPropertiesSet.AssertIsEmpty("not initial state"); + mPropertiesImportant.AssertIsEmpty("not initial state"); + + for (uint32_t i = 0; i < eCSSProperty_COUNT_no_shorthands; ++i) { + nsCSSPropertyID prop = nsCSSPropertyID(i); + MOZ_ASSERT(PropertyAt(prop)->GetUnit() == eCSSUnit_Null, + "not initial state"); + } +} +#endif |