Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 * compact representation of the property-value pairs within a CSS
8 * declaration, and the code for expanding and compacting it
9 */
11 #include "nsCSSDataBlock.h"
12 #include "mozilla/MemoryReporting.h"
13 #include "mozilla/css/Declaration.h"
14 #include "mozilla/css/ImageLoader.h"
15 #include "nsRuleData.h"
16 #include "nsStyleSet.h"
17 #include "nsStyleContext.h"
18 #include "nsIDocument.h"
20 using namespace mozilla;
22 /**
23 * Does a fast move of aSource to aDest. The previous value in
24 * aDest is cleanly destroyed, and aSource is cleared. Returns
25 * true if, before the copy, the value at aSource compared unequal
26 * to the value at aDest; false otherwise.
27 */
28 static bool
29 MoveValue(nsCSSValue* aSource, nsCSSValue* aDest)
30 {
31 bool changed = (*aSource != *aDest);
32 aDest->~nsCSSValue();
33 memcpy(aDest, aSource, sizeof(nsCSSValue));
34 new (aSource) nsCSSValue();
35 return changed;
36 }
38 static bool
39 ShouldIgnoreColors(nsRuleData *aRuleData)
40 {
41 return aRuleData->mLevel != nsStyleSet::eAgentSheet &&
42 aRuleData->mLevel != nsStyleSet::eUserSheet &&
43 !aRuleData->mPresContext->UseDocumentColors();
44 }
46 /**
47 * Tries to call |nsCSSValue::StartImageLoad()| on an image source.
48 * Image sources are specified by |url()| or |-moz-image-rect()| function.
49 */
50 static void
51 TryToStartImageLoadOnValue(const nsCSSValue& aValue, nsIDocument* aDocument,
52 nsCSSValueTokenStream* aTokenStream)
53 {
54 MOZ_ASSERT(aDocument);
56 if (aValue.GetUnit() == eCSSUnit_URL) {
57 aValue.StartImageLoad(aDocument);
58 if (aTokenStream) {
59 aTokenStream->mImageValues.PutEntry(aValue.GetImageStructValue());
60 }
61 }
62 else if (aValue.GetUnit() == eCSSUnit_Image) {
63 // If we already have a request, see if this document needs to clone it.
64 imgIRequest* request = aValue.GetImageValue(nullptr);
66 if (request) {
67 mozilla::css::ImageValue* imageValue = aValue.GetImageStructValue();
68 aDocument->StyleImageLoader()->MaybeRegisterCSSImage(imageValue);
69 if (aTokenStream) {
70 aTokenStream->mImageValues.PutEntry(imageValue);
71 }
72 }
73 }
74 else if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) {
75 nsCSSValue::Array* arguments = aValue.GetArrayValue();
76 NS_ABORT_IF_FALSE(arguments->Count() == 6, "unexpected num of arguments");
78 const nsCSSValue& image = arguments->Item(1);
79 TryToStartImageLoadOnValue(image, aDocument, aTokenStream);
80 }
81 }
83 static void
84 TryToStartImageLoad(const nsCSSValue& aValue, nsIDocument* aDocument,
85 nsCSSProperty aProperty,
86 nsCSSValueTokenStream* aTokenStream)
87 {
88 if (aValue.GetUnit() == eCSSUnit_List) {
89 for (const nsCSSValueList* l = aValue.GetListValue(); l; l = l->mNext) {
90 TryToStartImageLoad(l->mValue, aDocument, aProperty, aTokenStream);
91 }
92 } else if (nsCSSProps::PropHasFlags(aProperty,
93 CSS_PROPERTY_IMAGE_IS_IN_ARRAY_0)) {
94 if (aValue.GetUnit() == eCSSUnit_Array) {
95 TryToStartImageLoadOnValue(aValue.GetArrayValue()->Item(0), aDocument,
96 aTokenStream);
97 }
98 } else {
99 TryToStartImageLoadOnValue(aValue, aDocument, aTokenStream);
100 }
101 }
103 static inline bool
104 ShouldStartImageLoads(nsRuleData *aRuleData, nsCSSProperty aProperty)
105 {
106 // Don't initiate image loads for if-visited styles. This is
107 // important because:
108 // (1) it's a waste of CPU and bandwidth
109 // (2) in some cases we'd start the image load on a style change
110 // where we wouldn't have started the load initially, which makes
111 // which links are visited detectable to Web pages (see bug
112 // 557287)
113 return !aRuleData->mStyleContext->IsStyleIfVisited() &&
114 nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_START_IMAGE_LOADS);
115 }
117 static void
118 MapSinglePropertyInto(nsCSSProperty aProp,
119 const nsCSSValue* aValue,
120 nsCSSValue* aTarget,
121 nsRuleData* aRuleData)
122 {
123 NS_ABORT_IF_FALSE(aValue->GetUnit() != eCSSUnit_Null, "oops");
125 // Although aTarget is the nsCSSValue we are going to write into,
126 // we also look at its value before writing into it. This is done
127 // when aTarget is a token stream value, which is the case when we
128 // have just re-parsed a property that had a variable reference (in
129 // nsCSSParser::ParsePropertyWithVariableReferences). TryToStartImageLoad
130 // then records any resulting ImageValue objects on the
131 // nsCSSValueTokenStream object we found on aTarget. See the comment
132 // above nsCSSValueTokenStream::mImageValues for why.
133 NS_ABORT_IF_FALSE(aTarget->GetUnit() == eCSSUnit_TokenStream ||
134 aTarget->GetUnit() == eCSSUnit_Null,
135 "aTarget must only be a token stream (when re-parsing "
136 "properties with variable references) or null");
138 nsCSSValueTokenStream* tokenStream =
139 aTarget->GetUnit() == eCSSUnit_TokenStream ?
140 aTarget->GetTokenStreamValue() :
141 nullptr;
143 if (ShouldStartImageLoads(aRuleData, aProp)) {
144 nsIDocument* doc = aRuleData->mPresContext->Document();
145 TryToStartImageLoad(*aValue, doc, aProp, tokenStream);
146 }
147 *aTarget = *aValue;
148 if (nsCSSProps::PropHasFlags(aProp,
149 CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED) &&
150 ShouldIgnoreColors(aRuleData))
151 {
152 if (aProp == eCSSProperty_background_color) {
153 // Force non-'transparent' background
154 // colors to the user's default.
155 if (aTarget->IsNonTransparentColor()) {
156 aTarget->SetColorValue(aRuleData->mPresContext->
157 DefaultBackgroundColor());
158 }
159 } else {
160 // Ignore 'color', 'border-*-color', etc.
161 *aTarget = nsCSSValue();
162 }
163 }
164 }
166 void
167 nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const
168 {
169 // If we have no data for these structs, then return immediately.
170 // This optimization should make us return most of the time, so we
171 // have to worry much less (although still some) about the speed of
172 // the rest of the function.
173 if (!(aRuleData->mSIDs & mStyleBits))
174 return;
176 for (uint32_t i = 0; i < mNumProps; i++) {
177 nsCSSProperty iProp = PropertyAtIndex(i);
178 if (nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[iProp]) &
179 aRuleData->mSIDs) {
180 nsCSSValue* target = aRuleData->ValueFor(iProp);
181 if (target->GetUnit() == eCSSUnit_Null) {
182 const nsCSSValue *val = ValueAtIndex(i);
183 MapSinglePropertyInto(iProp, val, target, aRuleData);
184 }
185 }
186 }
187 }
189 const nsCSSValue*
190 nsCSSCompressedDataBlock::ValueFor(nsCSSProperty aProperty) const
191 {
192 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
193 "Don't call for shorthands");
195 // If we have no data for this struct, then return immediately.
196 // This optimization should make us return most of the time, so we
197 // have to worry much less (although still some) about the speed of
198 // the rest of the function.
199 if (!(nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[aProperty]) &
200 mStyleBits))
201 return nullptr;
203 for (uint32_t i = 0; i < mNumProps; i++) {
204 if (PropertyAtIndex(i) == aProperty) {
205 return ValueAtIndex(i);
206 }
207 }
209 return nullptr;
210 }
212 bool
213 nsCSSCompressedDataBlock::TryReplaceValue(nsCSSProperty aProperty,
214 nsCSSExpandedDataBlock& aFromBlock,
215 bool *aChanged)
216 {
217 nsCSSValue* newValue = aFromBlock.PropertyAt(aProperty);
218 NS_ABORT_IF_FALSE(newValue && newValue->GetUnit() != eCSSUnit_Null,
219 "cannot replace with empty value");
221 const nsCSSValue* oldValue = ValueFor(aProperty);
222 if (!oldValue) {
223 *aChanged = false;
224 return false;
225 }
227 *aChanged = MoveValue(newValue, const_cast<nsCSSValue*>(oldValue));
228 aFromBlock.ClearPropertyBit(aProperty);
229 return true;
230 }
232 nsCSSCompressedDataBlock*
233 nsCSSCompressedDataBlock::Clone() const
234 {
235 nsAutoPtr<nsCSSCompressedDataBlock>
236 result(new(mNumProps) nsCSSCompressedDataBlock(mNumProps));
238 result->mStyleBits = mStyleBits;
240 for (uint32_t i = 0; i < mNumProps; i++) {
241 result->SetPropertyAtIndex(i, PropertyAtIndex(i));
242 result->CopyValueToIndex(i, ValueAtIndex(i));
243 }
245 return result.forget();
246 }
248 nsCSSCompressedDataBlock::~nsCSSCompressedDataBlock()
249 {
250 for (uint32_t i = 0; i < mNumProps; i++) {
251 #ifdef DEBUG
252 (void)PropertyAtIndex(i); // this checks the property is in range
253 #endif
254 const nsCSSValue* val = ValueAtIndex(i);
255 NS_ABORT_IF_FALSE(val->GetUnit() != eCSSUnit_Null, "oops");
256 val->~nsCSSValue();
257 }
258 }
260 /* static */ nsCSSCompressedDataBlock*
261 nsCSSCompressedDataBlock::CreateEmptyBlock()
262 {
263 nsCSSCompressedDataBlock *result = new(0) nsCSSCompressedDataBlock(0);
264 return result;
265 }
267 size_t
268 nsCSSCompressedDataBlock::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
269 {
270 size_t n = aMallocSizeOf(this);
271 for (uint32_t i = 0; i < mNumProps; i++) {
272 n += ValueAtIndex(i)->SizeOfExcludingThis(aMallocSizeOf);
273 }
274 return n;
275 }
277 bool
278 nsCSSCompressedDataBlock::HasDefaultBorderImageSlice() const
279 {
280 const nsCSSValueList *slice =
281 ValueFor(eCSSProperty_border_image_slice)->GetListValue();
282 return !slice->mNext &&
283 slice->mValue.GetRectValue().AllSidesEqualTo(
284 nsCSSValue(1.0f, eCSSUnit_Percent));
285 }
287 bool
288 nsCSSCompressedDataBlock::HasDefaultBorderImageWidth() const
289 {
290 const nsCSSRect &width =
291 ValueFor(eCSSProperty_border_image_width)->GetRectValue();
292 return width.AllSidesEqualTo(nsCSSValue(1.0f, eCSSUnit_Number));
293 }
295 bool
296 nsCSSCompressedDataBlock::HasDefaultBorderImageOutset() const
297 {
298 const nsCSSRect &outset =
299 ValueFor(eCSSProperty_border_image_outset)->GetRectValue();
300 return outset.AllSidesEqualTo(nsCSSValue(0.0f, eCSSUnit_Number));
301 }
303 bool
304 nsCSSCompressedDataBlock::HasDefaultBorderImageRepeat() const
305 {
306 const nsCSSValuePair &repeat =
307 ValueFor(eCSSProperty_border_image_repeat)->GetPairValue();
308 return repeat.BothValuesEqualTo(
309 nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH, eCSSUnit_Enumerated));
310 }
312 /*****************************************************************************/
314 nsCSSExpandedDataBlock::nsCSSExpandedDataBlock()
315 {
316 AssertInitialState();
317 }
319 nsCSSExpandedDataBlock::~nsCSSExpandedDataBlock()
320 {
321 AssertInitialState();
322 }
324 void
325 nsCSSExpandedDataBlock::DoExpand(nsCSSCompressedDataBlock *aBlock,
326 bool aImportant)
327 {
328 /*
329 * Save needless copying and allocation by copying the memory
330 * corresponding to the stored data in the compressed block.
331 */
332 for (uint32_t i = 0; i < aBlock->mNumProps; i++) {
333 nsCSSProperty iProp = aBlock->PropertyAtIndex(i);
334 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(iProp), "out of range");
335 NS_ABORT_IF_FALSE(!HasPropertyBit(iProp),
336 "compressed block has property multiple times");
337 SetPropertyBit(iProp);
338 if (aImportant)
339 SetImportantBit(iProp);
341 const nsCSSValue* val = aBlock->ValueAtIndex(i);
342 nsCSSValue* dest = PropertyAt(iProp);
343 NS_ABORT_IF_FALSE(val->GetUnit() != eCSSUnit_Null, "oops");
344 NS_ABORT_IF_FALSE(dest->GetUnit() == eCSSUnit_Null,
345 "expanding into non-empty block");
346 #ifdef NS_BUILD_REFCNT_LOGGING
347 dest->~nsCSSValue();
348 #endif
349 memcpy(dest, val, sizeof(nsCSSValue));
350 }
352 // Set the number of properties to zero so that we don't destroy the
353 // remnants of what we just copied.
354 aBlock->SetNumPropsToZero();
355 delete aBlock;
356 }
358 void
359 nsCSSExpandedDataBlock::Expand(nsCSSCompressedDataBlock *aNormalBlock,
360 nsCSSCompressedDataBlock *aImportantBlock)
361 {
362 NS_ABORT_IF_FALSE(aNormalBlock, "unexpected null block");
363 AssertInitialState();
365 DoExpand(aNormalBlock, false);
366 if (aImportantBlock) {
367 DoExpand(aImportantBlock, true);
368 }
369 }
371 void
372 nsCSSExpandedDataBlock::ComputeNumProps(uint32_t* aNumPropsNormal,
373 uint32_t* aNumPropsImportant)
374 {
375 *aNumPropsNormal = *aNumPropsImportant = 0;
376 for (size_t iHigh = 0; iHigh < nsCSSPropertySet::kChunkCount; ++iHigh) {
377 if (!mPropertiesSet.HasPropertyInChunk(iHigh))
378 continue;
379 for (size_t iLow = 0; iLow < nsCSSPropertySet::kBitsInChunk; ++iLow) {
380 if (!mPropertiesSet.HasPropertyAt(iHigh, iLow))
381 continue;
382 #ifdef DEBUG
383 nsCSSProperty iProp = nsCSSPropertySet::CSSPropertyAt(iHigh, iLow);
384 #endif
385 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(iProp), "out of range");
386 NS_ABORT_IF_FALSE(PropertyAt(iProp)->GetUnit() != eCSSUnit_Null,
387 "null value while computing size");
388 if (mPropertiesImportant.HasPropertyAt(iHigh, iLow))
389 (*aNumPropsImportant)++;
390 else
391 (*aNumPropsNormal)++;
392 }
393 }
394 }
396 void
397 nsCSSExpandedDataBlock::Compress(nsCSSCompressedDataBlock **aNormalBlock,
398 nsCSSCompressedDataBlock **aImportantBlock)
399 {
400 nsAutoPtr<nsCSSCompressedDataBlock> result_normal, result_important;
401 uint32_t i_normal = 0, i_important = 0;
403 uint32_t numPropsNormal, numPropsImportant;
404 ComputeNumProps(&numPropsNormal, &numPropsImportant);
406 result_normal =
407 new(numPropsNormal) nsCSSCompressedDataBlock(numPropsNormal);
409 if (numPropsImportant != 0) {
410 result_important =
411 new(numPropsImportant) nsCSSCompressedDataBlock(numPropsImportant);
412 } else {
413 result_important = nullptr;
414 }
416 /*
417 * Save needless copying and allocation by copying the memory
418 * corresponding to the stored data in the expanded block, and then
419 * clearing the data in the expanded block.
420 */
421 for (size_t iHigh = 0; iHigh < nsCSSPropertySet::kChunkCount; ++iHigh) {
422 if (!mPropertiesSet.HasPropertyInChunk(iHigh))
423 continue;
424 for (size_t iLow = 0; iLow < nsCSSPropertySet::kBitsInChunk; ++iLow) {
425 if (!mPropertiesSet.HasPropertyAt(iHigh, iLow))
426 continue;
427 nsCSSProperty iProp = nsCSSPropertySet::CSSPropertyAt(iHigh, iLow);
428 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(iProp), "out of range");
429 bool important =
430 mPropertiesImportant.HasPropertyAt(iHigh, iLow);
431 nsCSSCompressedDataBlock *result =
432 important ? result_important : result_normal;
433 uint32_t* ip = important ? &i_important : &i_normal;
434 nsCSSValue* val = PropertyAt(iProp);
435 NS_ABORT_IF_FALSE(val->GetUnit() != eCSSUnit_Null,
436 "Null value while compressing");
437 result->SetPropertyAtIndex(*ip, iProp);
438 result->RawCopyValueToIndex(*ip, val);
439 new (val) nsCSSValue();
440 (*ip)++;
441 result->mStyleBits |=
442 nsCachedStyleData::GetBitForSID(nsCSSProps::kSIDTable[iProp]);
443 }
444 }
446 NS_ABORT_IF_FALSE(numPropsNormal == i_normal, "bad numProps");
448 if (result_important) {
449 NS_ABORT_IF_FALSE(numPropsImportant == i_important, "bad numProps");
450 }
452 ClearSets();
453 AssertInitialState();
454 *aNormalBlock = result_normal.forget();
455 *aImportantBlock = result_important.forget();
456 }
458 void
459 nsCSSExpandedDataBlock::AddLonghandProperty(nsCSSProperty aProperty,
460 const nsCSSValue& aValue)
461 {
462 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
463 "property out of range");
464 nsCSSValue& storage = *static_cast<nsCSSValue*>(PropertyAt(aProperty));
465 storage = aValue;
466 SetPropertyBit(aProperty);
467 }
469 void
470 nsCSSExpandedDataBlock::Clear()
471 {
472 for (size_t iHigh = 0; iHigh < nsCSSPropertySet::kChunkCount; ++iHigh) {
473 if (!mPropertiesSet.HasPropertyInChunk(iHigh))
474 continue;
475 for (size_t iLow = 0; iLow < nsCSSPropertySet::kBitsInChunk; ++iLow) {
476 if (!mPropertiesSet.HasPropertyAt(iHigh, iLow))
477 continue;
478 nsCSSProperty iProp = nsCSSPropertySet::CSSPropertyAt(iHigh, iLow);
479 ClearLonghandProperty(iProp);
480 }
481 }
483 AssertInitialState();
484 }
486 void
487 nsCSSExpandedDataBlock::ClearProperty(nsCSSProperty aPropID)
488 {
489 if (nsCSSProps::IsShorthand(aPropID)) {
490 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID) {
491 ClearLonghandProperty(*p);
492 }
493 } else {
494 ClearLonghandProperty(aPropID);
495 }
496 }
498 void
499 nsCSSExpandedDataBlock::ClearLonghandProperty(nsCSSProperty aPropID)
500 {
501 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aPropID), "out of range");
503 ClearPropertyBit(aPropID);
504 ClearImportantBit(aPropID);
505 PropertyAt(aPropID)->Reset();
506 }
508 bool
509 nsCSSExpandedDataBlock::TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock,
510 nsCSSProperty aPropID,
511 bool aIsImportant,
512 bool aOverrideImportant,
513 bool aMustCallValueAppended,
514 css::Declaration* aDeclaration)
515 {
516 if (!nsCSSProps::IsShorthand(aPropID)) {
517 return DoTransferFromBlock(aFromBlock, aPropID,
518 aIsImportant, aOverrideImportant,
519 aMustCallValueAppended, aDeclaration);
520 }
522 bool changed = false;
523 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID) {
524 changed |= DoTransferFromBlock(aFromBlock, *p,
525 aIsImportant, aOverrideImportant,
526 aMustCallValueAppended, aDeclaration);
527 }
528 return changed;
529 }
531 bool
532 nsCSSExpandedDataBlock::DoTransferFromBlock(nsCSSExpandedDataBlock& aFromBlock,
533 nsCSSProperty aPropID,
534 bool aIsImportant,
535 bool aOverrideImportant,
536 bool aMustCallValueAppended,
537 css::Declaration* aDeclaration)
538 {
539 bool changed = false;
540 NS_ABORT_IF_FALSE(aFromBlock.HasPropertyBit(aPropID), "oops");
541 if (aIsImportant) {
542 if (!HasImportantBit(aPropID))
543 changed = true;
544 SetImportantBit(aPropID);
545 } else {
546 if (HasImportantBit(aPropID)) {
547 // When parsing a declaration block, an !important declaration
548 // is not overwritten by an ordinary declaration of the same
549 // property later in the block. However, CSSOM manipulations
550 // come through here too, and in that case we do want to
551 // overwrite the property.
552 if (!aOverrideImportant) {
553 aFromBlock.ClearLonghandProperty(aPropID);
554 return false;
555 }
556 changed = true;
557 ClearImportantBit(aPropID);
558 }
559 }
561 if (aMustCallValueAppended || !HasPropertyBit(aPropID)) {
562 aDeclaration->ValueAppended(aPropID);
563 }
565 SetPropertyBit(aPropID);
566 aFromBlock.ClearPropertyBit(aPropID);
568 /*
569 * Save needless copying and allocation by calling the destructor in
570 * the destination, copying memory directly, and then using placement
571 * new.
572 */
573 changed |= MoveValue(aFromBlock.PropertyAt(aPropID), PropertyAt(aPropID));
574 return changed;
575 }
577 void
578 nsCSSExpandedDataBlock::MapRuleInfoInto(nsCSSProperty aPropID,
579 nsRuleData* aRuleData) const
580 {
581 MOZ_ASSERT(!nsCSSProps::IsShorthand(aPropID));
583 const nsCSSValue* src = PropertyAt(aPropID);
584 MOZ_ASSERT(src->GetUnit() != eCSSUnit_Null);
586 nsCSSValue* dest = aRuleData->ValueFor(aPropID);
587 MOZ_ASSERT(dest->GetUnit() == eCSSUnit_TokenStream &&
588 dest->GetTokenStreamValue()->mPropertyID == aPropID);
590 MapSinglePropertyInto(aPropID, src, dest, aRuleData);
591 }
593 #ifdef DEBUG
594 void
595 nsCSSExpandedDataBlock::DoAssertInitialState()
596 {
597 mPropertiesSet.AssertIsEmpty("not initial state");
598 mPropertiesImportant.AssertIsEmpty("not initial state");
600 for (uint32_t i = 0; i < eCSSProperty_COUNT_no_shorthands; ++i) {
601 nsCSSProperty prop = nsCSSProperty(i);
602 NS_ABORT_IF_FALSE(PropertyAt(prop)->GetUnit() == eCSSUnit_Null,
603 "not initial state");
604 }
605 }
606 #endif