Rename AtomicString to AtomString
[WebKit-https.git] / Source / WebCore / loader / ImageLoader.cpp
index c73533b..86d64ea 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
- * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -22,6 +22,7 @@
 #include "config.h"
 #include "ImageLoader.h"
 
+#include "BitmapImage.h"
 #include "CachedImage.h"
 #include "CachedResourceLoader.h"
 #include "CachedResourceRequest.h"
 #include "Document.h"
 #include "Element.h"
 #include "Event.h"
+#include "EventNames.h"
 #include "EventSender.h"
 #include "Frame.h"
+#include "FrameLoader.h"
 #include "HTMLNames.h"
 #include "HTMLObjectElement.h"
 #include "HTMLParserIdioms.h"
+#include "InspectorInstrumentation.h"
 #include "Page.h"
 #include "RenderImage.h"
 #include "RenderSVGImage.h"
-#include "SecurityOrigin.h"
 #include <wtf/NeverDestroyed.h>
 
 #if ENABLE(VIDEO)
@@ -84,12 +87,12 @@ static ImageEventSender& errorEventSender()
 static inline bool pageIsBeingDismissed(Document& document)
 {
     Frame* frame = document.frame();
-    return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal;
+    return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None;
 }
 
 ImageLoader::ImageLoader(Element& element)
     : m_element(element)
-    , m_image(0)
+    , m_image(nullptr)
     , m_derefElementTimer(*this, &ImageLoader::timerFired)
     , m_hasPendingBeforeLoadEvent(false)
     , m_hasPendingLoadEvent(false)
@@ -103,7 +106,7 @@ ImageLoader::ImageLoader(Element& element)
 ImageLoader::~ImageLoader()
 {
     if (m_image)
-        m_image->removeClient(this);
+        m_image->removeClient(*this);
 
     ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(*this));
     if (m_hasPendingBeforeLoadEvent)
@@ -116,11 +119,6 @@ ImageLoader::~ImageLoader()
     ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(*this));
     if (m_hasPendingErrorEvent)
         errorEventSender().cancelEvent(*this);
-
-    // If the ImageLoader is being destroyed but it is still protecting its image-loading Element,
-    // remove that protection here.
-    if (m_elementIsProtected)
-        element().deref();
 }
 
 void ImageLoader::clearImage()
@@ -152,7 +150,7 @@ void ImageLoader::clearImageWithoutConsideringPendingLoadEvent()
         }
         m_imageComplete = true;
         if (oldImage)
-            oldImage->removeClient(this);
+            oldImage->removeClient(*this);
     }
 
     if (RenderImageResource* imageResource = renderImageResource())
@@ -161,40 +159,45 @@ void ImageLoader::clearImageWithoutConsideringPendingLoadEvent()
 
 void ImageLoader::updateFromElement()
 {
-    // If we're not making renderers for the page, then don't load images.  We don't want to slow
+    // If we're not making renderers for the page, then don't load images. We don't want to slow
     // down the raw HTML parsing case by loading images we don't intend to display.
     Document& document = element().document();
     if (!document.hasLivingRenderTree())
         return;
 
-    AtomicString attr = element().imageSourceURL();
+    AtomString attr = element().imageSourceURL();
 
-    if (attr == m_failedLoadURL)
+    // Avoid loading a URL we already failed to load.
+    if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
         return;
 
     // Do not load any image if the 'src' attribute is missing or if it is
     // an empty string.
-    CachedResourceHandle<CachedImage> newImage = 0;
+    CachedResourceHandle<CachedImage> newImage = nullptr;
     if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
-        CachedResourceRequest request(ResourceRequest(document.completeURL(sourceURI(attr))));
-        request.setInitiator(&element());
+        ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
+        options.contentSecurityPolicyImposition = element().isInUserAgentShadowTree() ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
+        options.sameOriginDataURLFlag = SameOriginDataURLFlag::Set;
 
-        String crossOriginMode = element().fastGetAttribute(HTMLNames::crossoriginAttr);
-        if (!crossOriginMode.isNull()) {
-            StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
-            updateRequestForAccessControl(request.mutableResourceRequest(), document.securityOrigin(), allowCredentials);
-        }
+        auto crossOriginAttribute = element().attributeWithoutSynchronization(HTMLNames::crossoriginAttr);
+
+        ResourceRequest resourceRequest(document.completeURL(sourceURI(attr)));
+        resourceRequest.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(m_element));
+
+        auto request = createPotentialAccessControlRequest(WTFMove(resourceRequest), document, crossOriginAttribute, WTFMove(options));
+        request.setInitiator(element());
 
         if (m_loadManually) {
             bool autoLoadOtherImages = document.cachedResourceLoader().autoLoadImages();
             document.cachedResourceLoader().setAutoLoadImages(false);
-            newImage = new CachedImage(request.resourceRequest(), m_element.document().page()->sessionID());
+            auto* page = m_element.document().page();
+            newImage = new CachedImage(WTFMove(request), page->sessionID(), &page->cookieJar());
+            newImage->setStatus(CachedResource::Pending);
             newImage->setLoading(true);
-            newImage->setOwningCachedResourceLoader(&document.cachedResourceLoader());
             document.cachedResourceLoader().m_documentResources.set(newImage->url(), newImage.get());
             document.cachedResourceLoader().setAutoLoadImages(autoLoadOtherImages);
         } else
-            newImage = document.cachedResourceLoader().requestImage(request);
+            newImage = document.cachedResourceLoader().requestImage(WTFMove(request)).value_or(nullptr);
 
         // If we do not have an image here, it means that a cross-site
         // violation occurred, or that the image was blocked via Content
@@ -212,7 +215,7 @@ void ImageLoader::updateFromElement()
         m_hasPendingErrorEvent = true;
         errorEventSender().dispatchEventSoon(*this);
     }
-    
+
     CachedImage* oldImage = m_image.get();
     if (newImage != oldImage) {
         if (m_hasPendingBeforeLoadEvent) {
@@ -250,10 +253,12 @@ void ImageLoader::updateFromElement()
             // If newImage is cached, addClient() will result in the load event
             // being queued to fire. Ensure this happens after beforeload is
             // dispatched.
-            newImage->addClient(this);
+            newImage->addClient(*this);
+        }
+        if (oldImage) {
+            oldImage->removeClient(*this);
+            updateRenderer();
         }
-        if (oldImage)
-            oldImage->removeClient(this);
     }
 
     if (RenderImageResource* imageResource = renderImageResource())
@@ -270,10 +275,10 @@ void ImageLoader::updateFromElementIgnoringPreviousError()
     updateFromElement();
 }
 
-void ImageLoader::notifyFinished(CachedResource* resource)
+void ImageLoader::notifyFinished(CachedResource& resource)
 {
     ASSERT(m_failedLoadURL.isEmpty());
-    ASSERT(resource == m_image.get());
+    ASSERT_UNUSED(resource, &resource == m_image.get());
 
     m_imageComplete = true;
     if (!hasPendingBeforeLoadEvent())
@@ -282,18 +287,20 @@ void ImageLoader::notifyFinished(CachedResource* resource)
     if (!m_hasPendingLoadEvent)
         return;
 
-    if (element().fastHasAttribute(HTMLNames::crossoriginAttr)
-        && !element().document().securityOrigin()->canRequest(image()->response().url())
-        && !resource->passesAccessControlCheck(element().document().securityOrigin())) {
+    if (m_image->resourceError().isAccessControl()) {
+        URL imageURL = m_image->url();
 
         clearImageWithoutConsideringPendingLoadEvent();
 
         m_hasPendingErrorEvent = true;
         errorEventSender().dispatchEventSoon(*this);
 
-        DEPRECATED_DEFINE_STATIC_LOCAL(String, consoleMessage, (ASCIILiteral("Cross-origin image load denied by Cross-Origin Resource Sharing policy.")));
-        element().document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, consoleMessage);
+        auto message = makeString("Cannot load image ", imageURL.string(), " due to access control checks.");
+        element().document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
 
+        if (hasPendingDecodePromises())
+            decodeError("Access control error.");
+        
         ASSERT(!m_hasPendingLoadEvent);
 
         // Only consider updating the protection ref-count of the Element immediately before returning
@@ -302,7 +309,9 @@ void ImageLoader::notifyFinished(CachedResource* resource)
         return;
     }
 
-    if (resource->wasCanceled()) {
+    if (m_image->wasCanceled()) {
+        if (hasPendingDecodePromises())
+            decodeError("Loading was canceled.");
         m_hasPendingLoadEvent = false;
         // Only consider updating the protection ref-count of the Element immediately before returning
         // from this function as doing so might result in the destruction of this ImageLoader.
@@ -310,6 +319,8 @@ void ImageLoader::notifyFinished(CachedResource* resource)
         return;
     }
 
+    if (hasPendingDecodePromises())
+        decode();
     loadEventSender().dispatchEventSoon(*this);
 }
 
@@ -343,7 +354,7 @@ void ImageLoader::updateRenderer()
         return;
 
     // Only update the renderer if it doesn't have an image or if what we have
-    // is a complete image.  This prevents flickering in the case where a dynamic
+    // is a complete image. This prevents flickering in the case where a dynamic
     // change is happening between two images.
     CachedImage* cachedImage = imageResource->cachedImage();
     if (m_image != cachedImage && (m_imageComplete || !cachedImage))
@@ -365,22 +376,76 @@ void ImageLoader::updatedHasPendingEvent()
         if (m_derefElementTimer.isActive())
             m_derefElementTimer.stop();
         else
-            element().ref();
+            m_protectedElement = &element();
     } else {
         ASSERT(!m_derefElementTimer.isActive());
-        m_derefElementTimer.startOneShot(0);
+        m_derefElementTimer.startOneShot(0_s);
     }   
 }
 
+void ImageLoader::decode(Ref<DeferredPromise>&& promise)
+{
+    m_decodingPromises.append(WTFMove(promise));
+    
+    if (!element().document().domWindow()) {
+        decodeError("Inactive document.");
+        return;
+    }
+    
+    AtomString attr = element().imageSourceURL();
+    if (stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
+        decodeError("Missing source URL.");
+        return;
+    }
+    
+    if (m_imageComplete)
+        decode();
+}
+
+void ImageLoader::decodeError(const char* message)
+{
+    ASSERT(hasPendingDecodePromises());
+    for (auto& promise : m_decodingPromises)
+        promise->reject(Exception { EncodingError, message });
+    m_decodingPromises.clear();
+}
+
+void ImageLoader::decode()
+{
+    ASSERT(hasPendingDecodePromises());
+    
+    if (!element().document().domWindow()) {
+        decodeError("Inactive document.");
+        return;
+    }
+
+    if (!m_image || !m_image->image() || m_image->errorOccurred()) {
+        decodeError("Loading error.");
+        return;
+    }
+
+    Image* image = m_image->image();
+    if (!is<BitmapImage>(image)) {
+        decodeError("Invalid image type.");
+        return;
+    }
+
+    auto& bitmapImage = downcast<BitmapImage>(*image);
+    bitmapImage.decode([promises = WTFMove(m_decodingPromises)]() mutable {
+        for (auto& promise : promises)
+            promise->resolve();
+    });
+}
+
 void ImageLoader::timerFired()
 {
-    element().deref();
+    m_protectedElement = nullptr;
 }
 
 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
 {
     ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
-    const AtomicString& eventType = eventSender->eventType();
+    const AtomString& eventType = eventSender->eventType();
     if (eventType == eventNames().beforeloadEvent)
         dispatchPendingBeforeLoadEvent();
     if (eventType == eventNames().loadEvent)
@@ -398,12 +463,17 @@ void ImageLoader::dispatchPendingBeforeLoadEvent()
     if (!element().document().hasLivingRenderTree())
         return;
     m_hasPendingBeforeLoadEvent = false;
+    Ref<Document> originalDocument = element().document();
     if (element().dispatchBeforeLoadEvent(m_image->url())) {
+        bool didEventListenerDisconnectThisElement = !element().isConnected() || &element().document() != originalDocument.ptr();
+        if (didEventListenerDisconnectThisElement)
+            return;
+        
         updateRenderer();
         return;
     }
     if (m_image) {
-        m_image->removeClient(this);
+        m_image->removeClient(*this);
         m_image = nullptr;
     }
 
@@ -439,7 +509,7 @@ void ImageLoader::dispatchPendingErrorEvent()
         return;
     m_hasPendingErrorEvent = false;
     if (element().document().hasLivingRenderTree())
-        element().dispatchEvent(Event::create(eventNames().errorEvent, false, false));
+        element().dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No));
 
     // Only consider updating the protection ref-count of the Element immediately before returning
     // from this function as doing so might result in the destruction of this ImageLoader.
@@ -469,7 +539,7 @@ void ImageLoader::elementDidMoveToNewDocument()
 
 inline void ImageLoader::clearFailedLoadURL()
 {
-    m_failedLoadURL = AtomicString();
+    m_failedLoadURL = nullAtom();
 }
 
 }