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