forked from mirrors/gecko-dev
		
	 90a32588be
			
		
	
	
		90a32588be
		
	
	
	
	
		
			
			MozReview-Commit-ID: HWq2IOLsflD --HG-- extra : rebase_source : eb5c3db6e2d88164bcd6a86448c4ad04cd251c93
		
			
				
	
	
		
			801 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			801 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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) {
 | |
|     // 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;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     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::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(eSideTop == 0 && eSideRight == 1 &&
 | |
|                   eSideBottom == 2 && eSideLeft == 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
 |