| /* |
| * Copyright (C) 2007-2009, 2013, 2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "JSCSSStyleDeclarationCustom.h" |
| |
| #include "CSSParser.h" |
| #include "CSSPrimitiveValue.h" |
| #include "CSSPropertyNames.h" |
| #include "CSSValue.h" |
| #include "HashTools.h" |
| #include "JSCSSValue.h" |
| #include "JSNode.h" |
| #include "RuntimeEnabledFeatures.h" |
| #include "Settings.h" |
| #include "StyleProperties.h" |
| #include "StyledElement.h" |
| #include <runtime/IdentifierInlines.h> |
| #include <runtime/StringPrototype.h> |
| #include <wtf/ASCIICType.h> |
| #include <wtf/text/AtomicString.h> |
| #include <wtf/text/StringConcatenate.h> |
| #include <wtf/text/WTFString.h> |
| |
| using namespace JSC; |
| |
| namespace WebCore { |
| |
| void JSCSSStyleDeclaration::visitAdditionalChildren(SlotVisitor& visitor) |
| { |
| visitor.addOpaqueRoot(root(&wrapped())); |
| } |
| |
| class CSSPropertyInfo { |
| public: |
| CSSPropertyID propertyID; |
| bool hadPixelOrPosPrefix; |
| }; |
| |
| enum PropertyNamePrefix { |
| PropertyNamePrefixNone, |
| PropertyNamePrefixCSS, |
| PropertyNamePrefixPixel, |
| PropertyNamePrefixPos, |
| #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
| PropertyNamePrefixApple, |
| #endif |
| PropertyNamePrefixEpub, |
| #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
| PropertyNamePrefixKHTML, |
| #endif |
| PropertyNamePrefixWebKit |
| }; |
| |
| template<size_t prefixCStringLength> |
| static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength]) |
| { |
| size_t prefixLength = prefixCStringLength - 1; |
| |
| ASSERT(toASCIILower(propertyName[0]) == prefix[0]); |
| const size_t offset = 1; |
| |
| #ifndef NDEBUG |
| for (size_t i = 0; i < prefixLength; ++i) |
| ASSERT(isASCIILower(prefix[i])); |
| ASSERT(!prefix[prefixLength]); |
| ASSERT(propertyName.length()); |
| #endif |
| |
| // The prefix within the property name must be followed by a capital letter. |
| // Other characters in the prefix within the property name must be lowercase. |
| if (propertyName.length() < (prefixLength + 1)) |
| return false; |
| |
| for (size_t i = offset; i < prefixLength; ++i) { |
| if (propertyName[i] != prefix[i]) |
| return false; |
| } |
| |
| if (!isASCIIUpper(propertyName[prefixLength])) |
| return false; |
| return true; |
| } |
| |
| static PropertyNamePrefix getCSSPropertyNamePrefix(const StringImpl& propertyName) |
| { |
| ASSERT(propertyName.length()); |
| |
| // First character of the prefix within the property name may be upper or lowercase. |
| UChar firstChar = toASCIILower(propertyName[0]); |
| switch (firstChar) { |
| #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
| case 'a': |
| if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "apple")) |
| return PropertyNamePrefixApple; |
| break; |
| #endif |
| case 'c': |
| if (matchesCSSPropertyNamePrefix(propertyName, "css")) |
| return PropertyNamePrefixCSS; |
| break; |
| #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
| case 'k': |
| if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "khtml")) |
| return PropertyNamePrefixKHTML; |
| break; |
| #endif |
| case 'e': |
| if (matchesCSSPropertyNamePrefix(propertyName, "epub")) |
| return PropertyNamePrefixEpub; |
| break; |
| case 'p': |
| if (matchesCSSPropertyNamePrefix(propertyName, "pos")) |
| return PropertyNamePrefixPos; |
| if (matchesCSSPropertyNamePrefix(propertyName, "pixel")) |
| return PropertyNamePrefixPixel; |
| break; |
| case 'w': |
| if (matchesCSSPropertyNamePrefix(propertyName, "webkit")) |
| return PropertyNamePrefixWebKit; |
| break; |
| default: |
| break; |
| } |
| return PropertyNamePrefixNone; |
| } |
| |
| static inline void writeWebKitPrefix(char*& buffer) |
| { |
| *buffer++ = '-'; |
| *buffer++ = 'w'; |
| *buffer++ = 'e'; |
| *buffer++ = 'b'; |
| *buffer++ = 'k'; |
| *buffer++ = 'i'; |
| *buffer++ = 't'; |
| *buffer++ = '-'; |
| } |
| |
| static inline void writeEpubPrefix(char*& buffer) |
| { |
| *buffer++ = '-'; |
| *buffer++ = 'e'; |
| *buffer++ = 'p'; |
| *buffer++ = 'u'; |
| *buffer++ = 'b'; |
| *buffer++ = '-'; |
| } |
| |
| static CSSPropertyInfo cssPropertyIDForJSCSSPropertyName(PropertyName propertyName) |
| { |
| CSSPropertyInfo propertyInfo = {CSSPropertyInvalid, false}; |
| bool hadPixelOrPosPrefix = false; |
| |
| StringImpl* propertyNameString = propertyName.publicName(); |
| if (!propertyNameString) |
| return propertyInfo; |
| unsigned length = propertyNameString->length(); |
| if (!length) |
| return propertyInfo; |
| |
| String stringForCache = String(propertyNameString); |
| typedef HashMap<String, CSSPropertyInfo> CSSPropertyInfoMap; |
| static NeverDestroyed<CSSPropertyInfoMap> propertyInfoCache; |
| propertyInfo = propertyInfoCache.get().get(stringForCache); |
| if (propertyInfo.propertyID) |
| return propertyInfo; |
| |
| const size_t bufferSize = maxCSSPropertyNameLength + 1; |
| char buffer[bufferSize]; |
| char* bufferPtr = buffer; |
| const char* name = bufferPtr; |
| |
| unsigned i = 0; |
| // Prefixes CSS, Pixel, Pos are ignored. |
| // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-". |
| // The prefix "Epub" becomes "-epub-". |
| switch (getCSSPropertyNamePrefix(*propertyNameString)) { |
| case PropertyNamePrefixNone: |
| if (isASCIIUpper((*propertyNameString)[0])) |
| return propertyInfo; |
| break; |
| case PropertyNamePrefixCSS: |
| i += 3; |
| break; |
| case PropertyNamePrefixPixel: |
| i += 5; |
| hadPixelOrPosPrefix = true; |
| break; |
| case PropertyNamePrefixPos: |
| i += 3; |
| hadPixelOrPosPrefix = true; |
| break; |
| #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
| case PropertyNamePrefixApple: |
| case PropertyNamePrefixKHTML: |
| ASSERT(RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled()); |
| writeWebKitPrefix(bufferPtr); |
| i += 5; |
| break; |
| #endif |
| case PropertyNamePrefixEpub: |
| writeEpubPrefix(bufferPtr); |
| i += 4; |
| break; |
| case PropertyNamePrefixWebKit: |
| writeWebKitPrefix(bufferPtr); |
| i += 6; |
| break; |
| } |
| |
| *bufferPtr++ = toASCIILower((*propertyNameString)[i++]); |
| |
| char* bufferEnd = buffer + bufferSize; |
| char* stringEnd = bufferEnd - 1; |
| size_t bufferSizeLeft = stringEnd - bufferPtr; |
| size_t propertySizeLeft = length - i; |
| if (propertySizeLeft > bufferSizeLeft) |
| return propertyInfo; |
| |
| for (; i < length; ++i) { |
| UChar c = (*propertyNameString)[i]; |
| if (!c || c >= 0x7F) |
| return propertyInfo; // illegal character |
| if (isASCIIUpper(c)) { |
| size_t bufferSizeLeft = stringEnd - bufferPtr; |
| size_t propertySizeLeft = length - i + 1; |
| if (propertySizeLeft > bufferSizeLeft) |
| return propertyInfo; |
| *bufferPtr++ = '-'; |
| *bufferPtr++ = toASCIILower(c); |
| } else |
| *bufferPtr++ = c; |
| ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd); |
| } |
| ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd); |
| *bufferPtr = '\0'; |
| |
| unsigned outputLength = bufferPtr - buffer; |
| #if PLATFORM(IOS) |
| cssPropertyNameIOSAliasing(buffer, name, outputLength); |
| #endif |
| |
| const Property* hashTableEntry = findProperty(name, outputLength); |
| int propertyID = hashTableEntry ? hashTableEntry->id : 0; |
| if (propertyID) { |
| propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix; |
| propertyInfo.propertyID = static_cast<CSSPropertyID>(propertyID); |
| propertyInfoCache.get().add(stringForCache, propertyInfo); |
| } |
| return propertyInfo; |
| } |
| |
| static inline JSValue getPropertyValueFallback(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned index) |
| { |
| // If the property is a shorthand property (such as "padding"), |
| // it can only be accessed using getPropertyValue. |
| return jsStringWithCache(exec, thisObj->wrapped().getPropertyValueInternal(static_cast<CSSPropertyID>(index))); |
| } |
| |
| static inline JSValue cssPropertyGetterPixelOrPosPrefix(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID) |
| { |
| // Set up pixelOrPos boolean to handle the fact that |
| // pixelTop returns "CSS Top" as number value in unit pixels |
| // posTop returns "CSS top" as number value in unit pixels _if_ its a |
| // positioned element. if it is not a positioned element, return 0 |
| // from MSIE documentation FIXME: IMPLEMENT THAT (Dirk) |
| RefPtr<CSSValue> v = thisObj->wrapped().getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID)); |
| if (v) { |
| if (v->isPrimitiveValue()) |
| return jsNumber(static_pointer_cast<CSSPrimitiveValue>(v)->getFloatValue(CSSPrimitiveValue::CSS_PX)); |
| return jsStringOrNull(exec, v->cssText()); |
| } |
| |
| return getPropertyValueFallback(exec, thisObj, propertyID); |
| } |
| |
| static inline JSValue cssPropertyGetter(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID) |
| { |
| RefPtr<CSSValue> v = thisObj->wrapped().getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID)); |
| if (v) |
| return jsStringOrNull(exec, v->cssText()); |
| |
| return getPropertyValueFallback(exec, thisObj, propertyID); |
| } |
| |
| bool JSCSSStyleDeclaration::getOwnPropertySlotDelegate(ExecState* exec, PropertyName propertyIdentifier, PropertySlot& slot) |
| { |
| CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyIdentifier); |
| if (!propertyInfo.propertyID) |
| return false; |
| |
| if (propertyInfo.hadPixelOrPosPrefix) |
| slot.setValue(this, DontDelete, cssPropertyGetterPixelOrPosPrefix(exec, this, propertyInfo.propertyID)); |
| else |
| slot.setValue(this, DontDelete, cssPropertyGetter(exec, this, propertyInfo.propertyID)); |
| return true; |
| } |
| |
| bool JSCSSStyleDeclaration::putDelegate(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot&, bool& putResult) |
| { |
| CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyName); |
| if (!propertyInfo.propertyID) |
| return false; |
| |
| String propValue = valueToStringTreatingNullAsEmptyString(exec, value); |
| if (propertyInfo.hadPixelOrPosPrefix) |
| propValue.append("px"); |
| |
| bool important = false; |
| if (Settings::shouldRespectPriorityInCSSAttributeSetters()) { |
| size_t importantIndex = propValue.find("!important", 0, false); |
| if (importantIndex != notFound) { |
| important = true; |
| propValue = propValue.left(importantIndex - 1); |
| } |
| } |
| |
| ExceptionCode ec = 0; |
| CSSPropertyID propertyID = static_cast<CSSPropertyID>(propertyInfo.propertyID); |
| putResult = wrapped().setPropertyInternal(propertyID, propValue, important, ec); |
| setDOMException(exec, ec); |
| |
| return true; |
| } |
| |
| JSValue JSCSSStyleDeclaration::getPropertyCSSValue(ExecState& state) |
| { |
| VM& vm = state.vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (UNLIKELY(state.argumentCount() < 1)) |
| return throwException(&state, scope, createNotEnoughArgumentsError(&state)); |
| |
| String propertyName = state.uncheckedArgument(0).toWTFString(&state); |
| RETURN_IF_EXCEPTION(scope, JSValue()); |
| |
| RefPtr<CSSValue> cssValue = wrapped().getPropertyCSSValue(propertyName); |
| if (!cssValue) |
| return jsNull(); |
| |
| globalObject()->world().m_cssValueRoots.add(cssValue.get(), root(&wrapped())); // Balanced by JSCSSValueOwner::finalize(). |
| return toJS(&state, globalObject(), *cssValue); |
| } |
| |
| void JSCSSStyleDeclaration::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) |
| { |
| JSCSSStyleDeclaration* thisObject = jsCast<JSCSSStyleDeclaration*>(object); |
| ASSERT_GC_OBJECT_INHERITS(thisObject, info()); |
| |
| unsigned length = thisObject->wrapped().length(); |
| for (unsigned i = 0; i < length; ++i) |
| propertyNames.add(Identifier::from(exec, i)); |
| |
| static Identifier* propertyIdentifiers = 0; |
| if (!propertyIdentifiers) { |
| Vector<String, numCSSProperties> jsPropertyNames; |
| for (int id = firstCSSProperty; id < firstCSSProperty + numCSSProperties; ++id) |
| jsPropertyNames.append(getJSPropertyName(static_cast<CSSPropertyID>(id))); |
| std::sort(jsPropertyNames.begin(), jsPropertyNames.end(), WTF::codePointCompareLessThan); |
| |
| propertyIdentifiers = new Identifier[numCSSProperties]; |
| for (int i = 0; i < numCSSProperties; ++i) |
| propertyIdentifiers[i] = Identifier::fromString(exec, jsPropertyNames[i]); |
| } |
| |
| for (int i = 0; i < numCSSProperties; ++i) |
| propertyNames.add(propertyIdentifiers[i]); |
| |
| Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode); |
| } |
| |
| } // namespace WebCore |