De-templatize Timer
[WebKit-https.git] / Source / WebCore / loader / ImageLoader.cpp
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.
49 namespace WTF {
50
51 template<> 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
64 namespace WebCore {
65
66 static ImageEventSender& beforeLoadEventSender()
67 {
68     static NeverDestroyed<ImageEventSender> sender(eventNames().beforeloadEvent);
69     return sender;
70 }
71
72 static ImageEventSender& loadEventSender()
73 {
74     static NeverDestroyed<ImageEventSender> sender(eventNames().loadEvent);
75     return sender;
76 }
77
78 static ImageEventSender& errorEventSender()
79 {
80     static NeverDestroyed<ImageEventSender> sender(eventNames().errorEvent);
81     return sender;
82 }
83
84 static inline bool pageIsBeingDismissed(Document& document)
85 {
86     Frame* frame = document.frame();
87     return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal;
88 }
89
90 ImageLoader::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
103 ImageLoader::~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
126 void 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
135 void 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
164 void 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
269 void ImageLoader::updateFromElementIgnoringPreviousError()
270 {
271     clearFailedLoadURL();
272     updateFromElement();
273 }
274
275 void 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
318 RenderImageResource* 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 (is<RenderImage>(*renderer) && !downcast<RenderImage>(*renderer).isGeneratedContent())
327         return &downcast<RenderImage>(*renderer).imageResource();
328
329     if (is<RenderSVGImage>(*renderer))
330         return &downcast<RenderSVGImage>(*renderer).imageResource();
331
332 #if ENABLE(VIDEO)
333     if (is<RenderVideo>(*renderer))
334         return &downcast<RenderVideo>(*renderer).imageResource();
335 #endif
336
337     return nullptr;
338 }
339
340 void 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
355 void 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
377 void ImageLoader::timerFired(Timer&)
378 {
379     element().deref();
380 }
381
382 void 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
394 void 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 = nullptr;
410     }
411
412     loadEventSender().cancelEvent(*this);
413     m_hasPendingLoadEvent = false;
414     
415     if (is<HTMLObjectElement>(element()))
416         downcast<HTMLObjectElement>(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
423 void 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
438 void 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
451 void ImageLoader::dispatchPendingBeforeLoadEvents()
452 {
453     beforeLoadEventSender().dispatchPendingEvents();
454 }
455
456 void ImageLoader::dispatchPendingLoadEvents()
457 {
458     loadEventSender().dispatchPendingEvents();
459 }
460
461 void ImageLoader::dispatchPendingErrorEvents()
462 {
463     errorEventSender().dispatchPendingEvents();
464 }
465
466 void ImageLoader::elementDidMoveToNewDocument()
467 {
468     clearFailedLoadURL();
469     setImage(0);
470 }
471
472 inline void ImageLoader::clearFailedLoadURL()
473 {
474     m_failedLoadURL = AtomicString();
475 }
476
477 }