1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@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
46namespace blink {
47
48static ImageEventSender& loadEventSender()
49{
50    DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load));
51    return sender;
52}
53
54static ImageEventSender& errorEventSender()
55{
56    DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error));
57    return sender;
58}
59
60static inline bool pageIsBeingDismissed(Document* document)
61{
62    return document->pageDismissalEventBeingDispatched() != Document::NoDismissal;
63}
64
65static 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
75class ImageLoader::Task : public blink::WebThread::Task {
76public:
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
107private:
108    ImageLoader* m_loader;
109    BypassMainWorldBehavior m_shouldBypassMainWorldCSP;
110    WeakPtrFactory<Task> m_weakFactory;
111    UpdateFromElementBehavior m_updateBehavior;
112};
113
114ImageLoader::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
128ImageLoader::~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
148void ImageLoader::trace(Visitor* visitor)
149{
150    visitor->trace(m_element);
151}
152
153void 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
162void 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
188static 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
198ResourcePtr<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
209inline void ImageLoader::crossSiteOrCSPViolationOccured(AtomicString imageSourceURL)
210{
211    m_failedLoadURL = imageSourceURL;
212    m_hasPendingErrorEvent = true;
213    errorEventSender().dispatchEventSoon(this);
214}
215
216inline void ImageLoader::clearFailedLoadURL()
217{
218    m_failedLoadURL = AtomicString();
219}
220
221inline 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
229void 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
319void 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
344KURL 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
361bool 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
371void 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
407RenderImageResource* 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
428void 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
443void 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
465void ImageLoader::timerFired(Timer<ImageLoader>*)
466{
467    m_keepAlive.clear();
468}
469
470void 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
481void 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
496void 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
510void 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
523void 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
533void ImageLoader::removeClient(ImageLoaderClient* client)
534{
535    willRemoveClient(*client);
536    m_clients.remove(client);
537}
538
539void ImageLoader::dispatchPendingLoadEvents()
540{
541    loadEventSender().dispatchPendingEvents();
542}
543
544void ImageLoader::dispatchPendingErrorEvents()
545{
546    errorEventSender().dispatchPendingEvents();
547}
548
549void ImageLoader::elementDidMoveToNewDocument()
550{
551    if (m_loadDelayCounter)
552        m_loadDelayCounter->documentChanged(m_element->document());
553    clearFailedLoadURL();
554    setImage(0);
555}
556
557void 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)
574ImageLoader::ImageLoaderClientRemover::~ImageLoaderClientRemover()
575{
576    m_loader.willRemoveClient(m_client);
577}
578#endif
579
580}
581