|
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, 2010 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 "Attribute.h" |
|
28 #include "CSSHelper.h" |
|
29 #include "CachedCSSStyleSheet.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 "MediaList.h" |
|
38 #include "MediaQueryEvaluator.h" |
|
39 #include "Page.h" |
|
40 #include "ResourceHandle.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 inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document* document, bool createdByParser) |
|
50 : HTMLElement(tagName, document) |
|
51 , m_disabledState(Unset) |
|
52 , m_loading(false) |
|
53 , m_createdByParser(createdByParser) |
|
54 , m_shouldProcessAfterAttach(false) |
|
55 { |
|
56 ASSERT(hasTagName(linkTag)); |
|
57 } |
|
58 |
|
59 PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document* document, bool createdByParser) |
|
60 { |
|
61 return adoptRef(new HTMLLinkElement(tagName, document, createdByParser)); |
|
62 } |
|
63 |
|
64 HTMLLinkElement::~HTMLLinkElement() |
|
65 { |
|
66 if (m_cachedSheet) { |
|
67 m_cachedSheet->removeClient(this); |
|
68 if (m_loading && !isDisabled() && !isAlternate()) |
|
69 document()->removePendingSheet(); |
|
70 } |
|
71 } |
|
72 |
|
73 void HTMLLinkElement::setDisabledState(bool _disabled) |
|
74 { |
|
75 DisabledState oldDisabledState = m_disabledState; |
|
76 m_disabledState = _disabled ? Disabled : EnabledViaScript; |
|
77 if (oldDisabledState != m_disabledState) { |
|
78 // If we change the disabled state while the sheet is still loading, then we have to |
|
79 // perform three checks: |
|
80 if (isLoading()) { |
|
81 // Check #1: If the sheet becomes disabled while it was loading, and if it was either |
|
82 // a main sheet or a sheet that was previously enabled via script, then we need |
|
83 // to remove it from the list of pending sheets. |
|
84 if (m_disabledState == Disabled && (!m_relAttribute.m_isAlternate || oldDisabledState == EnabledViaScript)) |
|
85 document()->removePendingSheet(); |
|
86 |
|
87 // Check #2: An alternate sheet becomes enabled while it is still loading. |
|
88 if (m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript) |
|
89 document()->addPendingSheet(); |
|
90 |
|
91 // Check #3: A main sheet becomes enabled while it was still loading and |
|
92 // after it was disabled via script. It takes really terrible code to make this |
|
93 // happen (a double toggle for no reason essentially). This happens on |
|
94 // virtualplastic.net, which manages to do about 12 enable/disables on only 3 |
|
95 // sheets. :) |
|
96 if (!m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript && oldDisabledState == Disabled) |
|
97 document()->addPendingSheet(); |
|
98 |
|
99 // If the sheet is already loading just bail. |
|
100 return; |
|
101 } |
|
102 |
|
103 // Load the sheet, since it's never been loaded before. |
|
104 if (!m_sheet && m_disabledState == EnabledViaScript) |
|
105 process(); |
|
106 else |
|
107 document()->updateStyleSelector(); // Update the style selector. |
|
108 } |
|
109 } |
|
110 |
|
111 StyleSheet* HTMLLinkElement::sheet() const |
|
112 { |
|
113 return m_sheet.get(); |
|
114 } |
|
115 |
|
116 void HTMLLinkElement::parseMappedAttribute(Attribute* attr) |
|
117 { |
|
118 if (attr->name() == relAttr) { |
|
119 tokenizeRelAttribute(attr->value(), m_relAttribute); |
|
120 process(); |
|
121 } else if (attr->name() == hrefAttr) { |
|
122 m_url = document()->completeURL(deprecatedParseURL(attr->value())); |
|
123 process(); |
|
124 } else if (attr->name() == typeAttr) { |
|
125 m_type = attr->value(); |
|
126 process(); |
|
127 } else if (attr->name() == mediaAttr) { |
|
128 m_media = attr->value().string().lower(); |
|
129 process(); |
|
130 } else if (attr->name() == disabledAttr) |
|
131 setDisabledState(!attr->isNull()); |
|
132 else if (attr->name() == onbeforeloadAttr) |
|
133 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr)); |
|
134 else { |
|
135 if (attr->name() == titleAttr && m_sheet) |
|
136 m_sheet->setTitle(attr->value()); |
|
137 HTMLElement::parseMappedAttribute(attr); |
|
138 } |
|
139 } |
|
140 |
|
141 void HTMLLinkElement::tokenizeRelAttribute(const AtomicString& rel, RelAttribute& relAttribute) |
|
142 { |
|
143 relAttribute.m_isStyleSheet = false; |
|
144 relAttribute.m_isIcon = false; |
|
145 relAttribute.m_isAlternate = false; |
|
146 relAttribute.m_isDNSPrefetch = false; |
|
147 #if ENABLE(LINK_PREFETCH) |
|
148 relAttribute.m_isLinkPrefetch = false; |
|
149 #endif |
|
150 if (equalIgnoringCase(rel, "stylesheet")) |
|
151 relAttribute.m_isStyleSheet = true; |
|
152 else if (equalIgnoringCase(rel, "icon") || equalIgnoringCase(rel, "shortcut icon")) |
|
153 relAttribute.m_isIcon = true; |
|
154 else if (equalIgnoringCase(rel, "dns-prefetch")) |
|
155 relAttribute.m_isDNSPrefetch = true; |
|
156 #if ENABLE(LINK_PREFETCH) |
|
157 else if (equalIgnoringCase(rel, "prefetch")) |
|
158 relAttribute.m_isLinkPrefetch = true; |
|
159 #endif |
|
160 else if (equalIgnoringCase(rel, "alternate stylesheet") || equalIgnoringCase(rel, "stylesheet alternate")) { |
|
161 relAttribute.m_isStyleSheet = true; |
|
162 relAttribute.m_isAlternate = true; |
|
163 } else { |
|
164 // Tokenize the rel attribute and set bits based on specific keywords that we find. |
|
165 String relString = rel.string(); |
|
166 relString.replace('\n', ' '); |
|
167 Vector<String> list; |
|
168 relString.split(' ', list); |
|
169 Vector<String>::const_iterator end = list.end(); |
|
170 for (Vector<String>::const_iterator it = list.begin(); it != end; ++it) { |
|
171 if (equalIgnoringCase(*it, "stylesheet")) |
|
172 relAttribute.m_isStyleSheet = true; |
|
173 else if (equalIgnoringCase(*it, "alternate")) |
|
174 relAttribute.m_isAlternate = true; |
|
175 else if (equalIgnoringCase(*it, "icon")) |
|
176 relAttribute.m_isIcon = true; |
|
177 } |
|
178 } |
|
179 } |
|
180 |
|
181 void HTMLLinkElement::process() |
|
182 { |
|
183 if (!inDocument()) |
|
184 return; |
|
185 |
|
186 String type = m_type.lower(); |
|
187 |
|
188 // IE extension: location of small icon for locationbar / bookmarks |
|
189 // We'll record this URL per document, even if we later only use it in top level frames |
|
190 if (m_relAttribute.m_isIcon && m_url.isValid() && !m_url.isEmpty()) |
|
191 document()->setIconURL(m_url.string(), type); |
|
192 |
|
193 if (m_relAttribute.m_isDNSPrefetch && document()->isDNSPrefetchEnabled() && m_url.isValid() && !m_url.isEmpty()) |
|
194 ResourceHandle::prepareForURL(m_url); |
|
195 |
|
196 #if ENABLE(LINK_PREFETCH) |
|
197 if (m_relAttribute.m_isLinkPrefetch && m_url.isValid()) |
|
198 document()->docLoader()->requestLinkPrefetch(m_url); |
|
199 #endif |
|
200 |
|
201 bool acceptIfTypeContainsTextCSS = document()->page() && document()->page()->settings() && document()->page()->settings()->treatsAnyTextCSSLinkAsStylesheet(); |
|
202 |
|
203 // Stylesheet |
|
204 // This was buggy and would incorrectly match <link rel="alternate">, which has a different specified meaning. -dwh |
|
205 if (m_disabledState != Disabled && (m_relAttribute.m_isStyleSheet || (acceptIfTypeContainsTextCSS && type.contains("text/css"))) && document()->frame() && m_url.isValid()) { |
|
206 // also, don't load style sheets for standalone documents |
|
207 |
|
208 String charset = getAttribute(charsetAttr); |
|
209 if (charset.isEmpty() && document()->frame()) |
|
210 charset = document()->frame()->loader()->writer()->encoding(); |
|
211 |
|
212 if (m_cachedSheet) { |
|
213 if (m_loading) |
|
214 document()->removePendingSheet(); |
|
215 m_cachedSheet->removeClient(this); |
|
216 m_cachedSheet = 0; |
|
217 } |
|
218 |
|
219 if (!dispatchBeforeLoadEvent(m_url)) |
|
220 return; |
|
221 |
|
222 m_loading = true; |
|
223 |
|
224 // Add ourselves as a pending sheet, but only if we aren't an alternate |
|
225 // stylesheet. Alternate stylesheets don't hold up render tree construction. |
|
226 if (!isAlternate()) |
|
227 document()->addPendingSheet(); |
|
228 |
|
229 m_cachedSheet = document()->docLoader()->requestCSSStyleSheet(m_url, charset); |
|
230 |
|
231 if (m_cachedSheet) |
|
232 m_cachedSheet->addClient(this); |
|
233 else { |
|
234 // The request may have been denied if (for example) the stylesheet is local and the document is remote. |
|
235 m_loading = false; |
|
236 if (!isAlternate()) |
|
237 document()->removePendingSheet(); |
|
238 } |
|
239 } else if (m_sheet) { |
|
240 // we no longer contain a stylesheet, e.g. perhaps rel or type was changed |
|
241 m_sheet = 0; |
|
242 document()->updateStyleSelector(); |
|
243 } |
|
244 } |
|
245 |
|
246 void HTMLLinkElement::processCallback(Node* node) |
|
247 { |
|
248 ASSERT_ARG(node, node && node->hasTagName(linkTag)); |
|
249 static_cast<HTMLLinkElement*>(node)->process(); |
|
250 } |
|
251 |
|
252 void HTMLLinkElement::insertedIntoDocument() |
|
253 { |
|
254 HTMLElement::insertedIntoDocument(); |
|
255 document()->addStyleSheetCandidateNode(this, m_createdByParser); |
|
256 |
|
257 // Since processing a stylesheet link causes a beforeload event |
|
258 // to fire, it is possible for JavaScript to remove the element in the midst |
|
259 // of it being inserted into the DOM, which can lead to assertion failures |
|
260 // and crashes. Avoid this by postponing the beforeload/load until after |
|
261 // attach. |
|
262 m_shouldProcessAfterAttach = true; |
|
263 } |
|
264 |
|
265 void HTMLLinkElement::removedFromDocument() |
|
266 { |
|
267 HTMLElement::removedFromDocument(); |
|
268 |
|
269 document()->removeStyleSheetCandidateNode(this); |
|
270 |
|
271 // FIXME: It's terrible to do a synchronous update of the style selector just because a <style> or <link> element got removed. |
|
272 if (document()->renderer()) |
|
273 document()->updateStyleSelector(); |
|
274 |
|
275 m_shouldProcessAfterAttach = false; |
|
276 } |
|
277 |
|
278 void HTMLLinkElement::attach() |
|
279 { |
|
280 if (m_shouldProcessAfterAttach) { |
|
281 m_shouldProcessAfterAttach = false; |
|
282 queuePostAttachCallback(&HTMLLinkElement::processCallback, this); |
|
283 } |
|
284 |
|
285 HTMLElement::attach(); |
|
286 } |
|
287 |
|
288 void HTMLLinkElement::finishParsingChildren() |
|
289 { |
|
290 m_createdByParser = false; |
|
291 HTMLElement::finishParsingChildren(); |
|
292 } |
|
293 |
|
294 void HTMLLinkElement::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* sheet) |
|
295 { |
|
296 m_sheet = CSSStyleSheet::create(this, href, baseURL, charset); |
|
297 |
|
298 bool strictParsing = !document()->inCompatMode(); |
|
299 bool enforceMIMEType = strictParsing; |
|
300 bool crossOriginCSS = false; |
|
301 bool validMIMEType = false; |
|
302 bool needsSiteSpecificQuirks = document()->page() && document()->page()->settings()->needsSiteSpecificQuirks(); |
|
303 |
|
304 // Check to see if we should enforce the MIME type of the CSS resource in strict mode. |
|
305 // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748> |
|
306 if (enforceMIMEType && document()->page() && !document()->page()->settings()->enforceCSSMIMETypeInStrictMode()) |
|
307 enforceMIMEType = false; |
|
308 |
|
309 #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) |
|
310 if (enforceMIMEType && needsSiteSpecificQuirks) { |
|
311 // Covers both http and https, with or without "www." |
|
312 if (baseURL.string().contains("mcafee.com/japan/", false)) |
|
313 enforceMIMEType = false; |
|
314 } |
|
315 #endif |
|
316 |
|
317 String sheetText = sheet->sheetText(enforceMIMEType, &validMIMEType); |
|
318 m_sheet->parseString(sheetText, strictParsing); |
|
319 |
|
320 // If we're loading a stylesheet cross-origin, and the MIME type is not |
|
321 // standard, require the CSS to at least start with a syntactically |
|
322 // valid CSS rule. |
|
323 // This prevents an attacker playing games by injecting CSS strings into |
|
324 // HTML, XML, JSON, etc. etc. |
|
325 if (!document()->securityOrigin()->canRequest(baseURL)) |
|
326 crossOriginCSS = true; |
|
327 |
|
328 if (crossOriginCSS && !validMIMEType && !m_sheet->hasSyntacticallyValidCSSHeader()) |
|
329 m_sheet = CSSStyleSheet::create(this, href, baseURL, charset); |
|
330 |
|
331 if (strictParsing && needsSiteSpecificQuirks) { |
|
332 // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>. |
|
333 DEFINE_STATIC_LOCAL(const String, slashKHTMLFixesDotCss, ("/KHTMLFixes.css")); |
|
334 DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n")); |
|
335 // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet, |
|
336 // while the other lacks the second trailing newline. |
|
337 if (baseURL.string().endsWith(slashKHTMLFixesDotCss) && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText) |
|
338 && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1) { |
|
339 ASSERT(m_sheet->length() == 1); |
|
340 ExceptionCode ec; |
|
341 m_sheet->deleteRule(0, ec); |
|
342 } |
|
343 } |
|
344 |
|
345 m_sheet->setTitle(title()); |
|
346 |
|
347 RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media); |
|
348 m_sheet->setMedia(media.get()); |
|
349 |
|
350 m_loading = false; |
|
351 m_sheet->checkLoaded(); |
|
352 } |
|
353 |
|
354 bool HTMLLinkElement::isLoading() const |
|
355 { |
|
356 if (m_loading) |
|
357 return true; |
|
358 if (!m_sheet) |
|
359 return false; |
|
360 return static_cast<CSSStyleSheet *>(m_sheet.get())->isLoading(); |
|
361 } |
|
362 |
|
363 bool HTMLLinkElement::sheetLoaded() |
|
364 { |
|
365 if (!isLoading() && !isDisabled() && !isAlternate()) { |
|
366 document()->removePendingSheet(); |
|
367 return true; |
|
368 } |
|
369 return false; |
|
370 } |
|
371 |
|
372 bool HTMLLinkElement::isURLAttribute(Attribute *attr) const |
|
373 { |
|
374 return attr->name() == hrefAttr; |
|
375 } |
|
376 |
|
377 KURL HTMLLinkElement::href() const |
|
378 { |
|
379 return document()->completeURL(getAttribute(hrefAttr)); |
|
380 } |
|
381 |
|
382 String HTMLLinkElement::rel() const |
|
383 { |
|
384 return getAttribute(relAttr); |
|
385 } |
|
386 |
|
387 String HTMLLinkElement::target() const |
|
388 { |
|
389 return getAttribute(targetAttr); |
|
390 } |
|
391 |
|
392 String HTMLLinkElement::type() const |
|
393 { |
|
394 return getAttribute(typeAttr); |
|
395 } |
|
396 |
|
397 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const |
|
398 { |
|
399 HTMLElement::addSubresourceAttributeURLs(urls); |
|
400 |
|
401 // Favicons are handled by a special case in LegacyWebArchive::create() |
|
402 if (m_relAttribute.m_isIcon) |
|
403 return; |
|
404 |
|
405 if (!m_relAttribute.m_isStyleSheet) |
|
406 return; |
|
407 |
|
408 // Append the URL of this link element. |
|
409 addSubresourceURL(urls, href()); |
|
410 |
|
411 // Walk the URLs linked by the linked-to stylesheet. |
|
412 if (StyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet()) |
|
413 styleSheet->addSubresourceStyleURLs(urls); |
|
414 } |
|
415 |
|
416 } |