Abstract ImageEventSender into a general purpose EventSender
[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 "CrossOriginAccessControl.h"
28 #include "Document.h"
29 #include "Element.h"
30 #include "Event.h"
31 #include "EventSender.h"
32 #include "HTMLNames.h"
33 #include "HTMLObjectElement.h"
34 #include "HTMLParserIdioms.h"
35 #include "RenderImage.h"
36 #include "ScriptCallStack.h"
37 #include "SecurityOrigin.h"
38
39 #if ENABLE(SVG)
40 #include "RenderSVGImage.h"
41 #endif
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         ASSERT(p->element());
57         ValueCheck<WebCore::Element*>::checkConsistency(p->element());
58     }
59 };
60
61 }
62 #endif
63
64 namespace WebCore {
65
66 static ImageEventSender& beforeLoadEventSender()
67 {
68     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().beforeloadEvent));
69     return sender;
70 }
71
72 static ImageEventSender& loadEventSender()
73 {
74     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().loadEvent));
75     return sender;
76 }
77
78 ImageLoader::ImageLoader(Element* element)
79     : m_element(element)
80     , m_image(0)
81     , m_firedBeforeLoad(true)
82     , m_firedLoad(true)
83     , m_imageComplete(true)
84     , m_loadManually(false)
85 {
86 }
87
88 ImageLoader::~ImageLoader()
89 {
90     if (m_image)
91         m_image->removeClient(this);
92
93     ASSERT(!m_firedBeforeLoad || !beforeLoadEventSender().hasPendingEvents(this));
94     if (!m_firedBeforeLoad)
95         beforeLoadEventSender().cancelEvent(this);
96
97     ASSERT(!m_firedLoad || !loadEventSender().hasPendingEvents(this));
98     if (!m_firedLoad)
99         loadEventSender().cancelEvent(this);
100 }
101
102 void ImageLoader::setImage(CachedImage* newImage)
103 {
104     ASSERT(m_failedLoadURL.isEmpty());
105     CachedImage* oldImage = m_image.get();
106     if (newImage != oldImage) {
107         m_image = newImage;
108         if (!m_firedBeforeLoad) {
109             beforeLoadEventSender().cancelEvent(this);
110             m_firedBeforeLoad = true;
111         }
112         if (!m_firedLoad) {
113             loadEventSender().cancelEvent(this);
114             m_firedLoad = true;
115         }
116         m_imageComplete = true;
117         if (newImage)
118             newImage->addClient(this);
119         if (oldImage)
120             oldImage->removeClient(this);
121     }
122
123     if (RenderImageResource* imageResource = renderImageResource())
124         imageResource->resetAnimation();
125 }
126
127 void ImageLoader::updateFromElement()
128 {
129     // If we're not making renderers for the page, then don't load images.  We don't want to slow
130     // down the raw HTML parsing case by loading images we don't intend to display.
131     Document* document = m_element->document();
132     if (!document->renderer())
133         return;
134
135     AtomicString attr = m_element->getAttribute(m_element->imageSourceAttributeName());
136
137     if (attr == m_failedLoadURL)
138         return;
139
140     // Do not load any image if the 'src' attribute is missing or if it is
141     // an empty string.
142     CachedImage* newImage = 0;
143     if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
144         ResourceRequest request = ResourceRequest(document->completeURL(sourceURI(attr)));
145
146         String crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
147         if (!crossOriginMode.isNull()) {
148             StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
149             updateRequestForAccessControl(request, document->securityOrigin(), allowCredentials);
150         }
151
152         if (m_loadManually) {
153             bool autoLoadOtherImages = document->cachedResourceLoader()->autoLoadImages();
154             document->cachedResourceLoader()->setAutoLoadImages(false);
155             newImage = new CachedImage(request);
156             newImage->setLoading(true);
157             newImage->setOwningCachedResourceLoader(document->cachedResourceLoader());
158             document->cachedResourceLoader()->m_documentResources.set(newImage->url(), newImage);
159             document->cachedResourceLoader()->setAutoLoadImages(autoLoadOtherImages);
160         } else
161             newImage = document->cachedResourceLoader()->requestImage(request);
162
163         // If we do not have an image here, it means that a cross-site
164         // violation occurred.
165         m_failedLoadURL = !newImage ? attr : AtomicString();
166     } else if (!attr.isNull()) // Fire an error event if the url is empty.
167         m_element->dispatchEvent(Event::create(eventNames().errorEvent, false, false));
168     
169     CachedImage* oldImage = m_image.get();
170     if (newImage != oldImage) {
171         if (!m_firedBeforeLoad)
172             beforeLoadEventSender().cancelEvent(this);
173         if (!m_firedLoad)
174             loadEventSender().cancelEvent(this);
175
176         m_image = newImage;
177         m_firedBeforeLoad = !newImage;
178         m_firedLoad = !newImage;
179         m_imageComplete = !newImage;
180
181         if (newImage) {
182             if (!m_element->document()->hasListenerType(Document::BEFORELOAD_LISTENER))
183                 dispatchPendingBeforeLoadEvent();
184             else
185                 beforeLoadEventSender().dispatchEventSoon(this);
186
187             // If newImage is cached, addClient() will result in the load event
188             // being queued to fire. Ensure this happens after beforeload is
189             // dispatched.
190             newImage->addClient(this);
191         }
192         if (oldImage)
193             oldImage->removeClient(this);
194     }
195
196     if (RenderImageResource* imageResource = renderImageResource())
197         imageResource->resetAnimation();
198 }
199
200 void ImageLoader::updateFromElementIgnoringPreviousError()
201 {
202     // Clear previous error.
203     m_failedLoadURL = AtomicString();
204     updateFromElement();
205 }
206
207 void ImageLoader::notifyFinished(CachedResource* resource)
208 {
209     ASSERT(m_failedLoadURL.isEmpty());
210     ASSERT(resource == m_image.get());
211
212     m_imageComplete = true;
213     if (haveFiredBeforeLoadEvent())
214         updateRenderer();
215
216     if (m_firedLoad)
217         return;
218
219     if (m_element->fastHasAttribute(HTMLNames::crossoriginAttr)
220         && !m_element->document()->securityOrigin()->canRequest(image()->response().url())
221         && !resource->passesAccessControlCheck(m_element->document()->securityOrigin())) {
222
223         setImage(0);
224
225         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Cross-origin image load denied by Cross-Origin Resource Sharing policy."));
226         m_element->document()->addConsoleMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage);
227
228         ASSERT(m_firedLoad);
229         return;
230     }
231
232     if (resource->wasCanceled()) {
233         m_firedLoad = true;
234         return;
235     }
236
237     loadEventSender().dispatchEventSoon(this);
238 }
239
240 RenderImageResource* ImageLoader::renderImageResource()
241 {
242     RenderObject* renderer = m_element->renderer();
243
244     if (!renderer)
245         return 0;
246
247     // We don't return style generated image because it doesn't belong to the ImageLoader.
248     // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
249     if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
250         return toRenderImage(renderer)->imageResource();
251
252 #if ENABLE(SVG)
253     if (renderer->isSVGImage())
254         return toRenderSVGImage(renderer)->imageResource();
255 #endif
256
257 #if ENABLE(VIDEO)
258     if (renderer->isVideo())
259         return toRenderVideo(renderer)->imageResource();
260 #endif
261
262     return 0;
263 }
264
265 void ImageLoader::updateRenderer()
266 {
267     RenderImageResource* imageResource = renderImageResource();
268
269     if (!imageResource)
270         return;
271
272     // Only update the renderer if it doesn't have an image or if what we have
273     // is a complete image.  This prevents flickering in the case where a dynamic
274     // change is happening between two images.
275     CachedImage* cachedImage = imageResource->cachedImage();
276     if (m_image != cachedImage && (m_imageComplete || !cachedImage))
277         imageResource->setCachedImage(m_image.get());
278 }
279
280 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
281 {
282     ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender());
283     const AtomicString& eventType = eventSender->eventType();
284     if (eventType == eventNames().beforeloadEvent)
285         dispatchPendingBeforeLoadEvent();
286     if (eventType == eventNames().loadEvent)
287         dispatchPendingLoadEvent();
288 }
289
290 void ImageLoader::dispatchPendingBeforeLoadEvent()
291 {
292     if (m_firedBeforeLoad)
293         return;
294     if (!m_image)
295         return;
296     if (!m_element->document()->attached())
297         return;
298     m_firedBeforeLoad = true;
299     if (m_element->dispatchBeforeLoadEvent(m_image->url())) {
300         updateRenderer();
301         return;
302     }
303     if (m_image) {
304         m_image->removeClient(this);
305         m_image = 0;
306     }
307
308     loadEventSender().cancelEvent(this);
309     m_firedLoad = true;
310     
311     if (m_element->hasTagName(HTMLNames::objectTag))
312         static_cast<HTMLObjectElement*>(m_element)->renderFallbackContent();
313 }
314
315 void ImageLoader::dispatchPendingLoadEvent()
316 {
317     if (m_firedLoad)
318         return;
319     if (!m_image)
320         return;
321     if (!m_element->document()->attached())
322         return;
323     m_firedLoad = true;
324     dispatchLoadEvent();
325 }
326
327 void ImageLoader::dispatchPendingBeforeLoadEvents()
328 {
329     beforeLoadEventSender().dispatchPendingEvents();
330 }
331
332 void ImageLoader::dispatchPendingLoadEvents()
333 {
334     loadEventSender().dispatchPendingEvents();
335 }
336
337 void ImageLoader::elementDidMoveToNewDocument()
338 {
339     setImage(0);
340 }
341
342 }