Source/WebCore: Crash in Document::recalcStyleSelector
[WebKit-https.git] / Source / WebCore / html / HTMLLinkElement.cpp
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  * Copyright (C) 2011 Google Inc. 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
25 #include "config.h"
26 #include "HTMLLinkElement.h"
27
28 #include "Attribute.h"
29 #include "CachedCSSStyleSheet.h"
30 #include "CachedResource.h"
31 #include "CachedResourceLoader.h"
32 #include "CSSStyleSelector.h"
33 #include "Document.h"
34 #include "Frame.h"
35 #include "FrameLoader.h"
36 #include "FrameLoaderClient.h"
37 #include "FrameTree.h"
38 #include "FrameView.h"
39 #include "HTMLNames.h"
40 #include "HTMLParserIdioms.h"
41 #include "MediaList.h"
42 #include "MediaQueryEvaluator.h"
43 #include "Page.h"
44 #include "ResourceHandle.h"
45 #include "ScriptEventListener.h"
46 #include "SecurityOrigin.h"
47 #include "Settings.h"
48 #include <wtf/StdLibExtras.h>
49
50 namespace WebCore {
51
52 using namespace HTMLNames;
53
54 inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document* document, bool createdByParser)
55     : HTMLElement(tagName, document)
56     , m_linkLoader(this)
57     , m_sizes(DOMSettableTokenList::create())
58     , m_loading(false)
59     , m_isEnabledViaScript(false)
60     , m_createdByParser(createdByParser)
61     , m_isInShadowTree(false)
62     , m_pendingSheetType(None)
63 {
64     ASSERT(hasTagName(linkTag));
65 }
66
67 PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document* document, bool createdByParser)
68 {
69     return adoptRef(new HTMLLinkElement(tagName, document, createdByParser));
70 }
71
72 HTMLLinkElement::~HTMLLinkElement()
73 {
74     if (m_sheet)
75         m_sheet->clearOwnerNode();
76
77     if (m_cachedSheet) {
78         m_cachedSheet->removeClient(this);
79         removePendingSheet();
80     }
81
82     if (inDocument())
83         document()->removeStyleSheetCandidateNode(this);
84 }
85
86 void HTMLLinkElement::setDisabled(bool disabled)
87 {
88     if (!m_sheet)
89         return;
90
91     bool wasDisabled = m_sheet->disabled();
92     if (wasDisabled == disabled)
93         return;
94
95     m_sheet->setDisabled(disabled);
96     m_isEnabledViaScript = !disabled;
97
98     // If we change the disabled state while the sheet is still loading, then we have to
99     // perform three checks:
100     if (isLoading()) {
101         // Check #1: The sheet becomes disabled while loading.
102         if (disabled)
103             removePendingSheet();
104
105         // Check #2: An alternate sheet becomes enabled while it is still loading.
106         if (m_relAttribute.m_isAlternate && !disabled)
107             addPendingSheet(Blocking);
108
109         // Check #3: A main sheet becomes enabled while it was still loading and
110         // after it was disabled via script. It takes really terrible code to make this
111         // happen (a double toggle for no reason essentially). This happens on
112         // virtualplastic.net, which manages to do about 12 enable/disables on only 3
113         // sheets. :)
114         if (!m_relAttribute.m_isAlternate && !disabled && wasDisabled)
115             addPendingSheet(Blocking);
116
117         // If the sheet is already loading just bail.
118         return;
119     }
120
121     if (!disabled)
122         process();
123 }
124
125 StyleSheet* HTMLLinkElement::sheet() const
126 {
127     return m_sheet.get();
128 }
129
130 void HTMLLinkElement::parseMappedAttribute(Attribute* attr)
131 {
132     if (attr->name() == relAttr) {
133         m_relAttribute = LinkRelAttribute(attr->value());
134         process();
135     } else if (attr->name() == hrefAttr) {
136         m_url = document()->completeURL(stripLeadingAndTrailingHTMLSpaces(attr->value()));
137         process();
138     } else if (attr->name() == typeAttr) {
139         m_type = attr->value();
140         process();
141     } else if (attr->name() == sizesAttr) {
142         setSizes(attr->value());
143         process();
144     } else if (attr->name() == mediaAttr) {
145         m_media = attr->value().string().lower();
146         process();
147     } else if (attr->name() == onbeforeloadAttr)
148         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
149 #if ENABLE(LINK_PREFETCH)
150     else if (attr->name() == onloadAttr)
151         setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
152     else if (attr->name() == onerrorAttr)
153         setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attr));
154 #endif
155     else {
156         if (attr->name() == titleAttr && m_sheet)
157             m_sheet->setTitle(attr->value());
158         HTMLElement::parseMappedAttribute(attr);
159     }
160 }
161
162 bool HTMLLinkElement::shouldLoadLink()
163 {
164     RefPtr<Document> originalDocument = document();
165     if (!dispatchBeforeLoadEvent(m_url))
166         return false;
167     // A beforeload handler might have removed us from the document or changed the document.
168     if (!inDocument() || document() != originalDocument)
169         return false;
170     return true;
171 }
172
173 void HTMLLinkElement::process()
174 {
175     if (!inDocument() || m_isInShadowTree) {
176         ASSERT(!m_sheet);
177         return;
178     }
179
180     String type = m_type.lower();
181
182     if (!m_linkLoader.loadLink(m_relAttribute, type, m_url, document()))
183         return;
184
185     bool acceptIfTypeContainsTextCSS = document()->page() && document()->page()->settings() && document()->page()->settings()->treatsAnyTextCSSLinkAsStylesheet();
186
187     if (!disabled() && (m_relAttribute.m_isStyleSheet || (acceptIfTypeContainsTextCSS && type.contains("text/css")))
188         && document()->frame() && m_url.isValid()) {
189         
190         String charset = getAttribute(charsetAttr);
191         if (charset.isEmpty() && document()->frame())
192             charset = document()->charset();
193         
194         if (m_cachedSheet) {
195             removePendingSheet();
196             m_cachedSheet->removeClient(this);
197             m_cachedSheet = 0;
198         }
199
200         if (!shouldLoadLink())
201             return;
202
203         m_loading = true;
204
205         bool mediaQueryMatches = true;
206         if (!m_media.isEmpty()) {
207             RefPtr<RenderStyle> documentStyle = CSSStyleSelector::styleForDocument(document());
208             RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media);
209             MediaQueryEvaluator evaluator(document()->frame()->view()->mediaType(), document()->frame(), documentStyle.get());
210             mediaQueryMatches = evaluator.eval(media.get());
211         }
212
213         // Don't hold up render tree construction and script execution on stylesheets
214         // that are not needed for the rendering at the moment.
215         bool blocking = mediaQueryMatches && !isAlternate();
216         addPendingSheet(blocking ? Blocking : NonBlocking);
217
218         // Load stylesheets that are not needed for the rendering immediately with low priority.
219         ResourceLoadPriority priority = blocking ? ResourceLoadPriorityUnresolved : ResourceLoadPriorityVeryLow;
220         ResourceRequest request(document()->completeURL(m_url));
221         m_cachedSheet = document()->cachedResourceLoader()->requestCSSStyleSheet(request, charset, priority);
222         
223         if (m_cachedSheet)
224             m_cachedSheet->addClient(this);
225         else {
226             // The request may have been denied if (for example) the stylesheet is local and the document is remote.
227             m_loading = false;
228             removePendingSheet();
229         }
230     } else if (m_sheet) {
231         // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
232         m_sheet = 0;
233         document()->styleSelectorChanged(DeferRecalcStyle);
234     }
235 }
236
237 void HTMLLinkElement::insertedIntoDocument()
238 {
239     HTMLElement::insertedIntoDocument();
240
241     m_isInShadowTree = isInShadowTree();
242     if (m_isInShadowTree)
243         return;
244
245     document()->addStyleSheetCandidateNode(this, m_createdByParser);
246
247     process();
248 }
249
250 void HTMLLinkElement::removedFromDocument()
251 {
252     HTMLElement::removedFromDocument();
253
254     if (m_isInShadowTree) {
255         ASSERT(!m_sheet);
256         return;
257     }
258     document()->removeStyleSheetCandidateNode(this);
259
260     if (m_sheet) {
261         ASSERT(m_sheet->ownerNode() == this);
262         m_sheet->clearOwnerNode();
263         m_sheet = 0;
264     }
265
266     if (document()->renderer())
267         document()->styleSelectorChanged(DeferRecalcStyle);
268 }
269
270 void HTMLLinkElement::finishParsingChildren()
271 {
272     m_createdByParser = false;
273     HTMLElement::finishParsingChildren();
274 }
275
276 void HTMLLinkElement::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* sheet)
277 {
278     if (!inDocument()) {
279         ASSERT(!m_sheet);
280         return;
281     }
282
283     m_sheet = CSSStyleSheet::create(this, href, baseURL, charset);
284
285     bool strictParsing = !document()->inQuirksMode();
286     bool enforceMIMEType = strictParsing;
287     bool crossOriginCSS = false;
288     bool validMIMEType = false;
289     bool needsSiteSpecificQuirks = document()->page() && document()->page()->settings()->needsSiteSpecificQuirks();
290
291     // Check to see if we should enforce the MIME type of the CSS resource in strict mode.
292     // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748>
293     if (enforceMIMEType && document()->page() && !document()->page()->settings()->enforceCSSMIMETypeInNoQuirksMode())
294         enforceMIMEType = false;
295
296 #ifdef BUILDING_ON_LEOPARD
297     if (enforceMIMEType && needsSiteSpecificQuirks) {
298         // Covers both http and https, with or without "www."
299         if (baseURL.string().contains("mcafee.com/japan/", false))
300             enforceMIMEType = false;
301     }
302 #endif
303
304     String sheetText = sheet->sheetText(enforceMIMEType, &validMIMEType);
305     m_sheet->parseString(sheetText, strictParsing);
306
307     // If we're loading a stylesheet cross-origin, and the MIME type is not
308     // standard, require the CSS to at least start with a syntactically
309     // valid CSS rule.
310     // This prevents an attacker playing games by injecting CSS strings into
311     // HTML, XML, JSON, etc. etc.
312     if (!document()->securityOrigin()->canRequest(baseURL))
313         crossOriginCSS = true;
314
315     if (crossOriginCSS && !validMIMEType && !m_sheet->hasSyntacticallyValidCSSHeader())
316         m_sheet = CSSStyleSheet::create(this, href, baseURL, charset);
317
318     if (strictParsing && needsSiteSpecificQuirks) {
319         // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>.
320         DEFINE_STATIC_LOCAL(const String, slashKHTMLFixesDotCss, ("/KHTMLFixes.css"));
321         DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n"));
322         // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet,
323         // while the other lacks the second trailing newline.
324         if (baseURL.string().endsWith(slashKHTMLFixesDotCss) && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText)
325                 && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1) {
326             ASSERT(m_sheet->length() == 1);
327             ExceptionCode ec;
328             m_sheet->deleteRule(0, ec);
329         }
330     }
331
332     m_sheet->setTitle(title());
333
334     RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media);
335     m_sheet->setMedia(media.get());
336
337     m_loading = false;
338     m_sheet->checkLoaded();
339 }
340
341 bool HTMLLinkElement::isLoading() const
342 {
343     if (m_loading)
344         return true;
345     if (!m_sheet)
346         return false;
347     return static_cast<CSSStyleSheet *>(m_sheet.get())->isLoading();
348 }
349
350 void HTMLLinkElement::linkLoaded()
351 {
352     dispatchEvent(Event::create(eventNames().loadEvent, false, false));
353 }
354
355 void HTMLLinkElement::linkLoadingErrored()
356 {
357     dispatchEvent(Event::create(eventNames().errorEvent, false, false));
358 }
359
360 bool HTMLLinkElement::sheetLoaded()
361 {
362     if (!isLoading()) {
363         removePendingSheet();
364         return true;
365     }
366     return false;
367 }
368
369 void HTMLLinkElement::startLoadingDynamicSheet()
370 {
371     // We don't support multiple blocking sheets.
372     ASSERT(m_pendingSheetType < Blocking);
373     addPendingSheet(Blocking);
374 }
375
376 bool HTMLLinkElement::isURLAttribute(Attribute *attr) const
377 {
378     return attr->name() == hrefAttr;
379 }
380
381 KURL HTMLLinkElement::href() const
382 {
383     return document()->completeURL(getAttribute(hrefAttr));
384 }
385
386 String HTMLLinkElement::rel() const
387 {
388     return getAttribute(relAttr);
389 }
390
391 String HTMLLinkElement::target() const
392 {
393     return getAttribute(targetAttr);
394 }
395
396 String HTMLLinkElement::type() const
397 {
398     return getAttribute(typeAttr);
399 }
400
401 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
402 {
403     HTMLElement::addSubresourceAttributeURLs(urls);
404
405     // Favicons are handled by a special case in LegacyWebArchive::create()
406     if (m_relAttribute.m_iconType != InvalidIcon)
407         return;
408
409     if (!m_relAttribute.m_isStyleSheet)
410         return;
411     
412     // Append the URL of this link element.
413     addSubresourceURL(urls, href());
414     
415     // Walk the URLs linked by the linked-to stylesheet.
416     if (StyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet())
417         styleSheet->addSubresourceStyleURLs(urls);
418 }
419
420 void HTMLLinkElement::addPendingSheet(PendingSheetType type)
421 {
422     if (type <= m_pendingSheetType)
423         return;
424     m_pendingSheetType = type;
425
426     if (m_pendingSheetType == NonBlocking)
427         return;
428     document()->addPendingSheet();
429 }
430
431 void HTMLLinkElement::removePendingSheet()
432 {
433     PendingSheetType type = m_pendingSheetType;
434     m_pendingSheetType = None;
435
436     if (type == None)
437         return;
438     if (type == NonBlocking) {
439         // Document::removePendingSheet() triggers the style selector recalc for blocking sheets.
440         document()->styleSelectorChanged(RecalcStyleImmediately);
441         return;
442     }
443     document()->removePendingSheet();
444 }
445
446 bool HTMLLinkElement::disabled() const
447 {
448     return m_sheet && m_sheet->disabled();
449 }
450
451 DOMSettableTokenList* HTMLLinkElement::sizes() const
452 {
453     return m_sizes.get();
454 }
455
456 void HTMLLinkElement::setSizes(const String& value)
457 {
458     m_sizes->setValue(value);
459 }
460
461 } // namespace WebCore