/WebCore/html/HTMLLinkElement.cpp

http://github.com/CyanogenMod/android_external_webkit · C++ · 502 lines · 366 code · 75 blank · 61 comment · 125 complexity · 8422d2a22457bddf171fd0a251b57806 MD5 · raw file

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