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.
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.
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.
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.
26 #include "core/html/HTMLLinkElement.h"
28 #include "bindings/core/v8/ScriptEventListener.h"
29 #include "bindings/core/v8/V8DOMActivityLogger.h"
30 #include "core/HTMLNames.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/dom/StyleEngine.h"
38 #include "core/events/Event.h"
39 #include "core/events/EventSender.h"
40 #include "core/fetch/CSSStyleSheetResource.h"
41 #include "core/fetch/FetchRequest.h"
42 #include "core/fetch/ResourceFetcher.h"
43 #include "core/frame/FrameView.h"
44 #include "core/frame/LocalFrame.h"
45 #include "core/frame/SubresourceIntegrity.h"
46 #include "core/frame/UseCounter.h"
47 #include "core/frame/csp/ContentSecurityPolicy.h"
48 #include "core/html/LinkManifest.h"
49 #include "core/html/imports/LinkImport.h"
50 #include "core/inspector/ConsoleMessage.h"
51 #include "core/loader/FrameLoader.h"
52 #include "core/loader/FrameLoaderClient.h"
53 #include "core/loader/NetworkHintsInterface.h"
54 #include "core/style/StyleInheritedData.h"
55 #include "platform/RuntimeEnabledFeatures.h"
56 #include "wtf/StdLibExtras.h"
60 using namespace HTMLNames
;
62 template <typename CharacterType
>
63 static void parseSizes(const CharacterType
* value
, unsigned length
, Vector
<IntSize
>& iconSizes
)
73 State state
= ParseStart
;
75 for (; i
< length
; ++i
) {
76 if (state
== ParseWidth
) {
77 if (value
[i
] == 'x' || value
[i
] == 'X') {
82 width
= charactersToInt(value
+ start
, i
- start
);
85 } else if (value
[i
] < '0' || value
[i
] > '9') {
89 } else if (state
== ParseHeight
) {
90 if (value
[i
] == ' ') {
95 int height
= charactersToInt(value
+ start
, i
- start
);
96 iconSizes
.append(IntSize(width
, height
));
99 } else if (value
[i
] < '0' || value
[i
] > '9') {
103 } else if (state
== ParseStart
) {
104 if (value
[i
] >= '0' && value
[i
] <= '9') {
107 } else if (value
[i
] != ' ') {
113 if (invalid
|| state
== ParseWidth
|| (state
== ParseHeight
&& start
== i
)) {
117 if (state
== ParseHeight
&& i
> start
) {
118 int height
= charactersToInt(value
+ start
, i
- start
);
119 iconSizes
.append(IntSize(width
, height
));
123 static LinkEventSender
& linkLoadEventSender()
125 DEFINE_STATIC_LOCAL(LinkEventSender
, sharedLoadEventSender
, (EventTypeNames::load
));
126 return sharedLoadEventSender
;
129 void HTMLLinkElement::parseSizesAttribute(const AtomicString
& value
, Vector
<IntSize
>& iconSizes
)
131 ASSERT(iconSizes
.isEmpty());
135 parseSizes(value
.characters8(), value
.length(), iconSizes
);
137 parseSizes(value
.characters16(), value
.length(), iconSizes
);
140 inline HTMLLinkElement::HTMLLinkElement(Document
& document
, bool createdByParser
)
141 : HTMLElement(linkTag
, document
)
143 , m_sizes(DOMSettableTokenList::create())
144 , m_createdByParser(createdByParser
)
145 , m_isInShadowTree(false)
149 PassRefPtrWillBeRawPtr
<HTMLLinkElement
> HTMLLinkElement::create(Document
& document
, bool createdByParser
)
151 return adoptRefWillBeNoop(new HTMLLinkElement(document
, createdByParser
));
154 HTMLLinkElement::~HTMLLinkElement()
160 document().styleEngine().removeStyleSheetCandidateNode(this);
163 linkLoadEventSender().cancelEvent(this);
166 void HTMLLinkElement::parseAttribute(const QualifiedName
& name
, const AtomicString
& value
)
168 if (name
== relAttr
) {
169 m_relAttribute
= LinkRelAttribute(value
);
171 } else if (name
== hrefAttr
) {
173 } else if (name
== typeAttr
) {
176 } else if (name
== asAttr
) {
179 } else if (name
== sizesAttr
) {
180 m_sizes
->setValue(value
);
181 parseSizesAttribute(value
, m_iconSizes
);
183 } else if (name
== mediaAttr
) {
184 m_media
= value
.lower();
186 } else if (name
== disabledAttr
) {
187 UseCounter::count(document(), UseCounter::HTMLLinkElementDisabled
);
188 if (LinkStyle
* link
= linkStyle())
189 link
->setDisabledState(!value
.isNull());
191 if (name
== titleAttr
) {
192 if (LinkStyle
* link
= linkStyle())
193 link
->setSheetTitle(value
);
196 HTMLElement::parseAttribute(name
, value
);
200 bool HTMLLinkElement::shouldLoadLink()
205 bool HTMLLinkElement::loadLink(const String
& type
, const String
& as
, const KURL
& url
)
207 return m_linkLoader
.loadLink(m_relAttribute
, fastGetAttribute(HTMLNames::crossoriginAttr
), type
, as
, url
, document(), NetworkHintsInterfaceImpl());
210 LinkResource
* HTMLLinkElement::linkResourceToProcess()
212 bool visible
= inDocument() && !m_isInShadowTree
;
214 ASSERT(!linkStyle() || !linkStyle()->hasSheet());
219 if (m_relAttribute
.isImport()) {
220 m_link
= LinkImport::create(this);
221 } else if (m_relAttribute
.isManifest()) {
222 m_link
= LinkManifest::create(this);
224 OwnPtrWillBeRawPtr
<LinkStyle
> link
= LinkStyle::create(this);
225 if (fastHasAttribute(disabledAttr
)) {
226 UseCounter::count(document(), UseCounter::HTMLLinkElementDisabled
);
227 link
->setDisabledState(true);
229 m_link
= link
.release();
236 LinkStyle
* HTMLLinkElement::linkStyle() const
238 if (!m_link
|| m_link
->type() != LinkResource::Style
)
240 return static_cast<LinkStyle
*>(m_link
.get());
243 LinkImport
* HTMLLinkElement::linkImport() const
245 if (!m_link
|| m_link
->type() != LinkResource::Import
)
247 return static_cast<LinkImport
*>(m_link
.get());
250 Document
* HTMLLinkElement::import() const
252 if (LinkImport
* link
= linkImport())
253 return link
->importedDocument();
257 void HTMLLinkElement::process()
259 if (LinkResource
* link
= linkResourceToProcess())
263 Node::InsertionNotificationRequest
HTMLLinkElement::insertedInto(ContainerNode
* insertionPoint
)
265 if (insertionPoint
->inDocument()) {
266 V8DOMActivityLogger
* activityLogger
= V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
267 if (activityLogger
) {
270 argv
.append(fastGetAttribute(relAttr
));
271 argv
.append(fastGetAttribute(hrefAttr
));
272 activityLogger
->logEvent("blinkAddElement", argv
.size(), argv
.data());
275 HTMLElement::insertedInto(insertionPoint
);
276 if (!insertionPoint
->inDocument())
277 return InsertionDone
;
279 m_isInShadowTree
= isInShadowTree();
280 if (m_isInShadowTree
) {
281 String message
= "HTML element <link> is ignored in shadow tree.";
282 document().addConsoleMessage(ConsoleMessage::create(JSMessageSource
, WarningMessageLevel
, message
));
283 return InsertionDone
;
286 document().styleEngine().addStyleSheetCandidateNode(this, m_createdByParser
);
291 m_link
->ownerInserted();
293 return InsertionDone
;
296 void HTMLLinkElement::removedFrom(ContainerNode
* insertionPoint
)
298 HTMLElement::removedFrom(insertionPoint
);
299 if (!insertionPoint
->inDocument())
302 m_linkLoader
.released();
304 if (m_isInShadowTree
) {
305 ASSERT(!linkStyle() || !linkStyle()->hasSheet());
308 document().styleEngine().removeStyleSheetCandidateNode(this);
310 RefPtrWillBeRawPtr
<StyleSheet
> removedSheet
= sheet();
313 m_link
->ownerRemoved();
315 document().removedStyleSheet(removedSheet
.get());
318 void HTMLLinkElement::finishParsingChildren()
320 m_createdByParser
= false;
321 HTMLElement::finishParsingChildren();
324 bool HTMLLinkElement::styleSheetIsLoading() const
326 return linkStyle() && linkStyle()->styleSheetIsLoading();
329 void HTMLLinkElement::linkLoaded()
331 dispatchEvent(Event::create(EventTypeNames::load
));
334 void HTMLLinkElement::linkLoadingErrored()
336 dispatchEvent(Event::create(EventTypeNames::error
));
339 void HTMLLinkElement::didStartLinkPrerender()
341 dispatchEvent(Event::create(EventTypeNames::webkitprerenderstart
));
344 void HTMLLinkElement::didStopLinkPrerender()
346 dispatchEvent(Event::create(EventTypeNames::webkitprerenderstop
));
349 void HTMLLinkElement::didSendLoadForLinkPrerender()
351 dispatchEvent(Event::create(EventTypeNames::webkitprerenderload
));
354 void HTMLLinkElement::didSendDOMContentLoadedForLinkPrerender()
356 dispatchEvent(Event::create(EventTypeNames::webkitprerenderdomcontentloaded
));
359 bool HTMLLinkElement::sheetLoaded()
362 return linkStyle()->sheetLoaded();
365 void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(LoadedSheetErrorStatus errorStatus
)
368 linkStyle()->notifyLoadedSheetAndAllCriticalSubresources(errorStatus
);
371 void HTMLLinkElement::dispatchPendingLoadEvents()
373 linkLoadEventSender().dispatchPendingEvents();
376 void HTMLLinkElement::dispatchPendingEvent(LinkEventSender
* eventSender
)
378 ASSERT_UNUSED(eventSender
, eventSender
== &linkLoadEventSender());
380 if (m_link
->hasLoaded())
383 linkLoadingErrored();
386 void HTMLLinkElement::scheduleEvent()
388 linkLoadEventSender().dispatchEventSoon(this);
391 void HTMLLinkElement::startLoadingDynamicSheet()
394 linkStyle()->startLoadingDynamicSheet();
397 bool HTMLLinkElement::isURLAttribute(const Attribute
& attribute
) const
399 return attribute
.name().localName() == hrefAttr
|| HTMLElement::isURLAttribute(attribute
);
402 bool HTMLLinkElement::hasLegalLinkAttribute(const QualifiedName
& name
) const
404 return name
== hrefAttr
|| HTMLElement::hasLegalLinkAttribute(name
);
407 const QualifiedName
& HTMLLinkElement::subResourceAttributeName() const
409 // If the link element is not css, ignore it.
410 if (equalIgnoringCase(getAttribute(typeAttr
), "text/css")) {
411 // FIXME: Add support for extracting links of sub-resources which
412 // are inside style-sheet such as @import, @font-face, url(), etc.
415 return HTMLElement::subResourceAttributeName();
418 KURL
HTMLLinkElement::href() const
420 return document().completeURL(getAttribute(hrefAttr
));
423 const AtomicString
& HTMLLinkElement::rel() const
425 return getAttribute(relAttr
);
428 const AtomicString
& HTMLLinkElement::type() const
430 return getAttribute(typeAttr
);
433 bool HTMLLinkElement::async() const
435 return fastHasAttribute(HTMLNames::asyncAttr
);
438 IconType
HTMLLinkElement::iconType() const
440 return m_relAttribute
.iconType();
443 const Vector
<IntSize
>& HTMLLinkElement::iconSizes() const
448 DOMSettableTokenList
* HTMLLinkElement::sizes() const
450 return m_sizes
.get();
453 DEFINE_TRACE(HTMLLinkElement
)
455 visitor
->trace(m_link
);
456 visitor
->trace(m_sizes
);
457 visitor
->trace(m_linkLoader
);
458 HTMLElement::trace(visitor
);
461 void HTMLLinkElement::attributeWillChange(const QualifiedName
& name
, const AtomicString
& oldValue
, const AtomicString
& newValue
)
463 if (name
== hrefAttr
&& inDocument()) {
464 V8DOMActivityLogger
* activityLogger
= V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
465 if (activityLogger
) {
468 argv
.append(hrefAttr
.toString());
469 argv
.append(oldValue
);
470 argv
.append(newValue
);
471 activityLogger
->logEvent("blinkSetAttribute", argv
.size(), argv
.data());
474 HTMLElement::attributeWillChange(name
, oldValue
, newValue
);
477 PassOwnPtrWillBeRawPtr
<LinkStyle
> LinkStyle::create(HTMLLinkElement
* owner
)
479 return adoptPtrWillBeNoop(new LinkStyle(owner
));
482 LinkStyle::LinkStyle(HTMLLinkElement
* owner
)
483 : LinkResource(owner
)
484 , m_disabledState(Unset
)
485 , m_pendingSheetType(None
)
488 , m_loadedSheet(false)
489 , m_fetchFollowingCORS(false)
493 LinkStyle::~LinkStyle()
497 m_sheet
->clearOwnerNode();
501 Document
& LinkStyle::document()
503 return m_owner
->document();
506 void LinkStyle::setCSSStyleSheet(const String
& href
, const KURL
& baseURL
, const String
& charset
, const CSSStyleSheetResource
* cachedStyleSheet
)
508 if (!m_owner
->inDocument()) {
513 // See the comment in ScriptLoader.cpp about why this check is necessary
514 // here. https://crbug.com/500701.
515 if (!cachedStyleSheet
->errorOccurred() && !SubresourceIntegrity::CheckSubresourceIntegrity(*m_owner
, cachedStyleSheet
->sheetText(), KURL(baseURL
, href
), *cachedStyleSheet
)) {
517 removePendingSheet();
518 notifyLoadedSheetAndAllCriticalSubresources(Node::ErrorOccurredLoadingSubresource
);
521 // While the stylesheet is asynchronously loading, the owner can be moved under
522 // shadow tree. In that case, cancel any processing on the loaded content.
523 if (m_owner
->isInShadowTree()) {
525 removePendingSheet();
530 // Completing the sheet load may cause scripts to execute.
531 RefPtrWillBeRawPtr
<Node
> protector(m_owner
.get());
533 CSSParserContext
parserContext(m_owner
->document(), 0, baseURL
, charset
);
535 if (RefPtrWillBeRawPtr
<StyleSheetContents
> restoredSheet
= const_cast<CSSStyleSheetResource
*>(cachedStyleSheet
)->restoreParsedStyleSheet(parserContext
)) {
536 ASSERT(restoredSheet
->isCacheable());
537 ASSERT(!restoredSheet
->isLoading());
541 m_sheet
= CSSStyleSheet::create(restoredSheet
, m_owner
);
542 m_sheet
->setMediaQueries(MediaQuerySet::create(m_owner
->media()));
543 m_sheet
->setTitle(m_owner
->title());
544 setCrossOriginStylesheetStatus(m_sheet
.get());
547 restoredSheet
->checkLoaded();
551 RefPtrWillBeRawPtr
<StyleSheetContents
> styleSheet
= StyleSheetContents::create(href
, parserContext
);
556 m_sheet
= CSSStyleSheet::create(styleSheet
, m_owner
);
557 m_sheet
->setMediaQueries(MediaQuerySet::create(m_owner
->media()));
558 m_sheet
->setTitle(m_owner
->title());
559 setCrossOriginStylesheetStatus(m_sheet
.get());
561 styleSheet
->parseAuthorStyleSheet(cachedStyleSheet
, m_owner
->document().securityOrigin());
564 styleSheet
->notifyLoadedSheet(cachedStyleSheet
);
565 styleSheet
->checkLoaded();
567 if (styleSheet
->isCacheable())
568 const_cast<CSSStyleSheetResource
*>(cachedStyleSheet
)->saveParsedStyleSheet(styleSheet
);
571 bool LinkStyle::sheetLoaded()
573 if (!styleSheetIsLoading()) {
574 removePendingSheet();
580 void LinkStyle::notifyLoadedSheetAndAllCriticalSubresources(Node::LoadedSheetErrorStatus errorStatus
)
584 m_loadedSheet
= (errorStatus
== Node::NoErrorLoadingSubresource
);
586 m_owner
->scheduleEvent();
590 void LinkStyle::startLoadingDynamicSheet()
592 ASSERT(m_pendingSheetType
< Blocking
);
593 addPendingSheet(Blocking
);
596 void LinkStyle::clearSheet()
599 ASSERT(m_sheet
->ownerNode() == m_owner
);
600 m_sheet
->clearOwnerNode();
604 bool LinkStyle::styleSheetIsLoading() const
610 return m_sheet
->contents()->isLoading();
613 void LinkStyle::addPendingSheet(PendingSheetType type
)
615 if (type
<= m_pendingSheetType
)
617 m_pendingSheetType
= type
;
619 if (m_pendingSheetType
== NonBlocking
)
621 m_owner
->document().styleEngine().addPendingSheet();
624 void LinkStyle::removePendingSheet()
626 PendingSheetType type
= m_pendingSheetType
;
627 m_pendingSheetType
= None
;
631 if (type
== NonBlocking
) {
632 // Tell StyleEngine to re-compute styleSheets of this m_owner's treescope.
633 m_owner
->document().styleEngine().modifiedStyleSheetCandidateNode(m_owner
);
634 // Document::removePendingSheet() triggers the style selector recalc for blocking sheets.
635 // FIXME: We don't have enough knowledge at this point to know if we're adding or removing a sheet
636 // so we can't call addedStyleSheet() or removedStyleSheet().
637 m_owner
->document().styleResolverChanged();
641 m_owner
->document().styleEngine().removePendingSheet(m_owner
);
644 void LinkStyle::setDisabledState(bool disabled
)
646 LinkStyle::DisabledState oldDisabledState
= m_disabledState
;
647 m_disabledState
= disabled
? Disabled
: EnabledViaScript
;
648 if (oldDisabledState
!= m_disabledState
) {
649 // If we change the disabled state while the sheet is still loading, then we have to
650 // perform three checks:
651 if (styleSheetIsLoading()) {
652 // Check #1: The sheet becomes disabled while loading.
653 if (m_disabledState
== Disabled
)
654 removePendingSheet();
656 // Check #2: An alternate sheet becomes enabled while it is still loading.
657 if (m_owner
->relAttribute().isAlternate() && m_disabledState
== EnabledViaScript
)
658 addPendingSheet(Blocking
);
660 // Check #3: A main sheet becomes enabled while it was still loading and
661 // after it was disabled via script. It takes really terrible code to make this
662 // happen (a double toggle for no reason essentially). This happens on
663 // virtualplastic.net, which manages to do about 12 enable/disables on only 3
665 if (!m_owner
->relAttribute().isAlternate() && m_disabledState
== EnabledViaScript
&& oldDisabledState
== Disabled
)
666 addPendingSheet(Blocking
);
668 // If the sheet is already loading just bail.
673 m_sheet
->setDisabled(disabled
);
675 // Load the sheet, since it's never been loaded before.
676 if (!m_sheet
&& m_disabledState
== EnabledViaScript
) {
677 if (m_owner
->shouldProcessStyle())
680 // FIXME: We don't have enough knowledge here to know if we should call addedStyleSheet() or removedStyleSheet().
681 m_owner
->document().styleResolverChanged();
686 void LinkStyle::setCrossOriginStylesheetStatus(CSSStyleSheet
* sheet
)
688 if (m_fetchFollowingCORS
&& resource() && !resource()->errorOccurred()) {
689 // Record the security origin the CORS access check succeeded at, if cross origin.
690 // Only origins that are script accessible to it may access the stylesheet's rules.
691 sheet
->setAllowRuleAccessFromOrigin(m_owner
->document().securityOrigin());
693 m_fetchFollowingCORS
= false;
696 void LinkStyle::process()
698 ASSERT(m_owner
->shouldProcessStyle());
699 String type
= m_owner
->typeValue().lower();
700 String as
= m_owner
->asValue().lower();
701 LinkRequestBuilder
builder(m_owner
);
703 if (m_owner
->relAttribute().iconType() != InvalidIcon
&& builder
.url().isValid() && !builder
.url().isEmpty()) {
704 if (!m_owner
->shouldLoadLink())
706 if (!document().securityOrigin()->canDisplay(builder
.url()))
708 if (!document().contentSecurityPolicy()->allowImageFromSource(builder
.url()))
710 if (document().frame() && document().frame()->loader().client())
711 document().frame()->loader().client()->dispatchDidChangeIcons(m_owner
->relAttribute().iconType());
714 if (!m_owner
->loadLink(type
, as
, builder
.url()))
717 if (m_disabledState
!= Disabled
&& m_owner
->relAttribute().isStyleSheet() && shouldLoadResource() && builder
.url().isValid()) {
720 removePendingSheet();
724 if (!m_owner
->shouldLoadLink())
729 bool mediaQueryMatches
= true;
730 LocalFrame
* frame
= loadingFrame();
731 if (!m_owner
->media().isEmpty() && frame
&& frame
->document()) {
732 RefPtr
<ComputedStyle
> documentStyle
= StyleResolver::styleForDocument(*frame
->document());
733 RefPtrWillBeRawPtr
<MediaQuerySet
> media
= MediaQuerySet::create(m_owner
->media());
734 MediaQueryEvaluator
evaluator(frame
);
735 mediaQueryMatches
= evaluator
.eval(media
.get());
738 // Don't hold up layout tree construction and script execution on stylesheets
739 // that are not needed for the layout at the moment.
740 bool blocking
= mediaQueryMatches
&& !m_owner
->isAlternate();
741 addPendingSheet(blocking
? Blocking
: NonBlocking
);
743 // Load stylesheets that are not needed for the layout immediately with low priority.
744 FetchRequest request
= builder
.build(blocking
);
745 AtomicString crossOriginMode
= m_owner
->fastGetAttribute(HTMLNames::crossoriginAttr
);
746 if (!crossOriginMode
.isNull()) {
747 request
.setCrossOriginAccessControl(document().securityOrigin(), crossOriginMode
);
748 setFetchFollowingCORS();
750 setResource(CSSStyleSheetResource::fetch(request
, document().fetcher()));
753 // The request may have been denied if (for example) the stylesheet is local and the document is remote, or if there was a Content Security Policy Failure.
755 removePendingSheet();
756 notifyLoadedSheetAndAllCriticalSubresources(Node::ErrorOccurredLoadingSubresource
);
758 } else if (m_sheet
) {
759 // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
760 RefPtrWillBeRawPtr
<StyleSheet
> removedSheet
= m_sheet
.get();
762 document().removedStyleSheet(removedSheet
.get());
766 void LinkStyle::setSheetTitle(const String
& title
)
769 m_sheet
->setTitle(title
);
772 void LinkStyle::ownerRemoved()
777 if (styleSheetIsLoading())
778 removePendingSheet();
781 DEFINE_TRACE(LinkStyle
)
783 visitor
->trace(m_sheet
);
784 LinkResource::trace(visitor
);