c34f02ec16bccfc040d4cd38bea15f8aa1036a3e
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / 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 "core/html/HTMLLinkElement.h"
27
28 #include "HTMLNames.h"
29 #include "RuntimeEnabledFeatures.h"
30 #include "bindings/v8/ScriptEventListener.h"
31 #include "core/css/MediaList.h"
32 #include "core/css/MediaQueryEvaluator.h"
33 #include "core/css/StyleSheetContents.h"
34 #include "core/css/resolver/StyleResolver.h"
35 #include "core/dom/Attribute.h"
36 #include "core/dom/Document.h"
37 #include "core/events/Event.h"
38 #include "core/events/EventSender.h"
39 #include "core/dom/StyleEngine.h"
40 #include "core/fetch/CSSStyleSheetResource.h"
41 #include "core/fetch/FetchRequest.h"
42 #include "core/fetch/ResourceFetcher.h"
43 #include "core/html/LinkImport.h"
44 #include "core/loader/FrameLoader.h"
45 #include "core/loader/FrameLoaderClient.h"
46 #include "core/frame/ContentSecurityPolicy.h"
47 #include "core/frame/Frame.h"
48 #include "core/frame/FrameView.h"
49 #include "wtf/StdLibExtras.h"
50
51 namespace WebCore {
52
53 using namespace HTMLNames;
54
55 static LinkEventSender& linkLoadEventSender()
56 {
57     DEFINE_STATIC_LOCAL(LinkEventSender, sharedLoadEventSender, (EventTypeNames::load));
58     return sharedLoadEventSender;
59 }
60
61 inline HTMLLinkElement::HTMLLinkElement(Document& document, bool createdByParser)
62     : HTMLElement(linkTag, document)
63     , m_linkLoader(this)
64     , m_sizes(DOMSettableTokenList::create())
65     , m_createdByParser(createdByParser)
66     , m_isInShadowTree(false)
67     , m_beforeLoadRecurseCount(0)
68 {
69     ScriptWrappable::init(this);
70 }
71
72 PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(Document& document, bool createdByParser)
73 {
74     return adoptRef(new HTMLLinkElement(document, createdByParser));
75 }
76
77 HTMLLinkElement::~HTMLLinkElement()
78 {
79     m_link.clear();
80
81     if (inDocument())
82         document().styleEngine()->removeStyleSheetCandidateNode(this);
83
84     linkLoadEventSender().cancelEvent(this);
85 }
86
87 void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
88 {
89     if (name == relAttr) {
90         m_relAttribute = LinkRelAttribute(value);
91         process();
92     } else if (name == hrefAttr) {
93         process();
94     } else if (name == typeAttr) {
95         m_type = value;
96         process();
97     } else if (name == sizesAttr) {
98         m_sizes->setValue(value);
99         process();
100     } else if (name == mediaAttr) {
101         m_media = value.string().lower();
102         process();
103     } else if (name == disabledAttr) {
104         if (LinkStyle* link = linkStyle())
105             link->setDisabledState(!value.isNull());
106     } else if (name == onbeforeloadAttr)
107         setAttributeEventListener(EventTypeNames::beforeload, createAttributeEventListener(this, name, value));
108     else {
109         if (name == titleAttr) {
110             if (LinkStyle* link = linkStyle())
111                 link->setSheetTitle(value);
112         }
113
114         HTMLElement::parseAttribute(name, value);
115     }
116 }
117
118 bool HTMLLinkElement::shouldLoadLink()
119 {
120     bool continueLoad = true;
121     RefPtr<Document> originalDocument(document());
122     int recursionRank = ++m_beforeLoadRecurseCount;
123     if (!dispatchBeforeLoadEvent(getNonEmptyURLAttribute(hrefAttr)))
124         continueLoad = false;
125
126     // A beforeload handler might have removed us from the document or changed the document.
127     if (continueLoad && (!inDocument() || document() != originalDocument))
128         continueLoad = false;
129
130     // If the beforeload handler recurses into the link element by mutating it, we should only
131     // let the latest (innermost) mutation occur.
132     if (recursionRank != m_beforeLoadRecurseCount)
133         continueLoad = false;
134
135     if (recursionRank == 1)
136         m_beforeLoadRecurseCount = 0;
137
138     return continueLoad;
139 }
140
141 bool HTMLLinkElement::loadLink(const String& type, const KURL& url)
142 {
143     return m_linkLoader.loadLink(m_relAttribute, fastGetAttribute(HTMLNames::crossoriginAttr), type, url, document());
144 }
145
146 LinkResource* HTMLLinkElement::linkResourceToProcess()
147 {
148     bool visible = inDocument() && !m_isInShadowTree;
149     if (!visible) {
150         ASSERT(!linkStyle() || !linkStyle()->hasSheet());
151         return 0;
152     }
153
154     if (!m_link) {
155         if (m_relAttribute.isImport() && RuntimeEnabledFeatures::htmlImportsEnabled())
156             m_link = LinkImport::create(this);
157         else {
158             OwnPtr<LinkStyle> link = LinkStyle::create(this);
159             if (fastHasAttribute(disabledAttr))
160                 link->setDisabledState(true);
161             m_link = link.release();
162         }
163     }
164
165     return m_link.get();
166 }
167
168 LinkStyle* HTMLLinkElement::linkStyle() const
169 {
170     if (!m_link || m_link->type() != LinkResource::Style)
171         return 0;
172     return static_cast<LinkStyle*>(m_link.get());
173 }
174
175 LinkImport* HTMLLinkElement::linkImport() const
176 {
177     if (!m_link || m_link->type() != LinkResource::Import)
178         return 0;
179     return static_cast<LinkImport*>(m_link.get());
180 }
181
182 bool HTMLLinkElement::importOwnsLoader() const
183 {
184     LinkImport* import = linkImport();
185     if (!import)
186         return false;
187     return import->ownsLoader();
188 }
189
190 Document* HTMLLinkElement::import() const
191 {
192     if (LinkImport* link = linkImport())
193         return link->importedDocument();
194     return 0;
195 }
196
197 void HTMLLinkElement::process()
198 {
199     if (LinkResource* link = linkResourceToProcess())
200         link->process();
201 }
202
203 Node::InsertionNotificationRequest HTMLLinkElement::insertedInto(ContainerNode* insertionPoint)
204 {
205     HTMLElement::insertedInto(insertionPoint);
206     if (!insertionPoint->inDocument())
207         return InsertionDone;
208
209     m_isInShadowTree = isInShadowTree();
210     if (m_isInShadowTree)
211         return InsertionDone;
212
213     document().styleEngine()->addStyleSheetCandidateNode(this, m_createdByParser);
214
215     process();
216     return InsertionDone;
217 }
218
219 void HTMLLinkElement::removedFrom(ContainerNode* insertionPoint)
220 {
221     HTMLElement::removedFrom(insertionPoint);
222     if (!insertionPoint->inDocument())
223         return;
224
225     m_linkLoader.released();
226
227     if (m_isInShadowTree) {
228         ASSERT(!linkStyle() || !linkStyle()->hasSheet());
229         return;
230     }
231     document().styleEngine()->removeStyleSheetCandidateNode(this);
232
233     RefPtr<StyleSheet> removedSheet = sheet();
234
235     if (m_link)
236         m_link->ownerRemoved();
237
238     document().removedStyleSheet(removedSheet.get());
239 }
240
241 void HTMLLinkElement::finishParsingChildren()
242 {
243     m_createdByParser = false;
244     HTMLElement::finishParsingChildren();
245 }
246
247 bool HTMLLinkElement::styleSheetIsLoading() const
248 {
249     return linkStyle() && linkStyle()->styleSheetIsLoading();
250 }
251
252 void HTMLLinkElement::linkLoaded()
253 {
254     dispatchEvent(Event::create(EventTypeNames::load));
255 }
256
257 void HTMLLinkElement::linkLoadingErrored()
258 {
259     dispatchEvent(Event::create(EventTypeNames::error));
260 }
261
262 void HTMLLinkElement::didStartLinkPrerender()
263 {
264     dispatchEvent(Event::create(EventTypeNames::webkitprerenderstart));
265 }
266
267 void HTMLLinkElement::didStopLinkPrerender()
268 {
269     dispatchEvent(Event::create(EventTypeNames::webkitprerenderstop));
270 }
271
272 void HTMLLinkElement::didSendLoadForLinkPrerender()
273 {
274     dispatchEvent(Event::create(EventTypeNames::webkitprerenderload));
275 }
276
277 void HTMLLinkElement::didSendDOMContentLoadedForLinkPrerender()
278 {
279     dispatchEvent(Event::create(EventTypeNames::webkitprerenderdomcontentloaded));
280 }
281
282 bool HTMLLinkElement::sheetLoaded()
283 {
284     ASSERT(linkStyle());
285     return linkStyle()->sheetLoaded();
286 }
287
288 void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
289 {
290     ASSERT(linkStyle());
291     linkStyle()->notifyLoadedSheetAndAllCriticalSubresources(errorOccurred);
292 }
293
294 void HTMLLinkElement::dispatchPendingLoadEvents()
295 {
296     linkLoadEventSender().dispatchPendingEvents();
297 }
298
299 void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender)
300 {
301     ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender());
302     ASSERT(m_link);
303     dispatchEventImmediately();
304 }
305
306 void HTMLLinkElement::dispatchEventImmediately()
307 {
308     if (m_link->hasLoaded())
309         linkLoaded();
310     else
311         linkLoadingErrored();
312 }
313
314 void HTMLLinkElement::scheduleEvent()
315 {
316     linkLoadEventSender().dispatchEventSoon(this);
317 }
318
319 void HTMLLinkElement::startLoadingDynamicSheet()
320 {
321     ASSERT(linkStyle());
322     linkStyle()->startLoadingDynamicSheet();
323 }
324
325 bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const
326 {
327     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
328 }
329
330 KURL HTMLLinkElement::href() const
331 {
332     return document().completeURL(getAttribute(hrefAttr));
333 }
334
335 const AtomicString& HTMLLinkElement::rel() const
336 {
337     return getAttribute(relAttr);
338 }
339
340 const AtomicString& HTMLLinkElement::type() const
341 {
342     return getAttribute(typeAttr);
343 }
344
345 IconType HTMLLinkElement::iconType() const
346 {
347     return m_relAttribute.iconType();
348 }
349
350 const AtomicString& HTMLLinkElement::iconSizes() const
351 {
352     return m_sizes->toString();
353 }
354
355 DOMSettableTokenList* HTMLLinkElement::sizes() const
356 {
357     return m_sizes.get();
358 }
359
360 PassOwnPtr<LinkStyle> LinkStyle::create(HTMLLinkElement* owner)
361 {
362     return adoptPtr(new LinkStyle(owner));
363 }
364
365 LinkStyle::LinkStyle(HTMLLinkElement* owner)
366     : LinkResource(owner)
367     , m_disabledState(Unset)
368     , m_pendingSheetType(None)
369     , m_loading(false)
370     , m_firedLoad(false)
371     , m_loadedSheet(false)
372 {
373 }
374
375 LinkStyle::~LinkStyle()
376 {
377     if (m_sheet)
378         m_sheet->clearOwnerNode();
379 }
380
381 Document& LinkStyle::document()
382 {
383     return m_owner->document();
384 }
385
386 void LinkStyle::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CSSStyleSheetResource* cachedStyleSheet)
387 {
388     if (!m_owner->inDocument()) {
389         ASSERT(!m_sheet);
390         return;
391
392     }
393     // Completing the sheet load may cause scripts to execute.
394     RefPtr<Node> protector(m_owner);
395
396     CSSParserContext parserContext(m_owner->document(), 0, baseURL, charset);
397
398     if (RefPtr<StyleSheetContents> restoredSheet = const_cast<CSSStyleSheetResource*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext)) {
399         ASSERT(restoredSheet->isCacheable());
400         ASSERT(!restoredSheet->isLoading());
401
402         if (m_sheet)
403             clearSheet();
404         m_sheet = CSSStyleSheet::create(restoredSheet, m_owner);
405         m_sheet->setMediaQueries(MediaQuerySet::create(m_owner->media()));
406         m_sheet->setTitle(m_owner->title());
407
408         m_loading = false;
409         restoredSheet->checkLoaded();
410         return;
411     }
412
413     RefPtr<StyleSheetContents> styleSheet = StyleSheetContents::create(href, parserContext);
414
415     if (m_sheet)
416         clearSheet();
417     m_sheet = CSSStyleSheet::create(styleSheet, m_owner);
418     m_sheet->setMediaQueries(MediaQuerySet::create(m_owner->media()));
419     m_sheet->setTitle(m_owner->title());
420
421     styleSheet->parseAuthorStyleSheet(cachedStyleSheet, m_owner->document().securityOrigin());
422
423     m_loading = false;
424     styleSheet->notifyLoadedSheet(cachedStyleSheet);
425     styleSheet->checkLoaded();
426
427     if (styleSheet->isCacheable())
428         const_cast<CSSStyleSheetResource*>(cachedStyleSheet)->saveParsedStyleSheet(styleSheet);
429 }
430
431 bool LinkStyle::sheetLoaded()
432 {
433     if (!styleSheetIsLoading()) {
434         removePendingSheet();
435         return true;
436     }
437     return false;
438 }
439
440 void LinkStyle::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
441 {
442     if (m_firedLoad)
443         return;
444     m_loadedSheet = !errorOccurred;
445     if (m_owner)
446         m_owner->scheduleEvent();
447     m_firedLoad = true;
448 }
449
450 void LinkStyle::startLoadingDynamicSheet()
451 {
452     ASSERT(m_pendingSheetType < Blocking);
453     addPendingSheet(Blocking);
454 }
455
456 void LinkStyle::clearSheet()
457 {
458     ASSERT(m_sheet);
459     ASSERT(m_sheet->ownerNode() == m_owner);
460     m_sheet->clearOwnerNode();
461     m_sheet = 0;
462 }
463
464 bool LinkStyle::styleSheetIsLoading() const
465 {
466     if (m_loading)
467         return true;
468     if (!m_sheet)
469         return false;
470     return m_sheet->contents()->isLoading();
471 }
472
473 void LinkStyle::addPendingSheet(PendingSheetType type)
474 {
475     if (type <= m_pendingSheetType)
476         return;
477     m_pendingSheetType = type;
478
479     if (m_pendingSheetType == NonBlocking)
480         return;
481     m_owner->document().styleEngine()->addPendingSheet();
482 }
483
484 void LinkStyle::removePendingSheet(RemovePendingSheetNotificationType notification)
485 {
486     PendingSheetType type = m_pendingSheetType;
487     m_pendingSheetType = None;
488
489     if (type == None)
490         return;
491     if (type == NonBlocking) {
492         // Tell StyleEngine to re-compute styleSheets of this m_owner's treescope.
493         m_owner->document().styleEngine()->modifiedStyleSheetCandidateNode(m_owner);
494         // Document::removePendingSheet() triggers the style selector recalc for blocking sheets.
495         // FIXME: We don't have enough knowledge at this point to know if we're adding or removing a sheet
496         // so we can't call addedStyleSheet() or removedStyleSheet().
497         m_owner->document().styleResolverChanged(RecalcStyleImmediately);
498         return;
499     }
500
501     m_owner->document().styleEngine()->removePendingSheet(m_owner,
502         notification == RemovePendingSheetNotifyImmediately
503         ? StyleEngine::RemovePendingSheetNotifyImmediately
504         : StyleEngine::RemovePendingSheetNotifyLater);
505 }
506
507 void LinkStyle::setDisabledState(bool disabled)
508 {
509     LinkStyle::DisabledState oldDisabledState = m_disabledState;
510     m_disabledState = disabled ? Disabled : EnabledViaScript;
511     if (oldDisabledState != m_disabledState) {
512         // If we change the disabled state while the sheet is still loading, then we have to
513         // perform three checks:
514         if (styleSheetIsLoading()) {
515             // Check #1: The sheet becomes disabled while loading.
516             if (m_disabledState == Disabled)
517                 removePendingSheet();
518
519             // Check #2: An alternate sheet becomes enabled while it is still loading.
520             if (m_owner->relAttribute().isAlternate() && m_disabledState == EnabledViaScript)
521                 addPendingSheet(Blocking);
522
523             // Check #3: A main sheet becomes enabled while it was still loading and
524             // after it was disabled via script. It takes really terrible code to make this
525             // happen (a double toggle for no reason essentially). This happens on
526             // virtualplastic.net, which manages to do about 12 enable/disables on only 3
527             // sheets. :)
528             if (!m_owner->relAttribute().isAlternate() && m_disabledState == EnabledViaScript && oldDisabledState == Disabled)
529                 addPendingSheet(Blocking);
530
531             // If the sheet is already loading just bail.
532             return;
533         }
534
535         if (m_sheet)
536             m_sheet->setDisabled(disabled);
537
538         // Load the sheet, since it's never been loaded before.
539         if (!m_sheet && m_disabledState == EnabledViaScript) {
540             if (m_owner->shouldProcessStyle())
541                 process();
542         } else {
543             // FIXME: We don't have enough knowledge here to know if we should call addedStyleSheet() or removedStyleSheet().
544             m_owner->document().styleResolverChanged(RecalcStyleDeferred);
545         }
546     }
547 }
548
549 void LinkStyle::process()
550 {
551     ASSERT(m_owner->shouldProcessStyle());
552     String type = m_owner->typeValue().lower();
553     LinkRequestBuilder builder(m_owner);
554
555     if (m_owner->relAttribute().iconType() != InvalidIcon && builder.url().isValid() && !builder.url().isEmpty()) {
556         if (!m_owner->shouldLoadLink())
557             return;
558         if (!document().securityOrigin()->canDisplay(builder.url()))
559             return;
560         if (!document().contentSecurityPolicy()->allowImageFromSource(builder.url()))
561             return;
562         if (document().frame())
563             document().frame()->loader().client()->dispatchDidChangeIcons(m_owner->relAttribute().iconType());
564     }
565
566     if (!m_owner->loadLink(type, builder.url()))
567         return;
568
569     if ((m_disabledState != Disabled) && m_owner->relAttribute().isStyleSheet()
570         && shouldLoadResource() && builder.url().isValid()) {
571
572         if (resource()) {
573             removePendingSheet();
574             clearResource();
575         }
576
577         if (!m_owner->shouldLoadLink())
578             return;
579
580         m_loading = true;
581
582         bool mediaQueryMatches = true;
583         if (!m_owner->media().isEmpty()) {
584             Frame* frame = loadingFrame();
585             if (Document* document = loadingFrame()->document()) {
586                 RefPtr<RenderStyle> documentStyle = StyleResolver::styleForDocument(*document);
587                 RefPtr<MediaQuerySet> media = MediaQuerySet::create(m_owner->media());
588                 MediaQueryEvaluator evaluator(frame->view()->mediaType(), frame, documentStyle.get());
589                 mediaQueryMatches = evaluator.eval(media.get());
590             }
591         }
592
593         // Don't hold up render tree construction and script execution on stylesheets
594         // that are not needed for the rendering at the moment.
595         bool blocking = mediaQueryMatches && !m_owner->isAlternate();
596         addPendingSheet(blocking ? Blocking : NonBlocking);
597
598         // Load stylesheets that are not needed for the rendering immediately with low priority.
599         FetchRequest request = builder.build(blocking);
600         AtomicString crossOriginMode = m_owner->fastGetAttribute(HTMLNames::crossoriginAttr);
601         if (!crossOriginMode.isNull()) {
602             StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
603             request.setCrossOriginAccessControl(document().securityOrigin(), allowCredentials);
604         }
605         setResource(document().fetcher()->fetchCSSStyleSheet(request));
606
607         if (!resource()) {
608             // The request may have been denied if (for example) the stylesheet is local and the document is remote.
609             m_loading = false;
610             removePendingSheet();
611         }
612     } else if (m_sheet) {
613         // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
614         RefPtr<StyleSheet> removedSheet = m_sheet;
615         clearSheet();
616         document().removedStyleSheet(removedSheet.get());
617     }
618 }
619
620 void LinkStyle::setSheetTitle(const String& title)
621 {
622     if (m_sheet)
623         m_sheet->setTitle(title);
624 }
625
626 void LinkStyle::ownerRemoved()
627 {
628     if (m_sheet)
629         clearSheet();
630
631     if (styleSheetIsLoading())
632         removePendingSheet(RemovePendingSheetNotifyLater);
633 }
634
635 } // namespace WebCore