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