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