#include "config.h"
#include "ImageLoader.h"
#include "CachedImage.h"
#include "CachedResourceLoader.h"
#include "CachedResourceRequest.h"
#include "CrossOriginAccessControl.h"
#include "Document.h"
#include "Element.h"
#include "Event.h"
#include "EventSender.h"
#include "Frame.h"
#include "HTMLNames.h"
#include "HTMLObjectElement.h"
#include "HTMLParserIdioms.h"
#include "Page.h"
#include "RenderImage.h"
#include "RenderSVGImage.h"
#include "SecurityOrigin.h"
#include <wtf/NeverDestroyed.h>
#if ENABLE(VIDEO)
#include "RenderVideo.h"
#endif
#if !ASSERT_DISABLED
namespace WTF {
template<> struct ValueCheck<WebCore::ImageLoader*> {
typedef WebCore::ImageLoader* TraitType;
static void checkConsistency(const WebCore::ImageLoader* p)
{
if (!p)
return;
ValueCheck<WebCore::Element*>::checkConsistency(&p->element());
}
};
}
#endif
namespace WebCore {
static ImageEventSender& beforeLoadEventSender()
{
static NeverDestroyed<ImageEventSender> sender(eventNames().beforeloadEvent);
return sender;
}
static ImageEventSender& loadEventSender()
{
static NeverDestroyed<ImageEventSender> sender(eventNames().loadEvent);
return sender;
}
static ImageEventSender& errorEventSender()
{
static NeverDestroyed<ImageEventSender> sender(eventNames().errorEvent);
return sender;
}
static inline bool pageIsBeingDismissed(Document& document)
{
Frame* frame = document.frame();
return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal;
}
ImageLoader::ImageLoader(Element& element)
: m_element(element)
, m_image(0)
, m_derefElementTimer(this, &ImageLoader::timerFired)
, m_hasPendingBeforeLoadEvent(false)
, m_hasPendingLoadEvent(false)
, m_hasPendingErrorEvent(false)
, m_imageComplete(true)
, m_loadManually(false)
, m_elementIsProtected(false)
{
}
ImageLoader::~ImageLoader()
{
if (m_image)
m_image->removeClient(this);
ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(this));
if (m_hasPendingBeforeLoadEvent)
beforeLoadEventSender().cancelEvent(this);
ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
if (m_hasPendingLoadEvent)
loadEventSender().cancelEvent(this);
ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
if (m_hasPendingErrorEvent)
errorEventSender().cancelEvent(this);
if (m_elementIsProtected)
element().deref();
}
void ImageLoader::setImage(CachedImage* newImage)
{
setImageWithoutConsideringPendingLoadEvent(newImage);
updatedHasPendingEvent();
}
void ImageLoader::setImageWithoutConsideringPendingLoadEvent(CachedImage* newImage)
{
ASSERT(m_failedLoadURL.isEmpty());
CachedImage* oldImage = m_image.get();
if (newImage != oldImage) {
m_image = newImage;
if (m_hasPendingBeforeLoadEvent) {
beforeLoadEventSender().cancelEvent(this);
m_hasPendingBeforeLoadEvent = false;
}
if (m_hasPendingLoadEvent) {
loadEventSender().cancelEvent(this);
m_hasPendingLoadEvent = false;
}
if (m_hasPendingErrorEvent) {
errorEventSender().cancelEvent(this);
m_hasPendingErrorEvent = false;
}
m_imageComplete = true;
if (newImage)
newImage->addClient(this);
if (oldImage)
oldImage->removeClient(this);
}
if (RenderImageResource* imageResource = renderImageResource())
imageResource->resetAnimation();
}
void ImageLoader::updateFromElement()
{
Document& document = element().document();
if (!document.hasLivingRenderTree())
return;
AtomicString attr = element().imageSourceURL();
if (attr == m_failedLoadURL)
return;
CachedResourceHandle<CachedImage> newImage = 0;
if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
CachedResourceRequest request(ResourceRequest(document.completeURL(sourceURI(attr))));
request.setInitiator(&element());
String crossOriginMode = element().fastGetAttribute(HTMLNames::crossoriginAttr);
if (!crossOriginMode.isNull()) {
StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
updateRequestForAccessControl(request.mutableResourceRequest(), document.securityOrigin(), allowCredentials);
}
if (m_loadManually) {
bool autoLoadOtherImages = document.cachedResourceLoader()->autoLoadImages();
document.cachedResourceLoader()->setAutoLoadImages(false);
newImage = new CachedImage(request.resourceRequest(), m_element.document().page()->sessionID());
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);
if (!newImage && !pageIsBeingDismissed(document)) {
m_failedLoadURL = attr;
m_hasPendingErrorEvent = true;
errorEventSender().dispatchEventSoon(this);
} else
clearFailedLoadURL();
} else if (!attr.isNull()) {
m_failedLoadURL = attr;
m_hasPendingErrorEvent = true;
errorEventSender().dispatchEventSoon(this);
}
CachedImage* oldImage = m_image.get();
if (newImage != oldImage) {
if (m_hasPendingBeforeLoadEvent) {
beforeLoadEventSender().cancelEvent(this);
m_hasPendingBeforeLoadEvent = false;
}
if (m_hasPendingLoadEvent) {
loadEventSender().cancelEvent(this);
m_hasPendingLoadEvent = false;
}
if (m_hasPendingErrorEvent && newImage) {
errorEventSender().cancelEvent(this);
m_hasPendingErrorEvent = false;
}
m_image = newImage;
m_hasPendingBeforeLoadEvent = !document.isImageDocument() && newImage;
m_hasPendingLoadEvent = newImage;
m_imageComplete = !newImage;
if (newImage) {
if (!document.isImageDocument()) {
if (!document.hasListenerType(Document::BEFORELOAD_LISTENER))
dispatchPendingBeforeLoadEvent();
else
beforeLoadEventSender().dispatchEventSoon(this);
} else
updateRenderer();
newImage->addClient(this);
}
if (oldImage)
oldImage->removeClient(this);
}
if (RenderImageResource* imageResource = renderImageResource())
imageResource->resetAnimation();
updatedHasPendingEvent();
}
void ImageLoader::updateFromElementIgnoringPreviousError()
{
clearFailedLoadURL();
updateFromElement();
}
void ImageLoader::notifyFinished(CachedResource* resource)
{
ASSERT(m_failedLoadURL.isEmpty());
ASSERT(resource == m_image.get());
m_imageComplete = true;
if (!hasPendingBeforeLoadEvent())
updateRenderer();
if (!m_hasPendingLoadEvent)
return;
if (element().fastHasAttribute(HTMLNames::crossoriginAttr)
&& !element().document().securityOrigin()->canRequest(image()->response().url())
&& !resource->passesAccessControlCheck(element().document().securityOrigin())) {
setImageWithoutConsideringPendingLoadEvent(0);
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);
ASSERT(!m_hasPendingLoadEvent);
updatedHasPendingEvent();
return;
}
if (resource->wasCanceled()) {
m_hasPendingLoadEvent = false;
updatedHasPendingEvent();
return;
}
loadEventSender().dispatchEventSoon(this);
}
RenderImageResource* ImageLoader::renderImageResource()
{
auto renderer = element().renderer();
if (!renderer)
return nullptr;
if (renderer->isRenderImage() && !toRenderImage(*renderer).isGeneratedContent())
return &toRenderImage(*renderer).imageResource();
if (renderer->isSVGImage())
return &toRenderSVGImage(renderer)->imageResource();
#if ENABLE(VIDEO)
if (renderer->isVideo())
return &toRenderVideo(*renderer).imageResource();
#endif
return nullptr;
}
void ImageLoader::updateRenderer()
{
RenderImageResource* imageResource = renderImageResource();
if (!imageResource)
return;
CachedImage* cachedImage = imageResource->cachedImage();
if (m_image != cachedImage && (m_imageComplete || !cachedImage))
imageResource->setCachedImage(m_image.get());
}
void ImageLoader::updatedHasPendingEvent()
{
bool wasProtected = m_elementIsProtected;
m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
if (wasProtected == m_elementIsProtected)
return;
if (m_elementIsProtected) {
if (m_derefElementTimer.isActive())
m_derefElementTimer.stop();
else
element().ref();
} else {
ASSERT(!m_derefElementTimer.isActive());
m_derefElementTimer.startOneShot(0);
}
}
void ImageLoader::timerFired(Timer<ImageLoader>&)
{
element().deref();
}
void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
{
ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
const AtomicString& eventType = eventSender->eventType();
if (eventType == eventNames().beforeloadEvent)
dispatchPendingBeforeLoadEvent();
if (eventType == eventNames().loadEvent)
dispatchPendingLoadEvent();
if (eventType == eventNames().errorEvent)
dispatchPendingErrorEvent();
}
void ImageLoader::dispatchPendingBeforeLoadEvent()
{
if (!m_hasPendingBeforeLoadEvent)
return;
if (!m_image)
return;
if (!element().document().hasLivingRenderTree())
return;
m_hasPendingBeforeLoadEvent = false;
if (element().dispatchBeforeLoadEvent(m_image->url())) {
updateRenderer();
return;
}
if (m_image) {
m_image->removeClient(this);
m_image = 0;
}
loadEventSender().cancelEvent(this);
m_hasPendingLoadEvent = false;
if (isHTMLObjectElement(element()))
toHTMLObjectElement(element()).renderFallbackContent();
updatedHasPendingEvent();
}
void ImageLoader::dispatchPendingLoadEvent()
{
if (!m_hasPendingLoadEvent)
return;
if (!m_image)
return;
m_hasPendingLoadEvent = false;
if (element().document().hasLivingRenderTree())
dispatchLoadEvent();
updatedHasPendingEvent();
}
void ImageLoader::dispatchPendingErrorEvent()
{
if (!m_hasPendingErrorEvent)
return;
m_hasPendingErrorEvent = false;
if (element().document().hasLivingRenderTree())
element().dispatchEvent(Event::create(eventNames().errorEvent, false, false));
updatedHasPendingEvent();
}
void ImageLoader::dispatchPendingBeforeLoadEvents()
{
beforeLoadEventSender().dispatchPendingEvents();
}
void ImageLoader::dispatchPendingLoadEvents()
{
loadEventSender().dispatchPendingEvents();
}
void ImageLoader::dispatchPendingErrorEvents()
{
errorEventSender().dispatchPendingEvents();
}
void ImageLoader::elementDidMoveToNewDocument()
{
clearFailedLoadURL();
setImage(0);
}
inline void ImageLoader::clearFailedLoadURL()
{
m_failedLoadURL = AtomicString();
}
}