Home | History | Annotate | Download | only in loader
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved.
      5  *
      6  * This library is free software; you can redistribute it and/or
      7  * modify it under the terms of the GNU Library General Public
      8  * License as published by the Free Software Foundation; either
      9  * version 2 of the License, or (at your option) any later version.
     10  *
     11  * This library is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14  * Library General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU Library General Public License
     17  * along with this library; see the file COPYING.LIB.  If not, write to
     18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     19  * Boston, MA 02110-1301, USA.
     20  */
     21 
     22 #include "config.h"
     23 #include "core/loader/ImageLoader.h"
     24 
     25 #include "HTMLNames.h"
     26 #include "core/dom/Document.h"
     27 #include "core/dom/Element.h"
     28 #include "core/dom/Event.h"
     29 #include "core/dom/EventSender.h"
     30 #include "core/html/HTMLObjectElement.h"
     31 #include "core/html/parser/HTMLParserIdioms.h"
     32 #include "core/loader/CrossOriginAccessControl.h"
     33 #include "core/loader/cache/FetchRequest.h"
     34 #include "core/loader/cache/ImageResource.h"
     35 #include "core/loader/cache/ResourceFetcher.h"
     36 #include "core/page/Frame.h"
     37 #include "core/rendering/RenderImage.h"
     38 #include "core/rendering/RenderVideo.h"
     39 #include "core/rendering/svg/RenderSVGImage.h"
     40 #include "weborigin/SecurityOrigin.h"
     41 
     42 namespace WebCore {
     43 
     44 static ImageEventSender& beforeLoadEventSender()
     45 {
     46     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().beforeloadEvent));
     47     return sender;
     48 }
     49 
     50 static ImageEventSender& loadEventSender()
     51 {
     52     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().loadEvent));
     53     return sender;
     54 }
     55 
     56 static ImageEventSender& errorEventSender()
     57 {
     58     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().errorEvent));
     59     return sender;
     60 }
     61 
     62 static inline bool pageIsBeingDismissed(Document* document)
     63 {
     64     Frame* frame = document->frame();
     65     return frame && frame->loader()->pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal;
     66 }
     67 
     68 ImageLoader::ImageLoader(Element* element)
     69     : m_element(element)
     70     , m_image(0)
     71     , m_derefElementTimer(this, &ImageLoader::timerFired)
     72     , m_hasPendingBeforeLoadEvent(false)
     73     , m_hasPendingLoadEvent(false)
     74     , m_hasPendingErrorEvent(false)
     75     , m_imageComplete(true)
     76     , m_loadManually(false)
     77     , m_elementIsProtected(false)
     78     , m_highPriorityClientCount(0)
     79 {
     80 }
     81 
     82 ImageLoader::~ImageLoader()
     83 {
     84     if (m_image)
     85         m_image->removeClient(this);
     86 
     87     ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(this));
     88     if (m_hasPendingBeforeLoadEvent)
     89         beforeLoadEventSender().cancelEvent(this);
     90 
     91     ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
     92     if (m_hasPendingLoadEvent)
     93         loadEventSender().cancelEvent(this);
     94 
     95     ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
     96     if (m_hasPendingErrorEvent)
     97         errorEventSender().cancelEvent(this);
     98 
     99     // If the ImageLoader is being destroyed but it is still protecting its image-loading Element,
    100     // remove that protection here.
    101     if (m_elementIsProtected)
    102         m_element->deref();
    103 }
    104 
    105 void ImageLoader::setImage(ImageResource* newImage)
    106 {
    107     setImageWithoutConsideringPendingLoadEvent(newImage);
    108 
    109     // Only consider updating the protection ref-count of the Element immediately before returning
    110     // from this function as doing so might result in the destruction of this ImageLoader.
    111     updatedHasPendingEvent();
    112 }
    113 
    114 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage)
    115 {
    116     ASSERT(m_failedLoadURL.isEmpty());
    117     ImageResource* oldImage = m_image.get();
    118     if (newImage != oldImage) {
    119         sourceImageChanged();
    120         m_image = newImage;
    121         if (m_hasPendingBeforeLoadEvent) {
    122             beforeLoadEventSender().cancelEvent(this);
    123             m_hasPendingBeforeLoadEvent = false;
    124         }
    125         if (m_hasPendingLoadEvent) {
    126             loadEventSender().cancelEvent(this);
    127             m_hasPendingLoadEvent = false;
    128         }
    129         if (m_hasPendingErrorEvent) {
    130             errorEventSender().cancelEvent(this);
    131             m_hasPendingErrorEvent = false;
    132         }
    133         m_imageComplete = true;
    134         if (newImage)
    135             newImage->addClient(this);
    136         if (oldImage)
    137             oldImage->removeClient(this);
    138     }
    139 
    140     if (RenderImageResource* imageResource = renderImageResource())
    141         imageResource->resetAnimation();
    142 }
    143 
    144 void ImageLoader::updateFromElement()
    145 {
    146     // If we're not making renderers for the page, then don't load images.  We don't want to slow
    147     // down the raw HTML parsing case by loading images we don't intend to display.
    148     Document* document = m_element->document();
    149     if (!document->renderer())
    150         return;
    151 
    152     AtomicString attr = m_element->imageSourceURL();
    153 
    154     if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
    155         return;
    156 
    157     // Do not load any image if the 'src' attribute is missing or if it is
    158     // an empty string.
    159     ResourcePtr<ImageResource> newImage = 0;
    160     if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
    161         FetchRequest request(ResourceRequest(document->completeURL(sourceURI(attr))), element()->localName());
    162 
    163         String crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
    164         if (!crossOriginMode.isNull()) {
    165             StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
    166             updateRequestForAccessControl(request.mutableResourceRequest(), document->securityOrigin(), allowCredentials);
    167         }
    168 
    169         if (m_loadManually) {
    170             bool autoLoadOtherImages = document->fetcher()->autoLoadImages();
    171             document->fetcher()->setAutoLoadImages(false);
    172             newImage = new ImageResource(request.resourceRequest());
    173             newImage->setLoading(true);
    174             document->fetcher()->m_documentResources.set(newImage->url(), newImage.get());
    175             document->fetcher()->setAutoLoadImages(autoLoadOtherImages);
    176         } else {
    177             newImage = document->fetcher()->requestImage(request);
    178         }
    179 
    180         // If we do not have an image here, it means that a cross-site
    181         // violation occurred, or that the image was blocked via Content
    182         // Security Policy, or the page is being dismissed. Trigger an
    183         // error event if the page is not being dismissed.
    184         if (!newImage && !pageIsBeingDismissed(document)) {
    185             m_failedLoadURL = attr;
    186             m_hasPendingErrorEvent = true;
    187             errorEventSender().dispatchEventSoon(this);
    188         } else
    189             clearFailedLoadURL();
    190     } else if (!attr.isNull()) {
    191         // Fire an error event if the url is empty.
    192         m_hasPendingErrorEvent = true;
    193         errorEventSender().dispatchEventSoon(this);
    194     }
    195 
    196     ImageResource* oldImage = m_image.get();
    197     if (newImage != oldImage) {
    198         sourceImageChanged();
    199 
    200         if (m_hasPendingBeforeLoadEvent) {
    201             beforeLoadEventSender().cancelEvent(this);
    202             m_hasPendingBeforeLoadEvent = false;
    203         }
    204         if (m_hasPendingLoadEvent) {
    205             loadEventSender().cancelEvent(this);
    206             m_hasPendingLoadEvent = false;
    207         }
    208 
    209         // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
    210         // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
    211         // this load and we should not cancel the event.
    212         // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
    213         if (m_hasPendingErrorEvent && newImage) {
    214             errorEventSender().cancelEvent(this);
    215             m_hasPendingErrorEvent = false;
    216         }
    217 
    218         m_image = newImage;
    219         m_hasPendingBeforeLoadEvent = !m_element->document()->isImageDocument() && newImage;
    220         m_hasPendingLoadEvent = newImage;
    221         m_imageComplete = !newImage;
    222 
    223         if (newImage) {
    224             if (!m_element->document()->isImageDocument()) {
    225                 if (!m_element->document()->hasListenerType(Document::BEFORELOAD_LISTENER))
    226                     dispatchPendingBeforeLoadEvent();
    227                 else
    228                     beforeLoadEventSender().dispatchEventSoon(this);
    229             } else
    230                 updateRenderer();
    231 
    232             // If newImage is cached, addClient() will result in the load event
    233             // being queued to fire. Ensure this happens after beforeload is
    234             // dispatched.
    235             newImage->addClient(this);
    236         } else {
    237             updateRenderer();
    238         }
    239 
    240         if (oldImage)
    241             oldImage->removeClient(this);
    242     }
    243 
    244     if (RenderImageResource* imageResource = renderImageResource())
    245         imageResource->resetAnimation();
    246 
    247     // Only consider updating the protection ref-count of the Element immediately before returning
    248     // from this function as doing so might result in the destruction of this ImageLoader.
    249     updatedHasPendingEvent();
    250 }
    251 
    252 void ImageLoader::updateFromElementIgnoringPreviousError()
    253 {
    254     clearFailedLoadURL();
    255     updateFromElement();
    256 }
    257 
    258 void ImageLoader::notifyFinished(Resource* resource)
    259 {
    260     ASSERT(m_failedLoadURL.isEmpty());
    261     ASSERT(resource == m_image.get());
    262 
    263     m_imageComplete = true;
    264     if (!hasPendingBeforeLoadEvent())
    265         updateRenderer();
    266 
    267     if (!m_hasPendingLoadEvent)
    268         return;
    269 
    270     if (m_element->fastHasAttribute(HTMLNames::crossoriginAttr)
    271         && !m_element->document()->securityOrigin()->canRequest(image()->response().url())
    272         && !resource->passesAccessControlCheck(m_element->document()->securityOrigin())) {
    273 
    274         setImageWithoutConsideringPendingLoadEvent(0);
    275 
    276         m_hasPendingErrorEvent = true;
    277         errorEventSender().dispatchEventSoon(this);
    278 
    279         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Cross-origin image load denied by Cross-Origin Resource Sharing policy."));
    280         m_element->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, consoleMessage);
    281 
    282         ASSERT(!m_hasPendingLoadEvent);
    283 
    284         // Only consider updating the protection ref-count of the Element immediately before returning
    285         // from this function as doing so might result in the destruction of this ImageLoader.
    286         updatedHasPendingEvent();
    287         return;
    288     }
    289 
    290     if (resource->wasCanceled()) {
    291         m_hasPendingLoadEvent = false;
    292         // Only consider updating the protection ref-count of the Element immediately before returning
    293         // from this function as doing so might result in the destruction of this ImageLoader.
    294         updatedHasPendingEvent();
    295         return;
    296     }
    297 
    298     loadEventSender().dispatchEventSoon(this);
    299 }
    300 
    301 RenderImageResource* ImageLoader::renderImageResource()
    302 {
    303     RenderObject* renderer = m_element->renderer();
    304 
    305     if (!renderer)
    306         return 0;
    307 
    308     // We don't return style generated image because it doesn't belong to the ImageLoader.
    309     // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
    310     if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
    311         return toRenderImage(renderer)->imageResource();
    312 
    313     if (renderer->isSVGImage())
    314         return toRenderSVGImage(renderer)->imageResource();
    315 
    316     if (renderer->isVideo())
    317         return toRenderVideo(renderer)->imageResource();
    318 
    319     return 0;
    320 }
    321 
    322 void ImageLoader::updateRenderer()
    323 {
    324     RenderImageResource* imageResource = renderImageResource();
    325 
    326     if (!imageResource)
    327         return;
    328 
    329     // Only update the renderer if it doesn't have an image or if what we have
    330     // is a complete image.  This prevents flickering in the case where a dynamic
    331     // change is happening between two images.
    332     ImageResource* cachedImage = imageResource->cachedImage();
    333     if (m_image != cachedImage && (m_imageComplete || !cachedImage))
    334         imageResource->setImageResource(m_image.get());
    335 }
    336 
    337 void ImageLoader::updatedHasPendingEvent()
    338 {
    339     // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
    340     // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
    341     // destroyed by DOM manipulation or garbage collection.
    342     // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
    343     bool wasProtected = m_elementIsProtected;
    344     m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
    345     if (wasProtected == m_elementIsProtected)
    346         return;
    347 
    348     if (m_elementIsProtected) {
    349         if (m_derefElementTimer.isActive())
    350             m_derefElementTimer.stop();
    351         else
    352             m_element->ref();
    353     } else {
    354         ASSERT(!m_derefElementTimer.isActive());
    355         m_derefElementTimer.startOneShot(0);
    356     }
    357 }
    358 
    359 void ImageLoader::timerFired(Timer<ImageLoader>*)
    360 {
    361     m_element->deref();
    362 }
    363 
    364 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
    365 {
    366     ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
    367     const AtomicString& eventType = eventSender->eventType();
    368     if (eventType == eventNames().beforeloadEvent)
    369         dispatchPendingBeforeLoadEvent();
    370     if (eventType == eventNames().loadEvent)
    371         dispatchPendingLoadEvent();
    372     if (eventType == eventNames().errorEvent)
    373         dispatchPendingErrorEvent();
    374 }
    375 
    376 void ImageLoader::dispatchPendingBeforeLoadEvent()
    377 {
    378     if (!m_hasPendingBeforeLoadEvent)
    379         return;
    380     if (!m_image)
    381         return;
    382     if (!m_element->document()->attached())
    383         return;
    384     m_hasPendingBeforeLoadEvent = false;
    385     if (m_element->dispatchBeforeLoadEvent(m_image->url().string())) {
    386         updateRenderer();
    387         return;
    388     }
    389     if (m_image) {
    390         m_image->removeClient(this);
    391         m_image = 0;
    392     }
    393 
    394     loadEventSender().cancelEvent(this);
    395     m_hasPendingLoadEvent = false;
    396 
    397     if (m_element->hasTagName(HTMLNames::objectTag))
    398         static_cast<HTMLObjectElement*>(m_element)->renderFallbackContent();
    399 
    400     // Only consider updating the protection ref-count of the Element immediately before returning
    401     // from this function as doing so might result in the destruction of this ImageLoader.
    402     updatedHasPendingEvent();
    403 }
    404 
    405 void ImageLoader::dispatchPendingLoadEvent()
    406 {
    407     if (!m_hasPendingLoadEvent)
    408         return;
    409     if (!m_image)
    410         return;
    411     m_hasPendingLoadEvent = false;
    412     if (element()->document()->attached())
    413         dispatchLoadEvent();
    414 
    415     // Only consider updating the protection ref-count of the Element immediately before returning
    416     // from this function as doing so might result in the destruction of this ImageLoader.
    417     updatedHasPendingEvent();
    418 }
    419 
    420 void ImageLoader::dispatchPendingErrorEvent()
    421 {
    422     if (!m_hasPendingErrorEvent)
    423         return;
    424     m_hasPendingErrorEvent = false;
    425     if (element()->document()->attached())
    426         element()->dispatchEvent(Event::create(eventNames().errorEvent, false, false));
    427 
    428     // Only consider updating the protection ref-count of the Element immediately before returning
    429     // from this function as doing so might result in the destruction of this ImageLoader.
    430     updatedHasPendingEvent();
    431 }
    432 
    433 void ImageLoader::addClient(ImageLoaderClient* client)
    434 {
    435     if (client->requestsHighLiveResourceCachePriority()) {
    436         if (m_image && !m_highPriorityClientCount++)
    437             m_image->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityHigh);
    438     }
    439     m_clients.add(client);
    440 }
    441 void ImageLoader::removeClient(ImageLoaderClient* client)
    442 {
    443     if (client->requestsHighLiveResourceCachePriority()) {
    444         ASSERT(m_highPriorityClientCount);
    445         m_highPriorityClientCount--;
    446         if (m_image && !m_highPriorityClientCount)
    447             m_image->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityLow);
    448     }
    449     m_clients.remove(client);
    450 }
    451 
    452 void ImageLoader::dispatchPendingBeforeLoadEvents()
    453 {
    454     beforeLoadEventSender().dispatchPendingEvents();
    455 }
    456 
    457 void ImageLoader::dispatchPendingLoadEvents()
    458 {
    459     loadEventSender().dispatchPendingEvents();
    460 }
    461 
    462 void ImageLoader::dispatchPendingErrorEvents()
    463 {
    464     errorEventSender().dispatchPendingEvents();
    465 }
    466 
    467 void ImageLoader::elementDidMoveToNewDocument()
    468 {
    469     clearFailedLoadURL();
    470     setImage(0);
    471 }
    472 
    473 void ImageLoader::sourceImageChanged()
    474 {
    475     HashSet<ImageLoaderClient*>::iterator end = m_clients.end();
    476     for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) {
    477         ImageLoaderClient* handle = *it;
    478         handle->notifyImageSourceChanged();
    479     }
    480 }
    481 
    482 inline void ImageLoader::clearFailedLoadURL()
    483 {
    484     m_failedLoadURL = AtomicString();
    485 }
    486 
    487 }
    488