1/* 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 4 * (C) 2001 Dirk Mueller (mueller@kde.org) 5 * Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 6 * Copyright (C) 2009 Rob Buis (rwlbuis@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 45namespace WebCore { 46 47using namespace HTMLNames; 48 49HTMLLinkElement::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 67HTMLLinkElement::~HTMLLinkElement() 68{ 69 if (m_cachedSheet) { 70 m_cachedSheet->removeClient(this); 71 if (m_loading && !isDisabled() && !isAlternate()) 72 document()->removePendingSheet(); 73 } 74} 75 76void 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 114StyleSheet* HTMLLinkElement::sheet() const 115{ 116 return m_sheet.get(); 117} 118 119void 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 149void HTMLLinkElement::tokenizeRelAttribute(const AtomicString& rel, bool& styleSheet, bool& alternate, bool& icon, bool& touchIcon, bool& precomposedTouchIcon, bool& dnsPrefetch) 150#else 151void 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 195void 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 263void HTMLLinkElement::insertedIntoDocument() 264{ 265 HTMLElement::insertedIntoDocument(); 266 document()->addStyleSheetCandidateNode(this, m_createdByParser); 267 process(); 268} 269 270void 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 281void HTMLLinkElement::finishParsingChildren() 282{ 283 m_createdByParser = false; 284 HTMLElement::finishParsingChildren(); 285} 286 287void 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 347bool 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 356bool HTMLLinkElement::sheetLoaded() 357{ 358 if (!isLoading() && !isDisabled() && !isAlternate()) { 359 document()->removePendingSheet(); 360 return true; 361 } 362 return false; 363} 364 365bool HTMLLinkElement::isURLAttribute(Attribute *attr) const 366{ 367 return attr->name() == hrefAttr; 368} 369 370bool HTMLLinkElement::disabled() const 371{ 372 return !getAttribute(disabledAttr).isNull(); 373} 374 375void HTMLLinkElement::setDisabled(bool disabled) 376{ 377 setAttribute(disabledAttr, disabled ? "" : 0); 378} 379 380String HTMLLinkElement::charset() const 381{ 382 return getAttribute(charsetAttr); 383} 384 385void HTMLLinkElement::setCharset(const String& value) 386{ 387 setAttribute(charsetAttr, value); 388} 389 390KURL HTMLLinkElement::href() const 391{ 392 return document()->completeURL(getAttribute(hrefAttr)); 393} 394 395void HTMLLinkElement::setHref(const String& value) 396{ 397 setAttribute(hrefAttr, value); 398} 399 400String HTMLLinkElement::hreflang() const 401{ 402 return getAttribute(hreflangAttr); 403} 404 405void HTMLLinkElement::setHreflang(const String& value) 406{ 407 setAttribute(hreflangAttr, value); 408} 409 410String HTMLLinkElement::media() const 411{ 412 return getAttribute(mediaAttr); 413} 414 415void HTMLLinkElement::setMedia(const String& value) 416{ 417 setAttribute(mediaAttr, value); 418} 419 420String HTMLLinkElement::rel() const 421{ 422 return getAttribute(relAttr); 423} 424 425void HTMLLinkElement::setRel(const String& value) 426{ 427 setAttribute(relAttr, value); 428} 429 430String HTMLLinkElement::rev() const 431{ 432 return getAttribute(revAttr); 433} 434 435void HTMLLinkElement::setRev(const String& value) 436{ 437 setAttribute(revAttr, value); 438} 439 440String HTMLLinkElement::target() const 441{ 442 return getAttribute(targetAttr); 443} 444 445void HTMLLinkElement::setTarget(const String& value) 446{ 447 setAttribute(targetAttr, value); 448} 449 450String HTMLLinkElement::type() const 451{ 452 return getAttribute(typeAttr); 453} 454 455void HTMLLinkElement::setType(const String& value) 456{ 457 setAttribute(typeAttr, value); 458} 459 460void 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 480void* HTMLLinkElement::operator new(size_t size) 481{ 482 return Node::operator new(size); 483} 484 485void* HTMLLinkElement::operator new[](size_t size) 486{ 487 return Node::operator new[](size); 488} 489 490void HTMLLinkElement::operator delete(void* p, size_t size) 491{ 492 Node::operator delete(p, size); 493} 494 495void HTMLLinkElement::operator delete[](void* p, size_t size) 496{ 497 Node::operator delete[](p, size); 498} 499#endif 500 501} 502