CTTE: More Document references please
[WebKit-https.git] / Source / WebCore / html / HTMLLinkElement.cpp
index 9b954aa..6ab9b71 100644 (file)
 #include "CachedCSSStyleSheet.h"
 #include "CachedResource.h"
 #include "CachedResourceLoader.h"
-#include "CSSStyleSelector.h"
+#include "CachedResourceRequest.h"
 #include "Document.h"
+#include "DocumentStyleSheetCollection.h"
+#include "Event.h"
+#include "EventSender.h"
 #include "Frame.h"
 #include "FrameLoader.h"
 #include "FrameLoaderClient.h"
 #include "MediaList.h"
 #include "MediaQueryEvaluator.h"
 #include "Page.h"
-#include "ResourceHandle.h"
-#include "ScriptEventListener.h"
+#include "RenderStyle.h"
 #include "SecurityOrigin.h"
 #include "Settings.h"
+#include "StyleInheritedData.h"
+#include "StyleResolveForDocument.h"
+#include "StyleSheetContents.h"
+#include <wtf/Ref.h>
 #include <wtf/StdLibExtras.h>
 
 namespace WebCore {
 
 using namespace HTMLNames;
 
-inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document* document, bool createdByParser)
+static LinkEventSender& linkLoadEventSender()
+{
+    DEFINE_STATIC_LOCAL(LinkEventSender, sharedLoadEventSender, (eventNames().loadEvent));
+    return sharedLoadEventSender;
+}
+
+inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document& document, bool createdByParser)
     : HTMLElement(tagName, document)
     , m_linkLoader(this)
     , m_sizes(DOMSettableTokenList::create())
@@ -59,12 +71,14 @@ inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document*
     , m_loading(false)
     , m_createdByParser(createdByParser)
     , m_isInShadowTree(false)
-    , m_pendingSheetType(None)
+    , m_firedLoad(false)
+    , m_loadedSheet(false)
+    , m_pendingSheetType(Unknown)
 {
     ASSERT(hasTagName(linkTag));
 }
 
-PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document* document, bool createdByParser)
+PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document& document, bool createdByParser)
 {
     return adoptRef(new HTMLLinkElement(tagName, document, createdByParser));
 }
@@ -74,13 +88,13 @@ HTMLLinkElement::~HTMLLinkElement()
     if (m_sheet)
         m_sheet->clearOwnerNode();
 
-    if (m_cachedSheet) {
+    if (m_cachedSheet)
         m_cachedSheet->removeClient(this);
-        removePendingSheet();
-    }
 
     if (inDocument())
-        document()->removeStyleSheetCandidateNode(this);
+        document().styleSheetCollection()->removeStyleSheetCandidateNode(*this);
+
+    linkLoadEventSender().cancelEvent(this);
 }
 
 void HTMLLinkElement::setDisabledState(bool disabled)
@@ -90,14 +104,14 @@ void HTMLLinkElement::setDisabledState(bool disabled)
     if (oldDisabledState != m_disabledState) {
         // If we change the disabled state while the sheet is still loading, then we have to
         // perform three checks:
-        if (isLoading()) {
+        if (styleSheetIsLoading()) {
             // Check #1: The sheet becomes disabled while loading.
             if (m_disabledState == Disabled)
                 removePendingSheet();
 
             // Check #2: An alternate sheet becomes enabled while it is still loading.
             if (m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript)
-                addPendingSheet(Blocking);
+                addPendingSheet(ActiveSheet);
 
             // Check #3: A main sheet becomes enabled while it was still loading and
             // after it was disabled via script. It takes really terrible code to make this
@@ -105,7 +119,7 @@ void HTMLLinkElement::setDisabledState(bool disabled)
             // virtualplastic.net, which manages to do about 12 enable/disables on only 3
             // sheets. :)
             if (!m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript && oldDisabledState == Disabled)
-                addPendingSheet(Blocking);
+                addPendingSheet(ActiveSheet);
 
             // If the sheet is already loading just bail.
             return;
@@ -115,57 +129,44 @@ void HTMLLinkElement::setDisabledState(bool disabled)
         if (!m_sheet && m_disabledState == EnabledViaScript)
             process();
         else
-            document()->styleSelectorChanged(DeferRecalcStyle); // Update the style selector.
+            document().styleResolverChanged(DeferRecalcStyle); // Update the style selector.
     }
 }
 
-StyleSheet* HTMLLinkElement::sheet() const
+void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
 {
-    return m_sheet.get();
-}
-
-void HTMLLinkElement::parseMappedAttribute(Attribute* attr)
-{
-    if (attr->name() == relAttr) {
-        m_relAttribute = LinkRelAttribute(attr->value());
+    if (name == relAttr) {
+        m_relAttribute = LinkRelAttribute(value);
         process();
-    } else if (attr->name() == hrefAttr) {
-        String url = stripLeadingAndTrailingHTMLSpaces(attr->value());
-        m_url = url.isEmpty() ? KURL() : document()->completeURL(url);
+    } else if (name == hrefAttr) {
         process();
-    } else if (attr->name() == typeAttr) {
-        m_type = attr->value();
+    } else if (name == typeAttr) {
+        m_type = value;
         process();
-    } else if (attr->name() == sizesAttr) {
-        setSizes(attr->value());
+    } else if (name == sizesAttr) {
+        setSizes(value);
         process();
-    } else if (attr->name() == mediaAttr) {
-        m_media = attr->value().string().lower();
+    } else if (name == mediaAttr) {
+        m_media = value.string().lower();
         process();
-    } else if (attr->name() == disabledAttr)
-        setDisabledState(!attr->isNull());
-    else if (attr->name() == onbeforeloadAttr)
-        setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
-#if ENABLE(LINK_PREFETCH)
-    else if (attr->name() == onloadAttr)
-        setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
-    else if (attr->name() == onerrorAttr)
-        setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attr));
-#endif
+    } else if (name == disabledAttr)
+        setDisabledState(!value.isNull());
+    else if (name == onbeforeloadAttr)
+        setAttributeEventListener(eventNames().beforeloadEvent, name, value);
     else {
-        if (attr->name() == titleAttr && m_sheet)
-            m_sheet->setTitle(attr->value());
-        HTMLElement::parseMappedAttribute(attr);
+        if (name == titleAttr && m_sheet)
+            m_sheet->setTitle(value);
+        HTMLElement::parseAttribute(name, value);
     }
 }
 
 bool HTMLLinkElement::shouldLoadLink()
 {
-    RefPtr<Document> originalDocument = document();
-    if (!dispatchBeforeLoadEvent(m_url))
+    RefPtr<Document> originalDocument = &document();
+    if (!dispatchBeforeLoadEvent(getNonEmptyURLAttribute(hrefAttr)))
         return false;
     // A beforeload handler might have removed us from the document or changed the document.
-    if (!inDocument() || document() != originalDocument)
+    if (!inDocument() || &document() != originalDocument)
         return false;
     return true;
 }
@@ -178,18 +179,19 @@ void HTMLLinkElement::process()
     }
 
     String type = m_type.lower();
+    KURL url = getNonEmptyURLAttribute(hrefAttr);
 
-    if (!m_linkLoader.loadLink(m_relAttribute, type, m_url, document()))
+    if (!m_linkLoader.loadLink(m_relAttribute, type, m_sizes->toString(), url, &document()))
         return;
 
-    bool acceptIfTypeContainsTextCSS = document()->page() && document()->page()->settings() && document()->page()->settings()->treatsAnyTextCSSLinkAsStylesheet();
+    bool acceptIfTypeContainsTextCSS = document().page() && document().page()->settings().treatsAnyTextCSSLinkAsStylesheet();
 
     if (m_disabledState != Disabled && (m_relAttribute.m_isStyleSheet || (acceptIfTypeContainsTextCSS && type.contains("text/css")))
-        && document()->frame() && m_url.isValid()) {
+        && document().frame() && url.isValid()) {
         
         String charset = getAttribute(charsetAttr);
-        if (charset.isEmpty() && document()->frame())
-            charset = document()->charset();
+        if (charset.isEmpty() && document().frame())
+            charset = document().charset();
         
         if (m_cachedSheet) {
             removePendingSheet();
@@ -204,21 +206,22 @@ void HTMLLinkElement::process()
 
         bool mediaQueryMatches = true;
         if (!m_media.isEmpty()) {
-            RefPtr<RenderStyle> documentStyle = CSSStyleSelector::styleForDocument(document());
-            RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media);
-            MediaQueryEvaluator evaluator(document()->frame()->view()->mediaType(), document()->frame(), documentStyle.get());
+            RefPtr<RenderStyle> documentStyle = Style::resolveForDocument(document());
+            RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(m_media);
+            MediaQueryEvaluator evaluator(document().frame()->view()->mediaType(), document().frame(), documentStyle.get());
             mediaQueryMatches = evaluator.eval(media.get());
         }
 
         // Don't hold up render tree construction and script execution on stylesheets
         // that are not needed for the rendering at the moment.
-        bool blocking = mediaQueryMatches && !isAlternate();
-        addPendingSheet(blocking ? Blocking : NonBlocking);
+        bool isActive = mediaQueryMatches && !isAlternate();
+        addPendingSheet(isActive ? ActiveSheet : InactiveSheet);
 
         // Load stylesheets that are not needed for the rendering immediately with low priority.
-        ResourceLoadPriority priority = blocking ? ResourceLoadPriorityUnresolved : ResourceLoadPriorityVeryLow;
-        ResourceRequest request(document()->completeURL(m_url));
-        m_cachedSheet = document()->cachedResourceLoader()->requestCSSStyleSheet(request, charset, priority);
+        ResourceLoadPriority priority = isActive ? ResourceLoadPriorityUnresolved : ResourceLoadPriorityVeryLow;
+        CachedResourceRequest request(ResourceRequest(document().completeURL(url)), charset, priority);
+        request.setInitiator(this);
+        m_cachedSheet = document().cachedResourceLoader()->requestCSSStyleSheet(request);
         
         if (m_cachedSheet)
             m_cachedSheet->addClient(this);
@@ -229,42 +232,57 @@ void HTMLLinkElement::process()
         }
     } else if (m_sheet) {
         // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
-        m_sheet = 0;
-        document()->styleSelectorChanged(DeferRecalcStyle);
+        clearSheet();
+        document().styleResolverChanged(DeferRecalcStyle);
     }
 }
 
-void HTMLLinkElement::insertedIntoDocument()
+void HTMLLinkElement::clearSheet()
 {
-    HTMLElement::insertedIntoDocument();
+    ASSERT(m_sheet);
+    ASSERT(m_sheet->ownerNode() == this);
+    m_sheet->clearOwnerNode();
+    m_sheet = 0;
+}
+
+Node::InsertionNotificationRequest HTMLLinkElement::insertedInto(ContainerNode* insertionPoint)
+{
+    HTMLElement::insertedInto(insertionPoint);
+    if (!insertionPoint->inDocument())
+        return InsertionDone;
 
     m_isInShadowTree = isInShadowTree();
     if (m_isInShadowTree)
-        return;
+        return InsertionDone;
 
-    document()->addStyleSheetCandidateNode(this, m_createdByParser);
+    document().styleSheetCollection()->addStyleSheetCandidateNode(*this, m_createdByParser);
 
     process();
+    return InsertionDone;
 }
 
-void HTMLLinkElement::removedFromDocument()
+void HTMLLinkElement::removedFrom(ContainerNode* insertionPoint)
 {
-    HTMLElement::removedFromDocument();
+    HTMLElement::removedFrom(insertionPoint);
+    if (!insertionPoint->inDocument())
+        return;
+
+    m_linkLoader.released();
 
     if (m_isInShadowTree) {
         ASSERT(!m_sheet);
         return;
     }
-    document()->removeStyleSheetCandidateNode(this);
+    document().styleSheetCollection()->removeStyleSheetCandidateNode(*this);
 
-    if (m_sheet) {
-        ASSERT(m_sheet->ownerNode() == this);
-        m_sheet->clearOwnerNode();
-        m_sheet = 0;
-    }
+    if (m_sheet)
+        clearSheet();
+
+    if (styleSheetIsLoading())
+        removePendingSheet(RemovePendingSheetNotifyLater);
 
-    if (document()->renderer())
-        document()->styleSelectorChanged(DeferRecalcStyle);
+    if (document().hasLivingRenderTree())
+        document().styleResolverChanged(DeferRecalcStyleIfNeeded);
 }
 
 void HTMLLinkElement::finishParsingChildren()
@@ -273,78 +291,54 @@ void HTMLLinkElement::finishParsingChildren()
     HTMLElement::finishParsingChildren();
 }
 
-void HTMLLinkElement::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* sheet)
+void HTMLLinkElement::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* cachedStyleSheet)
 {
     if (!inDocument()) {
         ASSERT(!m_sheet);
         return;
     }
+    // Completing the sheet load may cause scripts to execute.
+    Ref<HTMLLinkElement> protect(*this);
 
-    m_sheet = CSSStyleSheet::create(this, href, baseURL, charset);
+    CSSParserContext parserContext(document(), baseURL, charset);
 
-    bool strictParsing = !document()->inQuirksMode();
-    bool enforceMIMEType = strictParsing;
-    bool crossOriginCSS = false;
-    bool validMIMEType = false;
-    bool needsSiteSpecificQuirks = document()->page() && document()->page()->settings()->needsSiteSpecificQuirks();
+    if (RefPtr<StyleSheetContents> restoredSheet = const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext)) {
+        ASSERT(restoredSheet->isCacheable());
+        ASSERT(!restoredSheet->isLoading());
 
-    // Check to see if we should enforce the MIME type of the CSS resource in strict mode.
-    // Running in iWeb 2 is one example of where we don't want to - <rdar://problem/6099748>
-    if (enforceMIMEType && document()->page() && !document()->page()->settings()->enforceCSSMIMETypeInNoQuirksMode())
-        enforceMIMEType = false;
+        m_sheet = CSSStyleSheet::create(restoredSheet.release(), this);
+        m_sheet->setMediaQueries(MediaQuerySet::createAllowingDescriptionSyntax(m_media));
+        m_sheet->setTitle(title());
 
-#ifdef BUILDING_ON_LEOPARD
-    if (enforceMIMEType && needsSiteSpecificQuirks) {
-        // Covers both http and https, with or without "www."
-        if (baseURL.string().contains("mcafee.com/japan/", false))
-            enforceMIMEType = false;
-    }
-#endif
-
-    String sheetText = sheet->sheetText(enforceMIMEType, &validMIMEType);
-    m_sheet->parseString(sheetText, strictParsing);
-
-    // If we're loading a stylesheet cross-origin, and the MIME type is not
-    // standard, require the CSS to at least start with a syntactically
-    // valid CSS rule.
-    // This prevents an attacker playing games by injecting CSS strings into
-    // HTML, XML, JSON, etc. etc.
-    if (!document()->securityOrigin()->canRequest(baseURL))
-        crossOriginCSS = true;
-
-    if (crossOriginCSS && !validMIMEType && !m_sheet->hasSyntacticallyValidCSSHeader())
-        m_sheet = CSSStyleSheet::create(this, href, baseURL, charset);
-
-    if (strictParsing && needsSiteSpecificQuirks) {
-        // Work around <https://bugs.webkit.org/show_bug.cgi?id=28350>.
-        DEFINE_STATIC_LOCAL(const String, slashKHTMLFixesDotCss, ("/KHTMLFixes.css"));
-        DEFINE_STATIC_LOCAL(const String, mediaWikiKHTMLFixesStyleSheet, ("/* KHTML fix stylesheet */\n/* work around the horizontal scrollbars */\n#column-content { margin-left: 0; }\n\n"));
-        // There are two variants of KHTMLFixes.css. One is equal to mediaWikiKHTMLFixesStyleSheet,
-        // while the other lacks the second trailing newline.
-        if (baseURL.string().endsWith(slashKHTMLFixesDotCss) && !sheetText.isNull() && mediaWikiKHTMLFixesStyleSheet.startsWith(sheetText)
-                && sheetText.length() >= mediaWikiKHTMLFixesStyleSheet.length() - 1) {
-            ASSERT(m_sheet->length() == 1);
-            ExceptionCode ec;
-            m_sheet->deleteRule(0, ec);
-        }
+        m_loading = false;
+        sheetLoaded();
+        notifyLoadedSheetAndAllCriticalSubresources(false);
+        return;
     }
 
+    RefPtr<StyleSheetContents> styleSheet = StyleSheetContents::create(href, parserContext);
+
+    m_sheet = CSSStyleSheet::create(styleSheet, this);
+    m_sheet->setMediaQueries(MediaQuerySet::createAllowingDescriptionSyntax(m_media));
     m_sheet->setTitle(title());
 
-    RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(m_media);
-    m_sheet->setMedia(media.get());
+    styleSheet->parseAuthorStyleSheet(cachedStyleSheet, document().securityOrigin());
 
     m_loading = false;
-    m_sheet->checkLoaded();
+    styleSheet->notifyLoadedSheet(cachedStyleSheet);
+    styleSheet->checkLoaded();
+
+    if (styleSheet->isCacheable())
+        const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->saveParsedStyleSheet(styleSheet.release());
 }
 
-bool HTMLLinkElement::isLoading() const
+bool HTMLLinkElement::styleSheetIsLoading() const
 {
     if (m_loading)
         return true;
     if (!m_sheet)
         return false;
-    return static_cast<CSSStyleSheet *>(m_sheet.get())->isLoading();
+    return m_sheet->contents()->isLoading();
 }
 
 void HTMLLinkElement::linkLoaded()
@@ -359,28 +353,51 @@ void HTMLLinkElement::linkLoadingErrored()
 
 bool HTMLLinkElement::sheetLoaded()
 {
-    if (!isLoading()) {
+    if (!styleSheetIsLoading()) {
         removePendingSheet();
         return true;
     }
     return false;
 }
 
+void HTMLLinkElement::dispatchPendingLoadEvents()
+{
+    linkLoadEventSender().dispatchPendingEvents();
+}
+
+void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender)
+{
+    ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender());
+    if (m_loadedSheet)
+        linkLoaded();
+    else
+        linkLoadingErrored();
+}
+
+void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
+{
+    if (m_firedLoad)
+        return;
+    m_loadedSheet = !errorOccurred;
+    linkLoadEventSender().dispatchEventSoon(this);
+    m_firedLoad = true;
+}
+
 void HTMLLinkElement::startLoadingDynamicSheet()
 {
-    // We don't support multiple blocking sheets.
-    ASSERT(m_pendingSheetType < Blocking);
-    addPendingSheet(Blocking);
+    // We don't support multiple active sheets.
+    ASSERT(m_pendingSheetType < ActiveSheet);
+    addPendingSheet(ActiveSheet);
 }
 
-bool HTMLLinkElement::isURLAttribute(Attribute *attr) const
+bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const
 {
-    return attr->name() == hrefAttr;
+    return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
 }
 
 KURL HTMLLinkElement::href() const
 {
-    return document()->completeURL(getAttribute(hrefAttr));
+    return document().completeURL(getAttribute(hrefAttr));
 }
 
 String HTMLLinkElement::rel() const
@@ -398,6 +415,16 @@ String HTMLLinkElement::type() const
     return getAttribute(typeAttr);
 }
 
+IconType HTMLLinkElement::iconType() const
+{
+    return m_relAttribute.m_iconType;
+}
+
+String HTMLLinkElement::iconSizes() const
+{
+    return m_sizes->toString();
+}
+
 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
 {
     HTMLElement::addSubresourceAttributeURLs(urls);
@@ -413,8 +440,8 @@ void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
     addSubresourceURL(urls, href());
     
     // Walk the URLs linked by the linked-to stylesheet.
-    if (StyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet())
-        styleSheet->addSubresourceStyleURLs(urls);
+    if (CSSStyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet())
+        styleSheet->contents()->addSubresourceStyleURLs(urls);
 }
 
 void HTMLLinkElement::addPendingSheet(PendingSheetType type)
@@ -423,24 +450,29 @@ void HTMLLinkElement::addPendingSheet(PendingSheetType type)
         return;
     m_pendingSheetType = type;
 
-    if (m_pendingSheetType == NonBlocking)
+    if (m_pendingSheetType == InactiveSheet)
         return;
-    document()->addPendingSheet();
+    document().styleSheetCollection()->addPendingSheet();
 }
 
-void HTMLLinkElement::removePendingSheet()
+void HTMLLinkElement::removePendingSheet(RemovePendingSheetNotificationType notification)
 {
     PendingSheetType type = m_pendingSheetType;
-    m_pendingSheetType = None;
+    m_pendingSheetType = Unknown;
 
-    if (type == None)
+    if (type == Unknown)
         return;
-    if (type == NonBlocking) {
-        // Document::removePendingSheet() triggers the style selector recalc for blocking sheets.
-        document()->styleSelectorChanged(RecalcStyleImmediately);
+
+    if (type == InactiveSheet) {
+        // Document just needs to know about the sheet for exposure through document.styleSheets
+        document().styleSheetCollection()->updateActiveStyleSheets(DocumentStyleSheetCollection::OptimizedUpdate);
         return;
     }
-    document()->removePendingSheet();
+
+    document().styleSheetCollection()->removePendingSheet(
+        notification == RemovePendingSheetNotifyImmediately
+        ? DocumentStyleSheetCollection::RemovePendingSheetNotifyImmediately
+        : DocumentStyleSheetCollection::RemovePendingSheetNotifyLater);
 }
 
 DOMSettableTokenList* HTMLLinkElement::sizes() const