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/csp/ContentSecurityPolicy.h"
46 #include "core/html/LinkManifest.h"
47 #include "core/html/imports/LinkImport.h"
48 #include "core/loader/FrameLoader.h"
49 #include "core/loader/FrameLoaderClient.h"
50 #include "core/rendering/style/StyleInheritedData.h"
51 #include "platform/RuntimeEnabledFeatures.h"
52 #include "wtf/StdLibExtras.h"
56 using namespace HTMLNames;
58 template <typename CharacterType>
59 static void parseSizes(const CharacterType* value, unsigned length, Vector<IntSize>& iconSizes)
69 State state = ParseStart;
71 for (; i < length; ++i) {
72 if (state == ParseWidth) {
73 if (value[i] == 'x' || value[i] == 'X') {
78 width = charactersToInt(value + start, i - start);
81 } else if (value[i] < '0' || value[i] > '9') {
85 } else if (state == ParseHeight) {
86 if (value[i] == ' ') {
91 int height = charactersToInt(value + start, i - start);
92 iconSizes.append(IntSize(width, height));
95 } else if (value[i] < '0' || value[i] > '9') {
99 } else if (state == ParseStart) {
100 if (value[i] >= '0' && value[i] <= '9') {
103 } else if (value[i] != ' ') {
109 if (invalid || state == ParseWidth || (state == ParseHeight && start == i)) {
113 if (state == ParseHeight && i > start) {
114 int height = charactersToInt(value + start, i - start);
115 iconSizes.append(IntSize(width, height));
119 static LinkEventSender& linkLoadEventSender()
121 DEFINE_STATIC_LOCAL(LinkEventSender, sharedLoadEventSender, (EventTypeNames::load));
122 return sharedLoadEventSender;
125 void HTMLLinkElement::parseSizesAttribute(const AtomicString& value, Vector<IntSize>& iconSizes)
127 ASSERT(iconSizes.isEmpty());
131 parseSizes(value.characters8(), value.length(), iconSizes);
133 parseSizes(value.characters16(), value.length(), iconSizes);
136 inline HTMLLinkElement::HTMLLinkElement(Document& document, bool createdByParser)
137 : HTMLElement(linkTag, document)
139 , m_sizes(DOMSettableTokenList::create())
140 , m_createdByParser(createdByParser)
141 , m_isInShadowTree(false)
145 PassRefPtrWillBeRawPtr<HTMLLinkElement> HTMLLinkElement::create(Document& document, bool createdByParser)
147 return adoptRefWillBeNoop(new HTMLLinkElement(document, createdByParser));
150 HTMLLinkElement::~HTMLLinkElement()
156 document().styleEngine()->removeStyleSheetCandidateNode(this);
159 linkLoadEventSender().cancelEvent(this);
162 void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
164 if (name == relAttr) {
165 m_relAttribute = LinkRelAttribute(value);
167 } else if (name == hrefAttr) {
169 } else if (name == typeAttr) {
172 } else if (name == sizesAttr) {
173 m_sizes->setValue(value);
174 parseSizesAttribute(value, m_iconSizes);
176 } else if (name == mediaAttr) {
177 m_media = value.lower();
179 } else if (name == disabledAttr) {
180 if (LinkStyle* link = linkStyle())
181 link->setDisabledState(!value.isNull());
183 if (name == titleAttr) {
184 if (LinkStyle* link = linkStyle())
185 link->setSheetTitle(value);
188 HTMLElement::parseAttribute(name, value);
192 bool HTMLLinkElement::shouldLoadLink()
197 bool HTMLLinkElement::loadLink(const String& type, const KURL& url)
199 return m_linkLoader.loadLink(m_relAttribute, fastGetAttribute(HTMLNames::crossoriginAttr), type, url, document());
202 LinkResource* HTMLLinkElement::linkResourceToProcess()
204 bool visible = inDocument() && !m_isInShadowTree;
206 ASSERT(!linkStyle() || !linkStyle()->hasSheet());
211 if (m_relAttribute.isImport()) {
212 m_link = LinkImport::create(this);
213 } else if (m_relAttribute.isManifest()) {
214 m_link = LinkManifest::create(this);
216 OwnPtrWillBeRawPtr<LinkStyle> link = LinkStyle::create(this);
217 if (fastHasAttribute(disabledAttr) || m_relAttribute.isTransitionExitingStylesheet())
218 link->setDisabledState(true);
219 m_link = link.release();
226 LinkStyle* HTMLLinkElement::linkStyle() const
228 if (!m_link || m_link->type() != LinkResource::Style)
230 return static_cast<LinkStyle*>(m_link.get());
233 LinkImport* HTMLLinkElement::linkImport() const
235 if (!m_link || m_link->type() != LinkResource::Import)
237 return static_cast<LinkImport*>(m_link.get());
240 Document* HTMLLinkElement::import() const
242 if (LinkImport* link = linkImport())
243 return link->importedDocument();
247 void HTMLLinkElement::process()
249 if (LinkResource* link = linkResourceToProcess())
253 void HTMLLinkElement::enableIfExitTransitionStyle()
255 if (m_relAttribute.isTransitionExitingStylesheet()) {
256 if (LinkStyle* link = linkStyle())
257 link->setDisabledState(false);
261 Node::InsertionNotificationRequest HTMLLinkElement::insertedInto(ContainerNode* insertionPoint)
263 if (insertionPoint->inDocument()) {
264 V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
265 if (activityLogger) {
268 argv.append(fastGetAttribute(relAttr));
269 argv.append(fastGetAttribute(hrefAttr));
270 activityLogger->logEvent("blinkAddElement", argv.size(), argv.data());
273 HTMLElement::insertedInto(insertionPoint);
274 if (!insertionPoint->inDocument())
275 return InsertionDone;
277 m_isInShadowTree = isInShadowTree();
278 if (m_isInShadowTree)
279 return InsertionDone;
281 document().styleEngine()->addStyleSheetCandidateNode(this, m_createdByParser);
286 m_link->ownerInserted();
288 return InsertionDone;
291 void HTMLLinkElement::removedFrom(ContainerNode* insertionPoint)
293 HTMLElement::removedFrom(insertionPoint);
294 if (!insertionPoint->inDocument())
297 m_linkLoader.released();
299 if (m_isInShadowTree) {
300 ASSERT(!linkStyle() || !linkStyle()->hasSheet());
303 document().styleEngine()->removeStyleSheetCandidateNode(this);
305 RefPtrWillBeRawPtr<StyleSheet> removedSheet = sheet();
308 m_link->ownerRemoved();
310 document().removedStyleSheet(removedSheet.get());
313 void HTMLLinkElement::finishParsingChildren()
315 m_createdByParser = false;
316 HTMLElement::finishParsingChildren();
319 bool HTMLLinkElement::styleSheetIsLoading() const
321 return linkStyle() && linkStyle()->styleSheetIsLoading();
324 void HTMLLinkElement::linkLoaded()
326 dispatchEvent(Event::create(EventTypeNames::load));
329 void HTMLLinkElement::linkLoadingErrored()
331 dispatchEvent(Event::create(EventTypeNames::error));
334 void HTMLLinkElement::didStartLinkPrerender()
336 dispatchEvent(Event::create(EventTypeNames::webkitprerenderstart));
339 void HTMLLinkElement::didStopLinkPrerender()
341 dispatchEvent(Event::create(EventTypeNames::webkitprerenderstop));
344 void HTMLLinkElement::didSendLoadForLinkPrerender()
346 dispatchEvent(Event::create(EventTypeNames::webkitprerenderload));
349 void HTMLLinkElement::didSendDOMContentLoadedForLinkPrerender()
351 dispatchEvent(Event::create(EventTypeNames::webkitprerenderdomcontentloaded));
354 bool HTMLLinkElement::sheetLoaded()
357 return linkStyle()->sheetLoaded();
360 void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
363 linkStyle()->notifyLoadedSheetAndAllCriticalSubresources(errorOccurred);
366 void HTMLLinkElement::dispatchPendingLoadEvents()
368 linkLoadEventSender().dispatchPendingEvents();
371 void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender)
373 ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender());
375 if (m_link->hasLoaded())
378 linkLoadingErrored();
381 void HTMLLinkElement::scheduleEvent()
383 linkLoadEventSender().dispatchEventSoon(this);
386 void HTMLLinkElement::startLoadingDynamicSheet()
389 linkStyle()->startLoadingDynamicSheet();
392 bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const
394 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
397 bool HTMLLinkElement::hasLegalLinkAttribute(const QualifiedName& name) const
399 return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
402 const QualifiedName& HTMLLinkElement::subResourceAttributeName() const
404 // If the link element is not css, ignore it.
405 if (equalIgnoringCase(getAttribute(typeAttr), "text/css")) {
406 // FIXME: Add support for extracting links of sub-resources which
407 // are inside style-sheet such as @import, @font-face, url(), etc.
410 return HTMLElement::subResourceAttributeName();
413 KURL HTMLLinkElement::href() const
415 return document().completeURL(getAttribute(hrefAttr));
418 const AtomicString& HTMLLinkElement::rel() const
420 return getAttribute(relAttr);
423 const AtomicString& HTMLLinkElement::type() const
425 return getAttribute(typeAttr);
428 bool HTMLLinkElement::async() const
430 return fastHasAttribute(HTMLNames::asyncAttr);
433 IconType HTMLLinkElement::iconType() const
435 return m_relAttribute.iconType();
438 const Vector<IntSize>& HTMLLinkElement::iconSizes() const
443 DOMSettableTokenList* HTMLLinkElement::sizes() const
445 return m_sizes.get();
448 void HTMLLinkElement::trace(Visitor* visitor)
450 visitor->trace(m_link);
451 visitor->trace(m_sizes);
452 HTMLElement::trace(visitor);
455 void HTMLLinkElement::attributeWillChange(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue)
457 if (name == hrefAttr && inDocument()) {
458 V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
459 if (activityLogger) {
462 argv.append(hrefAttr.toString());
463 argv.append(oldValue);
464 argv.append(newValue);
465 activityLogger->logEvent("blinkSetAttribute", argv.size(), argv.data());
468 HTMLElement::attributeWillChange(name, oldValue, newValue);
471 PassOwnPtrWillBeRawPtr<LinkStyle> LinkStyle::create(HTMLLinkElement* owner)
473 return adoptPtrWillBeNoop(new LinkStyle(owner));
476 LinkStyle::LinkStyle(HTMLLinkElement* owner)
477 : LinkResource(owner)
478 , m_disabledState(Unset)
479 , m_pendingSheetType(None)
482 , m_loadedSheet(false)
486 LinkStyle::~LinkStyle()
490 m_sheet->clearOwnerNode();
494 Document& LinkStyle::document()
496 return m_owner->document();
499 void LinkStyle::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CSSStyleSheetResource* cachedStyleSheet)
501 if (!m_owner->inDocument()) {
506 // Completing the sheet load may cause scripts to execute.
507 RefPtrWillBeRawPtr<Node> protector(m_owner.get());
509 CSSParserContext parserContext(m_owner->document(), 0, baseURL, charset);
511 if (RefPtrWillBeRawPtr<StyleSheetContents> restoredSheet = const_cast<CSSStyleSheetResource*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext)) {
512 ASSERT(restoredSheet->isCacheable());
513 ASSERT(!restoredSheet->isLoading());
517 m_sheet = CSSStyleSheet::create(restoredSheet, m_owner);
518 m_sheet->setMediaQueries(MediaQuerySet::create(m_owner->media()));
519 m_sheet->setTitle(m_owner->title());
522 restoredSheet->checkLoaded();
526 RefPtrWillBeRawPtr<StyleSheetContents> styleSheet = StyleSheetContents::create(href, parserContext);
530 m_sheet = CSSStyleSheet::create(styleSheet, m_owner);
531 m_sheet->setMediaQueries(MediaQuerySet::create(m_owner->media()));
532 m_sheet->setTitle(m_owner->title());
534 styleSheet->parseAuthorStyleSheet(cachedStyleSheet, m_owner->document().securityOrigin());
537 styleSheet->notifyLoadedSheet(cachedStyleSheet);
538 styleSheet->checkLoaded();
540 if (styleSheet->isCacheable())
541 const_cast<CSSStyleSheetResource*>(cachedStyleSheet)->saveParsedStyleSheet(styleSheet);
544 bool LinkStyle::sheetLoaded()
546 if (!styleSheetIsLoading()) {
547 removePendingSheet();
553 void LinkStyle::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
557 m_loadedSheet = !errorOccurred;
559 m_owner->scheduleEvent();
563 void LinkStyle::startLoadingDynamicSheet()
565 ASSERT(m_pendingSheetType < Blocking);
566 addPendingSheet(Blocking);
569 void LinkStyle::clearSheet()
572 ASSERT(m_sheet->ownerNode() == m_owner);
573 m_sheet->clearOwnerNode();
577 bool LinkStyle::styleSheetIsLoading() const
583 return m_sheet->contents()->isLoading();
586 void LinkStyle::addPendingSheet(PendingSheetType type)
588 if (type <= m_pendingSheetType)
590 m_pendingSheetType = type;
592 if (m_pendingSheetType == NonBlocking)
594 m_owner->document().styleEngine()->addPendingSheet();
597 void LinkStyle::removePendingSheet()
599 PendingSheetType type = m_pendingSheetType;
600 m_pendingSheetType = None;
604 if (type == NonBlocking) {
605 // Tell StyleEngine to re-compute styleSheets of this m_owner's treescope.
606 m_owner->document().styleEngine()->modifiedStyleSheetCandidateNode(m_owner);
607 // Document::removePendingSheet() triggers the style selector recalc for blocking sheets.
608 // FIXME: We don't have enough knowledge at this point to know if we're adding or removing a sheet
609 // so we can't call addedStyleSheet() or removedStyleSheet().
610 m_owner->document().styleResolverChanged();
614 m_owner->document().styleEngine()->removePendingSheet(m_owner);
617 void LinkStyle::setDisabledState(bool disabled)
619 LinkStyle::DisabledState oldDisabledState = m_disabledState;
620 m_disabledState = disabled ? Disabled : EnabledViaScript;
621 if (oldDisabledState != m_disabledState) {
622 // If we change the disabled state while the sheet is still loading, then we have to
623 // perform three checks:
624 if (styleSheetIsLoading()) {
625 // Check #1: The sheet becomes disabled while loading.
626 if (m_disabledState == Disabled)
627 removePendingSheet();
629 // Check #2: An alternate sheet becomes enabled while it is still loading.
630 if (m_owner->relAttribute().isAlternate() && m_disabledState == EnabledViaScript)
631 addPendingSheet(Blocking);
633 // Check #3: A main sheet becomes enabled while it was still loading and
634 // after it was disabled via script. It takes really terrible code to make this
635 // happen (a double toggle for no reason essentially). This happens on
636 // virtualplastic.net, which manages to do about 12 enable/disables on only 3
638 if (!m_owner->relAttribute().isAlternate() && m_disabledState == EnabledViaScript && oldDisabledState == Disabled)
639 addPendingSheet(Blocking);
641 // If the sheet is already loading just bail.
646 m_sheet->setDisabled(disabled);
648 // Load the sheet, since it's never been loaded before.
649 if (!m_sheet && m_disabledState == EnabledViaScript) {
650 if (m_owner->shouldProcessStyle())
653 // FIXME: We don't have enough knowledge here to know if we should call addedStyleSheet() or removedStyleSheet().
654 m_owner->document().styleResolverChanged();
659 void LinkStyle::process()
661 ASSERT(m_owner->shouldProcessStyle());
662 String type = m_owner->typeValue().lower();
663 LinkRequestBuilder builder(m_owner);
665 if (m_owner->relAttribute().iconType() != InvalidIcon && builder.url().isValid() && !builder.url().isEmpty()) {
666 if (!m_owner->shouldLoadLink())
668 if (!document().securityOrigin()->canDisplay(builder.url()))
670 if (!document().contentSecurityPolicy()->allowImageFromSource(builder.url()))
672 if (document().frame() && document().frame()->loader().client())
673 document().frame()->loader().client()->dispatchDidChangeIcons(m_owner->relAttribute().iconType());
676 if (!m_owner->loadLink(type, builder.url()))
679 if ((m_disabledState != Disabled) && (m_owner->relAttribute().isStyleSheet() || m_owner->relAttribute().isTransitionExitingStylesheet())
680 && shouldLoadResource() && builder.url().isValid()) {
683 removePendingSheet();
687 if (!m_owner->shouldLoadLink())
692 bool mediaQueryMatches = true;
693 LocalFrame* frame = loadingFrame();
694 if (!m_owner->media().isEmpty() && frame && frame->document()) {
695 RefPtr<RenderStyle> documentStyle = StyleResolver::styleForDocument(*frame->document());
696 RefPtrWillBeRawPtr<MediaQuerySet> media = MediaQuerySet::create(m_owner->media());
697 MediaQueryEvaluator evaluator(frame);
698 mediaQueryMatches = evaluator.eval(media.get());
701 // Don't hold up render tree construction and script execution on stylesheets
702 // that are not needed for the rendering at the moment.
703 bool blocking = mediaQueryMatches && !m_owner->isAlternate();
704 addPendingSheet(blocking ? Blocking : NonBlocking);
706 // Load stylesheets that are not needed for the rendering immediately with low priority.
707 FetchRequest request = builder.build(blocking);
708 AtomicString crossOriginMode = m_owner->fastGetAttribute(HTMLNames::crossoriginAttr);
709 if (!crossOriginMode.isNull())
710 request.setCrossOriginAccessControl(document().securityOrigin(), crossOriginMode);
711 setResource(document().fetcher()->fetchCSSStyleSheet(request));
714 // The request may have been denied if (for example) the stylesheet is local and the document is remote.
716 removePendingSheet();
718 } else if (m_sheet) {
719 // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
720 RefPtrWillBeRawPtr<StyleSheet> removedSheet = m_sheet.get();
722 document().removedStyleSheet(removedSheet.get());
726 void LinkStyle::setSheetTitle(const String& title)
729 m_sheet->setTitle(title);
732 void LinkStyle::ownerRemoved()
737 if (styleSheetIsLoading())
738 removePendingSheet();
741 void LinkStyle::trace(Visitor* visitor)
743 visitor->trace(m_sheet);
744 LinkResource::trace(visitor);