1 /* 2 * (C) 1999-2003 Lars Knoll (knoll (at) 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. 6 * 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. 11 * 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. 16 * 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. 21 */ 22 23 #include "config.h" 24 #include "core/css/StylePropertySerializer.h" 25 26 #include "core/CSSValueKeywords.h" 27 #include "core/StylePropertyShorthand.h" 28 #include "core/css/RuntimeCSSEnabled.h" 29 #include "wtf/BitArray.h" 30 #include "wtf/text/StringBuilder.h" 31 32 namespace WebCore { 33 34 static bool isInitialOrInherit(const String& value) 35 { 36 DEFINE_STATIC_LOCAL(String, initial, ("initial")); 37 DEFINE_STATIC_LOCAL(String, inherit, ("inherit")); 38 return value.length() == 7 && (value == initial || value == inherit); 39 } 40 41 StylePropertySerializer::StylePropertySerializer(const StylePropertySet& properties) 42 : m_propertySet(properties) 43 { 44 } 45 46 String StylePropertySerializer::getPropertyText(CSSPropertyID propertyID, const String& value, bool isImportant, bool isNotFirstDecl) const 47 { 48 StringBuilder result; 49 if (isNotFirstDecl) 50 result.append(' '); 51 result.append(getPropertyName(propertyID)); 52 result.appendLiteral(": "); 53 result.append(value); 54 if (isImportant) 55 result.appendLiteral(" !important"); 56 result.append(';'); 57 return result.toString(); 58 } 59 60 String StylePropertySerializer::asText() const 61 { 62 StringBuilder result; 63 64 BitArray<numCSSProperties> shorthandPropertyUsed; 65 BitArray<numCSSProperties> shorthandPropertyAppeared; 66 67 unsigned size = m_propertySet.propertyCount(); 68 unsigned numDecls = 0; 69 for (unsigned n = 0; n < size; ++n) { 70 StylePropertySet::PropertyReference property = m_propertySet.propertyAt(n); 71 CSSPropertyID propertyID = property.id(); 72 // Only enabled or internal properties should be part of the style. 73 ASSERT(RuntimeCSSEnabled::isCSSPropertyEnabled(propertyID) || isInternalProperty(propertyID)); 74 CSSPropertyID shorthandPropertyID = CSSPropertyInvalid; 75 CSSPropertyID borderFallbackShorthandProperty = CSSPropertyInvalid; 76 String value; 77 78 switch (propertyID) { 79 case CSSPropertyBackgroundAttachment: 80 case CSSPropertyBackgroundClip: 81 case CSSPropertyBackgroundColor: 82 case CSSPropertyBackgroundImage: 83 case CSSPropertyBackgroundOrigin: 84 case CSSPropertyBackgroundPositionX: 85 case CSSPropertyBackgroundPositionY: 86 case CSSPropertyBackgroundSize: 87 case CSSPropertyBackgroundRepeatX: 88 case CSSPropertyBackgroundRepeatY: 89 shorthandPropertyAppeared.set(CSSPropertyBackground - firstCSSProperty); 90 continue; 91 case CSSPropertyContent: 92 if (property.value()->isValueList()) 93 value = toCSSValueList(property.value())->customCSSText(AlwaysQuoteCSSString); 94 break; 95 case CSSPropertyBorderTopWidth: 96 case CSSPropertyBorderRightWidth: 97 case CSSPropertyBorderBottomWidth: 98 case CSSPropertyBorderLeftWidth: 99 if (!borderFallbackShorthandProperty) 100 borderFallbackShorthandProperty = CSSPropertyBorderWidth; 101 case CSSPropertyBorderTopStyle: 102 case CSSPropertyBorderRightStyle: 103 case CSSPropertyBorderBottomStyle: 104 case CSSPropertyBorderLeftStyle: 105 if (!borderFallbackShorthandProperty) 106 borderFallbackShorthandProperty = CSSPropertyBorderStyle; 107 case CSSPropertyBorderTopColor: 108 case CSSPropertyBorderRightColor: 109 case CSSPropertyBorderBottomColor: 110 case CSSPropertyBorderLeftColor: 111 if (!borderFallbackShorthandProperty) 112 borderFallbackShorthandProperty = CSSPropertyBorderColor; 113 114 // FIXME: Deal with cases where only some of border-(top|right|bottom|left) are specified. 115 if (!shorthandPropertyAppeared.get(CSSPropertyBorder - firstCSSProperty)) { 116 value = borderPropertyValue(ReturnNullOnUncommonValues); 117 if (value.isNull()) 118 shorthandPropertyAppeared.set(CSSPropertyBorder - firstCSSProperty); 119 else 120 shorthandPropertyID = CSSPropertyBorder; 121 } else if (shorthandPropertyUsed.get(CSSPropertyBorder - firstCSSProperty)) 122 shorthandPropertyID = CSSPropertyBorder; 123 if (!shorthandPropertyID) 124 shorthandPropertyID = borderFallbackShorthandProperty; 125 break; 126 case CSSPropertyWebkitBorderHorizontalSpacing: 127 case CSSPropertyWebkitBorderVerticalSpacing: 128 shorthandPropertyID = CSSPropertyBorderSpacing; 129 break; 130 case CSSPropertyFontFamily: 131 case CSSPropertyLineHeight: 132 case CSSPropertyFontSize: 133 case CSSPropertyFontStyle: 134 case CSSPropertyFontVariant: 135 case CSSPropertyFontWeight: 136 // Don't use CSSPropertyFont because old UAs can't recognize them but are important for editing. 137 break; 138 case CSSPropertyListStyleType: 139 case CSSPropertyListStylePosition: 140 case CSSPropertyListStyleImage: 141 shorthandPropertyID = CSSPropertyListStyle; 142 break; 143 case CSSPropertyMarginTop: 144 case CSSPropertyMarginRight: 145 case CSSPropertyMarginBottom: 146 case CSSPropertyMarginLeft: 147 shorthandPropertyID = CSSPropertyMargin; 148 break; 149 case CSSPropertyOutlineWidth: 150 case CSSPropertyOutlineStyle: 151 case CSSPropertyOutlineColor: 152 shorthandPropertyID = CSSPropertyOutline; 153 break; 154 case CSSPropertyOverflowX: 155 case CSSPropertyOverflowY: 156 shorthandPropertyID = CSSPropertyOverflow; 157 break; 158 case CSSPropertyPaddingTop: 159 case CSSPropertyPaddingRight: 160 case CSSPropertyPaddingBottom: 161 case CSSPropertyPaddingLeft: 162 shorthandPropertyID = CSSPropertyPadding; 163 break; 164 case CSSPropertyTransitionProperty: 165 case CSSPropertyTransitionDuration: 166 case CSSPropertyTransitionTimingFunction: 167 case CSSPropertyTransitionDelay: 168 shorthandPropertyID = CSSPropertyTransition; 169 break; 170 case CSSPropertyWebkitAnimationName: 171 case CSSPropertyWebkitAnimationDuration: 172 case CSSPropertyWebkitAnimationTimingFunction: 173 case CSSPropertyWebkitAnimationDelay: 174 case CSSPropertyWebkitAnimationIterationCount: 175 case CSSPropertyWebkitAnimationDirection: 176 case CSSPropertyWebkitAnimationFillMode: 177 shorthandPropertyID = CSSPropertyWebkitAnimation; 178 break; 179 case CSSPropertyFlexDirection: 180 case CSSPropertyFlexWrap: 181 shorthandPropertyID = CSSPropertyFlexFlow; 182 break; 183 case CSSPropertyFlexBasis: 184 case CSSPropertyFlexGrow: 185 case CSSPropertyFlexShrink: 186 shorthandPropertyID = CSSPropertyFlex; 187 break; 188 case CSSPropertyWebkitMaskPositionX: 189 case CSSPropertyWebkitMaskPositionY: 190 case CSSPropertyWebkitMaskRepeatX: 191 case CSSPropertyWebkitMaskRepeatY: 192 case CSSPropertyWebkitMaskImage: 193 case CSSPropertyWebkitMaskRepeat: 194 case CSSPropertyWebkitMaskPosition: 195 case CSSPropertyWebkitMaskClip: 196 case CSSPropertyWebkitMaskOrigin: 197 shorthandPropertyID = CSSPropertyWebkitMask; 198 break; 199 case CSSPropertyWebkitTransformOriginX: 200 case CSSPropertyWebkitTransformOriginY: 201 case CSSPropertyWebkitTransformOriginZ: 202 shorthandPropertyID = CSSPropertyWebkitTransformOrigin; 203 break; 204 case CSSPropertyWebkitTransitionProperty: 205 case CSSPropertyWebkitTransitionDuration: 206 case CSSPropertyWebkitTransitionTimingFunction: 207 case CSSPropertyWebkitTransitionDelay: 208 shorthandPropertyID = CSSPropertyWebkitTransition; 209 break; 210 default: 211 break; 212 } 213 214 unsigned shortPropertyIndex = shorthandPropertyID - firstCSSProperty; 215 if (shorthandPropertyID) { 216 if (shorthandPropertyUsed.get(shortPropertyIndex)) 217 continue; 218 if (!shorthandPropertyAppeared.get(shortPropertyIndex) && value.isNull()) 219 value = m_propertySet.getPropertyValue(shorthandPropertyID); 220 shorthandPropertyAppeared.set(shortPropertyIndex); 221 } 222 223 if (!value.isNull()) { 224 if (shorthandPropertyID) { 225 propertyID = shorthandPropertyID; 226 shorthandPropertyUsed.set(shortPropertyIndex); 227 } 228 } else 229 value = property.value()->cssText(); 230 231 if (value == "initial" && !CSSProperty::isInheritedProperty(propertyID)) 232 continue; 233 234 result.append(getPropertyText(propertyID, value, property.isImportant(), numDecls++)); 235 } 236 237 if (shorthandPropertyAppeared.get(CSSPropertyBackground - firstCSSProperty)) 238 appendBackgroundPropertyAsText(result, numDecls); 239 240 ASSERT(!numDecls ^ !result.isEmpty()); 241 return result.toString(); 242 } 243 244 String StylePropertySerializer::getPropertyValue(CSSPropertyID propertyID) const 245 { 246 // Shorthand and 4-values properties 247 switch (propertyID) { 248 case CSSPropertyAnimation: 249 return getLayeredShorthandValue(animationShorthand()); 250 case CSSPropertyBorderSpacing: 251 return borderSpacingValue(borderSpacingShorthand()); 252 case CSSPropertyBackgroundPosition: 253 return getLayeredShorthandValue(backgroundPositionShorthand()); 254 case CSSPropertyBackgroundRepeat: 255 return backgroundRepeatPropertyValue(); 256 case CSSPropertyBackground: 257 return getLayeredShorthandValue(backgroundShorthand()); 258 case CSSPropertyBorder: 259 return borderPropertyValue(OmitUncommonValues); 260 case CSSPropertyBorderTop: 261 return getShorthandValue(borderTopShorthand()); 262 case CSSPropertyBorderRight: 263 return getShorthandValue(borderRightShorthand()); 264 case CSSPropertyBorderBottom: 265 return getShorthandValue(borderBottomShorthand()); 266 case CSSPropertyBorderLeft: 267 return getShorthandValue(borderLeftShorthand()); 268 case CSSPropertyOutline: 269 return getShorthandValue(outlineShorthand()); 270 case CSSPropertyBorderColor: 271 return get4Values(borderColorShorthand()); 272 case CSSPropertyBorderWidth: 273 return get4Values(borderWidthShorthand()); 274 case CSSPropertyBorderStyle: 275 return get4Values(borderStyleShorthand()); 276 case CSSPropertyWebkitColumnRule: 277 return getShorthandValue(webkitColumnRuleShorthand()); 278 case CSSPropertyWebkitColumns: 279 return getShorthandValue(webkitColumnsShorthand()); 280 case CSSPropertyFlex: 281 return getShorthandValue(flexShorthand()); 282 case CSSPropertyFlexFlow: 283 return getShorthandValue(flexFlowShorthand()); 284 case CSSPropertyGridColumn: 285 return getShorthandValue(gridColumnShorthand()); 286 case CSSPropertyGridRow: 287 return getShorthandValue(gridRowShorthand()); 288 case CSSPropertyGridArea: 289 return getShorthandValue(gridAreaShorthand()); 290 case CSSPropertyFont: 291 return fontValue(); 292 case CSSPropertyMargin: 293 return get4Values(marginShorthand()); 294 case CSSPropertyWebkitMarginCollapse: 295 return getShorthandValue(webkitMarginCollapseShorthand()); 296 case CSSPropertyOverflow: 297 return getCommonValue(overflowShorthand()); 298 case CSSPropertyPadding: 299 return get4Values(paddingShorthand()); 300 case CSSPropertyTransition: 301 return getLayeredShorthandValue(transitionShorthand()); 302 case CSSPropertyListStyle: 303 return getShorthandValue(listStyleShorthand()); 304 case CSSPropertyWebkitMaskPosition: 305 return getLayeredShorthandValue(webkitMaskPositionShorthand()); 306 case CSSPropertyWebkitMaskRepeat: 307 return getLayeredShorthandValue(webkitMaskRepeatShorthand()); 308 case CSSPropertyWebkitMask: 309 return getLayeredShorthandValue(webkitMaskShorthand()); 310 case CSSPropertyWebkitTextEmphasis: 311 return getShorthandValue(webkitTextEmphasisShorthand()); 312 case CSSPropertyWebkitTextStroke: 313 return getShorthandValue(webkitTextStrokeShorthand()); 314 case CSSPropertyTransformOrigin: 315 case CSSPropertyWebkitTransformOrigin: 316 return getShorthandValue(webkitTransformOriginShorthand()); 317 case CSSPropertyWebkitTransition: 318 return getLayeredShorthandValue(webkitTransitionShorthand()); 319 case CSSPropertyWebkitAnimation: 320 return getLayeredShorthandValue(webkitAnimationShorthand()); 321 case CSSPropertyMarker: { 322 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyMarkerStart); 323 if (value) 324 return value->cssText(); 325 return String(); 326 } 327 case CSSPropertyBorderRadius: 328 return get4Values(borderRadiusShorthand()); 329 default: 330 return String(); 331 } 332 } 333 334 String StylePropertySerializer::borderSpacingValue(const StylePropertyShorthand& shorthand) const 335 { 336 RefPtrWillBeRawPtr<CSSValue> horizontalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[0]); 337 RefPtrWillBeRawPtr<CSSValue> verticalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[1]); 338 339 // While standard border-spacing property does not allow specifying border-spacing-vertical without 340 // specifying border-spacing-horizontal <http://www.w3.org/TR/CSS21/tables.html#separated-borders>, 341 // -webkit-border-spacing-vertical can be set without -webkit-border-spacing-horizontal. 342 if (!horizontalValue || !verticalValue) 343 return String(); 344 345 String horizontalValueCSSText = horizontalValue->cssText(); 346 String verticalValueCSSText = verticalValue->cssText(); 347 if (horizontalValueCSSText == verticalValueCSSText) 348 return horizontalValueCSSText; 349 return horizontalValueCSSText + ' ' + verticalValueCSSText; 350 } 351 352 void StylePropertySerializer::appendFontLonghandValueIfExplicit(CSSPropertyID propertyID, StringBuilder& result, String& commonValue) const 353 { 354 int foundPropertyIndex = m_propertySet.findPropertyIndex(propertyID); 355 if (foundPropertyIndex == -1) 356 return; // All longhands must have at least implicit values if "font" is specified. 357 358 if (m_propertySet.propertyAt(foundPropertyIndex).isImplicit()) { 359 commonValue = String(); 360 return; 361 } 362 363 char prefix = '\0'; 364 switch (propertyID) { 365 case CSSPropertyFontStyle: 366 break; // No prefix. 367 case CSSPropertyFontFamily: 368 case CSSPropertyFontVariant: 369 case CSSPropertyFontWeight: 370 prefix = ' '; 371 break; 372 case CSSPropertyLineHeight: 373 prefix = '/'; 374 break; 375 default: 376 ASSERT_NOT_REACHED(); 377 } 378 379 if (prefix && !result.isEmpty()) 380 result.append(prefix); 381 String value = m_propertySet.propertyAt(foundPropertyIndex).value()->cssText(); 382 result.append(value); 383 if (!commonValue.isNull() && commonValue != value) 384 commonValue = String(); 385 } 386 387 String StylePropertySerializer::fontValue() const 388 { 389 int fontSizePropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontSize); 390 int fontFamilyPropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontFamily); 391 if (fontSizePropertyIndex == -1 || fontFamilyPropertyIndex == -1) 392 return emptyString(); 393 394 StylePropertySet::PropertyReference fontSizeProperty = m_propertySet.propertyAt(fontSizePropertyIndex); 395 StylePropertySet::PropertyReference fontFamilyProperty = m_propertySet.propertyAt(fontFamilyPropertyIndex); 396 if (fontSizeProperty.isImplicit() || fontFamilyProperty.isImplicit()) 397 return emptyString(); 398 399 String commonValue = fontSizeProperty.value()->cssText(); 400 StringBuilder result; 401 appendFontLonghandValueIfExplicit(CSSPropertyFontStyle, result, commonValue); 402 appendFontLonghandValueIfExplicit(CSSPropertyFontVariant, result, commonValue); 403 appendFontLonghandValueIfExplicit(CSSPropertyFontWeight, result, commonValue); 404 if (!result.isEmpty()) 405 result.append(' '); 406 result.append(fontSizeProperty.value()->cssText()); 407 appendFontLonghandValueIfExplicit(CSSPropertyLineHeight, result, commonValue); 408 if (!result.isEmpty()) 409 result.append(' '); 410 result.append(fontFamilyProperty.value()->cssText()); 411 if (isInitialOrInherit(commonValue)) 412 return commonValue; 413 return result.toString(); 414 } 415 416 String StylePropertySerializer::get4Values(const StylePropertyShorthand& shorthand) const 417 { 418 // Assume the properties are in the usual order top, right, bottom, left. 419 int topValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[0]); 420 int rightValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[1]); 421 int bottomValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[2]); 422 int leftValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[3]); 423 424 if (topValueIndex == -1 || rightValueIndex == -1 || bottomValueIndex == -1 || leftValueIndex == -1) 425 return String(); 426 427 StylePropertySet::PropertyReference top = m_propertySet.propertyAt(topValueIndex); 428 StylePropertySet::PropertyReference right = m_propertySet.propertyAt(rightValueIndex); 429 StylePropertySet::PropertyReference bottom = m_propertySet.propertyAt(bottomValueIndex); 430 StylePropertySet::PropertyReference left = m_propertySet.propertyAt(leftValueIndex); 431 432 // All 4 properties must be specified. 433 if (!top.value() || !right.value() || !bottom.value() || !left.value()) 434 return String(); 435 436 if (top.isInherited() && right.isInherited() && bottom.isInherited() && left.isInherited()) 437 return getValueName(CSSValueInherit); 438 439 if (top.value()->isInitialValue() || right.value()->isInitialValue() || bottom.value()->isInitialValue() || left.value()->isInitialValue()) { 440 if (top.value()->isInitialValue() && right.value()->isInitialValue() && bottom.value()->isInitialValue() && left.value()->isInitialValue() && !top.isImplicit()) { 441 // All components are "initial" and "top" is not implicit. 442 return getValueName(CSSValueInitial); 443 } 444 return String(); 445 } 446 if (top.isImportant() != right.isImportant() || right.isImportant() != bottom.isImportant() || bottom.isImportant() != left.isImportant()) 447 return String(); 448 449 bool showLeft = !right.value()->equals(*left.value()); 450 bool showBottom = !top.value()->equals(*bottom.value()) || showLeft; 451 bool showRight = !top.value()->equals(*right.value()) || showBottom; 452 453 StringBuilder result; 454 result.append(top.value()->cssText()); 455 if (showRight) { 456 result.append(' '); 457 result.append(right.value()->cssText()); 458 } 459 if (showBottom) { 460 result.append(' '); 461 result.append(bottom.value()->cssText()); 462 } 463 if (showLeft) { 464 result.append(' '); 465 result.append(left.value()->cssText()); 466 } 467 return result.toString(); 468 } 469 470 String StylePropertySerializer::getLayeredShorthandValue(const StylePropertyShorthand& shorthand) const 471 { 472 StringBuilder result; 473 474 const unsigned size = shorthand.length(); 475 // Begin by collecting the properties into an array. 476 WillBeHeapVector<RefPtrWillBeMember<CSSValue> > values(size); 477 size_t numLayers = 0; 478 479 for (unsigned i = 0; i < size; ++i) { 480 values[i] = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); 481 if (values[i]) { 482 if (values[i]->isBaseValueList()) { 483 CSSValueList* valueList = toCSSValueList(values[i].get()); 484 numLayers = std::max(valueList->length(), numLayers); 485 } else { 486 numLayers = std::max<size_t>(1U, numLayers); 487 } 488 } 489 } 490 491 String commonValue; 492 bool commonValueInitialized = false; 493 494 // Now stitch the properties together. Implicit initial values are flagged as such and 495 // can safely be omitted. 496 for (size_t i = 0; i < numLayers; i++) { 497 StringBuilder layerResult; 498 bool useRepeatXShorthand = false; 499 bool useRepeatYShorthand = false; 500 bool useSingleWordShorthand = false; 501 bool foundPositionYCSSProperty = false; 502 for (unsigned j = 0; j < size; j++) { 503 RefPtrWillBeRawPtr<CSSValue> value = nullptr; 504 if (values[j]) { 505 if (values[j]->isBaseValueList()) 506 value = toCSSValueList(values[j].get())->item(i); 507 else { 508 value = values[j]; 509 510 // Color only belongs in the last layer. 511 if (shorthand.properties()[j] == CSSPropertyBackgroundColor) { 512 if (i != numLayers - 1) 513 value = nullptr; 514 } else if (i) { 515 // Other singletons only belong in the first layer. 516 value = nullptr; 517 } 518 } 519 } 520 521 // We need to report background-repeat as it was written in the CSS. If the property is implicit, 522 // then it was written with only one value. Here we figure out which value that was so we can 523 // report back correctly. 524 if ((shorthand.properties()[j] == CSSPropertyBackgroundRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j])) 525 || (shorthand.properties()[j] == CSSPropertyWebkitMaskRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j]))) { 526 527 // BUG 49055: make sure the value was not reset in the layer check just above. 528 if ((j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyBackgroundRepeatY && value) 529 || (j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyWebkitMaskRepeatY && value)) { 530 RefPtrWillBeRawPtr<CSSValue> yValue = nullptr; 531 RefPtrWillBeRawPtr<CSSValue> nextValue = values[j + 1]; 532 if (nextValue->isValueList()) 533 yValue = toCSSValueList(nextValue.get())->itemWithoutBoundsCheck(i); 534 else 535 yValue = nextValue; 536 537 // background-repeat-x(y) or mask-repeat-x(y) may be like this : "initial, repeat". We can omit the implicit initial values 538 // before starting to compare their values. 539 if (value->isImplicitInitialValue() || yValue->isImplicitInitialValue()) 540 continue; 541 542 // FIXME: At some point we need to fix this code to avoid returning an invalid shorthand, 543 // since some longhand combinations are not serializable into a single shorthand. 544 if (!value->isPrimitiveValue() || !yValue->isPrimitiveValue()) 545 continue; 546 547 CSSValueID xId = toCSSPrimitiveValue(value.get())->getValueID(); 548 CSSValueID yId = toCSSPrimitiveValue(yValue.get())->getValueID(); 549 if (xId != yId) { 550 if (xId == CSSValueRepeat && yId == CSSValueNoRepeat) { 551 useRepeatXShorthand = true; 552 ++j; 553 } else if (xId == CSSValueNoRepeat && yId == CSSValueRepeat) { 554 useRepeatYShorthand = true; 555 continue; 556 } 557 } else { 558 useSingleWordShorthand = true; 559 ++j; 560 } 561 } 562 } 563 564 String valueText; 565 if (value && !value->isImplicitInitialValue()) { 566 if (!layerResult.isEmpty()) 567 layerResult.append(' '); 568 if (foundPositionYCSSProperty 569 && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize)) 570 layerResult.appendLiteral("/ "); 571 if (!foundPositionYCSSProperty 572 && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize)) 573 continue; 574 575 if (useRepeatXShorthand) { 576 useRepeatXShorthand = false; 577 layerResult.append(getValueName(CSSValueRepeatX)); 578 } else if (useRepeatYShorthand) { 579 useRepeatYShorthand = false; 580 layerResult.append(getValueName(CSSValueRepeatY)); 581 } else { 582 if (useSingleWordShorthand) 583 useSingleWordShorthand = false; 584 valueText = value->cssText(); 585 layerResult.append(valueText); 586 } 587 588 if (shorthand.properties()[j] == CSSPropertyBackgroundPositionY 589 || shorthand.properties()[j] == CSSPropertyWebkitMaskPositionY) { 590 foundPositionYCSSProperty = true; 591 592 // background-position is a special case: if only the first offset is specified, 593 // the second one defaults to "center", not the same value. 594 if (commonValueInitialized && commonValue != "initial" && commonValue != "inherit") 595 commonValue = String(); 596 } 597 } 598 599 if (!commonValueInitialized) { 600 commonValue = valueText; 601 commonValueInitialized = true; 602 } else if (!commonValue.isNull() && commonValue != valueText) 603 commonValue = String(); 604 } 605 606 if (!layerResult.isEmpty()) { 607 if (!result.isEmpty()) 608 result.appendLiteral(", "); 609 result.append(layerResult); 610 } 611 } 612 613 if (isInitialOrInherit(commonValue)) 614 return commonValue; 615 616 if (result.isEmpty()) 617 return String(); 618 return result.toString(); 619 } 620 621 String StylePropertySerializer::getShorthandValue(const StylePropertyShorthand& shorthand) const 622 { 623 String commonValue; 624 StringBuilder result; 625 for (unsigned i = 0; i < shorthand.length(); ++i) { 626 if (!m_propertySet.isPropertyImplicit(shorthand.properties()[i])) { 627 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); 628 if (!value) 629 return String(); 630 String valueText = value->cssText(); 631 if (!i) 632 commonValue = valueText; 633 else if (!commonValue.isNull() && commonValue != valueText) 634 commonValue = String(); 635 if (value->isInitialValue()) 636 continue; 637 if (!result.isEmpty()) 638 result.append(' '); 639 result.append(valueText); 640 } else 641 commonValue = String(); 642 } 643 if (isInitialOrInherit(commonValue)) 644 return commonValue; 645 if (result.isEmpty()) 646 return String(); 647 return result.toString(); 648 } 649 650 // only returns a non-null value if all properties have the same, non-null value 651 String StylePropertySerializer::getCommonValue(const StylePropertyShorthand& shorthand) const 652 { 653 String res; 654 bool lastPropertyWasImportant = false; 655 for (unsigned i = 0; i < shorthand.length(); ++i) { 656 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); 657 // FIXME: CSSInitialValue::cssText should generate the right value. 658 if (!value) 659 return String(); 660 String text = value->cssText(); 661 if (text.isNull()) 662 return String(); 663 if (res.isNull()) 664 res = text; 665 else if (res != text) 666 return String(); 667 668 bool currentPropertyIsImportant = m_propertySet.propertyIsImportant(shorthand.properties()[i]); 669 if (i && lastPropertyWasImportant != currentPropertyIsImportant) 670 return String(); 671 lastPropertyWasImportant = currentPropertyIsImportant; 672 } 673 return res; 674 } 675 676 String StylePropertySerializer::borderPropertyValue(CommonValueMode valueMode) const 677 { 678 const StylePropertyShorthand properties[3] = { borderWidthShorthand(), borderStyleShorthand(), borderColorShorthand() }; 679 String commonValue; 680 StringBuilder result; 681 for (size_t i = 0; i < WTF_ARRAY_LENGTH(properties); ++i) { 682 String value = getCommonValue(properties[i]); 683 if (value.isNull()) { 684 if (valueMode == ReturnNullOnUncommonValues) 685 return String(); 686 ASSERT(valueMode == OmitUncommonValues); 687 continue; 688 } 689 if (!i) 690 commonValue = value; 691 else if (!commonValue.isNull() && commonValue != value) 692 commonValue = String(); 693 if (value == "initial") 694 continue; 695 if (!result.isEmpty()) 696 result.append(' '); 697 result.append(value); 698 } 699 if (isInitialOrInherit(commonValue)) 700 return commonValue; 701 return result.isEmpty() ? String() : result.toString(); 702 } 703 704 static void appendBackgroundRepeatValue(StringBuilder& builder, const CSSValue& repeatXCSSValue, const CSSValue& repeatYCSSValue) 705 { 706 // FIXME: Ensure initial values do not appear in CSS_VALUE_LISTS. 707 DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, initialRepeatValue, (CSSPrimitiveValue::create(CSSValueRepeat))); 708 const CSSPrimitiveValue& repeatX = repeatXCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatXCSSValue); 709 const CSSPrimitiveValue& repeatY = repeatYCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatYCSSValue); 710 CSSValueID repeatXValueId = repeatX.getValueID(); 711 CSSValueID repeatYValueId = repeatY.getValueID(); 712 if (repeatXValueId == repeatYValueId) { 713 builder.append(repeatX.cssText()); 714 } else if (repeatXValueId == CSSValueNoRepeat && repeatYValueId == CSSValueRepeat) { 715 builder.append("repeat-y"); 716 } else if (repeatXValueId == CSSValueRepeat && repeatYValueId == CSSValueNoRepeat) { 717 builder.append("repeat-x"); 718 } else { 719 builder.append(repeatX.cssText()); 720 builder.append(" "); 721 builder.append(repeatY.cssText()); 722 } 723 } 724 725 String StylePropertySerializer::backgroundRepeatPropertyValue() const 726 { 727 RefPtrWillBeRawPtr<CSSValue> repeatX = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatX); 728 RefPtrWillBeRawPtr<CSSValue> repeatY = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatY); 729 if (!repeatX || !repeatY) 730 return String(); 731 if (m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX) != m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatY)) 732 return String(); 733 if (repeatX->cssValueType() == repeatY->cssValueType() 734 && (repeatX->cssValueType() == CSSValue::CSS_INITIAL || repeatX->cssValueType() == CSSValue::CSS_INHERIT)) { 735 return repeatX->cssText(); 736 } 737 738 RefPtrWillBeRawPtr<CSSValueList> repeatXList; 739 if (repeatX->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) { 740 repeatXList = CSSValueList::createCommaSeparated(); 741 repeatXList->append(repeatX); 742 } else if (repeatX->cssValueType() == CSSValue::CSS_VALUE_LIST) { 743 repeatXList = toCSSValueList(repeatX.get()); 744 } else { 745 return String(); 746 } 747 748 RefPtrWillBeRawPtr<CSSValueList> repeatYList; 749 if (repeatY->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) { 750 repeatYList = CSSValueList::createCommaSeparated(); 751 repeatYList->append(repeatY); 752 } else if (repeatY->cssValueType() == CSSValue::CSS_VALUE_LIST) { 753 repeatYList = toCSSValueList(repeatY.get()); 754 } else { 755 return String(); 756 } 757 758 size_t shorthandLength = lowestCommonMultiple(repeatXList->length(), repeatYList->length()); 759 StringBuilder builder; 760 for (size_t i = 0; i < shorthandLength; ++i) { 761 if (i) 762 builder.append(", "); 763 appendBackgroundRepeatValue(builder, 764 *repeatXList->item(i % repeatXList->length()), 765 *repeatYList->item(i % repeatYList->length())); 766 } 767 return builder.toString(); 768 } 769 770 void StylePropertySerializer::appendBackgroundPropertyAsText(StringBuilder& result, unsigned& numDecls) const 771 { 772 if (isPropertyShorthandAvailable(backgroundShorthand())) { 773 String backgroundValue = getPropertyValue(CSSPropertyBackground); 774 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage); 775 result.append(getPropertyText(CSSPropertyBackground, backgroundValue, isImportant, numDecls++)); 776 return; 777 } 778 if (shorthandHasOnlyInitialOrInheritedValue(backgroundShorthand())) { 779 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundImage); 780 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage); 781 result.append(getPropertyText(CSSPropertyBackground, value->cssText(), isImportant, numDecls++)); 782 return; 783 } 784 785 // backgroundShorthandProperty without layered shorhand properties 786 const CSSPropertyID backgroundPropertyIds[] = { 787 CSSPropertyBackgroundImage, 788 CSSPropertyBackgroundAttachment, 789 CSSPropertyBackgroundColor, 790 CSSPropertyBackgroundSize, 791 CSSPropertyBackgroundOrigin, 792 CSSPropertyBackgroundClip 793 }; 794 795 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(backgroundPropertyIds); ++i) { 796 CSSPropertyID propertyID = backgroundPropertyIds[i]; 797 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(propertyID); 798 if (!value) 799 continue; 800 result.append(getPropertyText(propertyID, value->cssText(), m_propertySet.propertyIsImportant(propertyID), numDecls++)); 801 } 802 803 // FIXME: This is a not-so-nice way to turn x/y positions into single background-position in output. 804 // It is required because background-position-x/y are non-standard properties and WebKit generated output 805 // would not work in Firefox (<rdar://problem/5143183>) 806 // It would be a better solution if background-position was CSS_PAIR. 807 if (shorthandHasOnlyInitialOrInheritedValue(backgroundPositionShorthand())) { 808 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX); 809 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX); 810 result.append(getPropertyText(CSSPropertyBackgroundPosition, value->cssText(), isImportant, numDecls++)); 811 } else if (isPropertyShorthandAvailable(backgroundPositionShorthand())) { 812 String positionValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundPosition); 813 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX); 814 if (!positionValue.isNull()) 815 result.append(getPropertyText(CSSPropertyBackgroundPosition, positionValue, isImportant, numDecls++)); 816 } else { 817 // should check background-position-x or background-position-y. 818 if (RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX)) { 819 if (!value->isImplicitInitialValue()) { 820 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX); 821 result.append(getPropertyText(CSSPropertyBackgroundPositionX, value->cssText(), isImportant, numDecls++)); 822 } 823 } 824 if (RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionY)) { 825 if (!value->isImplicitInitialValue()) { 826 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionY); 827 result.append(getPropertyText(CSSPropertyBackgroundPositionY, value->cssText(), isImportant, numDecls++)); 828 } 829 } 830 } 831 832 String repeatValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundRepeat); 833 if (!repeatValue.isNull()) 834 result.append(getPropertyText(CSSPropertyBackgroundRepeat, repeatValue, m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX), numDecls++)); 835 } 836 837 bool StylePropertySerializer::isPropertyShorthandAvailable(const StylePropertyShorthand& shorthand) const 838 { 839 ASSERT(shorthand.length() > 0); 840 841 bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]); 842 for (unsigned i = 0; i < shorthand.length(); ++i) { 843 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); 844 if (!value || (value->isInitialValue() && !value->isImplicitInitialValue()) || value->isInheritedValue()) 845 return false; 846 if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i])) 847 return false; 848 } 849 return true; 850 } 851 852 bool StylePropertySerializer::shorthandHasOnlyInitialOrInheritedValue(const StylePropertyShorthand& shorthand) const 853 { 854 ASSERT(shorthand.length() > 0); 855 bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]); 856 bool isInitialValue = true; 857 bool isInheritedValue = true; 858 for (unsigned i = 0; i < shorthand.length(); ++i) { 859 RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); 860 if (!value) 861 return false; 862 if (!value->isInitialValue()) 863 isInitialValue = false; 864 if (!value->isInheritedValue()) 865 isInheritedValue = false; 866 if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i])) 867 return false; 868 } 869 return isInitialValue || isInheritedValue; 870 } 871 872 } 873