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 "bindings/v8/ScriptController.h"
     26 #include "core/dom/Document.h"
     27 #include "core/dom/Element.h"
     28 #include "core/dom/IncrementLoadEventDelayCount.h"
     29 #include "core/dom/Microtask.h"
     30 #include "core/events/Event.h"
     31 #include "core/events/EventSender.h"
     32 #include "core/fetch/CrossOriginAccessControl.h"
     33 #include "core/fetch/FetchRequest.h"
     34 #include "core/fetch/MemoryCache.h"
     35 #include "core/fetch/ResourceFetcher.h"
     36 #include "core/frame/LocalFrame.h"
     37 #include "core/html/HTMLImageElement.h"
     38 #include "core/html/parser/HTMLParserIdioms.h"
     39 #include "core/rendering/RenderImage.h"
     40 #include "core/rendering/RenderVideo.h"
     41 #include "core/rendering/svg/RenderSVGImage.h"
     42 #include "platform/weborigin/SecurityOrigin.h"
     43 
     44 namespace WebCore {
     45 
     46 static ImageEventSender& loadEventSender()
     47 {
     48     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load));
     49     return sender;
     50 }
     51 
     52 static ImageEventSender& errorEventSender()
     53 {
     54     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error));
     55     return sender;
     56 }
     57 
     58 static inline bool pageIsBeingDismissed(Document* document)
     59 {
     60     return document->pageDismissalEventBeingDispatched() != Document::NoDismissal;
     61 }
     62 
     63 class ImageLoader::Task : public blink::WebThread::Task {
     64 public:
     65     Task(ImageLoader* loader)
     66         : m_loader(loader)
     67         , m_shouldBypassMainWorldContentSecurityPolicy(false)
     68         , m_weakFactory(this)
     69     {
     70         LocalFrame* frame = loader->m_element->document().frame();
     71         m_shouldBypassMainWorldContentSecurityPolicy = frame->script().shouldBypassMainWorldContentSecurityPolicy();
     72     }
     73 
     74     virtual void run() OVERRIDE
     75     {
     76         if (m_loader) {
     77             m_loader->doUpdateFromElement(m_shouldBypassMainWorldContentSecurityPolicy);
     78         }
     79     }
     80 
     81     void clearLoader()
     82     {
     83         m_loader = 0;
     84     }
     85 
     86     WeakPtr<Task> createWeakPtr()
     87     {
     88         return m_weakFactory.createWeakPtr();
     89     }
     90 
     91 private:
     92     ImageLoader* m_loader;
     93     bool m_shouldBypassMainWorldContentSecurityPolicy;
     94     WeakPtrFactory<Task> m_weakFactory;
     95 };
     96 
     97 ImageLoader::ImageLoader(Element* element)
     98     : m_element(element)
     99     , m_image(0)
    100     , m_derefElementTimer(this, &ImageLoader::timerFired)
    101     , m_hasPendingLoadEvent(false)
    102     , m_hasPendingErrorEvent(false)
    103     , m_imageComplete(true)
    104     , m_loadManually(false)
    105     , m_elementIsProtected(false)
    106     , m_highPriorityClientCount(0)
    107 {
    108 }
    109 
    110 ImageLoader::~ImageLoader()
    111 {
    112     if (m_pendingTask)
    113         m_pendingTask->clearLoader();
    114 
    115     if (m_image)
    116         m_image->removeClient(this);
    117 
    118     ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
    119     if (m_hasPendingLoadEvent)
    120         loadEventSender().cancelEvent(this);
    121 
    122     ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
    123     if (m_hasPendingErrorEvent)
    124         errorEventSender().cancelEvent(this);
    125 }
    126 
    127 void ImageLoader::trace(Visitor* visitor)
    128 {
    129     visitor->trace(m_element);
    130 }
    131 
    132 void ImageLoader::setImage(ImageResource* newImage)
    133 {
    134     setImageWithoutConsideringPendingLoadEvent(newImage);
    135 
    136     // Only consider updating the protection ref-count of the Element immediately before returning
    137     // from this function as doing so might result in the destruction of this ImageLoader.
    138     updatedHasPendingEvent();
    139 }
    140 
    141 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage)
    142 {
    143     ASSERT(m_failedLoadURL.isEmpty());
    144     ImageResource* oldImage = m_image.get();
    145     if (newImage != oldImage) {
    146         sourceImageChanged();
    147         m_image = newImage;
    148         if (m_hasPendingLoadEvent) {
    149             loadEventSender().cancelEvent(this);
    150             m_hasPendingLoadEvent = false;
    151         }
    152         if (m_hasPendingErrorEvent) {
    153             errorEventSender().cancelEvent(this);
    154             m_hasPendingErrorEvent = false;
    155         }
    156         m_imageComplete = true;
    157         if (newImage)
    158             newImage->addClient(this);
    159         if (oldImage)
    160             oldImage->removeClient(this);
    161     }
    162 
    163     if (RenderImageResource* imageResource = renderImageResource())
    164         imageResource->resetAnimation();
    165 }
    166 
    167 void ImageLoader::doUpdateFromElement(bool bypassMainWorldCSP)
    168 {
    169     // We don't need to call clearLoader here: Either we were called from the
    170     // task, or our caller updateFromElement cleared the task's loader (and set
    171     // m_pendingTask to null).
    172     m_pendingTask.clear();
    173     // Make sure to only decrement the count when we exit this function
    174     OwnPtr<IncrementLoadEventDelayCount> delayLoad;
    175     delayLoad.swap(m_delayLoad);
    176 
    177     Document& document = m_element->document();
    178     if (!document.isActive())
    179         return;
    180 
    181     AtomicString attr = m_element->imageSourceURL();
    182 
    183     KURL url = imageURL();
    184     ResourcePtr<ImageResource> newImage = 0;
    185     if (!url.isNull()) {
    186         FetchRequest request(ResourceRequest(url), element()->localName());
    187         if (bypassMainWorldCSP)
    188             request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy);
    189 
    190         AtomicString crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
    191         if (!crossOriginMode.isNull())
    192             request.setCrossOriginAccessControl(document.securityOrigin(), crossOriginMode);
    193 
    194         if (m_loadManually) {
    195             bool autoLoadOtherImages = document.fetcher()->autoLoadImages();
    196             document.fetcher()->setAutoLoadImages(false);
    197             newImage = new ImageResource(request.resourceRequest());
    198             newImage->setLoading(true);
    199             document.fetcher()->m_documentResources.set(newImage->url(), newImage.get());
    200             document.fetcher()->setAutoLoadImages(autoLoadOtherImages);
    201         } else {
    202             newImage = document.fetcher()->fetchImage(request);
    203         }
    204 
    205         // If we do not have an image here, it means that a cross-site
    206         // violation occurred, or that the image was blocked via Content
    207         // Security Policy, or the page is being dismissed. Trigger an
    208         // error event if the page is not being dismissed.
    209         if (!newImage && !pageIsBeingDismissed(&document)) {
    210             m_failedLoadURL = attr;
    211             m_hasPendingErrorEvent = true;
    212             errorEventSender().dispatchEventSoon(this);
    213         } else
    214             clearFailedLoadURL();
    215     } else if (!attr.isNull()) {
    216         // Fire an error event if the url is empty.
    217         m_hasPendingErrorEvent = true;
    218         errorEventSender().dispatchEventSoon(this);
    219     }
    220 
    221     ImageResource* oldImage = m_image.get();
    222     if (newImage != oldImage) {
    223         sourceImageChanged();
    224 
    225         if (m_hasPendingLoadEvent) {
    226             loadEventSender().cancelEvent(this);
    227             m_hasPendingLoadEvent = false;
    228         }
    229 
    230         // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
    231         // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
    232         // this load and we should not cancel the event.
    233         // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
    234         if (m_hasPendingErrorEvent && newImage) {
    235             errorEventSender().cancelEvent(this);
    236             m_hasPendingErrorEvent = false;
    237         }
    238 
    239         m_image = newImage;
    240         m_hasPendingLoadEvent = newImage;
    241         m_imageComplete = !newImage;
    242 
    243         if (newImage) {
    244             updateRenderer();
    245 
    246             // If newImage is cached, addClient() will result in the load event
    247             // being queued to fire. Ensure this happens after beforeload is
    248             // dispatched.
    249             newImage->addClient(this);
    250         } else {
    251             updateRenderer();
    252         }
    253 
    254         if (oldImage)
    255             oldImage->removeClient(this);
    256     }
    257 
    258     if (RenderImageResource* imageResource = renderImageResource())
    259         imageResource->resetAnimation();
    260 
    261     // Only consider updating the protection ref-count of the Element immediately before returning
    262     // from this function as doing so might result in the destruction of this ImageLoader.
    263     updatedHasPendingEvent();
    264 }
    265 
    266 void ImageLoader::updateFromElement(LoadType loadType)
    267 {
    268     AtomicString attr = m_element->imageSourceURL();
    269 
    270     if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
    271         return;
    272 
    273     // If we have a pending task, we have to clear it -- either we're
    274     // now loading immediately, or we need to reset the task's state.
    275     if (m_pendingTask) {
    276         m_pendingTask->clearLoader();
    277         m_pendingTask.clear();
    278     }
    279 
    280     KURL url = imageURL();
    281     if (!attr.isNull() && !url.isNull()) {
    282         bool loadImmediately = shouldLoadImmediately(url) || (loadType == ForceLoadImmediately);
    283         if (loadImmediately) {
    284             doUpdateFromElement(false);
    285         } else {
    286             OwnPtr<Task> task = adoptPtr(new Task(this));
    287             m_pendingTask = task->createWeakPtr();
    288             Microtask::enqueueMicrotask(task.release());
    289             m_delayLoad = adoptPtr(new IncrementLoadEventDelayCount(m_element->document()));
    290             return;
    291         }
    292     } else {
    293         doUpdateFromElement(false);
    294     }
    295 }
    296 
    297 void ImageLoader::updateFromElementIgnoringPreviousError()
    298 {
    299     clearFailedLoadURL();
    300     updateFromElement();
    301 }
    302 
    303 KURL ImageLoader::imageURL() const
    304 {
    305     KURL url;
    306 
    307     // Don't load images for inactive documents. We don't want to slow down the
    308     // raw HTML parsing case by loading images we don't intend to display.
    309     Document& document = m_element->document();
    310     if (!document.isActive())
    311         return url;
    312 
    313     AtomicString attr = m_element->imageSourceURL();
    314 
    315     // Do not load any image if the 'src' attribute is missing or if it is
    316     // an empty string.
    317     if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
    318         url = document.completeURL(sourceURI(attr));
    319     }
    320     return url;
    321 }
    322 
    323 bool ImageLoader::shouldLoadImmediately(const KURL& url) const
    324 {
    325     if (m_loadManually)
    326         return true;
    327     if (isHTMLObjectElement(m_element) || isHTMLEmbedElement(m_element))
    328         return true;
    329 
    330     if (url.protocolIsData())
    331         return true;
    332     if (memoryCache()->resourceForURL(url))
    333         return true;
    334     return false;
    335 }
    336 
    337 void ImageLoader::notifyFinished(Resource* resource)
    338 {
    339     ASSERT(m_failedLoadURL.isEmpty());
    340     ASSERT(resource == m_image.get());
    341 
    342     m_imageComplete = true;
    343     updateRenderer();
    344 
    345     if (!m_hasPendingLoadEvent)
    346         return;
    347 
    348     if (resource->errorOccurred()) {
    349         loadEventSender().cancelEvent(this);
    350         m_hasPendingLoadEvent = false;
    351 
    352         m_hasPendingErrorEvent = true;
    353         errorEventSender().dispatchEventSoon(this);
    354 
    355         // Only consider updating the protection ref-count of the Element immediately before returning
    356         // from this function as doing so might result in the destruction of this ImageLoader.
    357         updatedHasPendingEvent();
    358         return;
    359     }
    360     if (resource->wasCanceled()) {
    361         m_hasPendingLoadEvent = false;
    362         // Only consider updating the protection ref-count of the Element immediately before returning
    363         // from this function as doing so might result in the destruction of this ImageLoader.
    364         updatedHasPendingEvent();
    365         return;
    366     }
    367     loadEventSender().dispatchEventSoon(this);
    368 }
    369 
    370 RenderImageResource* ImageLoader::renderImageResource()
    371 {
    372     RenderObject* renderer = m_element->renderer();
    373 
    374     if (!renderer)
    375         return 0;
    376 
    377     // We don't return style generated image because it doesn't belong to the ImageLoader.
    378     // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
    379     if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
    380         return toRenderImage(renderer)->imageResource();
    381 
    382     if (renderer->isSVGImage())
    383         return toRenderSVGImage(renderer)->imageResource();
    384 
    385     if (renderer->isVideo())
    386         return toRenderVideo(renderer)->imageResource();
    387 
    388     return 0;
    389 }
    390 
    391 void ImageLoader::updateRenderer()
    392 {
    393     RenderImageResource* imageResource = renderImageResource();
    394 
    395     if (!imageResource)
    396         return;
    397 
    398     // Only update the renderer if it doesn't have an image or if what we have
    399     // is a complete image.  This prevents flickering in the case where a dynamic
    400     // change is happening between two images.
    401     ImageResource* cachedImage = imageResource->cachedImage();
    402     if (m_image != cachedImage && (m_imageComplete || !cachedImage))
    403         imageResource->setImageResource(m_image.get());
    404 }
    405 
    406 void ImageLoader::updatedHasPendingEvent()
    407 {
    408     // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
    409     // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
    410     // destroyed by DOM manipulation or garbage collection.
    411     // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
    412     bool wasProtected = m_elementIsProtected;
    413     m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
    414     if (wasProtected == m_elementIsProtected)
    415         return;
    416 
    417     if (m_elementIsProtected) {
    418         if (m_derefElementTimer.isActive())
    419             m_derefElementTimer.stop();
    420         else
    421             m_keepAlive = m_element;
    422     } else {
    423         ASSERT(!m_derefElementTimer.isActive());
    424         m_derefElementTimer.startOneShot(0, FROM_HERE);
    425     }
    426 }
    427 
    428 void ImageLoader::timerFired(Timer<ImageLoader>*)
    429 {
    430     m_keepAlive.clear();
    431 }
    432 
    433 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
    434 {
    435     ASSERT(eventSender == &loadEventSender() || eventSender == &errorEventSender());
    436     const AtomicString& eventType = eventSender->eventType();
    437     if (eventType == EventTypeNames::load)
    438         dispatchPendingLoadEvent();
    439     if (eventType == EventTypeNames::error)
    440         dispatchPendingErrorEvent();
    441 }
    442 
    443 void ImageLoader::dispatchPendingLoadEvent()
    444 {
    445     if (!m_hasPendingLoadEvent)
    446         return;
    447     if (!m_image)
    448         return;
    449     m_hasPendingLoadEvent = false;
    450     if (element()->document().frame())
    451         dispatchLoadEvent();
    452 
    453     // Only consider updating the protection ref-count of the Element immediately before returning
    454     // from this function as doing so might result in the destruction of this ImageLoader.
    455     updatedHasPendingEvent();
    456 }
    457 
    458 void ImageLoader::dispatchPendingErrorEvent()
    459 {
    460     if (!m_hasPendingErrorEvent)
    461         return;
    462     m_hasPendingErrorEvent = false;
    463 
    464     if (element()->document().frame())
    465         element()->dispatchEvent(Event::create(EventTypeNames::error));
    466 
    467     // Only consider updating the protection ref-count of the Element immediately before returning
    468     // from this function as doing so might result in the destruction of this ImageLoader.
    469     updatedHasPendingEvent();
    470 }
    471 
    472 void ImageLoader::addClient(ImageLoaderClient* client)
    473 {
    474     if (client->requestsHighLiveResourceCachePriority()) {
    475         if (m_image && !m_highPriorityClientCount++)
    476             memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh);
    477     }
    478 #if ENABLE(OILPAN)
    479     m_clients.add(client, adoptPtr(new ImageLoaderClientRemover(*this, *client)));
    480 #else
    481     m_clients.add(client);
    482 #endif
    483 }
    484 
    485 void ImageLoader::willRemoveClient(ImageLoaderClient& client)
    486 {
    487     if (client.requestsHighLiveResourceCachePriority()) {
    488         ASSERT(m_highPriorityClientCount);
    489         m_highPriorityClientCount--;
    490         if (m_image && !m_highPriorityClientCount)
    491             memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow);
    492     }
    493 }
    494 
    495 void ImageLoader::removeClient(ImageLoaderClient* client)
    496 {
    497     willRemoveClient(*client);
    498     m_clients.remove(client);
    499 }
    500 
    501 void ImageLoader::dispatchPendingLoadEvents()
    502 {
    503     loadEventSender().dispatchPendingEvents();
    504 }
    505 
    506 void ImageLoader::dispatchPendingErrorEvents()
    507 {
    508     errorEventSender().dispatchPendingEvents();
    509 }
    510 
    511 void ImageLoader::elementDidMoveToNewDocument()
    512 {
    513     if (m_delayLoad) {
    514         m_delayLoad->documentChanged(m_element->document());
    515     }
    516     clearFailedLoadURL();
    517     setImage(0);
    518 }
    519 
    520 void ImageLoader::sourceImageChanged()
    521 {
    522 #if ENABLE(OILPAN)
    523     PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator end = m_clients.end();
    524     for (PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator it = m_clients.begin(); it != end; ++it) {
    525         it->key->notifyImageSourceChanged();
    526     }
    527 #else
    528     HashSet<ImageLoaderClient*>::iterator end = m_clients.end();
    529     for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) {
    530         ImageLoaderClient* handle = *it;
    531         handle->notifyImageSourceChanged();
    532     }
    533 #endif
    534 }
    535 
    536 inline void ImageLoader::clearFailedLoadURL()
    537 {
    538     m_failedLoadURL = AtomicString();
    539 }
    540 
    541 #if ENABLE(OILPAN)
    542 ImageLoader::ImageLoaderClientRemover::~ImageLoaderClientRemover()
    543 {
    544     m_loader.willRemoveClient(m_client);
    545 }
    546 #endif
    547 
    548 }
    549