CSS: Move duplicate property elimination to parser.
[WebKit-https.git] / Source / WebCore / css / StylePropertySet.cpp
1 /*
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  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #include "config.h"
23 #include "StylePropertySet.h"
24
25 #include "CSSParser.h"
26 #include "CSSStyleSheet.h"
27 #include "CSSValueKeywords.h"
28 #include "CSSValueList.h"
29 #include "CSSValuePool.h"
30 #include "Document.h"
31 #include "PropertySetCSSStyleDeclaration.h"
32 #include "StylePropertyShorthand.h"
33 #include <wtf/BitVector.h>
34 #include <wtf/text/StringBuilder.h>
35
36 #ifndef NDEBUG
37 #include <stdio.h>
38 #include <wtf/ASCIICType.h>
39 #include <wtf/text/CString.h>
40 #endif
41
42 using namespace std;
43
44 namespace WebCore {
45
46 typedef HashMap<const StylePropertySet*, OwnPtr<PropertySetCSSStyleDeclaration> > PropertySetCSSOMWrapperMap;
47 static PropertySetCSSOMWrapperMap& propertySetCSSOMWrapperMap()
48 {
49     DEFINE_STATIC_LOCAL(PropertySetCSSOMWrapperMap, propertySetCSSOMWrapperMapInstance, ());
50     return propertySetCSSOMWrapperMapInstance;
51 }
52
53 StylePropertySet::StylePropertySet(CSSParserMode cssParserMode)
54     : m_cssParserMode(cssParserMode)
55     , m_ownsCSSOMWrapper(false)
56 {
57 }
58
59 StylePropertySet::StylePropertySet(const Vector<CSSProperty>& properties, CSSParserMode cssParserMode)
60     : m_properties(properties)
61     , m_cssParserMode(cssParserMode)
62     , m_ownsCSSOMWrapper(false)
63 {
64     m_properties.shrinkToFit();
65 }
66
67 StylePropertySet::StylePropertySet(const StylePropertySet& o)
68     : RefCounted<StylePropertySet>()
69     , m_properties(o.m_properties)
70     , m_cssParserMode(o.m_cssParserMode)
71     , m_ownsCSSOMWrapper(false)
72 {
73 }
74
75 StylePropertySet::~StylePropertySet()
76 {
77     ASSERT(!m_ownsCSSOMWrapper || propertySetCSSOMWrapperMap().contains(this));
78     if (m_ownsCSSOMWrapper)
79         propertySetCSSOMWrapperMap().remove(this);
80 }
81
82 void StylePropertySet::copyPropertiesFrom(const StylePropertySet& other)
83 {
84     m_properties = other.m_properties;
85 }
86
87 String StylePropertySet::getPropertyValue(CSSPropertyID propertyID) const
88 {
89     RefPtr<CSSValue> value = getPropertyCSSValue(propertyID);
90     if (value)
91         return value->cssText();
92
93     // Shorthand and 4-values properties
94     switch (propertyID) {
95     case CSSPropertyBorderSpacing:
96         return borderSpacingValue(borderSpacingShorthand());
97     case CSSPropertyBackgroundPosition:
98         return getLayeredShorthandValue(backgroundPositionShorthand());
99     case CSSPropertyBackgroundRepeat:
100         return getLayeredShorthandValue(backgroundRepeatShorthand());
101     case CSSPropertyBackground:
102         return getLayeredShorthandValue(backgroundShorthand());
103     case CSSPropertyBorder:
104         return borderPropertyValue(OmitUncommonValues);
105     case CSSPropertyBorderTop:
106         return getShorthandValue(borderTopShorthand());
107     case CSSPropertyBorderRight:
108         return getShorthandValue(borderRightShorthand());
109     case CSSPropertyBorderBottom:
110         return getShorthandValue(borderBottomShorthand());
111     case CSSPropertyBorderLeft:
112         return getShorthandValue(borderLeftShorthand());
113     case CSSPropertyOutline:
114         return getShorthandValue(outlineShorthand());
115     case CSSPropertyBorderColor:
116         return get4Values(borderColorShorthand());
117     case CSSPropertyBorderWidth:
118         return get4Values(borderWidthShorthand());
119     case CSSPropertyBorderStyle:
120         return get4Values(borderStyleShorthand());
121     case CSSPropertyWebkitFlexFlow:
122         return getShorthandValue(webkitFlexFlowShorthand());
123     case CSSPropertyFont:
124         return fontValue();
125     case CSSPropertyMargin:
126         return get4Values(marginShorthand());
127     case CSSPropertyOverflow:
128         return getCommonValue(overflowShorthand());
129     case CSSPropertyPadding:
130         return get4Values(paddingShorthand());
131     case CSSPropertyListStyle:
132         return getShorthandValue(listStyleShorthand());
133     case CSSPropertyWebkitMaskPosition:
134         return getLayeredShorthandValue(webkitMaskPositionShorthand());
135     case CSSPropertyWebkitMaskRepeat:
136         return getLayeredShorthandValue(webkitMaskRepeatShorthand());
137     case CSSPropertyWebkitMask:
138         return getLayeredShorthandValue(webkitMaskShorthand());
139     case CSSPropertyWebkitTransformOrigin:
140         return getShorthandValue(webkitTransformOriginShorthand());
141     case CSSPropertyWebkitTransition:
142         return getLayeredShorthandValue(webkitTransitionShorthand());
143     case CSSPropertyWebkitAnimation:
144         return getLayeredShorthandValue(webkitAnimationShorthand());
145     case CSSPropertyWebkitWrap:
146         return getShorthandValue(webkitWrapShorthand());
147 #if ENABLE(SVG)
148     case CSSPropertyMarker: {
149         RefPtr<CSSValue> value = getPropertyCSSValue(CSSPropertyMarkerStart);
150         if (value)
151             return value->cssText();
152         return String();
153     }
154 #endif
155     case CSSPropertyBorderRadius:
156         return get4Values(borderRadiusShorthand());
157     default:
158           return String();
159     }
160 }
161
162 String StylePropertySet::borderSpacingValue(const StylePropertyShorthand& shorthand) const
163 {
164     RefPtr<CSSValue> horizontalValue = getPropertyCSSValue(shorthand.properties()[0]);
165     RefPtr<CSSValue> verticalValue = getPropertyCSSValue(shorthand.properties()[1]);
166
167     // While standard border-spacing property does not allow specifying border-spacing-vertical without
168     // specifying border-spacing-horizontal <http://www.w3.org/TR/CSS21/tables.html#separated-borders>,
169     // -webkit-border-spacing-vertical can be set without -webkit-border-spacing-horizontal.
170     if (!horizontalValue || !verticalValue)
171         return String();
172
173     String horizontalValueCSSText = horizontalValue->cssText();
174     String verticalValueCSSText = verticalValue->cssText();
175     if (horizontalValueCSSText == verticalValueCSSText)
176         return horizontalValueCSSText;
177     return horizontalValueCSSText + ' ' + verticalValueCSSText;
178 }
179
180 bool StylePropertySet::appendFontLonghandValueIfExplicit(CSSPropertyID propertyId, StringBuilder& result) const
181 {
182     const CSSProperty* property = findPropertyWithId(propertyId);
183     if (!property)
184         return false; // All longhands must have at least implicit values if "font" is specified.
185     if (property->isImplicit())
186         return true;
187
188     char prefix = '\0';
189     switch (propertyId) {
190     case CSSPropertyFontStyle:
191         break; // No prefix.
192     case CSSPropertyFontFamily:
193     case CSSPropertyFontVariant:
194     case CSSPropertyFontWeight:
195         prefix = ' ';
196         break;
197     case CSSPropertyLineHeight:
198         prefix = '/';
199         break;
200     default:
201         ASSERT_NOT_REACHED();
202     }
203
204     if (prefix && !result.isEmpty())
205         result.append(prefix);
206     result.append(property->value()->cssText());
207
208     return true;
209 }
210
211 String StylePropertySet::fontValue() const
212 {
213     const CSSProperty* fontSizeProperty = findPropertyWithId(CSSPropertyFontSize);
214     if (!fontSizeProperty || fontSizeProperty->isImplicit())
215         return emptyString();
216
217     StringBuilder result;
218     bool success = true;
219     success &= appendFontLonghandValueIfExplicit(CSSPropertyFontStyle, result);
220     success &= appendFontLonghandValueIfExplicit(CSSPropertyFontVariant, result);
221     success &= appendFontLonghandValueIfExplicit(CSSPropertyFontWeight, result);
222     if (!result.isEmpty())
223         result.append(' ');
224     result.append(fontSizeProperty->value()->cssText());
225     success &= appendFontLonghandValueIfExplicit(CSSPropertyLineHeight, result);
226     success &= appendFontLonghandValueIfExplicit(CSSPropertyFontFamily, result);
227     if (!success) {
228         // An invalid "font" value has been built (should never happen, as at least implicit values
229         // for mandatory longhands are always found in the style), report empty value instead.
230         ASSERT_NOT_REACHED();
231         return emptyString();
232     }
233     return result.toString();
234 }
235
236 String StylePropertySet::get4Values(const StylePropertyShorthand& shorthand) const
237 {
238     // Assume the properties are in the usual order top, right, bottom, left.
239     const CSSProperty* top = findPropertyWithId(shorthand.properties()[0]);
240     const CSSProperty* right = findPropertyWithId(shorthand.properties()[1]);
241     const CSSProperty* bottom = findPropertyWithId(shorthand.properties()[2]);
242     const CSSProperty* left = findPropertyWithId(shorthand.properties()[3]);
243
244     // All 4 properties must be specified.
245     if (!top || !top->value() || !right || !right->value() || !bottom || !bottom->value() || !left || !left->value())
246         return String();
247     if (top->value()->isInitialValue() || right->value()->isInitialValue() || bottom->value()->isInitialValue() || left->value()->isInitialValue())
248         return String();
249     if (top->isImportant() != right->isImportant() || right->isImportant() != bottom->isImportant() || bottom->isImportant() != left->isImportant())
250         return String();
251
252     bool showLeft = right->value()->cssText() != left->value()->cssText();
253     bool showBottom = (top->value()->cssText() != bottom->value()->cssText()) || showLeft;
254     bool showRight = (top->value()->cssText() != right->value()->cssText()) || showBottom;
255
256     String res = top->value()->cssText();
257     if (showRight)
258         res += " " + right->value()->cssText();
259     if (showBottom)
260         res += " " + bottom->value()->cssText();
261     if (showLeft)
262         res += " " + left->value()->cssText();
263
264     return res;
265 }
266
267 String StylePropertySet::getLayeredShorthandValue(const StylePropertyShorthand& shorthand) const
268 {
269     String res;
270
271     const unsigned size = shorthand.length();
272     // Begin by collecting the properties into an array.
273     Vector< RefPtr<CSSValue> > values(size);
274     size_t numLayers = 0;
275
276     for (unsigned i = 0; i < size; ++i) {
277         values[i] = getPropertyCSSValue(shorthand.properties()[i]);
278         if (values[i]) {
279             if (values[i]->isValueList()) {
280                 CSSValueList* valueList = static_cast<CSSValueList*>(values[i].get());
281                 numLayers = max(valueList->length(), numLayers);
282             } else
283                 numLayers = max<size_t>(1U, numLayers);
284         }
285     }
286
287     // Now stitch the properties together.  Implicit initial values are flagged as such and
288     // can safely be omitted.
289     for (size_t i = 0; i < numLayers; i++) {
290         String layerRes;
291         bool useRepeatXShorthand = false;
292         bool useRepeatYShorthand = false;
293         bool useSingleWordShorthand = false;
294         bool foundBackgroundPositionYCSSProperty = false;
295         for (unsigned j = 0; j < size; j++) {
296             RefPtr<CSSValue> value;
297             if (values[j]) {
298                 if (values[j]->isValueList())
299                     value = static_cast<CSSValueList*>(values[j].get())->item(i);
300                 else {
301                     value = values[j];
302
303                     // Color only belongs in the last layer.
304                     if (shorthand.properties()[j] == CSSPropertyBackgroundColor) {
305                         if (i != numLayers - 1)
306                             value = 0;
307                     } else if (i != 0) // Other singletons only belong in the first layer.
308                         value = 0;
309                 }
310             }
311
312             // We need to report background-repeat as it was written in the CSS. If the property is implicit,
313             // then it was written with only one value. Here we figure out which value that was so we can
314             // report back correctly.
315             if (shorthand.properties()[j] == CSSPropertyBackgroundRepeatX && isPropertyImplicit(shorthand.properties()[j])) {
316
317                 // BUG 49055: make sure the value was not reset in the layer check just above.
318                 if (j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyBackgroundRepeatY && value) {
319                     RefPtr<CSSValue> yValue;
320                     RefPtr<CSSValue> nextValue = values[j + 1];
321                     if (nextValue->isValueList())
322                         yValue = static_cast<CSSValueList*>(nextValue.get())->itemWithoutBoundsCheck(i);
323                     else
324                         yValue = nextValue;
325
326                     int xId = static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
327                     int yId = static_cast<CSSPrimitiveValue*>(yValue.get())->getIdent();
328                     if (xId != yId) {
329                         if (xId == CSSValueRepeat && yId == CSSValueNoRepeat) {
330                             useRepeatXShorthand = true;
331                             ++j;
332                         } else if (xId == CSSValueNoRepeat && yId == CSSValueRepeat) {
333                             useRepeatYShorthand = true;
334                             continue;
335                         }
336                     } else {
337                         useSingleWordShorthand = true;
338                         ++j;
339                     }
340                 }
341             }
342
343             if (value && !value->isImplicitInitialValue()) {
344                 if (!layerRes.isNull())
345                     layerRes += " ";
346                 if (foundBackgroundPositionYCSSProperty && shorthand.properties()[j] == CSSPropertyBackgroundSize) 
347                     layerRes += "/ ";
348                 if (!foundBackgroundPositionYCSSProperty && shorthand.properties()[j] == CSSPropertyBackgroundSize) 
349                     continue;
350
351                 if (useRepeatXShorthand) {
352                     useRepeatXShorthand = false;
353                     layerRes += getValueName(CSSValueRepeatX);
354                 } else if (useRepeatYShorthand) {
355                     useRepeatYShorthand = false;
356                     layerRes += getValueName(CSSValueRepeatY);
357                 } else if (useSingleWordShorthand) {
358                     useSingleWordShorthand = false;
359                     layerRes += value->cssText();
360                 } else
361                     layerRes += value->cssText();
362
363                 if (shorthand.properties()[j] == CSSPropertyBackgroundPositionY)
364                     foundBackgroundPositionYCSSProperty = true;
365             }
366         }
367
368         if (!layerRes.isNull()) {
369             if (!res.isNull())
370                 res += ", ";
371             res += layerRes;
372         }
373     }
374     return res;
375 }
376
377 String StylePropertySet::getShorthandValue(const StylePropertyShorthand& shorthand) const
378 {
379     String res;
380     for (unsigned i = 0; i < shorthand.length(); ++i) {
381         if (!isPropertyImplicit(shorthand.properties()[i])) {
382             RefPtr<CSSValue> value = getPropertyCSSValue(shorthand.properties()[i]);
383             if (!value)
384                 return String();
385             if (value->isInitialValue())
386                 continue;
387             if (!res.isNull())
388                 res += " ";
389             res += value->cssText();
390         }
391     }
392     return res;
393 }
394
395 // only returns a non-null value if all properties have the same, non-null value
396 String StylePropertySet::getCommonValue(const StylePropertyShorthand& shorthand) const
397 {
398     String res;
399     bool lastPropertyWasImportant = false;
400     for (unsigned i = 0; i < shorthand.length(); ++i) {
401         RefPtr<CSSValue> value = getPropertyCSSValue(shorthand.properties()[i]);
402         // FIXME: CSSInitialValue::cssText should generate the right value.
403         if (!value)
404             return String();
405         String text = value->cssText();
406         if (text.isNull())
407             return String();
408         if (res.isNull())
409             res = text;
410         else if (res != text)
411             return String();
412
413         bool currentPropertyIsImportant = propertyIsImportant(shorthand.properties()[i]);
414         if (i && lastPropertyWasImportant != currentPropertyIsImportant)
415             return String();
416         lastPropertyWasImportant = currentPropertyIsImportant;
417     }
418     return res;
419 }
420
421 String StylePropertySet::borderPropertyValue(CommonValueMode valueMode) const
422 {
423     const StylePropertyShorthand properties[3] = { borderWidthShorthand(), borderStyleShorthand(), borderColorShorthand() };
424     StringBuilder result;
425     for (size_t i = 0; i < WTF_ARRAY_LENGTH(properties); ++i) {
426         String value = getCommonValue(properties[i]);
427         if (value.isNull()) {
428             if (valueMode == ReturnNullOnUncommonValues)
429                 return String();
430             ASSERT(valueMode == OmitUncommonValues);
431             continue;
432         }
433         if (value == "initial")
434             continue;
435         if (!result.isEmpty())
436             result.append(' ');
437         result.append(value);
438     }
439     return result.isEmpty() ? String() : result.toString();
440 }
441
442 PassRefPtr<CSSValue> StylePropertySet::getPropertyCSSValue(CSSPropertyID propertyID) const
443 {
444     const CSSProperty* property = findPropertyWithId(propertyID);
445     return property ? property->value() : 0;
446 }
447
448 bool StylePropertySet::removeShorthandProperty(CSSPropertyID propertyID)
449 {
450     StylePropertyShorthand shorthand = shorthandForProperty(propertyID);
451     if (!shorthand.length())
452         return false;
453     return removePropertiesInSet(shorthand.properties(), shorthand.length());
454 }
455
456 bool StylePropertySet::removeProperty(CSSPropertyID propertyID, String* returnText)
457 {
458     if (removeShorthandProperty(propertyID)) {
459         // FIXME: Return an equivalent shorthand when possible.
460         if (returnText)
461             *returnText = "";
462         return true;
463     }
464
465     CSSProperty* foundProperty = findPropertyWithId(propertyID);
466     if (!foundProperty) {
467         if (returnText)
468             *returnText = "";
469         return false;
470     }
471
472     if (returnText)
473         *returnText = foundProperty->value()->cssText();
474
475     // A more efficient removal strategy would involve marking entries as empty
476     // and sweeping them when the vector grows too big.
477     m_properties.remove(foundProperty - m_properties.data());
478     
479     return true;
480 }
481
482 bool StylePropertySet::propertyIsImportant(CSSPropertyID propertyID) const
483 {
484     const CSSProperty* property = findPropertyWithId(propertyID);
485     if (property)
486         return property->isImportant();
487
488     StylePropertyShorthand shorthand = shorthandForProperty(propertyID);
489     if (!shorthand.length())
490         return false;
491
492     for (unsigned i = 0; i < shorthand.length(); ++i) {
493         if (!propertyIsImportant(shorthand.properties()[i]))
494             return false;
495     }
496     return true;
497 }
498
499 CSSPropertyID StylePropertySet::getPropertyShorthand(CSSPropertyID propertyID) const
500 {
501     const CSSProperty* property = findPropertyWithId(propertyID);
502     return property ? property->shorthandID() : CSSPropertyInvalid;
503 }
504
505 bool StylePropertySet::isPropertyImplicit(CSSPropertyID propertyID) const
506 {
507     const CSSProperty* property = findPropertyWithId(propertyID);
508     return property ? property->isImplicit() : false;
509 }
510
511 bool StylePropertySet::setProperty(CSSPropertyID propertyID, const String& value, bool important, StyleSheetInternal* contextStyleSheet)
512 {
513     // Setting the value to an empty string just removes the property in both IE and Gecko.
514     // Setting it to null seems to produce less consistent results, but we treat it just the same.
515     if (value.isEmpty()) {
516         removeProperty(propertyID);
517         return true;
518     }
519
520     // When replacing an existing property value, this moves the property to the end of the list.
521     // Firefox preserves the position, and MSIE moves the property to the beginning.
522     return CSSParser::parseValue(this, propertyID, value, important, cssParserMode(), contextStyleSheet);
523 }
524
525 void StylePropertySet::setProperty(CSSPropertyID propertyID, PassRefPtr<CSSValue> prpValue, bool important)
526 {
527     StylePropertyShorthand shorthand = shorthandForProperty(propertyID);
528     if (!shorthand.length()) {
529         setProperty(CSSProperty(propertyID, prpValue, important));
530         return;
531     }
532
533     removePropertiesInSet(shorthand.properties(), shorthand.length());
534
535     RefPtr<CSSValue> value = prpValue;
536     for (unsigned i = 0; i < shorthand.length(); ++i)
537         m_properties.append(CSSProperty(shorthand.properties()[i], value, important));
538 }
539
540 void StylePropertySet::setProperty(const CSSProperty& property, CSSProperty* slot)
541 {
542     if (!removeShorthandProperty(property.id())) {
543         CSSProperty* toReplace = slot ? slot : findPropertyWithId(property.id());
544         if (toReplace) {
545             *toReplace = property;
546             return;
547         }
548     }
549     m_properties.append(property);
550 }
551
552 bool StylePropertySet::setProperty(CSSPropertyID propertyID, int identifier, bool important)
553 {
554     setProperty(CSSProperty(propertyID, cssValuePool().createIdentifierValue(identifier), important));
555     return true;
556 }
557
558 void StylePropertySet::parseDeclaration(const String& styleDeclaration, StyleSheetInternal* contextStyleSheet)
559 {
560     m_properties.clear();
561
562     CSSParserContext context(cssParserMode());
563     if (contextStyleSheet) {
564         context = contextStyleSheet->parserContext();
565         context.mode = cssParserMode();
566     }
567     CSSParser parser(context);
568     parser.parseDeclaration(this, styleDeclaration, 0, contextStyleSheet);
569 }
570
571 void StylePropertySet::addParsedProperties(const Vector<CSSProperty>& properties)
572 {
573     m_properties.reserveCapacity(m_properties.size() + properties.size());
574     for (unsigned i = 0; i < properties.size(); ++i)
575         addParsedProperty(properties[i]);
576 }
577
578 void StylePropertySet::addParsedProperty(const CSSProperty& property)
579 {
580     // Only add properties that have no !important counterpart present
581     if (!propertyIsImportant(property.id()) || property.isImportant())
582         setProperty(property);
583 }
584
585 String StylePropertySet::asText() const
586 {
587     StringBuilder result;
588
589     const CSSProperty* positionXProp = 0;
590     const CSSProperty* positionYProp = 0;
591     const CSSProperty* repeatXProp = 0;
592     const CSSProperty* repeatYProp = 0;
593
594     // FIXME: Stack-allocate the buffer for these BitVectors.
595     BitVector shorthandPropertyUsed;
596     BitVector shorthandPropertyAppeared;
597
598     unsigned size = m_properties.size();
599     for (unsigned n = 0; n < size; ++n) {
600         const CSSProperty& prop = m_properties[n];
601         CSSPropertyID propertyID = prop.id();
602         CSSPropertyID shorthandPropertyID = CSSPropertyInvalid;
603         CSSPropertyID borderFallbackShorthandProperty = CSSPropertyInvalid;
604         String value;
605
606         switch (propertyID) {
607         case CSSPropertyBackgroundPositionX:
608             positionXProp = &prop;
609             continue;
610         case CSSPropertyBackgroundPositionY:
611             positionYProp = &prop;
612             continue;
613         case CSSPropertyBackgroundRepeatX:
614             repeatXProp = &prop;
615             continue;
616         case CSSPropertyBackgroundRepeatY:
617             repeatYProp = &prop;
618             continue;
619         case CSSPropertyBorderTopWidth:
620         case CSSPropertyBorderRightWidth:
621         case CSSPropertyBorderBottomWidth:
622         case CSSPropertyBorderLeftWidth:
623             if (!borderFallbackShorthandProperty)
624                 borderFallbackShorthandProperty = CSSPropertyBorderWidth;
625         case CSSPropertyBorderTopStyle:
626         case CSSPropertyBorderRightStyle:
627         case CSSPropertyBorderBottomStyle:
628         case CSSPropertyBorderLeftStyle:
629             if (!borderFallbackShorthandProperty)
630                 borderFallbackShorthandProperty = CSSPropertyBorderStyle;
631         case CSSPropertyBorderTopColor:
632         case CSSPropertyBorderRightColor:
633         case CSSPropertyBorderBottomColor:
634         case CSSPropertyBorderLeftColor:
635             if (!borderFallbackShorthandProperty)
636                 borderFallbackShorthandProperty = CSSPropertyBorderColor;
637
638             // FIXME: Deal with cases where only some of border-(top|right|bottom|left) are specified.
639             if (!shorthandPropertyAppeared.get(CSSPropertyBorder - firstCSSProperty)) {
640                 value = borderPropertyValue(ReturnNullOnUncommonValues);
641                 if (value.isNull())
642                     shorthandPropertyAppeared.ensureSizeAndSet(CSSPropertyBorder - firstCSSProperty, numCSSProperties);
643                 else
644                     shorthandPropertyID = CSSPropertyBorder;
645             } else if (shorthandPropertyUsed.get(CSSPropertyBorder - firstCSSProperty))
646                 shorthandPropertyID = CSSPropertyBorder;
647             if (!shorthandPropertyID)
648                 shorthandPropertyID = borderFallbackShorthandProperty;
649             break;
650         case CSSPropertyWebkitBorderHorizontalSpacing:
651         case CSSPropertyWebkitBorderVerticalSpacing:
652             shorthandPropertyID = CSSPropertyBorderSpacing;
653             break;
654         case CSSPropertyFontFamily:
655         case CSSPropertyLineHeight:
656         case CSSPropertyFontSize:
657         case CSSPropertyFontStyle:
658         case CSSPropertyFontVariant:
659         case CSSPropertyFontWeight:
660             // Don't use CSSPropertyFont because old UAs can't recognize them but are important for editing.
661             break;
662         case CSSPropertyListStyleType:
663         case CSSPropertyListStylePosition:
664         case CSSPropertyListStyleImage:
665             shorthandPropertyID = CSSPropertyListStyle;
666             break;
667         case CSSPropertyMarginTop:
668         case CSSPropertyMarginRight:
669         case CSSPropertyMarginBottom:
670         case CSSPropertyMarginLeft:
671             shorthandPropertyID = CSSPropertyMargin;
672             break;
673         case CSSPropertyOutlineWidth:
674         case CSSPropertyOutlineStyle:
675         case CSSPropertyOutlineColor:
676             shorthandPropertyID = CSSPropertyOutline;
677             break;
678         case CSSPropertyOverflowX:
679         case CSSPropertyOverflowY:
680             shorthandPropertyID = CSSPropertyOverflow;
681             break;
682         case CSSPropertyPaddingTop:
683         case CSSPropertyPaddingRight:
684         case CSSPropertyPaddingBottom:
685         case CSSPropertyPaddingLeft:
686             shorthandPropertyID = CSSPropertyPadding;
687             break;
688         case CSSPropertyWebkitAnimationName:
689         case CSSPropertyWebkitAnimationDuration:
690         case CSSPropertyWebkitAnimationTimingFunction:
691         case CSSPropertyWebkitAnimationDelay:
692         case CSSPropertyWebkitAnimationIterationCount:
693         case CSSPropertyWebkitAnimationDirection:
694         case CSSPropertyWebkitAnimationFillMode:
695             shorthandPropertyID = CSSPropertyWebkitAnimation;
696             break;
697         case CSSPropertyWebkitFlexDirection:
698         case CSSPropertyWebkitFlexWrap:
699             shorthandPropertyID = CSSPropertyWebkitFlexFlow;
700             break;
701         case CSSPropertyWebkitMaskPositionX:
702         case CSSPropertyWebkitMaskPositionY:
703         case CSSPropertyWebkitMaskRepeatX:
704         case CSSPropertyWebkitMaskRepeatY:
705         case CSSPropertyWebkitMaskImage:
706         case CSSPropertyWebkitMaskRepeat:
707         case CSSPropertyWebkitMaskAttachment:
708         case CSSPropertyWebkitMaskPosition:
709         case CSSPropertyWebkitMaskClip:
710         case CSSPropertyWebkitMaskOrigin:
711             shorthandPropertyID = CSSPropertyWebkitMask;
712             break;
713         case CSSPropertyWebkitTransformOriginX:
714         case CSSPropertyWebkitTransformOriginY:
715         case CSSPropertyWebkitTransformOriginZ:
716             shorthandPropertyID = CSSPropertyWebkitTransformOrigin;
717             break;
718         case CSSPropertyWebkitTransitionProperty:
719         case CSSPropertyWebkitTransitionDuration:
720         case CSSPropertyWebkitTransitionTimingFunction:
721         case CSSPropertyWebkitTransitionDelay:
722             shorthandPropertyID = CSSPropertyWebkitTransition;
723             break;
724         case CSSPropertyWebkitWrapFlow:
725         case CSSPropertyWebkitWrapMargin:
726         case CSSPropertyWebkitWrapPadding:
727             shorthandPropertyID = CSSPropertyWebkitWrap;
728             break;
729         default:
730             break;
731         }
732
733         unsigned shortPropertyIndex = shorthandPropertyID - firstCSSProperty;
734         if (shorthandPropertyID) {
735             if (shorthandPropertyUsed.get(shortPropertyIndex))
736                 continue;
737             if (!shorthandPropertyAppeared.get(shortPropertyIndex) && value.isNull())
738                 value = getPropertyValue(shorthandPropertyID);
739             shorthandPropertyAppeared.ensureSizeAndSet(shortPropertyIndex, numCSSProperties);
740         }
741
742         if (!value.isNull()) {
743             propertyID = shorthandPropertyID;
744             shorthandPropertyUsed.ensureSizeAndSet(shortPropertyIndex, numCSSProperties);
745         } else
746             value = prop.value()->cssText();
747
748         if (value == "initial" && !CSSProperty::isInheritedProperty(propertyID))
749             continue;
750
751         result.append(getPropertyName(propertyID));
752         result.append(": ");
753         result.append(value);
754         result.append(prop.isImportant() ? " !important" : "");
755         result.append("; ");
756     }
757
758     // FIXME: This is a not-so-nice way to turn x/y positions into single background-position in output.
759     // It is required because background-position-x/y are non-standard properties and WebKit generated output
760     // would not work in Firefox (<rdar://problem/5143183>)
761     // It would be a better solution if background-position was CSS_PAIR.
762     if (positionXProp && positionYProp && positionXProp->isImportant() == positionYProp->isImportant()) {
763         result.append("background-position: ");
764         if (positionXProp->value()->isValueList() || positionYProp->value()->isValueList())
765             result.append(getLayeredShorthandValue(backgroundPositionShorthand()));
766         else {
767             result.append(positionXProp->value()->cssText());
768             result.append(" ");
769             result.append(positionYProp->value()->cssText());
770         }
771         if (positionXProp->isImportant())
772             result.append(" !important");
773         result.append("; ");
774     } else {
775         if (positionXProp)
776             result.append(positionXProp->cssText());
777         if (positionYProp)
778             result.append(positionYProp->cssText());
779     }
780
781     // FIXME: We need to do the same for background-repeat.
782     if (repeatXProp && repeatYProp && repeatXProp->isImportant() == repeatYProp->isImportant()) {
783         result.append("background-repeat: ");
784         if (repeatXProp->value()->isValueList() || repeatYProp->value()->isValueList())
785             result.append(getLayeredShorthandValue(backgroundRepeatShorthand()));
786         else {
787             result.append(repeatXProp->value()->cssText());
788             result.append(" ");
789             result.append(repeatYProp->value()->cssText());
790         }
791         if (repeatXProp->isImportant())
792             result.append(" !important");
793         result.append("; ");
794     } else {
795         if (repeatXProp)
796             result.append(repeatXProp->cssText());
797         if (repeatYProp)
798             result.append(repeatYProp->cssText());
799     }
800
801     return result.toString();
802 }
803
804 void StylePropertySet::merge(const StylePropertySet* other, bool argOverridesOnConflict)
805 {
806     unsigned size = other->m_properties.size();
807     for (unsigned n = 0; n < size; ++n) {
808         const CSSProperty& toMerge = other->m_properties[n];
809         CSSProperty* old = findPropertyWithId(toMerge.id());
810         if (old) {
811             if (!argOverridesOnConflict && old->value())
812                 continue;
813             setProperty(toMerge, old);
814         } else
815             m_properties.append(toMerge);
816     }
817 }
818
819 void StylePropertySet::addSubresourceStyleURLs(ListHashSet<KURL>& urls, StyleSheetInternal* contextStyleSheet)
820 {
821     size_t size = m_properties.size();
822     for (size_t i = 0; i < size; ++i)
823         m_properties[i].value()->addSubresourceStyleURLs(urls, contextStyleSheet);
824 }
825
826 // This is the list of properties we want to copy in the copyBlockProperties() function.
827 // It is the list of CSS properties that apply specially to block-level elements.
828 static const CSSPropertyID blockProperties[] = {
829     CSSPropertyOrphans,
830     CSSPropertyOverflow, // This can be also be applied to replaced elements
831     CSSPropertyWebkitAspectRatio,
832     CSSPropertyWebkitColumnCount,
833     CSSPropertyWebkitColumnGap,
834     CSSPropertyWebkitColumnRuleColor,
835     CSSPropertyWebkitColumnRuleStyle,
836     CSSPropertyWebkitColumnRuleWidth,
837     CSSPropertyWebkitColumnBreakBefore,
838     CSSPropertyWebkitColumnBreakAfter,
839     CSSPropertyWebkitColumnBreakInside,
840     CSSPropertyWebkitColumnWidth,
841     CSSPropertyPageBreakAfter,
842     CSSPropertyPageBreakBefore,
843     CSSPropertyPageBreakInside,
844     CSSPropertyWebkitRegionBreakAfter,
845     CSSPropertyWebkitRegionBreakBefore,
846     CSSPropertyWebkitRegionBreakInside,
847     CSSPropertyTextAlign,
848     CSSPropertyTextIndent,
849     CSSPropertyWidows
850 };
851
852 const unsigned numBlockProperties = WTF_ARRAY_LENGTH(blockProperties);
853
854 PassRefPtr<StylePropertySet> StylePropertySet::copyBlockProperties() const
855 {
856     return copyPropertiesInSet(blockProperties, numBlockProperties);
857 }
858
859 void StylePropertySet::removeBlockProperties()
860 {
861     removePropertiesInSet(blockProperties, numBlockProperties);
862 }
863
864 bool StylePropertySet::removePropertiesInSet(const CSSPropertyID* set, unsigned length)
865 {
866     if (m_properties.isEmpty())
867         return false;
868
869     // FIXME: This is always used with static sets and in that case constructing the hash repeatedly is pretty pointless.
870     HashSet<CSSPropertyID> toRemove;
871     for (unsigned i = 0; i < length; ++i)
872         toRemove.add(set[i]);
873
874     Vector<CSSProperty, 4> newProperties;
875     newProperties.reserveInitialCapacity(m_properties.size());
876
877     unsigned size = m_properties.size();
878     for (unsigned n = 0; n < size; ++n) {
879         const CSSProperty& property = m_properties[n];
880         // Not quite sure if the isImportant test is needed but it matches the existing behavior.
881         if (!property.isImportant()) {
882             if (toRemove.contains(property.id()))
883                 continue;
884         }
885         newProperties.append(property);
886     }
887
888     bool changed = newProperties.size() != m_properties.size();
889     m_properties = newProperties;
890     return changed;
891 }
892
893 const CSSProperty* StylePropertySet::findPropertyWithId(CSSPropertyID propertyID) const
894 {
895     for (int n = m_properties.size() - 1 ; n >= 0; --n) {
896         if (propertyID == m_properties[n].id())
897             return &m_properties[n];
898     }
899     return 0;
900 }
901
902 CSSProperty* StylePropertySet::findPropertyWithId(CSSPropertyID propertyID)
903 {
904     for (int n = m_properties.size() - 1 ; n >= 0; --n) {
905         if (propertyID == m_properties[n].id())
906             return &m_properties[n];
907     }
908     return 0;
909 }
910     
911 bool StylePropertySet::propertyMatches(const CSSProperty* property) const
912 {
913     RefPtr<CSSValue> value = getPropertyCSSValue(property->id());
914     return value && value->cssText() == property->value()->cssText();
915 }
916     
917 void StylePropertySet::removeEquivalentProperties(const StylePropertySet* style)
918 {
919     Vector<CSSPropertyID> propertiesToRemove;
920     size_t size = m_properties.size();
921     for (size_t i = 0; i < size; ++i) {
922         const CSSProperty& property = m_properties[i];
923         if (style->propertyMatches(&property))
924             propertiesToRemove.append(property.id());
925     }    
926     // FIXME: This should use mass removal.
927     for (unsigned i = 0; i < propertiesToRemove.size(); ++i)
928         removeProperty(propertiesToRemove[i]);
929 }
930
931 void StylePropertySet::removeEquivalentProperties(const CSSStyleDeclaration* style)
932 {
933     Vector<CSSPropertyID> propertiesToRemove;
934     size_t size = m_properties.size();
935     for (size_t i = 0; i < size; ++i) {
936         const CSSProperty& property = m_properties[i];
937         if (style->cssPropertyMatches(&property))
938             propertiesToRemove.append(property.id());
939     }    
940     // FIXME: This should use mass removal.
941     for (unsigned i = 0; i < propertiesToRemove.size(); ++i)
942         removeProperty(propertiesToRemove[i]);
943 }
944
945 PassRefPtr<StylePropertySet> StylePropertySet::copy() const
946 {
947     return adoptRef(new StylePropertySet(*this));
948 }
949
950 PassRefPtr<StylePropertySet> StylePropertySet::copyPropertiesInSet(const CSSPropertyID* set, unsigned length) const
951 {
952     Vector<CSSProperty> list;
953     list.reserveInitialCapacity(length);
954     for (unsigned i = 0; i < length; ++i) {
955         RefPtr<CSSValue> value = getPropertyCSSValue(set[i]);
956         if (value)
957             list.append(CSSProperty(set[i], value.release(), false));
958     }
959     return StylePropertySet::create(list);
960 }
961
962 CSSStyleDeclaration* StylePropertySet::ensureCSSStyleDeclaration() const
963 {
964     if (m_ownsCSSOMWrapper) {
965         ASSERT(!static_cast<CSSStyleDeclaration*>(propertySetCSSOMWrapperMap().get(this))->parentRule());
966         ASSERT(!propertySetCSSOMWrapperMap().get(this)->parentElement());
967         return propertySetCSSOMWrapperMap().get(this);
968     }
969     m_ownsCSSOMWrapper = true;
970     PropertySetCSSStyleDeclaration* cssomWrapper = new PropertySetCSSStyleDeclaration(const_cast<StylePropertySet*>(this));
971     propertySetCSSOMWrapperMap().add(this, adoptPtr(cssomWrapper));
972     return cssomWrapper;
973 }
974
975 CSSStyleDeclaration* StylePropertySet::ensureInlineCSSStyleDeclaration(const StyledElement* parentElement) const
976 {
977     if (m_ownsCSSOMWrapper) {
978         ASSERT(propertySetCSSOMWrapperMap().get(this)->parentElement() == parentElement);
979         return propertySetCSSOMWrapperMap().get(this);
980     }
981     m_ownsCSSOMWrapper = true;
982     PropertySetCSSStyleDeclaration* cssomWrapper = new InlineCSSStyleDeclaration(const_cast<StylePropertySet*>(this), const_cast<StyledElement*>(parentElement));
983     propertySetCSSOMWrapperMap().add(this, adoptPtr(cssomWrapper));
984     return cssomWrapper;
985 }
986
987 void StylePropertySet::clearParentElement(StyledElement* element)
988 {
989     if (!m_ownsCSSOMWrapper)
990         return;
991     ASSERT_UNUSED(element, propertySetCSSOMWrapperMap().get(this)->parentElement() == element);
992     propertySetCSSOMWrapperMap().get(this)->clearParentElement();
993 }
994
995 unsigned StylePropertySet::averageSizeInBytes()
996 {
997     // Please update this if the storage scheme changes so that this longer reflects the actual size.
998     return sizeof(StylePropertySet);
999 }
1000
1001 // See the function above if you need to update this.
1002 class SameSizeAsStylePropertySet : public RefCounted<SameSizeAsStylePropertySet> {
1003     Vector<CSSProperty, 4> properties;
1004     unsigned bitfield;
1005 };
1006 COMPILE_ASSERT(sizeof(StylePropertySet) == sizeof(SameSizeAsStylePropertySet), style_property_set_should_stay_small);
1007
1008 #ifndef NDEBUG
1009 void StylePropertySet::showStyle()
1010 {
1011     fprintf(stderr, "%s\n", asText().ascii().data());
1012 }
1013 #endif
1014
1015 } // namespace WebCore