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