1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 6 * Copyright (C) 2009 Rob Buis (rwlbuis (at) gmail.com) 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 "HTMLLinkElement.h" 26 27 #include "CSSHelper.h" 28 #include "CachedCSSStyleSheet.h" 29 #include "DNS.h" 30 #include "DocLoader.h" 31 #include "Document.h" 32 #include "Frame.h" 33 #include "FrameLoader.h" 34 #include "FrameLoaderClient.h" 35 #include "FrameTree.h" 36 #include "HTMLNames.h" 37 #include "MappedAttribute.h" 38 #include "MediaList.h" 39 #include "MediaQueryEvaluator.h" 40 #include "Page.h" 41 #include "ScriptEventListener.h" 42 #include "Settings.h" 43 #include <wtf/StdLibExtras.h> 44 45 namespace WebCore { 46 47 using namespace HTMLNames; 48 49 HTMLLinkElement::HTMLLinkElement(const QualifiedName& qName, Document *doc, bool createdByParser) 50 : HTMLElement(qName, doc) 51 , m_cachedSheet(0) 52 , m_disabledState(0) 53 , m_loading(false) 54 , m_alternate(false) 55 , m_isStyleSheet(false) 56 , m_isIcon(false) 57 #ifdef ANDROID_APPLE_TOUCH_ICON 58 , m_isTouchIcon(false) 59 , m_isPrecomposedTouchIcon(false) 60 #endif 61 , m_isDNSPrefetch(false) 62 , m_createdByParser(createdByParser) 63 { 64 ASSERT(hasTagName(linkTag)); 65 } 66 67 HTMLLinkElement::~HTMLLinkElement() 68 { 69 if (m_cachedSheet) { 70 m_cachedSheet->removeClient(this); 71 if (m_loading && !isDisabled() && !isAlternate()) 72 document()->removePendingSheet(); 73 } 74 } 75 76 void HTMLLinkElement::setDisabledState(bool _disabled) 77 { 78 int oldDisabledState = m_disabledState; 79 m_disabledState = _disabled ? 2 : 1; 80 if (oldDisabledState != m_disabledState) { 81 // If we change the disabled state while the sheet is still loading, then we have to 82 // perform three checks: 83 if (isLoading()) { 84 // Check #1: If the sheet becomes disabled while it was loading, and if it was either 85 // a main sheet or a sheet that was previously enabled via script, then we need 86 // to remove it from the list of pending sheets. 87 if (m_disabledState == 2 && (!m_alternate || oldDisabledState == 1)) 88 document()->removePendingSheet(); 89 90 // Check #2: An alternate sheet becomes enabled while it is still loading. 91 if (m_alternate && m_disabledState == 1) 92 document()->addPendingSheet(); 93 94 // Check #3: A main sheet becomes enabled while it was still loading and 95 // after it was disabled via script. It takes really terrible code to make this 96 // happen (a double toggle for no reason essentially). This happens on 97 // virtualplastic.net, which manages to do about 12 enable/disables on only 3 98 // sheets. :) 99 if (!m_alternate && m_disabledState == 1 && oldDisabledState == 2) 100 document()->addPendingSheet(); 101 102 // If the sheet is already loading just bail. 103 return; 104 } 105 106 // Load the sheet, since it's never been loaded before. 107 if (!m_sheet && m_disabledState == 1) 108 process(); 109 else 110 document()->updateStyleSelector(); // Update the style selector. 111 } 112 } 113 114 StyleSheet* HTMLLinkElement::sheet() const 115 { 116 return m_sheet.get(); 117 } 118 119 void HTMLLinkElement::parseMappedAttribute(MappedAttribute *attr) 120 { 121 if (attr->name() == relAttr) { 122 #ifdef ANDROID_APPLE_TOUCH_ICON 123 tokenizeRelAttribute(attr->value(), m_isStyleSheet, m_alternate, m_isIcon, m_isTouchIcon, m_isPrecomposedTouchIcon, m_isDNSPrefetch); 124 #else 125 tokenizeRelAttribute(attr->value(), m_isStyleSheet, m_alternate, m_isIcon, m_isDNSPrefetch); 126 #endif 127 process(); 128 } else if (attr->name() == hrefAttr) { 129 m_url = document()->completeURL(deprecatedParseURL(attr->value())); 130 process(); 131 } else if (attr->name() == typeAttr) { 132 m_type = attr->value(); 133 process(); 134 } else if (attr->name() == mediaAttr) { 135 m_media = attr->value().string().lower(); 136 process(); 137 } else if (attr->name() == disabledAttr) { 138 setDisabledState(!attr->isNull()); 139 } else if (attr->name() == onbeforeloadAttr) 140 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr)); 141 else { 142 if (attr->name() == titleAttr && m_sheet) 143 m_sheet->setTitle(attr->value()); 144 HTMLElement::parseMappedAttribute(attr); 145 } 146 } 147 148 #ifdef ANDROID_APPLE_TOUCH_ICON 149 void HTMLLinkElement::tokenizeRelAttribute(const AtomicString& rel, bool& styleSheet, bool& alternate, bool& icon, bool& touchIcon, bool& precomposedTouchIcon, bool& dnsPrefetch) 150 #else 151 void HTMLLinkElement::tokenizeRelAttribute(const AtomicString& rel, bool& styleSheet, bool& alternate, bool& icon, bool& dnsPrefetch) 152 #endif 153 { 154 styleSheet = false; 155 icon = false; 156 alternate = false; 157 dnsPrefetch = false; 158 #ifdef ANDROID_APPLE_TOUCH_ICON 159 touchIcon = false; 160 precomposedTouchIcon = false; 161 #endif 162 if (equalIgnoringCase(rel, "stylesheet")) 163 styleSheet = true; 164 else if (equalIgnoringCase(rel, "icon") || equalIgnoringCase(rel, "shortcut icon")) 165 icon = true; 166 #ifdef ANDROID_APPLE_TOUCH_ICON 167 else if (equalIgnoringCase(rel, "apple-touch-icon")) 168 touchIcon = true; 169 else if (equalIgnoringCase(rel, "apple-touch-icon-precomposed")) 170 precomposedTouchIcon = true; 171 #endif 172 else if (equalIgnoringCase(rel, "dns-prefetch")) 173 dnsPrefetch = true; 174 else if (equalIgnoringCase(rel, "alternate stylesheet") || equalIgnoringCase(rel, "stylesheet alternate")) { 175 styleSheet = true; 176 alternate = true; 177 } else { 178 // Tokenize the rel attribute and set bits based on specific keywords that we find. 179 String relString = rel.string(); 180 relString.replace('\n', ' '); 181 Vector<String> list; 182 relString.split(' ', list); 183 Vector<String>::const_iterator end = list.end(); 184 for (Vector<String>::const_iterator it = list.begin(); it != end; ++it) { 185 if (equalIgnoringCase(*it, "stylesheet")) 186 styleSheet = true; 187 else if (equalIgnoringCase(*it, "alternate")) 188 alternate = true; 189 else if (equalIgnoringCase(*it, "icon")) 190 icon = true; 191 } 192 } 193 } 194 195 void HTMLLinkElement::process() 196 { 197 if (!inDocument()) 198 return; 199 200 String type = m_type.lower(); 201 202 // IE extension: location of small icon for locationbar / bookmarks 203 // We'll record this URL per document, even if we later only use it in top level frames 204 if (m_isIcon && m_url.isValid() && !m_url.isEmpty()) 205 document()->setIconURL(m_url.string(), type); 206 207 #ifdef ANDROID_APPLE_TOUCH_ICON 208 if ((m_isTouchIcon || m_isPrecomposedTouchIcon) && m_url.isValid() 209 && !m_url.isEmpty()) 210 document()->frame()->loader()->client() 211 ->dispatchDidReceiveTouchIconURL(m_url.string(), 212 m_isPrecomposedTouchIcon); 213 #endif 214 215 if (m_isDNSPrefetch && m_url.isValid() && !m_url.isEmpty()) 216 prefetchDNS(m_url.host()); 217 218 bool acceptIfTypeContainsTextCSS = document()->page() && document()->page()->settings() && document()->page()->settings()->treatsAnyTextCSSLinkAsStylesheet(); 219 220 // Stylesheet 221 // This was buggy and would incorrectly match <link rel="alternate">, which has a different specified meaning. -dwh 222 if (m_disabledState != 2 && (m_isStyleSheet || (acceptIfTypeContainsTextCSS && type.contains("text/css"))) && document()->frame() && m_url.isValid()) { 223 // also, don't load style sheets for standalone documents 224 225 String charset = getAttribute(charsetAttr); 226 if (charset.isEmpty() && document()->frame()) 227 charset = document()->frame()->loader()->encoding(); 228 229 if (m_cachedSheet) { 230 if (m_loading) 231 document()->removePendingSheet(); 232 m_cachedSheet->removeClient(this); 233 m_cachedSheet = 0; 234 } 235 236 if (!dispatchBeforeLoadEvent(m_url)) 237 return; 238 239 m_loading = true; 240 241 // Add ourselves as a pending sheet, but only if we aren't an alternate 242 // stylesheet. Alternate stylesheets don't hold up render tree construction. 243 if (!isAlternate()) 244 document()->addPendingSheet(); 245 246 m_cachedSheet = document()->docLoader()->requestCSSStyleSheet(m_url, charset); 247 248 if (m_cachedSheet) 249 m_cachedSheet->addClient(this); 250 else { 251 // The request may have been denied if (for example) the stylesheet is local and the document is remote. 252 m_loading = false; 253 if (!isAlternate()) 254 document()->removePendingSheet(); 255 } 256 } else if (m_sheet) { 257 // we no longer contain a stylesheet, e.g. perhaps rel or type was changed 258 m_sheet = 0; 259 document()->updateStyleSelector(); 260 } 261 } 262 263 void HTMLLinkElement::insertedIntoDocument() 264 { 265 HTMLElement::insertedIntoDocument(); 266 document()->addStyleSheetCandidateNode(this, m_createdByParser); 267 process(); 268 } 269 270 void HTMLLinkElement::removedFromDocument() 271 { 272 HTMLElement::removedFromDocument(); 273 274 document()->removeStyleSheetCandidateNode(this); 275 276 // FIXME: It's terrible to do a synchronous update of the style selector just because a <style> or <link> element got removed. 277 if (document()->renderer()) 278 document()->updateStyleSelector(); 279 } 280 281 void HTMLLinkElement::finishParsingChildren() 282 { 283 m_createdByParser = false; 284 HTMLElement::finishParsingChildren(); 285 } 286 287 void HTMLLinkElement::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* sheet) 288 { 289 m_sheet = CSSStyleSheet::create(this, href, baseURL, charset); 290 291 bool strictParsing = !document()->inCompatMode(); 292 bool enforceMIMEType = strictParsing; 293 bool crossOriginCSS = false; 294 bool validMIMEType = false; 295 bool needsSiteSpecificQuirks = document()->page() && document()->page()->settings()->needsSiteSpecificQuirks(); 296 297 // Check to see if we should enforce the MIME type of the CSS resource in strict mode. 298 // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748> 299 if (enforceMIMEType && document()->page() && !document()->page()->settings()->enforceCSSMIMETypeInStrictMode()) 300 enforceMIMEType = false; 301 302 #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) 303 if (enforceMIMEType && needsSiteSpecificQuirks) { 304 // Covers both http and https, with or without "www." 305 if (baseURL.string().contains("mcafee.com/japan/", false)) 306 enforceMIMEType = false; 307 } 308 #endif 309 310 String sheetText = sheet->sheetText(enforceMIMEType, &validMIMEType); 311 m_sheet->parseString(sheetText, strictParsing); 312 313 // If we're loading a stylesheet cross-origin, and the MIME type is not 314 // standard, require the CSS to at least start with a syntactically 315 // valid CSS rule. 316 // This prevents an attacker playing games by injecting CSS strings into 317 // HTML, XML, JSON, etc. etc. 318 if (!document()->securityOrigin()->canRequest(baseURL)) 319 crossOriginCSS = true; 320 321 if (crossOriginCSS && !validMIMEType && !m_sheet->hasSyntacticallyValidCSSHeader()) 322 m_sheet = CSSStyleSheet::create(this, href, baseURL, charset); 323 324 if (strictParsing && needsSiteSpecificQuirks) { 325 // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>. 326 DEFINE_STATIC_LOCAL(const String, slashKHTMLFixesDotCss, ("/KHTMLFixes.css")); 327 DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n")); 328 // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet, 329 // while the other lacks the second trailing newline. 330 if (baseURL.string().endsWith(slashKHTMLFixesDotCss) && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText) 331 && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1) { 332 ASSERT(m_sheet->length() == 1); 333 ExceptionCode ec; 334 m_sheet->deleteRule(0, ec); 335 } 336 } 337 338 m_sheet->setTitle(title()); 339 340 RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media); 341 m_sheet->setMedia(media.get()); 342 343 m_loading = false; 344 m_sheet->checkLoaded(); 345 } 346 347 bool HTMLLinkElement::isLoading() const 348 { 349 if (m_loading) 350 return true; 351 if (!m_sheet) 352 return false; 353 return static_cast<CSSStyleSheet *>(m_sheet.get())->isLoading(); 354 } 355 356 bool HTMLLinkElement::sheetLoaded() 357 { 358 if (!isLoading() && !isDisabled() && !isAlternate()) { 359 document()->removePendingSheet(); 360 return true; 361 } 362 return false; 363 } 364 365 bool HTMLLinkElement::isURLAttribute(Attribute *attr) const 366 { 367 return attr->name() == hrefAttr; 368 } 369 370 bool HTMLLinkElement::disabled() const 371 { 372 return !getAttribute(disabledAttr).isNull(); 373 } 374 375 void HTMLLinkElement::setDisabled(bool disabled) 376 { 377 setAttribute(disabledAttr, disabled ? "" : 0); 378 } 379 380 String HTMLLinkElement::charset() const 381 { 382 return getAttribute(charsetAttr); 383 } 384 385 void HTMLLinkElement::setCharset(const String& value) 386 { 387 setAttribute(charsetAttr, value); 388 } 389 390 KURL HTMLLinkElement::href() const 391 { 392 return document()->completeURL(getAttribute(hrefAttr)); 393 } 394 395 void HTMLLinkElement::setHref(const String& value) 396 { 397 setAttribute(hrefAttr, value); 398 } 399 400 String HTMLLinkElement::hreflang() const 401 { 402 return getAttribute(hreflangAttr); 403 } 404 405 void HTMLLinkElement::setHreflang(const String& value) 406 { 407 setAttribute(hreflangAttr, value); 408 } 409 410 String HTMLLinkElement::media() const 411 { 412 return getAttribute(mediaAttr); 413 } 414 415 void HTMLLinkElement::setMedia(const String& value) 416 { 417 setAttribute(mediaAttr, value); 418 } 419 420 String HTMLLinkElement::rel() const 421 { 422 return getAttribute(relAttr); 423 } 424 425 void HTMLLinkElement::setRel(const String& value) 426 { 427 setAttribute(relAttr, value); 428 } 429 430 String HTMLLinkElement::rev() const 431 { 432 return getAttribute(revAttr); 433 } 434 435 void HTMLLinkElement::setRev(const String& value) 436 { 437 setAttribute(revAttr, value); 438 } 439 440 String HTMLLinkElement::target() const 441 { 442 return getAttribute(targetAttr); 443 } 444 445 void HTMLLinkElement::setTarget(const String& value) 446 { 447 setAttribute(targetAttr, value); 448 } 449 450 String HTMLLinkElement::type() const 451 { 452 return getAttribute(typeAttr); 453 } 454 455 void HTMLLinkElement::setType(const String& value) 456 { 457 setAttribute(typeAttr, value); 458 } 459 460 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const 461 { 462 HTMLElement::addSubresourceAttributeURLs(urls); 463 464 // Favicons are handled by a special case in LegacyWebArchive::create() 465 if (m_isIcon) 466 return; 467 468 if (!m_isStyleSheet) 469 return; 470 471 // Append the URL of this link element. 472 addSubresourceURL(urls, href()); 473 474 // Walk the URLs linked by the linked-to stylesheet. 475 if (StyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet()) 476 styleSheet->addSubresourceStyleURLs(urls); 477 } 478 479 #ifdef ANDROID_INSTRUMENT 480 void* HTMLLinkElement::operator new(size_t size) 481 { 482 return Node::operator new(size); 483 } 484 485 void* HTMLLinkElement::operator new[](size_t size) 486 { 487 return Node::operator new[](size); 488 } 489 490 void HTMLLinkElement::operator delete(void* p, size_t size) 491 { 492 Node::operator delete(p, size); 493 } 494 495 void HTMLLinkElement::operator delete[](void* p, size_t size) 496 { 497 Node::operator delete[](p, size); 498 } 499 #endif 500 501 } 502