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