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