1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Peter Kelly (pmk (at) post.com) 5 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 6 * Copyright (C) 2004, 2005, 2006, 2008, 2010 Apple Inc. All rights reserved. 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 */ 23 24 #include "config.h" 25 #include "StyledElement.h" 26 27 #include "Attribute.h" 28 #include "CSSMutableStyleDeclaration.h" 29 #include "CSSStyleSelector.h" 30 #include "CSSStyleSheet.h" 31 #include "CSSValueKeywords.h" 32 #include "ClassList.h" 33 #include "DOMTokenList.h" 34 #include "Document.h" 35 #include "HTMLNames.h" 36 #include "HTMLParserIdioms.h" 37 #include <wtf/HashFunctions.h> 38 39 using namespace std; 40 41 namespace WebCore { 42 43 using namespace HTMLNames; 44 45 struct MappedAttributeKey { 46 uint16_t type; 47 StringImpl* name; 48 StringImpl* value; 49 MappedAttributeKey(MappedAttributeEntry t = eNone, StringImpl* n = 0, StringImpl* v = 0) 50 : type(t), name(n), value(v) { } 51 }; 52 53 static inline bool operator==(const MappedAttributeKey& a, const MappedAttributeKey& b) 54 { return a.type == b.type && a.name == b.name && a.value == b.value; } 55 56 struct MappedAttributeKeyTraits : WTF::GenericHashTraits<MappedAttributeKey> { 57 static const bool emptyValueIsZero = true; 58 static const bool needsDestruction = false; 59 static void constructDeletedValue(MappedAttributeKey& slot) { slot.type = eLastEntry; } 60 static bool isDeletedValue(const MappedAttributeKey& value) { return value.type == eLastEntry; } 61 }; 62 63 struct MappedAttributeHash { 64 static unsigned hash(const MappedAttributeKey&); 65 static bool equal(const MappedAttributeKey& a, const MappedAttributeKey& b) { return a == b; } 66 static const bool safeToCompareToEmptyOrDeleted = true; 67 }; 68 69 typedef HashMap<MappedAttributeKey, CSSMappedAttributeDeclaration*, MappedAttributeHash, MappedAttributeKeyTraits> MappedAttributeDecls; 70 71 static MappedAttributeDecls* mappedAttributeDecls = 0; 72 73 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr) 74 { 75 if (!mappedAttributeDecls) 76 return 0; 77 return mappedAttributeDecls->get(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl())); 78 } 79 80 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry type, const QualifiedName& name, const AtomicString& value) 81 { 82 if (!mappedAttributeDecls) 83 return 0; 84 return mappedAttributeDecls->get(MappedAttributeKey(type, name.localName().impl(), value.impl())); 85 } 86 87 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr, CSSMappedAttributeDeclaration* decl) 88 { 89 if (!mappedAttributeDecls) 90 mappedAttributeDecls = new MappedAttributeDecls; 91 mappedAttributeDecls->set(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()), decl); 92 } 93 94 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& name, const AtomicString& value, CSSMappedAttributeDeclaration* decl) 95 { 96 if (!mappedAttributeDecls) 97 mappedAttributeDecls = new MappedAttributeDecls; 98 mappedAttributeDecls->set(MappedAttributeKey(entryType, name.localName().impl(), value.impl()), decl); 99 } 100 101 void StyledElement::removeMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& attrName, const AtomicString& attrValue) 102 { 103 if (!mappedAttributeDecls) 104 return; 105 mappedAttributeDecls->remove(MappedAttributeKey(entryType, attrName.localName().impl(), attrValue.impl())); 106 } 107 108 void StyledElement::updateStyleAttribute() const 109 { 110 ASSERT(!isStyleAttributeValid()); 111 setIsStyleAttributeValid(); 112 setIsSynchronizingStyleAttribute(); 113 if (m_inlineStyleDecl) 114 const_cast<StyledElement*>(this)->setAttribute(styleAttr, m_inlineStyleDecl->cssText()); 115 clearIsSynchronizingStyleAttribute(); 116 } 117 118 StyledElement::~StyledElement() 119 { 120 destroyInlineStyleDecl(); 121 } 122 123 PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value) 124 { 125 return Attribute::createMapped(name, value); 126 } 127 128 void StyledElement::createInlineStyleDecl() 129 { 130 m_inlineStyleDecl = CSSMutableStyleDeclaration::create(); 131 m_inlineStyleDecl->setParent(document()->elementSheet()); 132 m_inlineStyleDecl->setNode(this); 133 m_inlineStyleDecl->setStrictParsing(isHTMLElement() && !document()->inQuirksMode()); 134 } 135 136 void StyledElement::destroyInlineStyleDecl() 137 { 138 if (m_inlineStyleDecl) { 139 m_inlineStyleDecl->setNode(0); 140 m_inlineStyleDecl->setParent(0); 141 m_inlineStyleDecl = 0; 142 } 143 } 144 145 void StyledElement::attributeChanged(Attribute* attr, bool preserveDecls) 146 { 147 if (!attr->isMappedAttribute()) { 148 Element::attributeChanged(attr, preserveDecls); 149 return; 150 } 151 152 if (attr->decl() && !preserveDecls) { 153 attr->setDecl(0); 154 setNeedsStyleRecalc(); 155 if (attributeMap()) 156 attributeMap()->declRemoved(); 157 } 158 159 bool checkDecl = true; 160 MappedAttributeEntry entry; 161 bool needToParse = mapToEntry(attr->name(), entry); 162 if (preserveDecls) { 163 if (attr->decl()) { 164 setNeedsStyleRecalc(); 165 if (attributeMap()) 166 attributeMap()->declAdded(); 167 checkDecl = false; 168 } 169 } else if (!attr->isNull() && entry != eNone) { 170 CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr); 171 if (decl) { 172 attr->setDecl(decl); 173 setNeedsStyleRecalc(); 174 if (attributeMap()) 175 attributeMap()->declAdded(); 176 checkDecl = false; 177 } else 178 needToParse = true; 179 } 180 181 // parseMappedAttribute() might create a CSSMappedAttributeDeclaration on the attribute. 182 // Normally we would be concerned about reseting the parent of those declarations in StyledElement::didMoveToNewOwnerDocument(). 183 // But currently we always clear its parent and node below when adding it to the decl table. 184 // If that changes for some reason moving between documents will be buggy. 185 // webarchive/adopt-attribute-styled-node-webarchive.html should catch any resulting crashes. 186 if (needToParse) 187 parseMappedAttribute(attr); 188 189 if (entry == eNone) 190 recalcStyleIfNeededAfterAttributeChanged(attr); 191 192 if (checkDecl && attr->decl()) { 193 // Add the decl to the table in the appropriate spot. 194 setMappedAttributeDecl(entry, attr, attr->decl()); 195 attr->decl()->setMappedState(entry, attr->name(), attr->value()); 196 attr->decl()->setParent(0); 197 attr->decl()->setNode(0); 198 if (attributeMap()) 199 attributeMap()->declAdded(); 200 } 201 202 updateAfterAttributeChanged(attr); 203 } 204 205 bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const 206 { 207 result = eNone; 208 if (attrName == styleAttr) 209 return !isSynchronizingStyleAttribute(); 210 return true; 211 } 212 213 void StyledElement::classAttributeChanged(const AtomicString& newClassString) 214 { 215 const UChar* characters = newClassString.characters(); 216 unsigned length = newClassString.length(); 217 unsigned i; 218 for (i = 0; i < length; ++i) { 219 if (isNotHTMLSpace(characters[i])) 220 break; 221 } 222 bool hasClass = i < length; 223 setHasClass(hasClass); 224 if (hasClass) { 225 attributes()->setClass(newClassString); 226 if (DOMTokenList* classList = optionalClassList()) 227 static_cast<ClassList*>(classList)->reset(newClassString); 228 } else if (attributeMap()) 229 attributeMap()->clearClass(); 230 setNeedsStyleRecalc(); 231 dispatchSubtreeModifiedEvent(); 232 } 233 234 void StyledElement::parseMappedAttribute(Attribute* attr) 235 { 236 if (isIdAttributeName(attr->name())) 237 idAttributeChanged(attr); 238 else if (attr->name() == classAttr) 239 classAttributeChanged(attr->value()); 240 else if (attr->name() == styleAttr) { 241 if (attr->isNull()) 242 destroyInlineStyleDecl(); 243 else 244 getInlineStyleDecl()->parseDeclaration(attr->value()); 245 setIsStyleAttributeValid(); 246 setNeedsStyleRecalc(); 247 } 248 } 249 250 CSSMutableStyleDeclaration* StyledElement::getInlineStyleDecl() 251 { 252 if (!m_inlineStyleDecl) 253 createInlineStyleDecl(); 254 return m_inlineStyleDecl.get(); 255 } 256 257 CSSStyleDeclaration* StyledElement::style() 258 { 259 return getInlineStyleDecl(); 260 } 261 262 void StyledElement::addCSSProperty(Attribute* attribute, int id, const String &value) 263 { 264 if (!attribute->decl()) 265 createMappedDecl(attribute); 266 attribute->decl()->setProperty(id, value, false); 267 } 268 269 void StyledElement::addCSSProperty(Attribute* attribute, int id, int value) 270 { 271 if (!attribute->decl()) 272 createMappedDecl(attribute); 273 attribute->decl()->setProperty(id, value, false); 274 } 275 276 void StyledElement::addCSSImageProperty(Attribute* attribute, int id, const String& url) 277 { 278 if (!attribute->decl()) 279 createMappedDecl(attribute); 280 attribute->decl()->setImageProperty(id, url, false); 281 } 282 283 void StyledElement::addCSSLength(Attribute* attr, int id, const String &value) 284 { 285 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct 286 // length unit and make the appropriate parsed value. 287 if (!attr->decl()) 288 createMappedDecl(attr); 289 290 // strip attribute garbage.. 291 StringImpl* v = value.impl(); 292 if (v) { 293 unsigned int l = 0; 294 295 while (l < v->length() && (*v)[l] <= ' ') 296 l++; 297 298 for (; l < v->length(); l++) { 299 UChar cc = (*v)[l]; 300 if (cc > '9') 301 break; 302 if (cc < '0') { 303 if (cc == '%' || cc == '*') 304 l++; 305 if (cc != '.') 306 break; 307 } 308 } 309 310 if (l != v->length()) { 311 attr->decl()->setLengthProperty(id, v->substring(0, l), false); 312 return; 313 } 314 } 315 316 attr->decl()->setLengthProperty(id, value, false); 317 } 318 319 /* color parsing that tries to match as close as possible IE 6. */ 320 void StyledElement::addCSSColor(Attribute* attr, int id, const String& c) 321 { 322 // this is the only case no color gets applied in IE. 323 if (!c.length()) 324 return; 325 326 if (!attr->decl()) 327 createMappedDecl(attr); 328 329 if (attr->decl()->setProperty(id, c, false)) 330 return; 331 332 String color = c; 333 // not something that fits the specs. 334 335 // we're emulating IEs color parser here. It maps transparent to black, otherwise it tries to build a rgb value 336 // out of everything you put in. The algorithm is experimentally determined, but seems to work for all test cases I have. 337 338 // the length of the color value is rounded up to the next 339 // multiple of 3. each part of the rgb triple then gets one third 340 // of the length. 341 // 342 // Each triplet is parsed byte by byte, mapping 343 // each number to a hex value (0-9a-fA-F to their values 344 // everything else to 0). 345 // 346 // The highest non zero digit in all triplets is remembered, and 347 // used as a normalization point to normalize to values between 0 348 // and 255. 349 350 if (!equalIgnoringCase(color, "transparent")) { 351 if (color[0] == '#') 352 color.remove(0, 1); 353 int basicLength = (color.length() + 2) / 3; 354 if (basicLength > 1) { 355 // IE ignores colors with three digits or less 356 int colors[3] = { 0, 0, 0 }; 357 int component = 0; 358 int pos = 0; 359 int maxDigit = basicLength-1; 360 while (component < 3) { 361 // search forward for digits in the string 362 int numDigits = 0; 363 while (pos < (int)color.length() && numDigits < basicLength) { 364 colors[component] <<= 4; 365 if (isASCIIHexDigit(color[pos])) { 366 colors[component] += toASCIIHexValue(color[pos]); 367 maxDigit = min(maxDigit, numDigits); 368 } 369 numDigits++; 370 pos++; 371 } 372 while (numDigits++ < basicLength) 373 colors[component] <<= 4; 374 component++; 375 } 376 maxDigit = basicLength - maxDigit; 377 378 // normalize to 00-ff. The highest filled digit counts, minimum is 2 digits 379 maxDigit -= 2; 380 colors[0] >>= 4 * maxDigit; 381 colors[1] >>= 4 * maxDigit; 382 colors[2] >>= 4 * maxDigit; 383 384 color = String::format("#%02x%02x%02x", colors[0], colors[1], colors[2]); 385 if (attr->decl()->setProperty(id, color, false)) 386 return; 387 } 388 } 389 attr->decl()->setProperty(id, CSSValueBlack, false); 390 } 391 392 void StyledElement::createMappedDecl(Attribute* attr) 393 { 394 RefPtr<CSSMappedAttributeDeclaration> decl = CSSMappedAttributeDeclaration::create(); 395 attr->setDecl(decl); 396 decl->setParent(document()->elementSheet()); 397 decl->setNode(this); 398 decl->setStrictParsing(false); // Mapped attributes are just always quirky. 399 } 400 401 unsigned MappedAttributeHash::hash(const MappedAttributeKey& key) 402 { 403 COMPILE_ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8, key_name_size); 404 COMPILE_ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8, key_value_size); 405 406 StringHasher hasher; 407 const UChar* data; 408 409 data = reinterpret_cast<const UChar*>(&key.name); 410 hasher.addCharacters(data[0], data[1]); 411 if (sizeof(key.name) == 8) 412 hasher.addCharacters(data[2], data[3]); 413 414 data = reinterpret_cast<const UChar*>(&key.value); 415 hasher.addCharacters(data[0], data[1]); 416 if (sizeof(key.value) == 8) 417 hasher.addCharacters(data[2], data[3]); 418 419 return hasher.hash(); 420 } 421 422 void StyledElement::copyNonAttributeProperties(const Element *sourceElement) 423 { 424 const StyledElement* source = static_cast<const StyledElement*>(sourceElement); 425 if (!source->m_inlineStyleDecl) 426 return; 427 428 *getInlineStyleDecl() = *source->m_inlineStyleDecl; 429 setIsStyleAttributeValid(source->isStyleAttributeValid()); 430 setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute()); 431 432 Element::copyNonAttributeProperties(sourceElement); 433 } 434 435 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const 436 { 437 if (CSSMutableStyleDeclaration* style = inlineStyleDecl()) 438 style->addSubresourceStyleURLs(urls); 439 } 440 441 442 void StyledElement::didMoveToNewOwnerDocument() 443 { 444 if (m_inlineStyleDecl) 445 m_inlineStyleDecl->setParent(document()->elementSheet()); 446 447 Element::didMoveToNewOwnerDocument(); 448 } 449 450 } 451