85f6b2227c2e8a0a44f2c543274f2a6be1767898
[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 "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.
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         ASSERT(p->element());
59         ValueCheck<WebCore::Element*>::checkConsistency(p->element());
60     }
61 };
62
63 }
64 #endif
65
66 namespace WebCore {
67
68 static ImageEventSender& beforeLoadEventSender()
69 {
70     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().beforeloadEvent));
71     return sender;
72 }
73
74 static ImageEventSender& loadEventSender()
75 {
76     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().loadEvent));
77     return sender;
78 }
79
80 static ImageEventSender& errorEventSender()
81 {
82     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().errorEvent));
83     return sender;
84 }
85
86 static inline bool pageIsBeingDismissed(Document& document)
87 {
88     Frame* frame = document.frame();
89     return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal;
90 }
91
92 ImageLoader::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
105 ImageLoader::~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
128 void 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
137 void 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
166 void 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.hasLivingRenderTree())
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 = !document.isImageDocument() && newImage;
242         m_hasPendingLoadEvent = newImage;
243         m_imageComplete = !newImage;
244
245         if (newImage) {
246             if (!document.isImageDocument()) {
247                 if (!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
271 void ImageLoader::updateFromElementIgnoringPreviousError()
272 {
273     clearFailedLoadURL();
274     updateFromElement();
275 }
276
277 void 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
320 RenderImageResource* ImageLoader::renderImageResource()
321 {
322     auto renderer = m_element->renderer();
323     if (!renderer)
324         return nullptr;
325
326     // We don't return style generated image because it doesn't belong to the ImageLoader.
327     // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
328     if (renderer->isImage() && !toRenderImage(*renderer).isGeneratedContent())
329         return toRenderImage(*renderer).imageResource();
330
331 #if ENABLE(SVG)
332     if (renderer->isSVGImage())
333         return toRenderSVGImage(renderer)->imageResource();
334 #endif
335
336 #if ENABLE(VIDEO)
337     if (renderer->isVideo())
338         return toRenderVideo(*renderer).imageResource();
339 #endif
340
341     return nullptr;
342 }
343
344 void ImageLoader::updateRenderer()
345 {
346     RenderImageResource* imageResource = renderImageResource();
347
348     if (!imageResource)
349         return;
350
351     // Only update the renderer if it doesn't have an image or if what we have
352     // is a complete image.  This prevents flickering in the case where a dynamic
353     // change is happening between two images.
354     CachedImage* cachedImage = imageResource->cachedImage();
355     if (m_image != cachedImage && (m_imageComplete || !cachedImage))
356         imageResource->setCachedImage(m_image.get());
357 }
358
359 void ImageLoader::updatedHasPendingEvent()
360 {
361     // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
362     // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
363     // destroyed by DOM manipulation or garbage collection.
364     // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
365     bool wasProtected = m_elementIsProtected;
366     m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
367     if (wasProtected == m_elementIsProtected)
368         return;
369
370     if (m_elementIsProtected) {
371         if (m_derefElementTimer.isActive())
372             m_derefElementTimer.stop();
373         else
374             m_element->ref();
375     } else {
376         ASSERT(!m_derefElementTimer.isActive());
377         m_derefElementTimer.startOneShot(0);
378     }   
379 }
380
381 void ImageLoader::timerFired(Timer<ImageLoader>*)
382 {
383     m_element->deref();
384 }
385
386 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
387 {
388     ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
389     const AtomicString& eventType = eventSender->eventType();
390     if (eventType == eventNames().beforeloadEvent)
391         dispatchPendingBeforeLoadEvent();
392     if (eventType == eventNames().loadEvent)
393         dispatchPendingLoadEvent();
394     if (eventType == eventNames().errorEvent)
395         dispatchPendingErrorEvent();
396 }
397
398 void ImageLoader::dispatchPendingBeforeLoadEvent()
399 {
400     if (!m_hasPendingBeforeLoadEvent)
401         return;
402     if (!m_image)
403         return;
404     if (!m_element->document().hasLivingRenderTree())
405         return;
406     m_hasPendingBeforeLoadEvent = false;
407     if (m_element->dispatchBeforeLoadEvent(m_image->url())) {
408         updateRenderer();
409         return;
410     }
411     if (m_image) {
412         m_image->removeClient(this);
413         m_image = 0;
414     }
415
416     loadEventSender().cancelEvent(this);
417     m_hasPendingLoadEvent = false;
418     
419     if (isHTMLObjectElement(m_element))
420         toHTMLObjectElement(m_element)->renderFallbackContent();
421
422     // Only consider updating the protection ref-count of the Element immediately before returning
423     // from this function as doing so might result in the destruction of this ImageLoader.
424     updatedHasPendingEvent();
425 }
426
427 void ImageLoader::dispatchPendingLoadEvent()
428 {
429     if (!m_hasPendingLoadEvent)
430         return;
431     if (!m_image)
432         return;
433     m_hasPendingLoadEvent = false;
434     if (m_element->document().hasLivingRenderTree())
435         dispatchLoadEvent();
436
437     // Only consider updating the protection ref-count of the Element immediately before returning
438     // from this function as doing so might result in the destruction of this ImageLoader.
439     updatedHasPendingEvent();
440 }
441
442 void ImageLoader::dispatchPendingErrorEvent()
443 {
444     if (!m_hasPendingErrorEvent)
445         return;
446     m_hasPendingErrorEvent = false;
447     if (m_element->document().hasLivingRenderTree())
448         m_element->dispatchEvent(Event::create(eventNames().errorEvent, false, false));
449
450     // Only consider updating the protection ref-count of the Element immediately before returning
451     // from this function as doing so might result in the destruction of this ImageLoader.
452     updatedHasPendingEvent();
453 }
454
455 void ImageLoader::dispatchPendingBeforeLoadEvents()
456 {
457     beforeLoadEventSender().dispatchPendingEvents();
458 }
459
460 void ImageLoader::dispatchPendingLoadEvents()
461 {
462     loadEventSender().dispatchPendingEvents();
463 }
464
465 void ImageLoader::dispatchPendingErrorEvents()
466 {
467     errorEventSender().dispatchPendingEvents();
468 }
469
470 void ImageLoader::elementDidMoveToNewDocument()
471 {
472     clearFailedLoadURL();
473     setImage(0);
474 }
475
476 inline void ImageLoader::clearFailedLoadURL()
477 {
478     m_failedLoadURL = AtomicString();
479 }
480
481 }