summaryrefslogtreecommitdiffstats
path: root/layout/style/nsCSSDataBlock.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/nsCSSDataBlock.cpp')
-rw-r--r--layout/style/nsCSSDataBlock.cpp802
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