2 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
4 * Copyright (C) 2011 Research In Motion Limited. All rights reserved.
5 * Copyright (C) 2013 Intel Corporation. All rights reserved.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
24 #include "core/css/StylePropertySerializer.h"
26 #include "core/CSSValueKeywords.h"
27 #include "core/StylePropertyShorthand.h"
28 #include "core/css/CSSPropertyMetadata.h"
29 #include "core/css/CSSValuePool.h"
30 #include "wtf/BitArray.h"
31 #include "wtf/text/StringBuilder.h"
35 static bool isInitialOrInherit(const String& value)
37 DEFINE_STATIC_LOCAL(String, initial, ("initial"));
38 DEFINE_STATIC_LOCAL(String, inherit, ("inherit"));
39 return value.length() == 7 && (value == initial || value == inherit);
42 StylePropertySerializer::StylePropertySetForSerializer::StylePropertySetForSerializer(const StylePropertySet& properties)
43 : m_propertySet(properties)
44 , m_allIndex(m_propertySet.findPropertyIndex(CSSPropertyAll))
45 , m_needToExpandAll(false)
47 if (!hasAllProperty())
50 StylePropertySet::PropertyReference allProperty = m_propertySet.propertyAt(m_allIndex);
51 for (unsigned i = 0; i < m_propertySet.propertyCount(); ++i) {
52 StylePropertySet::PropertyReference property = m_propertySet.propertyAt(i);
53 if (CSSProperty::isAffectedByAllProperty(property.id())) {
54 if (allProperty.isImportant() && !property.isImportant())
56 if (static_cast<unsigned>(m_allIndex) >= i)
58 if (property.value()->equals(*allProperty.value())
59 && property.isImportant() == allProperty.isImportant())
61 m_needToExpandAll = true;
63 m_longhandPropertyUsed.set(property.id() - firstCSSProperty);
67 unsigned StylePropertySerializer::StylePropertySetForSerializer::propertyCount() const
69 if (!hasExpandedAllProperty())
70 return m_propertySet.propertyCount();
71 return lastCSSProperty - firstCSSProperty + 1;
74 StylePropertySerializer::PropertyValueForSerializer StylePropertySerializer::StylePropertySetForSerializer::propertyAt(unsigned index) const
76 if (!hasExpandedAllProperty())
77 return StylePropertySerializer::PropertyValueForSerializer(m_propertySet.propertyAt(index));
79 CSSPropertyID propertyID = static_cast<CSSPropertyID>(index + firstCSSProperty);
80 ASSERT(firstCSSProperty <= propertyID && propertyID <= lastCSSProperty);
81 if (m_longhandPropertyUsed.get(index)) {
82 int index = m_propertySet.findPropertyIndex(propertyID);
84 return StylePropertySerializer::PropertyValueForSerializer(m_propertySet.propertyAt(index));
87 StylePropertySet::PropertyReference property = m_propertySet.propertyAt(m_allIndex);
88 const CSSValue* value = property.value();
90 // FIXME: Firefox shows properties with "unset" when some cssRule has
91 // expanded "all" with "unset". So we should use "unset" here.
92 // After implementing "unset" value correctly, (i.e. StyleBuilder should
93 // support "display: unset", "color: unset", ... and so on),
94 // we should fix the following code.
95 if (!value->isInitialValue() && !value->isInheritedValue()) {
96 if (CSSPropertyMetadata::isInheritedProperty(propertyID))
97 value = cssValuePool().createInheritedValue().get();
99 value = cssValuePool().createExplicitInitialValue().get();
101 return StylePropertySerializer::PropertyValueForSerializer(propertyID, value, property.isImportant());
104 bool StylePropertySerializer::StylePropertySetForSerializer::shouldProcessPropertyAt(unsigned index) const
106 // StylePropertySet has all valid longhands. We should process.
107 if (!hasAllProperty())
110 // If all is not expanded, we need to process "all" and properties which
111 // are not overwritten by "all".
112 if (!m_needToExpandAll) {
113 StylePropertySet::PropertyReference property = m_propertySet.propertyAt(index);
114 if (property.id() == CSSPropertyAll || !CSSProperty::isAffectedByAllProperty(property.id()))
116 return m_longhandPropertyUsed.get(property.id() - firstCSSProperty);
119 CSSPropertyID propertyID = static_cast<CSSPropertyID>(index + firstCSSProperty);
120 ASSERT(firstCSSProperty <= propertyID && propertyID <= lastCSSProperty);
122 // Since "all" is expanded, we don't need to process "all".
123 // We should not process expanded shorthands (e.g. font, background,
124 // and so on) either.
125 if (isShorthandProperty(propertyID) || propertyID == CSSPropertyAll)
127 // We should not serialize internal properties.
128 if (isInternalProperty(propertyID))
131 // The all property is a shorthand that resets all CSS properties except
132 // direction and unicode-bidi. It only accepts the CSS-wide keywords.
133 // c.f. http://dev.w3.org/csswg/css-cascade/#all-shorthand
134 if (!CSSProperty::isAffectedByAllProperty(propertyID))
135 return m_longhandPropertyUsed.get(index);
140 int StylePropertySerializer::StylePropertySetForSerializer::findPropertyIndex(CSSPropertyID propertyID) const
142 if (!hasExpandedAllProperty())
143 return m_propertySet.findPropertyIndex(propertyID);
144 return propertyID - firstCSSProperty;
147 const CSSValue* StylePropertySerializer::StylePropertySetForSerializer::getPropertyCSSValue(CSSPropertyID propertyID) const
149 int index = findPropertyIndex(propertyID);
152 StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index);
153 return value.value();
156 String StylePropertySerializer::StylePropertySetForSerializer::getPropertyValue(CSSPropertyID propertyID) const
158 if (!hasExpandedAllProperty())
159 return m_propertySet.getPropertyValue(propertyID);
161 const CSSValue* value = getPropertyCSSValue(propertyID);
164 return value->cssText();
167 bool StylePropertySerializer::StylePropertySetForSerializer::isPropertyImplicit(CSSPropertyID propertyID) const
169 int index = findPropertyIndex(propertyID);
172 StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index);
173 return value.isImplicit();
176 bool StylePropertySerializer::StylePropertySetForSerializer::propertyIsImportant(CSSPropertyID propertyID) const
178 int index = findPropertyIndex(propertyID);
181 StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index);
182 return value.isImportant();
185 StylePropertySerializer::StylePropertySerializer(const StylePropertySet& properties)
186 : m_propertySet(properties)
190 String StylePropertySerializer::getPropertyText(CSSPropertyID propertyID, const String& value, bool isImportant, bool isNotFirstDecl) const
192 StringBuilder result;
195 result.append(getPropertyName(propertyID));
196 result.appendLiteral(": ");
197 result.append(value);
199 result.appendLiteral(" !important");
201 return result.toString();
204 String StylePropertySerializer::asText() const
206 StringBuilder result;
208 BitArray<numCSSProperties> shorthandPropertyUsed;
209 BitArray<numCSSProperties> shorthandPropertyAppeared;
211 unsigned size = m_propertySet.propertyCount();
212 unsigned numDecls = 0;
213 for (unsigned n = 0; n < size; ++n) {
214 if (!m_propertySet.shouldProcessPropertyAt(n))
217 StylePropertySerializer::PropertyValueForSerializer property = m_propertySet.propertyAt(n);
218 CSSPropertyID propertyID = property.id();
219 // Only enabled or internal properties should be part of the style.
220 ASSERT(CSSPropertyMetadata::isEnabledProperty(propertyID) || isInternalProperty(propertyID));
221 CSSPropertyID shorthandPropertyID = CSSPropertyInvalid;
222 CSSPropertyID borderFallbackShorthandProperty = CSSPropertyInvalid;
224 ASSERT(!isShorthandProperty(propertyID));
226 switch (propertyID) {
227 case CSSPropertyBackgroundAttachment:
228 case CSSPropertyBackgroundClip:
229 case CSSPropertyBackgroundColor:
230 case CSSPropertyBackgroundImage:
231 case CSSPropertyBackgroundOrigin:
232 case CSSPropertyBackgroundPositionX:
233 case CSSPropertyBackgroundPositionY:
234 case CSSPropertyBackgroundSize:
235 case CSSPropertyBackgroundRepeatX:
236 case CSSPropertyBackgroundRepeatY:
237 shorthandPropertyAppeared.set(CSSPropertyBackground - firstCSSProperty);
239 case CSSPropertyContent:
240 if (property.value()->isValueList())
241 value = toCSSValueList(property.value())->customCSSText(AlwaysQuoteCSSString);
243 case CSSPropertyBorderTopWidth:
244 case CSSPropertyBorderRightWidth:
245 case CSSPropertyBorderBottomWidth:
246 case CSSPropertyBorderLeftWidth:
247 if (!borderFallbackShorthandProperty)
248 borderFallbackShorthandProperty = CSSPropertyBorderWidth;
249 case CSSPropertyBorderTopStyle:
250 case CSSPropertyBorderRightStyle:
251 case CSSPropertyBorderBottomStyle:
252 case CSSPropertyBorderLeftStyle:
253 if (!borderFallbackShorthandProperty)
254 borderFallbackShorthandProperty = CSSPropertyBorderStyle;
255 case CSSPropertyBorderTopColor:
256 case CSSPropertyBorderRightColor:
257 case CSSPropertyBorderBottomColor:
258 case CSSPropertyBorderLeftColor:
259 if (!borderFallbackShorthandProperty)
260 borderFallbackShorthandProperty = CSSPropertyBorderColor;
262 // FIXME: Deal with cases where only some of border-(top|right|bottom|left) are specified.
263 if (!shorthandPropertyAppeared.get(CSSPropertyBorder - firstCSSProperty)) {
264 value = borderPropertyValue(ReturnNullOnUncommonValues);
266 shorthandPropertyAppeared.set(CSSPropertyBorder - firstCSSProperty);
268 shorthandPropertyID = CSSPropertyBorder;
269 } else if (shorthandPropertyUsed.get(CSSPropertyBorder - firstCSSProperty))
270 shorthandPropertyID = CSSPropertyBorder;
271 if (!shorthandPropertyID)
272 shorthandPropertyID = borderFallbackShorthandProperty;
274 case CSSPropertyBorderTopLeftRadius:
275 case CSSPropertyBorderTopRightRadius:
276 case CSSPropertyBorderBottomLeftRadius:
277 case CSSPropertyBorderBottomRightRadius:
278 shorthandPropertyID = CSSPropertyBorderRadius;
280 case CSSPropertyWebkitBorderHorizontalSpacing:
281 case CSSPropertyWebkitBorderVerticalSpacing:
282 shorthandPropertyID = CSSPropertyBorderSpacing;
284 case CSSPropertyFontFamily:
285 case CSSPropertyLineHeight:
286 case CSSPropertyFontSize:
287 case CSSPropertyFontStretch:
288 case CSSPropertyFontStyle:
289 case CSSPropertyFontVariant:
290 case CSSPropertyFontWeight:
291 // Don't use CSSPropertyFont because old UAs can't recognize them but are important for editing.
293 case CSSPropertyListStyleType:
294 case CSSPropertyListStylePosition:
295 case CSSPropertyListStyleImage:
296 shorthandPropertyID = CSSPropertyListStyle;
298 case CSSPropertyMarginTop:
299 case CSSPropertyMarginRight:
300 case CSSPropertyMarginBottom:
301 case CSSPropertyMarginLeft:
302 shorthandPropertyID = CSSPropertyMargin;
304 case CSSPropertyOutlineWidth:
305 case CSSPropertyOutlineStyle:
306 case CSSPropertyOutlineColor:
307 shorthandPropertyID = CSSPropertyOutline;
309 case CSSPropertyOverflowX:
310 case CSSPropertyOverflowY:
311 shorthandPropertyID = CSSPropertyOverflow;
313 case CSSPropertyPaddingTop:
314 case CSSPropertyPaddingRight:
315 case CSSPropertyPaddingBottom:
316 case CSSPropertyPaddingLeft:
317 shorthandPropertyID = CSSPropertyPadding;
319 case CSSPropertyTransitionProperty:
320 case CSSPropertyTransitionDuration:
321 case CSSPropertyTransitionTimingFunction:
322 case CSSPropertyTransitionDelay:
323 shorthandPropertyID = CSSPropertyTransition;
325 case CSSPropertyWebkitAnimationName:
326 case CSSPropertyWebkitAnimationDuration:
327 case CSSPropertyWebkitAnimationTimingFunction:
328 case CSSPropertyWebkitAnimationDelay:
329 case CSSPropertyWebkitAnimationIterationCount:
330 case CSSPropertyWebkitAnimationDirection:
331 case CSSPropertyWebkitAnimationFillMode:
332 shorthandPropertyID = CSSPropertyWebkitAnimation;
334 case CSSPropertyFlexDirection:
335 case CSSPropertyFlexWrap:
336 shorthandPropertyID = CSSPropertyFlexFlow;
338 case CSSPropertyFlexBasis:
339 case CSSPropertyFlexGrow:
340 case CSSPropertyFlexShrink:
341 shorthandPropertyID = CSSPropertyFlex;
343 case CSSPropertyWebkitMaskPositionX:
344 case CSSPropertyWebkitMaskPositionY:
345 case CSSPropertyWebkitMaskRepeatX:
346 case CSSPropertyWebkitMaskRepeatY:
347 case CSSPropertyWebkitMaskImage:
348 case CSSPropertyWebkitMaskRepeat:
349 case CSSPropertyWebkitMaskPosition:
350 case CSSPropertyWebkitMaskClip:
351 case CSSPropertyWebkitMaskOrigin:
352 shorthandPropertyID = CSSPropertyWebkitMask;
354 case CSSPropertyWebkitTransitionProperty:
355 case CSSPropertyWebkitTransitionDuration:
356 case CSSPropertyWebkitTransitionTimingFunction:
357 case CSSPropertyWebkitTransitionDelay:
358 shorthandPropertyID = CSSPropertyWebkitTransition;
361 result.append(getPropertyText(propertyID, property.value()->cssText(), property.isImportant(), numDecls++));
367 unsigned shortPropertyIndex = shorthandPropertyID - firstCSSProperty;
368 if (shorthandPropertyID) {
369 if (shorthandPropertyUsed.get(shortPropertyIndex))
371 if (!shorthandPropertyAppeared.get(shortPropertyIndex) && value.isNull())
372 value = m_propertySet.getPropertyValue(shorthandPropertyID);
373 shorthandPropertyAppeared.set(shortPropertyIndex);
376 if (!value.isNull()) {
377 if (shorthandPropertyID) {
378 propertyID = shorthandPropertyID;
379 shorthandPropertyUsed.set(shortPropertyIndex);
382 // We should not show "initial" when the "initial" is implicit.
383 // If explicit "initial", we need to show.
384 if (property.value()->isImplicitInitialValue())
386 value = property.value()->cssText();
389 result.append(getPropertyText(propertyID, value, property.isImportant(), numDecls++));
392 if (shorthandPropertyAppeared.get(CSSPropertyBackground - firstCSSProperty))
393 appendBackgroundPropertyAsText(result, numDecls);
395 ASSERT(!numDecls ^ !result.isEmpty());
396 return result.toString();
399 String StylePropertySerializer::getPropertyValue(CSSPropertyID propertyID) const
401 // Shorthand and 4-values properties
402 switch (propertyID) {
403 case CSSPropertyAnimation:
404 return getLayeredShorthandValue(animationShorthand(), true);
405 case CSSPropertyBorderSpacing:
406 return borderSpacingValue(borderSpacingShorthand());
407 case CSSPropertyBackgroundPosition:
408 return getLayeredShorthandValue(backgroundPositionShorthand());
409 case CSSPropertyBackgroundRepeat:
410 return backgroundRepeatPropertyValue();
411 case CSSPropertyBackground:
412 return getLayeredShorthandValue(backgroundShorthand());
413 case CSSPropertyBorder:
414 return borderPropertyValue(OmitUncommonValues);
415 case CSSPropertyBorderTop:
416 return getShorthandValue(borderTopShorthand());
417 case CSSPropertyBorderRight:
418 return getShorthandValue(borderRightShorthand());
419 case CSSPropertyBorderBottom:
420 return getShorthandValue(borderBottomShorthand());
421 case CSSPropertyBorderLeft:
422 return getShorthandValue(borderLeftShorthand());
423 case CSSPropertyOutline:
424 return getShorthandValue(outlineShorthand());
425 case CSSPropertyBorderColor:
426 return get4Values(borderColorShorthand());
427 case CSSPropertyBorderWidth:
428 return get4Values(borderWidthShorthand());
429 case CSSPropertyBorderStyle:
430 return get4Values(borderStyleShorthand());
431 case CSSPropertyWebkitColumnRule:
432 return getShorthandValue(webkitColumnRuleShorthand());
433 case CSSPropertyWebkitColumns:
434 return getShorthandValue(webkitColumnsShorthand());
435 case CSSPropertyFlex:
436 return getShorthandValue(flexShorthand());
437 case CSSPropertyFlexFlow:
438 return getShorthandValue(flexFlowShorthand());
439 case CSSPropertyGridColumn:
440 return getShorthandValue(gridColumnShorthand());
441 case CSSPropertyGridRow:
442 return getShorthandValue(gridRowShorthand());
443 case CSSPropertyGridArea:
444 return getShorthandValue(gridAreaShorthand());
445 case CSSPropertyFont:
447 case CSSPropertyMargin:
448 return get4Values(marginShorthand());
449 case CSSPropertyWebkitMarginCollapse:
450 return getShorthandValue(webkitMarginCollapseShorthand());
451 case CSSPropertyOverflow:
452 return getCommonValue(overflowShorthand());
453 case CSSPropertyPadding:
454 return get4Values(paddingShorthand());
455 case CSSPropertyTransition:
456 return getLayeredShorthandValue(transitionShorthand(), true);
457 case CSSPropertyListStyle:
458 return getShorthandValue(listStyleShorthand());
459 case CSSPropertyWebkitMaskPosition:
460 return getLayeredShorthandValue(webkitMaskPositionShorthand());
461 case CSSPropertyWebkitMaskRepeat:
462 return getLayeredShorthandValue(webkitMaskRepeatShorthand());
463 case CSSPropertyWebkitMask:
464 return getLayeredShorthandValue(webkitMaskShorthand());
465 case CSSPropertyWebkitTextEmphasis:
466 return getShorthandValue(webkitTextEmphasisShorthand());
467 case CSSPropertyWebkitTextStroke:
468 return getShorthandValue(webkitTextStrokeShorthand());
469 case CSSPropertyWebkitTransition:
470 return getLayeredShorthandValue(webkitTransitionShorthand(), true);
471 case CSSPropertyWebkitAnimation:
472 return getLayeredShorthandValue(webkitAnimationShorthand(), true);
473 case CSSPropertyMarker: {
474 if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyMarkerStart))
475 return value->cssText();
478 case CSSPropertyBorderRadius:
479 return get4Values(borderRadiusShorthand());
485 String StylePropertySerializer::borderSpacingValue(const StylePropertyShorthand& shorthand) const
487 const CSSValue* horizontalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[0]);
488 const CSSValue* verticalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[1]);
490 // While standard border-spacing property does not allow specifying border-spacing-vertical without
491 // specifying border-spacing-horizontal <http://www.w3.org/TR/CSS21/tables.html#separated-borders>,
492 // -webkit-border-spacing-vertical can be set without -webkit-border-spacing-horizontal.
493 if (!horizontalValue || !verticalValue)
496 String horizontalValueCSSText = horizontalValue->cssText();
497 String verticalValueCSSText = verticalValue->cssText();
498 if (horizontalValueCSSText == verticalValueCSSText)
499 return horizontalValueCSSText;
500 return horizontalValueCSSText + ' ' + verticalValueCSSText;
503 void StylePropertySerializer::appendFontLonghandValueIfExplicit(CSSPropertyID propertyID, StringBuilder& result, String& commonValue) const
505 int foundPropertyIndex = m_propertySet.findPropertyIndex(propertyID);
506 if (foundPropertyIndex == -1)
507 return; // All longhands must have at least implicit values if "font" is specified.
509 if (m_propertySet.propertyAt(foundPropertyIndex).isImplicit()) {
510 commonValue = String();
515 switch (propertyID) {
516 case CSSPropertyFontStyle:
518 case CSSPropertyFontFamily:
519 case CSSPropertyFontStretch:
520 case CSSPropertyFontVariant:
521 case CSSPropertyFontWeight:
524 case CSSPropertyLineHeight:
528 ASSERT_NOT_REACHED();
531 if (prefix && !result.isEmpty())
532 result.append(prefix);
533 String value = m_propertySet.propertyAt(foundPropertyIndex).value()->cssText();
534 result.append(value);
535 if (!commonValue.isNull() && commonValue != value)
536 commonValue = String();
539 String StylePropertySerializer::fontValue() const
541 int fontSizePropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontSize);
542 int fontFamilyPropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontFamily);
543 if (fontSizePropertyIndex == -1 || fontFamilyPropertyIndex == -1)
544 return emptyString();
546 PropertyValueForSerializer fontSizeProperty = m_propertySet.propertyAt(fontSizePropertyIndex);
547 PropertyValueForSerializer fontFamilyProperty = m_propertySet.propertyAt(fontFamilyPropertyIndex);
548 if (fontSizeProperty.isImplicit() || fontFamilyProperty.isImplicit())
549 return emptyString();
551 String commonValue = fontSizeProperty.value()->cssText();
552 StringBuilder result;
553 appendFontLonghandValueIfExplicit(CSSPropertyFontStyle, result, commonValue);
554 appendFontLonghandValueIfExplicit(CSSPropertyFontVariant, result, commonValue);
555 appendFontLonghandValueIfExplicit(CSSPropertyFontWeight, result, commonValue);
556 appendFontLonghandValueIfExplicit(CSSPropertyFontStretch, result, commonValue);
557 if (!result.isEmpty())
559 result.append(fontSizeProperty.value()->cssText());
560 appendFontLonghandValueIfExplicit(CSSPropertyLineHeight, result, commonValue);
561 if (!result.isEmpty())
563 result.append(fontFamilyProperty.value()->cssText());
564 if (isInitialOrInherit(commonValue))
566 return result.toString();
569 String StylePropertySerializer::get4Values(const StylePropertyShorthand& shorthand) const
571 // Assume the properties are in the usual order top, right, bottom, left.
572 int topValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[0]);
573 int rightValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[1]);
574 int bottomValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[2]);
575 int leftValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[3]);
577 if (topValueIndex == -1 || rightValueIndex == -1 || bottomValueIndex == -1 || leftValueIndex == -1)
580 PropertyValueForSerializer top = m_propertySet.propertyAt(topValueIndex);
581 PropertyValueForSerializer right = m_propertySet.propertyAt(rightValueIndex);
582 PropertyValueForSerializer bottom = m_propertySet.propertyAt(bottomValueIndex);
583 PropertyValueForSerializer left = m_propertySet.propertyAt(leftValueIndex);
585 // All 4 properties must be specified.
586 if (!top.value() || !right.value() || !bottom.value() || !left.value())
589 if (top.isInherited() && right.isInherited() && bottom.isInherited() && left.isInherited())
590 return getValueName(CSSValueInherit);
592 if (top.value()->isInitialValue() || right.value()->isInitialValue() || bottom.value()->isInitialValue() || left.value()->isInitialValue()) {
593 if (top.value()->isInitialValue() && right.value()->isInitialValue() && bottom.value()->isInitialValue() && left.value()->isInitialValue() && !top.isImplicit()) {
594 // All components are "initial" and "top" is not implicit.
595 return getValueName(CSSValueInitial);
599 if (top.isImportant() != right.isImportant() || right.isImportant() != bottom.isImportant() || bottom.isImportant() != left.isImportant())
602 bool showLeft = !right.value()->equals(*left.value());
603 bool showBottom = !top.value()->equals(*bottom.value()) || showLeft;
604 bool showRight = !top.value()->equals(*right.value()) || showBottom;
606 StringBuilder result;
607 result.append(top.value()->cssText());
610 result.append(right.value()->cssText());
614 result.append(bottom.value()->cssText());
618 result.append(left.value()->cssText());
620 return result.toString();
623 String StylePropertySerializer::getLayeredShorthandValue(const StylePropertyShorthand& shorthand, bool checkShorthandAvailable) const
625 StringBuilder result;
627 const unsigned size = shorthand.length();
628 // Begin by collecting the properties into an array.
629 WillBeHeapVector<const CSSValue*> values(size);
630 size_t numLayers = 0;
632 for (unsigned i = 0; i < size; ++i) {
633 values[i] = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
635 if (values[i]->isBaseValueList()) {
636 const CSSValueList* valueList = toCSSValueList(values[i]);
637 numLayers = std::max(valueList->length(), numLayers);
639 numLayers = std::max<size_t>(1U, numLayers);
641 } else if (checkShorthandAvailable) {
648 bool commonValueInitialized = false;
650 // Now stitch the properties together. Implicit initial values are flagged as such and
651 // can safely be omitted.
652 for (size_t i = 0; i < numLayers; i++) {
653 StringBuilder layerResult;
654 bool useRepeatXShorthand = false;
655 bool useRepeatYShorthand = false;
656 bool useSingleWordShorthand = false;
657 bool foundPositionYCSSProperty = false;
658 for (unsigned j = 0; j < size; j++) {
659 const CSSValue* value = 0;
661 if (values[j]->isBaseValueList()) {
662 value = toCSSValueList(values[j])->itemWithBoundsCheck(i);
666 // Color only belongs in the last layer.
667 if (shorthand.properties()[j] == CSSPropertyBackgroundColor) {
668 if (i != numLayers - 1)
671 // Other singletons only belong in the first layer.
677 // We need to report background-repeat as it was written in the CSS. If the property is implicit,
678 // then it was written with only one value. Here we figure out which value that was so we can
679 // report back correctly.
680 if ((shorthand.properties()[j] == CSSPropertyBackgroundRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j]))
681 || (shorthand.properties()[j] == CSSPropertyWebkitMaskRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j]))) {
683 // BUG 49055: make sure the value was not reset in the layer check just above.
684 if ((j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyBackgroundRepeatY && value)
685 || (j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyWebkitMaskRepeatY && value)) {
686 const CSSValue* yValue = 0;
687 const CSSValue* nextValue = values[j + 1];
688 if (nextValue->isValueList())
689 yValue = toCSSValueList(nextValue)->item(i);
693 // background-repeat-x(y) or mask-repeat-x(y) may be like this : "initial, repeat". We can omit the implicit initial values
694 // before starting to compare their values.
695 if (value->isImplicitInitialValue() || yValue->isImplicitInitialValue())
698 // FIXME: At some point we need to fix this code to avoid returning an invalid shorthand,
699 // since some longhand combinations are not serializable into a single shorthand.
700 if (!value->isPrimitiveValue() || !yValue->isPrimitiveValue())
703 CSSValueID xId = toCSSPrimitiveValue(value)->getValueID();
704 CSSValueID yId = toCSSPrimitiveValue(yValue)->getValueID();
706 if (xId == CSSValueRepeat && yId == CSSValueNoRepeat) {
707 useRepeatXShorthand = true;
709 } else if (xId == CSSValueNoRepeat && yId == CSSValueRepeat) {
710 useRepeatYShorthand = true;
714 useSingleWordShorthand = true;
721 if (value && !value->isImplicitInitialValue()) {
722 if (!layerResult.isEmpty())
723 layerResult.append(' ');
724 if (foundPositionYCSSProperty
725 && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
726 layerResult.appendLiteral("/ ");
727 if (!foundPositionYCSSProperty
728 && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
731 if (useRepeatXShorthand) {
732 useRepeatXShorthand = false;
733 layerResult.append(getValueName(CSSValueRepeatX));
734 } else if (useRepeatYShorthand) {
735 useRepeatYShorthand = false;
736 layerResult.append(getValueName(CSSValueRepeatY));
738 if (useSingleWordShorthand)
739 useSingleWordShorthand = false;
740 valueText = value->cssText();
741 layerResult.append(valueText);
744 if (shorthand.properties()[j] == CSSPropertyBackgroundPositionY
745 || shorthand.properties()[j] == CSSPropertyWebkitMaskPositionY) {
746 foundPositionYCSSProperty = true;
748 // background-position is a special case: if only the first offset is specified,
749 // the second one defaults to "center", not the same value.
750 if (commonValueInitialized && commonValue != "initial" && commonValue != "inherit")
751 commonValue = String();
755 if (!commonValueInitialized) {
756 commonValue = valueText;
757 commonValueInitialized = true;
758 } else if (!commonValue.isNull() && commonValue != valueText)
759 commonValue = String();
762 if (!layerResult.isEmpty()) {
763 if (!result.isEmpty())
764 result.appendLiteral(", ");
765 result.append(layerResult);
769 if (isInitialOrInherit(commonValue))
772 if (result.isEmpty())
774 return result.toString();
777 String StylePropertySerializer::getShorthandValue(const StylePropertyShorthand& shorthand) const
780 StringBuilder result;
781 for (unsigned i = 0; i < shorthand.length(); ++i) {
782 if (!m_propertySet.isPropertyImplicit(shorthand.properties()[i])) {
783 const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
786 String valueText = value->cssText();
788 commonValue = valueText;
789 else if (!commonValue.isNull() && commonValue != valueText)
790 commonValue = String();
791 if (value->isInitialValue())
793 if (!result.isEmpty())
795 result.append(valueText);
797 commonValue = String();
799 if (isInitialOrInherit(commonValue))
801 if (result.isEmpty())
803 return result.toString();
806 // only returns a non-null value if all properties have the same, non-null value
807 String StylePropertySerializer::getCommonValue(const StylePropertyShorthand& shorthand) const
810 bool lastPropertyWasImportant = false;
811 for (unsigned i = 0; i < shorthand.length(); ++i) {
812 const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
813 // FIXME: CSSInitialValue::cssText should generate the right value.
816 String text = value->cssText();
821 else if (res != text)
824 bool currentPropertyIsImportant = m_propertySet.propertyIsImportant(shorthand.properties()[i]);
825 if (i && lastPropertyWasImportant != currentPropertyIsImportant)
827 lastPropertyWasImportant = currentPropertyIsImportant;
832 String StylePropertySerializer::borderPropertyValue(CommonValueMode valueMode) const
834 const StylePropertyShorthand properties[3] = { borderWidthShorthand(), borderStyleShorthand(), borderColorShorthand() };
836 StringBuilder result;
837 for (size_t i = 0; i < WTF_ARRAY_LENGTH(properties); ++i) {
838 String value = getCommonValue(properties[i]);
839 if (value.isNull()) {
840 if (valueMode == ReturnNullOnUncommonValues)
842 ASSERT(valueMode == OmitUncommonValues);
847 else if (!commonValue.isNull() && commonValue != value)
848 commonValue = String();
849 if (value == "initial")
851 if (!result.isEmpty())
853 result.append(value);
855 if (isInitialOrInherit(commonValue))
857 return result.isEmpty() ? String() : result.toString();
860 static void appendBackgroundRepeatValue(StringBuilder& builder, const CSSValue& repeatXCSSValue, const CSSValue& repeatYCSSValue)
862 // FIXME: Ensure initial values do not appear in CSS_VALUE_LISTS.
863 DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, initialRepeatValue, (CSSPrimitiveValue::create(CSSValueRepeat)));
864 const CSSPrimitiveValue& repeatX = repeatXCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatXCSSValue);
865 const CSSPrimitiveValue& repeatY = repeatYCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatYCSSValue);
866 CSSValueID repeatXValueId = repeatX.getValueID();
867 CSSValueID repeatYValueId = repeatY.getValueID();
868 if (repeatXValueId == repeatYValueId) {
869 builder.append(repeatX.cssText());
870 } else if (repeatXValueId == CSSValueNoRepeat && repeatYValueId == CSSValueRepeat) {
871 builder.appendLiteral("repeat-y");
872 } else if (repeatXValueId == CSSValueRepeat && repeatYValueId == CSSValueNoRepeat) {
873 builder.appendLiteral("repeat-x");
875 builder.append(repeatX.cssText());
876 builder.appendLiteral(" ");
877 builder.append(repeatY.cssText());
881 String StylePropertySerializer::backgroundRepeatPropertyValue() const
883 const CSSValue* repeatX = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatX);
884 const CSSValue* repeatY = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatY);
885 if (!repeatX || !repeatY)
887 if (m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX) != m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatY))
889 if (repeatX->cssValueType() == repeatY->cssValueType()
890 && (repeatX->cssValueType() == CSSValue::CSS_INITIAL || repeatX->cssValueType() == CSSValue::CSS_INHERIT)) {
891 return repeatX->cssText();
894 const CSSValueList* repeatXList = 0;
895 int repeatXLength = 1;
896 if (repeatX->cssValueType() == CSSValue::CSS_VALUE_LIST) {
897 repeatXList = toCSSValueList(repeatX);
898 repeatXLength = repeatXList->length();
899 } else if (repeatX->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE) {
903 const CSSValueList* repeatYList = 0;
904 int repeatYLength = 1;
905 if (repeatY->cssValueType() == CSSValue::CSS_VALUE_LIST) {
906 repeatYList = toCSSValueList(repeatY);
907 repeatYLength = repeatYList->length();
908 } else if (repeatY->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE) {
912 size_t shorthandLength = lowestCommonMultiple(repeatXLength, repeatYLength);
913 StringBuilder builder;
914 for (size_t i = 0; i < shorthandLength; ++i) {
916 builder.appendLiteral(", ");
918 const CSSValue* xValue = repeatXList ? repeatXList->item(i % repeatXList->length()) : repeatX;
919 const CSSValue* yValue = repeatYList ? repeatYList->item(i % repeatYList->length()) : repeatY;
920 appendBackgroundRepeatValue(builder, *xValue, *yValue);
922 return builder.toString();
925 void StylePropertySerializer::appendBackgroundPropertyAsText(StringBuilder& result, unsigned& numDecls) const
927 if (isPropertyShorthandAvailable(backgroundShorthand())) {
928 String backgroundValue = getPropertyValue(CSSPropertyBackground);
929 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage);
930 result.append(getPropertyText(CSSPropertyBackground, backgroundValue, isImportant, numDecls++));
933 if (shorthandHasOnlyInitialOrInheritedValue(backgroundShorthand())) {
934 const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundImage);
935 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage);
936 result.append(getPropertyText(CSSPropertyBackground, value->cssText(), isImportant, numDecls++));
940 // backgroundShorthandProperty without layered shorhand properties
941 const CSSPropertyID backgroundPropertyIds[] = {
942 CSSPropertyBackgroundImage,
943 CSSPropertyBackgroundAttachment,
944 CSSPropertyBackgroundColor,
945 CSSPropertyBackgroundSize,
946 CSSPropertyBackgroundOrigin,
947 CSSPropertyBackgroundClip
950 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(backgroundPropertyIds); ++i) {
951 CSSPropertyID propertyID = backgroundPropertyIds[i];
952 const CSSValue* value = m_propertySet.getPropertyCSSValue(propertyID);
955 result.append(getPropertyText(propertyID, value->cssText(), m_propertySet.propertyIsImportant(propertyID), numDecls++));
958 // FIXME: This is a not-so-nice way to turn x/y positions into single background-position in output.
959 // It is required because background-position-x/y are non-standard properties and WebKit generated output
960 // would not work in Firefox (<rdar://problem/5143183>)
961 // It would be a better solution if background-position was CSS_PAIR.
962 if (shorthandHasOnlyInitialOrInheritedValue(backgroundPositionShorthand())) {
963 const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX);
964 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
965 result.append(getPropertyText(CSSPropertyBackgroundPosition, value->cssText(), isImportant, numDecls++));
966 } else if (isPropertyShorthandAvailable(backgroundPositionShorthand())) {
967 String positionValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundPosition);
968 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
969 if (!positionValue.isNull())
970 result.append(getPropertyText(CSSPropertyBackgroundPosition, positionValue, isImportant, numDecls++));
972 // should check background-position-x or background-position-y.
973 if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX)) {
974 if (!value->isImplicitInitialValue()) {
975 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
976 result.append(getPropertyText(CSSPropertyBackgroundPositionX, value->cssText(), isImportant, numDecls++));
979 if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionY)) {
980 if (!value->isImplicitInitialValue()) {
981 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionY);
982 result.append(getPropertyText(CSSPropertyBackgroundPositionY, value->cssText(), isImportant, numDecls++));
987 String repeatValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundRepeat);
988 if (!repeatValue.isNull())
989 result.append(getPropertyText(CSSPropertyBackgroundRepeat, repeatValue, m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX), numDecls++));
992 bool StylePropertySerializer::isPropertyShorthandAvailable(const StylePropertyShorthand& shorthand) const
994 ASSERT(shorthand.length() > 0);
996 bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]);
997 for (unsigned i = 0; i < shorthand.length(); ++i) {
998 const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
999 if (!value || (value->isInitialValue() && !value->isImplicitInitialValue()) || value->isInheritedValue())
1001 if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i]))
1007 bool StylePropertySerializer::shorthandHasOnlyInitialOrInheritedValue(const StylePropertyShorthand& shorthand) const
1009 ASSERT(shorthand.length() > 0);
1010 bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]);
1011 bool isInitialValue = true;
1012 bool isInheritedValue = true;
1013 for (unsigned i = 0; i < shorthand.length(); ++i) {
1014 const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
1017 if (!value->isInitialValue())
1018 isInitialValue = false;
1019 if (!value->isInheritedValue())
1020 isInheritedValue = false;
1021 if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i]))
1024 return isInitialValue || isInheritedValue;