• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "Settings.h"
42 
43 namespace WebCore {
44 
45 using namespace HTMLNames;
46 
HTMLLinkElement(const QualifiedName & qName,Document * doc,bool createdByParser)47 HTMLLinkElement::HTMLLinkElement(const QualifiedName& qName, Document *doc, bool createdByParser)
48     : HTMLElement(qName, doc)
49     , m_cachedSheet(0)
50     , m_disabledState(0)
51     , m_loading(false)
52     , m_alternate(false)
53     , m_isStyleSheet(false)
54     , m_isIcon(false)
55 #ifdef ANDROID_APPLE_TOUCH_ICON
56     , m_isTouchIcon(false)
57     , m_isPrecomposedTouchIcon(false)
58 #endif
59     , m_isDNSPrefetch(false)
60     , m_createdByParser(createdByParser)
61 {
62     ASSERT(hasTagName(linkTag));
63 }
64 
~HTMLLinkElement()65 HTMLLinkElement::~HTMLLinkElement()
66 {
67     if (m_cachedSheet) {
68         m_cachedSheet->removeClient(this);
69         if (m_loading && !isDisabled() && !isAlternate())
70             document()->removePendingSheet();
71     }
72 }
73 
setDisabledState(bool _disabled)74 void HTMLLinkElement::setDisabledState(bool _disabled)
75 {
76     int oldDisabledState = m_disabledState;
77     m_disabledState = _disabled ? 2 : 1;
78     if (oldDisabledState != m_disabledState) {
79         // If we change the disabled state while the sheet is still loading, then we have to
80         // perform three checks:
81         if (isLoading()) {
82             // Check #1: If the sheet becomes disabled while it was loading, and if it was either
83             // a main sheet or a sheet that was previously enabled via script, then we need
84             // to remove it from the list of pending sheets.
85             if (m_disabledState == 2 && (!m_alternate || oldDisabledState == 1))
86                 document()->removePendingSheet();
87 
88             // Check #2: An alternate sheet becomes enabled while it is still loading.
89             if (m_alternate && m_disabledState == 1)
90                 document()->addPendingSheet();
91 
92             // Check #3: A main sheet becomes enabled while it was still loading and
93             // after it was disabled via script.  It takes really terrible code to make this
94             // happen (a double toggle for no reason essentially).  This happens on
95             // virtualplastic.net, which manages to do about 12 enable/disables on only 3
96             // sheets. :)
97             if (!m_alternate && m_disabledState == 1 && oldDisabledState == 2)
98                 document()->addPendingSheet();
99 
100             // If the sheet is already loading just bail.
101             return;
102         }
103 
104         // Load the sheet, since it's never been loaded before.
105         if (!m_sheet && m_disabledState == 1)
106             process();
107         else
108             document()->updateStyleSelector(); // Update the style selector.
109     }
110 }
111 
sheet() const112 StyleSheet* HTMLLinkElement::sheet() const
113 {
114     return m_sheet.get();
115 }
116 
parseMappedAttribute(MappedAttribute * attr)117 void HTMLLinkElement::parseMappedAttribute(MappedAttribute *attr)
118 {
119     if (attr->name() == relAttr) {
120 #ifdef ANDROID_APPLE_TOUCH_ICON
121         tokenizeRelAttribute(attr->value(), m_isStyleSheet, m_alternate, m_isIcon, m_isTouchIcon, m_isPrecomposedTouchIcon, m_isDNSPrefetch);
122 #else
123         tokenizeRelAttribute(attr->value(), m_isStyleSheet, m_alternate, m_isIcon, m_isDNSPrefetch);
124 #endif
125         process();
126     } else if (attr->name() == hrefAttr) {
127         m_url = document()->completeURL(deprecatedParseURL(attr->value()));
128         process();
129     } else if (attr->name() == typeAttr) {
130         m_type = attr->value();
131         process();
132     } else if (attr->name() == mediaAttr) {
133         m_media = attr->value().string().lower();
134         process();
135     } else if (attr->name() == disabledAttr) {
136         setDisabledState(!attr->isNull());
137     } else {
138         if (attr->name() == titleAttr && m_sheet)
139             m_sheet->setTitle(attr->value());
140         HTMLElement::parseMappedAttribute(attr);
141     }
142 }
143 
144 #ifdef ANDROID_APPLE_TOUCH_ICON
tokenizeRelAttribute(const AtomicString & rel,bool & styleSheet,bool & alternate,bool & icon,bool & touchIcon,bool & precomposedTouchIcon,bool & dnsPrefetch)145 void HTMLLinkElement::tokenizeRelAttribute(const AtomicString& rel, bool& styleSheet, bool& alternate, bool& icon, bool& touchIcon, bool& precomposedTouchIcon, bool& dnsPrefetch)
146 #else
147 void HTMLLinkElement::tokenizeRelAttribute(const AtomicString& rel, bool& styleSheet, bool& alternate, bool& icon, bool& dnsPrefetch)
148 #endif
149 {
150     styleSheet = false;
151     icon = false;
152     alternate = false;
153     dnsPrefetch = false;
154 #ifdef ANDROID_APPLE_TOUCH_ICON
155     touchIcon = false;
156     precomposedTouchIcon = false;
157 #endif
158     if (equalIgnoringCase(rel, "stylesheet"))
159         styleSheet = true;
160     else if (equalIgnoringCase(rel, "icon") || equalIgnoringCase(rel, "shortcut icon"))
161         icon = true;
162 #ifdef ANDROID_APPLE_TOUCH_ICON
163     else if (equalIgnoringCase(rel, "apple-touch-icon"))
164         touchIcon = true;
165     else if (equalIgnoringCase(rel, "apple-touch-icon-precomposed"))
166         precomposedTouchIcon = true;
167 #endif
168     else if (equalIgnoringCase(rel, "dns-prefetch"))
169         dnsPrefetch = true;
170     else if (equalIgnoringCase(rel, "alternate stylesheet") || equalIgnoringCase(rel, "stylesheet alternate")) {
171         styleSheet = true;
172         alternate = true;
173     } else {
174         // Tokenize the rel attribute and set bits based on specific keywords that we find.
175         String relString = rel.string();
176         relString.replace('\n', ' ');
177         Vector<String> list;
178         relString.split(' ', list);
179         Vector<String>::const_iterator end = list.end();
180         for (Vector<String>::const_iterator it = list.begin(); it != end; ++it) {
181             if (equalIgnoringCase(*it, "stylesheet"))
182                 styleSheet = true;
183             else if (equalIgnoringCase(*it, "alternate"))
184                 alternate = true;
185             else if (equalIgnoringCase(*it, "icon"))
186                 icon = true;
187         }
188     }
189 }
190 
process()191 void HTMLLinkElement::process()
192 {
193     if (!inDocument())
194         return;
195 
196     String type = m_type.lower();
197 
198     // IE extension: location of small icon for locationbar / bookmarks
199     // We'll record this URL per document, even if we later only use it in top level frames
200     if (m_isIcon && m_url.isValid() && !m_url.isEmpty())
201         document()->setIconURL(m_url.string(), type);
202 
203 #ifdef ANDROID_APPLE_TOUCH_ICON
204     if ((m_isTouchIcon || m_isPrecomposedTouchIcon) && m_url.isValid()
205             && !m_url.isEmpty())
206         document()->frame()->loader()->client()
207                 ->dispatchDidReceiveTouchIconURL(m_url.string(),
208                         m_isPrecomposedTouchIcon);
209 #endif
210 
211     if (m_isDNSPrefetch && m_url.isValid() && !m_url.isEmpty())
212         prefetchDNS(m_url.host());
213 
214     bool acceptIfTypeContainsTextCSS = document()->page() && document()->page()->settings() && document()->page()->settings()->treatsAnyTextCSSLinkAsStylesheet();
215 
216     // Stylesheet
217     // This was buggy and would incorrectly match <link rel="alternate">, which has a different specified meaning. -dwh
218     if (m_disabledState != 2 && (m_isStyleSheet || acceptIfTypeContainsTextCSS && type.contains("text/css")) && document()->frame() && m_url.isValid()) {
219         // also, don't load style sheets for standalone documents
220         // Add ourselves as a pending sheet, but only if we aren't an alternate
221         // stylesheet.  Alternate stylesheets don't hold up render tree construction.
222         if (!isAlternate())
223             document()->addPendingSheet();
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         }
234         m_loading = true;
235         m_cachedSheet = document()->docLoader()->requestCSSStyleSheet(m_url, charset);
236         if (m_cachedSheet)
237             m_cachedSheet->addClient(this);
238         else if (!isAlternate()) { // The request may have been denied if stylesheet is local and document is remote.
239             m_loading = false;
240             document()->removePendingSheet();
241         }
242     } else if (m_sheet) {
243         // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
244         m_sheet = 0;
245         document()->updateStyleSelector();
246     }
247 }
248 
insertedIntoDocument()249 void HTMLLinkElement::insertedIntoDocument()
250 {
251     HTMLElement::insertedIntoDocument();
252     document()->addStyleSheetCandidateNode(this, m_createdByParser);
253     process();
254 }
255 
removedFromDocument()256 void HTMLLinkElement::removedFromDocument()
257 {
258     HTMLElement::removedFromDocument();
259 
260     document()->removeStyleSheetCandidateNode(this);
261 
262     // FIXME: It's terrible to do a synchronous update of the style selector just because a <style> or <link> element got removed.
263     if (document()->renderer())
264         document()->updateStyleSelector();
265 }
266 
finishParsingChildren()267 void HTMLLinkElement::finishParsingChildren()
268 {
269     m_createdByParser = false;
270     HTMLElement::finishParsingChildren();
271 }
272 
setCSSStyleSheet(const String & url,const String & charset,const CachedCSSStyleSheet * sheet)273 void HTMLLinkElement::setCSSStyleSheet(const String& url, const String& charset, const CachedCSSStyleSheet* sheet)
274 {
275     m_sheet = CSSStyleSheet::create(this, url, charset);
276 
277     bool strictParsing = !document()->inCompatMode();
278     bool enforceMIMEType = strictParsing;
279 
280     // Check to see if we should enforce the MIME type of the CSS resource in strict mode.
281     // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748>
282     if (enforceMIMEType && document()->page() && !document()->page()->settings()->enforceCSSMIMETypeInStrictMode())
283         enforceMIMEType = false;
284 
285     m_sheet->parseString(sheet->sheetText(enforceMIMEType), strictParsing);
286     m_sheet->setTitle(title());
287 
288     RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media);
289     m_sheet->setMedia(media.get());
290 
291     m_loading = false;
292     m_sheet->checkLoaded();
293 }
294 
isLoading() const295 bool HTMLLinkElement::isLoading() const
296 {
297     if (m_loading)
298         return true;
299     if (!m_sheet)
300         return false;
301     return static_cast<CSSStyleSheet *>(m_sheet.get())->isLoading();
302 }
303 
sheetLoaded()304 bool HTMLLinkElement::sheetLoaded()
305 {
306     if (!isLoading() && !isDisabled() && !isAlternate()) {
307         document()->removePendingSheet();
308         return true;
309     }
310     return false;
311 }
312 
isURLAttribute(Attribute * attr) const313 bool HTMLLinkElement::isURLAttribute(Attribute *attr) const
314 {
315     return attr->name() == hrefAttr;
316 }
317 
disabled() const318 bool HTMLLinkElement::disabled() const
319 {
320     return !getAttribute(disabledAttr).isNull();
321 }
322 
setDisabled(bool disabled)323 void HTMLLinkElement::setDisabled(bool disabled)
324 {
325     setAttribute(disabledAttr, disabled ? "" : 0);
326 }
327 
charset() const328 String HTMLLinkElement::charset() const
329 {
330     return getAttribute(charsetAttr);
331 }
332 
setCharset(const String & value)333 void HTMLLinkElement::setCharset(const String& value)
334 {
335     setAttribute(charsetAttr, value);
336 }
337 
href() const338 KURL HTMLLinkElement::href() const
339 {
340     return document()->completeURL(getAttribute(hrefAttr));
341 }
342 
setHref(const String & value)343 void HTMLLinkElement::setHref(const String& value)
344 {
345     setAttribute(hrefAttr, value);
346 }
347 
hreflang() const348 String HTMLLinkElement::hreflang() const
349 {
350     return getAttribute(hreflangAttr);
351 }
352 
setHreflang(const String & value)353 void HTMLLinkElement::setHreflang(const String& value)
354 {
355     setAttribute(hreflangAttr, value);
356 }
357 
media() const358 String HTMLLinkElement::media() const
359 {
360     return getAttribute(mediaAttr);
361 }
362 
setMedia(const String & value)363 void HTMLLinkElement::setMedia(const String& value)
364 {
365     setAttribute(mediaAttr, value);
366 }
367 
rel() const368 String HTMLLinkElement::rel() const
369 {
370     return getAttribute(relAttr);
371 }
372 
setRel(const String & value)373 void HTMLLinkElement::setRel(const String& value)
374 {
375     setAttribute(relAttr, value);
376 }
377 
rev() const378 String HTMLLinkElement::rev() const
379 {
380     return getAttribute(revAttr);
381 }
382 
setRev(const String & value)383 void HTMLLinkElement::setRev(const String& value)
384 {
385     setAttribute(revAttr, value);
386 }
387 
target() const388 String HTMLLinkElement::target() const
389 {
390     return getAttribute(targetAttr);
391 }
392 
setTarget(const String & value)393 void HTMLLinkElement::setTarget(const String& value)
394 {
395     setAttribute(targetAttr, value);
396 }
397 
type() const398 String HTMLLinkElement::type() const
399 {
400     return getAttribute(typeAttr);
401 }
402 
setType(const String & value)403 void HTMLLinkElement::setType(const String& value)
404 {
405     setAttribute(typeAttr, value);
406 }
407 
addSubresourceAttributeURLs(ListHashSet<KURL> & urls) const408 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
409 {
410     HTMLElement::addSubresourceAttributeURLs(urls);
411 
412     // Favicons are handled by a special case in LegacyWebArchive::create()
413     if (m_isIcon)
414         return;
415 
416     if (!m_isStyleSheet)
417         return;
418 
419     // Append the URL of this link element.
420     addSubresourceURL(urls, href());
421 
422     // Walk the URLs linked by the linked-to stylesheet.
423     if (StyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet())
424         styleSheet->addSubresourceStyleURLs(urls);
425 }
426 
427 }
428