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