1/* 2 * Copyright (C) 2007, 2008, 2009, 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "JSCSSStyleDeclarationCustom.h" 28 29#include "CSSParser.h" 30#include "CSSPrimitiveValue.h" 31#include "CSSPropertyNames.h" 32#include "CSSValue.h" 33#include "HashTools.h" 34#include "JSCSSValue.h" 35#include "JSNode.h" 36#include "RuntimeEnabledFeatures.h" 37#include "Settings.h" 38#include "StyleProperties.h" 39#include <runtime/IdentifierInlines.h> 40#include <runtime/StringPrototype.h> 41#include <wtf/ASCIICType.h> 42#include <wtf/text/AtomicString.h> 43#include <wtf/text/StringBuilder.h> 44#include <wtf/text/StringConcatenate.h> 45#include <wtf/text/WTFString.h> 46 47using namespace JSC; 48 49namespace WebCore { 50 51void JSCSSStyleDeclaration::visitAdditionalChildren(SlotVisitor& visitor) 52{ 53 visitor.addOpaqueRoot(root(&impl())); 54} 55 56class CSSPropertyInfo { 57public: 58 CSSPropertyID propertyID; 59 bool hadPixelOrPosPrefix; 60}; 61 62enum PropertyNamePrefix { 63 PropertyNamePrefixNone, 64 PropertyNamePrefixCSS, 65 PropertyNamePrefixPixel, 66 PropertyNamePrefixPos, 67#if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) 68 PropertyNamePrefixApple, 69#endif 70 PropertyNamePrefixEpub, 71#if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) 72 PropertyNamePrefixKHTML, 73#endif 74 PropertyNamePrefixWebKit 75}; 76 77template<size_t prefixCStringLength> 78static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength]) 79{ 80 size_t prefixLength = prefixCStringLength - 1; 81 82 ASSERT(toASCIILower(propertyName[0]) == prefix[0]); 83 const size_t offset = 1; 84 85#ifndef NDEBUG 86 for (size_t i = 0; i < prefixLength; ++i) 87 ASSERT(isASCIILower(prefix[i])); 88 ASSERT(!prefix[prefixLength]); 89 ASSERT(propertyName.length()); 90#endif 91 92 // The prefix within the property name must be followed by a capital letter. 93 // Other characters in the prefix within the property name must be lowercase. 94 if (propertyName.length() < (prefixLength + 1)) 95 return false; 96 97 for (size_t i = offset; i < prefixLength; ++i) { 98 if (propertyName[i] != prefix[i]) 99 return false; 100 } 101 102 if (!isASCIIUpper(propertyName[prefixLength])) 103 return false; 104 return true; 105} 106 107static PropertyNamePrefix getCSSPropertyNamePrefix(const StringImpl& propertyName) 108{ 109 ASSERT(propertyName.length()); 110 111 // First character of the prefix within the property name may be upper or lowercase. 112 UChar firstChar = toASCIILower(propertyName[0]); 113 switch (firstChar) { 114#if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) 115 case 'a': 116 if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "apple")) 117 return PropertyNamePrefixApple; 118 break; 119#endif 120 case 'c': 121 if (matchesCSSPropertyNamePrefix(propertyName, "css")) 122 return PropertyNamePrefixCSS; 123 break; 124#if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) 125 case 'k': 126 if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "khtml")) 127 return PropertyNamePrefixKHTML; 128 break; 129#endif 130 case 'e': 131 if (matchesCSSPropertyNamePrefix(propertyName, "epub")) 132 return PropertyNamePrefixEpub; 133 break; 134 case 'p': 135 if (matchesCSSPropertyNamePrefix(propertyName, "pos")) 136 return PropertyNamePrefixPos; 137 if (matchesCSSPropertyNamePrefix(propertyName, "pixel")) 138 return PropertyNamePrefixPixel; 139 break; 140 case 'w': 141 if (matchesCSSPropertyNamePrefix(propertyName, "webkit")) 142 return PropertyNamePrefixWebKit; 143 break; 144 default: 145 break; 146 } 147 return PropertyNamePrefixNone; 148} 149 150static inline void writeWebKitPrefix(char*& buffer) 151{ 152 *buffer++ = '-'; 153 *buffer++ = 'w'; 154 *buffer++ = 'e'; 155 *buffer++ = 'b'; 156 *buffer++ = 'k'; 157 *buffer++ = 'i'; 158 *buffer++ = 't'; 159 *buffer++ = '-'; 160} 161 162static inline void writeEpubPrefix(char*& buffer) 163{ 164 *buffer++ = '-'; 165 *buffer++ = 'e'; 166 *buffer++ = 'p'; 167 *buffer++ = 'u'; 168 *buffer++ = 'b'; 169 *buffer++ = '-'; 170} 171 172static CSSPropertyInfo cssPropertyIDForJSCSSPropertyName(PropertyName propertyName) 173{ 174 CSSPropertyInfo propertyInfo = {CSSPropertyInvalid, false}; 175 bool hadPixelOrPosPrefix = false; 176 177 StringImpl* propertyNameString = propertyName.publicName(); 178 if (!propertyNameString) 179 return propertyInfo; 180 unsigned length = propertyNameString->length(); 181 if (!length) 182 return propertyInfo; 183 184 String stringForCache = String(propertyNameString); 185 typedef HashMap<String, CSSPropertyInfo> CSSPropertyInfoMap; 186 DEPRECATED_DEFINE_STATIC_LOCAL(CSSPropertyInfoMap, propertyInfoCache, ()); 187 propertyInfo = propertyInfoCache.get(stringForCache); 188 if (propertyInfo.propertyID) 189 return propertyInfo; 190 191 const size_t bufferSize = maxCSSPropertyNameLength + 1; 192 char buffer[bufferSize]; 193 char* bufferPtr = buffer; 194 const char* name = bufferPtr; 195 196 unsigned i = 0; 197 // Prefixes CSS, Pixel, Pos are ignored. 198 // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-". 199 // The prefix "Epub" becomes "-epub-". 200 switch (getCSSPropertyNamePrefix(*propertyNameString)) { 201 case PropertyNamePrefixNone: 202 if (isASCIIUpper((*propertyNameString)[0])) 203 return propertyInfo; 204 break; 205 case PropertyNamePrefixCSS: 206 i += 3; 207 break; 208 case PropertyNamePrefixPixel: 209 i += 5; 210 hadPixelOrPosPrefix = true; 211 break; 212 case PropertyNamePrefixPos: 213 i += 3; 214 hadPixelOrPosPrefix = true; 215 break; 216#if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) 217 case PropertyNamePrefixApple: 218 case PropertyNamePrefixKHTML: 219 ASSERT(RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled()); 220 writeWebKitPrefix(bufferPtr); 221 i += 5; 222 break; 223#endif 224 case PropertyNamePrefixEpub: 225 writeEpubPrefix(bufferPtr); 226 i += 4; 227 break; 228 case PropertyNamePrefixWebKit: 229 writeWebKitPrefix(bufferPtr); 230 i += 6; 231 break; 232 } 233 234 *bufferPtr++ = toASCIILower((*propertyNameString)[i++]); 235 236 char* bufferEnd = buffer + bufferSize; 237 char* stringEnd = bufferEnd - 1; 238 size_t bufferSizeLeft = stringEnd - bufferPtr; 239 size_t propertySizeLeft = length - i; 240 if (propertySizeLeft > bufferSizeLeft) 241 return propertyInfo; 242 243 for (; i < length; ++i) { 244 UChar c = (*propertyNameString)[i]; 245 if (!c || c >= 0x7F) 246 return propertyInfo; // illegal character 247 if (isASCIIUpper(c)) { 248 size_t bufferSizeLeft = stringEnd - bufferPtr; 249 size_t propertySizeLeft = length - i + 1; 250 if (propertySizeLeft > bufferSizeLeft) 251 return propertyInfo; 252 *bufferPtr++ = '-'; 253 *bufferPtr++ = toASCIILower(c); 254 } else 255 *bufferPtr++ = c; 256 ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd); 257 } 258 ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd); 259 *bufferPtr = '\0'; 260 261 unsigned outputLength = bufferPtr - buffer; 262#if PLATFORM(IOS) 263 cssPropertyNameIOSAliasing(buffer, name, outputLength); 264#endif 265 266 const Property* hashTableEntry = findProperty(name, outputLength); 267 int propertyID = hashTableEntry ? hashTableEntry->id : 0; 268 if (propertyID) { 269 propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix; 270 propertyInfo.propertyID = static_cast<CSSPropertyID>(propertyID); 271 propertyInfoCache.add(stringForCache, propertyInfo); 272 } 273 return propertyInfo; 274} 275 276static inline JSValue getPropertyValueFallback(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned index) 277{ 278 // If the property is a shorthand property (such as "padding"), 279 // it can only be accessed using getPropertyValue. 280 return jsStringWithCache(exec, thisObj->impl().getPropertyValueInternal(static_cast<CSSPropertyID>(index))); 281} 282 283static inline JSValue cssPropertyGetterPixelOrPosPrefix(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID) 284{ 285 // Set up pixelOrPos boolean to handle the fact that 286 // pixelTop returns "CSS Top" as number value in unit pixels 287 // posTop returns "CSS top" as number value in unit pixels _if_ its a 288 // positioned element. if it is not a positioned element, return 0 289 // from MSIE documentation FIXME: IMPLEMENT THAT (Dirk) 290 RefPtr<CSSValue> v = thisObj->impl().getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID)); 291 if (v) { 292 if (v->isPrimitiveValue()) 293 return jsNumber(static_pointer_cast<CSSPrimitiveValue>(v)->getFloatValue(CSSPrimitiveValue::CSS_PX)); 294 return jsStringOrNull(exec, v->cssText()); 295 } 296 297 return getPropertyValueFallback(exec, thisObj, propertyID); 298} 299 300static inline JSValue cssPropertyGetter(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID) 301{ 302 RefPtr<CSSValue> v = thisObj->impl().getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID)); 303 if (v) 304 return jsStringOrNull(exec, v->cssText()); 305 306 return getPropertyValueFallback(exec, thisObj, propertyID); 307} 308 309bool JSCSSStyleDeclaration::getOwnPropertySlotDelegate(ExecState* exec, PropertyName propertyIdentifier, PropertySlot& slot) 310{ 311 CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyIdentifier); 312 if (!propertyInfo.propertyID) 313 return false; 314 315 if (propertyInfo.hadPixelOrPosPrefix) 316 slot.setValue(this, DontDelete, cssPropertyGetterPixelOrPosPrefix(exec, this, propertyInfo.propertyID)); 317 else 318 slot.setValue(this, DontDelete, cssPropertyGetter(exec, this, propertyInfo.propertyID)); 319 return true; 320} 321 322bool JSCSSStyleDeclaration::putDelegate(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot&) 323{ 324 CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyName); 325 if (!propertyInfo.propertyID) 326 return false; 327 328 String propValue = valueToStringWithNullCheck(exec, value); 329 if (propertyInfo.hadPixelOrPosPrefix) 330 propValue.append("px"); 331 332 bool important = false; 333 if (Settings::shouldRespectPriorityInCSSAttributeSetters()) { 334 size_t importantIndex = propValue.find("!important", 0, false); 335 if (importantIndex != notFound) { 336 important = true; 337 propValue = propValue.left(importantIndex - 1); 338 } 339 } 340 341 ExceptionCode ec = 0; 342 impl().setPropertyInternal(static_cast<CSSPropertyID>(propertyInfo.propertyID), propValue, important, ec); 343 setDOMException(exec, ec); 344 return true; 345} 346 347JSValue JSCSSStyleDeclaration::getPropertyCSSValue(ExecState* exec) 348{ 349 const String& propertyName = exec->argument(0).toString(exec)->value(exec); 350 if (exec->hadException()) 351 return jsUndefined(); 352 353 RefPtr<CSSValue> cssValue = impl().getPropertyCSSValue(propertyName); 354 if (!cssValue) 355 return jsNull(); 356 357 globalObject()->world().m_cssValueRoots.add(cssValue.get(), root(&impl())); // Balanced by JSCSSValueOwner::finalize(). 358 return toJS(exec, globalObject(), WTF::getPtr(cssValue)); 359} 360 361void JSCSSStyleDeclaration::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) 362{ 363 JSCSSStyleDeclaration* thisObject = jsCast<JSCSSStyleDeclaration*>(object); 364 ASSERT_GC_OBJECT_INHERITS(thisObject, info()); 365 366 unsigned length = thisObject->impl().length(); 367 for (unsigned i = 0; i < length; ++i) 368 propertyNames.add(Identifier::from(exec, i)); 369 370 static Identifier* propertyIdentifiers = 0; 371 if (!propertyIdentifiers) { 372 Vector<String, numCSSProperties> jsPropertyNames; 373 for (int id = firstCSSProperty; id < firstCSSProperty + numCSSProperties; ++id) 374 jsPropertyNames.append(getJSPropertyName(static_cast<CSSPropertyID>(id))); 375 std::sort(jsPropertyNames.begin(), jsPropertyNames.end(), WTF::codePointCompareLessThan); 376 377 propertyIdentifiers = new Identifier[numCSSProperties]; 378 for (int i = 0; i < numCSSProperties; ++i) 379 propertyIdentifiers[i] = Identifier(exec, jsPropertyNames[i].impl()); 380 } 381 382 for (int i = 0; i < numCSSProperties; ++i) 383 propertyNames.add(propertyIdentifiers[i]); 384 385 Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode); 386} 387 388} // namespace WebCore 389