Update std::expected to match libc++ coding style
[WebKit-https.git] / Source / WebCore / loader / LinkLoader.cpp
index d083ebf..895f1c3 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 Google Inc. All rights reserved.
+ * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
 #include "CachedResourceLoader.h"
 #include "CachedResourceRequest.h"
 #include "ContainerNode.h"
-#include "DNS.h"
+#include "CrossOriginAccessControl.h"
 #include "Document.h"
 #include "Frame.h"
+#include "FrameLoader.h"
+#include "FrameLoaderClient.h"
 #include "FrameView.h"
+#include "LinkHeader.h"
+#include "LinkPreloadResourceClients.h"
 #include "LinkRelAttribute.h"
+#include "LoaderStrategy.h"
+#include "MIMETypeRegistry.h"
+#include "MediaQueryEvaluator.h"
+#include "PlatformStrategies.h"
+#include "ResourceError.h"
+#include "RuntimeEnabledFeatures.h"
 #include "Settings.h"
 #include "StyleResolver.h"
 
-#if ENABLE(LINK_PRERENDER)
-#include "PrerenderHandle.h"
-#include "Prerenderer.h"
-#endif
-
 namespace WebCore {
 
-LinkLoader::LinkLoader(LinkLoaderClient* client)
+LinkLoader::LinkLoader(LinkLoaderClient& client)
     : m_client(client)
-    , m_linkLoadTimer(this, &LinkLoader::linkLoadTimerFired)
-    , m_linkLoadingErrorTimer(this, &LinkLoader::linkLoadingErrorTimerFired)
 {
 }
 
 LinkLoader::~LinkLoader()
 {
     if (m_cachedLinkResource)
-        m_cachedLinkResource->removeClient(this);
-}
-
-void LinkLoader::linkLoadTimerFired(Timer<LinkLoader>* timer)
-{
-    ASSERT_UNUSED(timer, timer == &m_linkLoadTimer);
-    m_client->linkLoaded();
+        m_cachedLinkResource->removeClient(*this);
+    if (m_preloadResourceClient)
+        m_preloadResourceClient->clear();
 }
 
-void LinkLoader::linkLoadingErrorTimerFired(Timer<LinkLoader>* timer)
+void LinkLoader::triggerEvents(const CachedResource& resource)
 {
-    ASSERT_UNUSED(timer, timer == &m_linkLoadingErrorTimer);
-    m_client->linkLoadingErrored();
+    if (resource.errorOccurred())
+        m_client.linkLoadingErrored();
+    else
+        m_client.linkLoaded();
 }
 
-void LinkLoader::notifyFinished(CachedResource* resource)
+void LinkLoader::notifyFinished(CachedResource& resource)
 {
-    ASSERT_UNUSED(resource, m_cachedLinkResource.get() == resource);
+    ASSERT_UNUSED(resource, m_cachedLinkResource.get() == &resource);
 
-    if (m_cachedLinkResource->errorOccurred())
-        m_linkLoadingErrorTimer.startOneShot(0);
-    else 
-        m_linkLoadTimer.startOneShot(0);
+    triggerEvents(*m_cachedLinkResource);
 
-    m_cachedLinkResource->removeClient(this);
-    m_cachedLinkResource = 0;
+    m_cachedLinkResource->removeClient(*this);
+    m_cachedLinkResource = nullptr;
 }
 
-#if ENABLE(LINK_PRERENDER)
-
-void LinkLoader::didStartPrerender()
+void LinkLoader::loadLinksFromHeader(const String& headerValue, const URL& baseURL, Document& document, MediaAttributeCheck mediaAttributeCheck)
 {
-    m_client->didStartLinkPrerender();
+    if (headerValue.isEmpty())
+        return;
+    LinkHeaderSet headerSet(headerValue);
+    for (auto& header : headerSet) {
+        if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty())
+            continue;
+        if (mediaAttributeCheck == MediaAttributeCheck::MediaAttributeNotEmpty) {
+            if (header.media().isEmpty())
+                continue;
+        } else {
+            if (!header.media().isEmpty())
+                continue;
+        }
+
+        LinkRelAttribute relAttribute(document, header.rel());
+        URL url(baseURL, header.url());
+        // Sanity check to avoid re-entrancy here.
+        if (equalIgnoringFragmentIdentifier(url, baseURL))
+            continue;
+        preloadIfNeeded(relAttribute, url, document, header.as(), header.media(), header.mimeType(), header.crossOrigin(), nullptr);
+    }
 }
 
-void LinkLoader::didStopPrerender()
+std::optional<CachedResource::Type> LinkLoader::resourceTypeFromAsAttribute(const String& as)
 {
-    m_client->didStopLinkPrerender();
+    if (equalLettersIgnoringASCIICase(as, "fetch"))
+        return CachedResource::RawResource;
+    if (equalLettersIgnoringASCIICase(as, "image"))
+        return CachedResource::ImageResource;
+    if (equalLettersIgnoringASCIICase(as, "script"))
+        return CachedResource::Script;
+    if (equalLettersIgnoringASCIICase(as, "style"))
+        return CachedResource::CSSStyleSheet;
+    if (RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled() && (equalLettersIgnoringASCIICase(as, "video") || equalLettersIgnoringASCIICase(as, "audio")))
+        return CachedResource::MediaResource;
+    if (equalLettersIgnoringASCIICase(as, "font"))
+        return CachedResource::FontResource;
+#if ENABLE(VIDEO_TRACK)
+    if (equalLettersIgnoringASCIICase(as, "track"))
+        return CachedResource::TextTrackResource;
+#endif
+    return std::nullopt;
 }
 
-void LinkLoader::didSendLoadForPrerender()
+static std::unique_ptr<LinkPreloadResourceClient> createLinkPreloadResourceClient(CachedResource& resource, LinkLoader& loader, CachedResource::Type type)
 {
-    m_client->didSendLoadForLinkPrerender();
+    switch (type) {
+    case CachedResource::ImageResource:
+        return LinkPreloadImageResourceClient::create(loader, static_cast<CachedImage&>(resource));
+    case CachedResource::Script:
+        return LinkPreloadScriptResourceClient::create(loader, static_cast<CachedScript&>(resource));
+    case CachedResource::CSSStyleSheet:
+        return LinkPreloadStyleResourceClient::create(loader, static_cast<CachedCSSStyleSheet&>(resource));
+    case CachedResource::FontResource:
+        return LinkPreloadFontResourceClient::create(loader, static_cast<CachedFont&>(resource));
+    case CachedResource::MediaResource:
+        if (!RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled())
+            ASSERT_NOT_REACHED();
+        FALLTHROUGH;
+#if ENABLE(VIDEO_TRACK)
+    case CachedResource::TextTrackResource:
+#endif
+    case CachedResource::RawResource:
+        return LinkPreloadRawResourceClient::create(loader, static_cast<CachedRawResource&>(resource));
+    case CachedResource::MainResource:
+    case CachedResource::Icon:
+#if ENABLE(SVG_FONTS)
+    case CachedResource::SVGFontResource:
+#endif
+    case CachedResource::SVGDocumentResource:
+#if ENABLE(XSLT)
+    case CachedResource::XSLStyleSheet:
+#endif
+    case CachedResource::Beacon:
+#if ENABLE(LINK_PREFETCH)
+    case CachedResource::LinkSubresource:
+    case CachedResource::LinkPrefetch:
+#endif
+        // None of these values is currently supported as an `as` value.
+        ASSERT_NOT_REACHED();
+    }
+    return nullptr;
 }
 
-void LinkLoader::didSendDOMContentLoadedForPrerender()
+bool LinkLoader::isSupportedType(CachedResource::Type resourceType, const String& mimeType)
 {
-    m_client->didSendDOMContentLoadedForLinkPrerender();
-}
+    if (mimeType.isEmpty())
+        return true;
+    switch (resourceType) {
+    case CachedResource::ImageResource:
+        return MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(mimeType);
+    case CachedResource::Script:
+        return MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType);
+    case CachedResource::CSSStyleSheet:
+        return MIMETypeRegistry::isSupportedStyleSheetMIMEType(mimeType);
+    case CachedResource::FontResource:
+        return MIMETypeRegistry::isSupportedFontMIMEType(mimeType);
+    case CachedResource::MediaResource:
+        if (!RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled())
+            ASSERT_NOT_REACHED();
+        return MIMETypeRegistry::isSupportedMediaMIMEType(mimeType);
 
+#if ENABLE(VIDEO_TRACK)
+    case CachedResource::TextTrackResource:
+        return MIMETypeRegistry::isSupportedTextTrackMIMEType(mimeType);
 #endif
+    case CachedResource::RawResource:
+        return true;
+    default:
+        ASSERT_NOT_REACHED();
+    }
+    return false;
+}
 
-bool LinkLoader::loadLink(const LinkRelAttribute& relAttribute, const String& type,
-                          const String& sizes, const KURL& href, Document* document)
+std::unique_ptr<LinkPreloadResourceClient> LinkLoader::preloadIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document, const String& as, const String& media, const String& mimeType, const String& crossOriginMode, LinkLoader* loader)
 {
-    // We'll record this URL per document, even if we later only use it in top level frames
-    if (relAttribute.m_iconType != InvalidIcon && href.isValid() && !href.isEmpty()) {
-        if (!m_client->shouldLoadLink()) 
-            return false;
-        document->addIconURL(href.string(), type, sizes, relAttribute.m_iconType);
+    if (!document.loader() || !relAttribute.isLinkPreload)
+        return nullptr;
+
+    ASSERT(RuntimeEnabledFeatures::sharedFeatures().linkPreloadEnabled());
+    if (!href.isValid()) {
+        document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, String("<link rel=preload> has an invalid `href` value"));
+        return nullptr;
+    }
+    auto type = LinkLoader::resourceTypeFromAsAttribute(as);
+    if (!type) {
+        document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, String("<link rel=preload> must have a valid `as` value"));
+        return nullptr;
     }
+    if (!MediaQueryEvaluator::mediaAttributeMatches(document, media))
+        return nullptr;
+    if (!isSupportedType(type.value(), mimeType))
+        return nullptr;
 
-    if (relAttribute.m_isDNSPrefetch) {
-        Settings* settings = document->settings();
+    CachedResourceRequest linkRequest(document.completeURL(href), CachedResourceLoader::defaultCachedResourceOptions(), CachedResource::defaultPriorityForResourceType(type.value()));
+    linkRequest.setInitiator("link");
+    linkRequest.setIgnoreForRequestCount(true);
+    linkRequest.setIsLinkPreload();
+
+    linkRequest.setAsPotentiallyCrossOrigin(crossOriginMode, document);
+    auto cachedLinkResource = document.cachedResourceLoader().preload(type.value(), WTFMove(linkRequest)).value_or(nullptr);
+
+    if (cachedLinkResource && loader)
+        return createLinkPreloadResourceClient(*cachedLinkResource, *loader, type.value());
+    return nullptr;
+}
+
+void LinkLoader::cancelLoad()
+{
+    if (m_preloadResourceClient)
+        m_preloadResourceClient->clear();
+}
+
+bool LinkLoader::loadLink(const LinkRelAttribute& relAttribute, const URL& href, const String& as, const String& media, const String& mimeType, const String& crossOrigin, Document& document)
+{
+    if (relAttribute.isDNSPrefetch) {
         // FIXME: The href attribute of the link element can be in "//hostname" form, and we shouldn't attempt
         // to complete that as URL <https://bugs.webkit.org/show_bug.cgi?id=48857>.
-        if (settings && settings->dnsPrefetchingEnabled() && href.isValid() && !href.isEmpty())
-            prefetchDNS(href.host());
+        if (document.settings().dnsPrefetchingEnabled() && href.isValid() && !href.isEmpty() && document.frame())
+            document.frame()->loader().client().prefetchDNS(href.host());
+    }
+
+    if (relAttribute.isLinkPreconnect && href.isValid() && href.protocolIsInHTTPFamily() && document.frame()) {
+        ASSERT(document.settings().linkPreconnectEnabled());
+        StoredCredentialsPolicy storageCredentialsPolicy = StoredCredentialsPolicy::Use;
+        if (equalIgnoringASCIICase(crossOrigin, "anonymous") && document.securityOrigin().canAccess(SecurityOrigin::create(href)))
+            storageCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
+        ASSERT(document.frame()->loader().networkingContext());
+        platformStrategies()->loaderStrategy()->preconnectTo(*document.frame()->loader().networkingContext(), href, storageCredentialsPolicy, [weakDocument = document.createWeakPtr(), href](ResourceError error) {
+            if (!weakDocument)
+                return;
+
+            if (!error.isNull())
+                weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Error, makeString(ASCIILiteral("Failed to preconnect to "), href.string(), ASCIILiteral(". Error: "), error.localizedDescription()));
+            else
+                weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Info, makeString(ASCIILiteral("Successfuly preconnected to "), href.string()));
+        });
+    }
+
+    if (m_client.shouldLoadLink()) {
+        auto resourceClient = preloadIfNeeded(relAttribute, href, document, as, media, mimeType, crossOrigin, this);
+        if (resourceClient)
+            m_preloadResourceClient = WTFMove(resourceClient);
+        else if (m_preloadResourceClient)
+            m_preloadResourceClient->clear();
     }
 
 #if ENABLE(LINK_PREFETCH)
-    if ((relAttribute.m_isLinkPrefetch || relAttribute.m_isLinkSubresource) && href.isValid() && document->frame()) {
-        if (!m_client->shouldLoadLink())
+    if ((relAttribute.isLinkPrefetch || relAttribute.isLinkSubresource) && href.isValid() && document.frame()) {
+        if (!m_client.shouldLoadLink())
             return false;
-        ResourceLoadPriority priority = ResourceLoadPriorityUnresolved;
+
+        std::optional<ResourceLoadPriority> priority;
         CachedResource::Type type = CachedResource::LinkPrefetch;
-        // We only make one request to the cachedresourcelodaer if multiple rel types are
-        // specified, 
-        if (relAttribute.m_isLinkSubresource) {
-            priority = ResourceLoadPriorityLow;
+        if (relAttribute.isLinkSubresource) {
+            // We only make one request to the cached resource loader if multiple rel types are specified;
+            // this is the higher priority, which should overwrite the lower priority.
+            priority = ResourceLoadPriority::Low;
             type = CachedResource::LinkSubresource;
         }
-        CachedResourceRequest linkRequest(ResourceRequest(document->completeURL(href)), priority);
-        
+
         if (m_cachedLinkResource) {
-            m_cachedLinkResource->removeClient(this);
-            m_cachedLinkResource = 0;
+            m_cachedLinkResource->removeClient(*this);
+            m_cachedLinkResource = nullptr;
         }
-        m_cachedLinkResource = document->cachedResourceLoader()->requestLinkResource(type, linkRequest);
+        ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
+        options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck;
+        m_cachedLinkResource = document.cachedResourceLoader().requestLinkResource(type, CachedResourceRequest(ResourceRequest(document.completeURL(href)), options, priority)).value_or(nullptr);
         if (m_cachedLinkResource)
-            m_cachedLinkResource->addClient(this);
+            m_cachedLinkResource->addClient(*this);
     }
 #endif
 
-#if ENABLE(LINK_PRERENDER)
-    if (relAttribute.m_isLinkPrerender) {
-        ASSERT(!m_prerenderHandle);
-        m_prerenderHandle = document->prerenderer()->render(this, href);
-    }
-#endif
     return true;
 }
 
-void LinkLoader::released()
-{
-    // Only prerenders need treatment here; other links either use the CachedResource interface, or are notionally
-    // atomic (dns prefetch).
-#if ENABLE(LINK_PRERENDER)
-    if (m_prerenderHandle) {
-        m_prerenderHandle->cancel();
-        m_prerenderHandle.clear();
-    }
-#endif
-}
-
 }