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 "Document.h"
28 #include "Element.h"
29 #include "HTMLNames.h"
30 #include "HTMLObjectElement.h"
31 #include "RenderImage.h"
32
33 #if ENABLE(SVG)
34 #include "RenderSVGImage.h"
35 #endif
36 #if ENABLE(VIDEO)
37 #include "RenderVideo.h"
38 #endif
39
40 #if !ASSERT_DISABLED
41 // ImageLoader objects are allocated as members of other objects, so generic pointer check would always fail.
42 namespace WTF {
43
44 template<> struct ValueCheck<WebCore::ImageLoader*> {
45 typedef WebCore::ImageLoader* TraitType;
checkConsistencyWTF::ValueCheck46 static void checkConsistency(const WebCore::ImageLoader* p)
47 {
48 if (!p)
49 return;
50 ASSERT(p->element());
51 ValueCheck<WebCore::Element*>::checkConsistency(p->element());
52 }
53 };
54
55 }
56 #endif
57
58 namespace WebCore {
59
60 class ImageEventSender {
61 WTF_MAKE_NONCOPYABLE(ImageEventSender); WTF_MAKE_FAST_ALLOCATED;
62 public:
63 ImageEventSender(const AtomicString& eventType);
64
65 void dispatchEventSoon(ImageLoader*);
66 void cancelEvent(ImageLoader*);
67
68 void dispatchPendingEvents();
69
70 #ifndef NDEBUG
hasPendingEvents(ImageLoader * loader) const71 bool hasPendingEvents(ImageLoader* loader) const
72 {
73 return m_dispatchSoonList.find(loader) != notFound || m_dispatchingList.find(loader) != notFound;
74 }
75 #endif
76
77 private:
78 void timerFired(Timer<ImageEventSender>*);
79
80 AtomicString m_eventType;
81 Timer<ImageEventSender> m_timer;
82 Vector<ImageLoader*> m_dispatchSoonList;
83 Vector<ImageLoader*> m_dispatchingList;
84 };
85
beforeLoadEventSender()86 static ImageEventSender& beforeLoadEventSender()
87 {
88 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().beforeloadEvent));
89 return sender;
90 }
91
loadEventSender()92 static ImageEventSender& loadEventSender()
93 {
94 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().loadEvent));
95 return sender;
96 }
97
ImageLoader(Element * element)98 ImageLoader::ImageLoader(Element* element)
99 : m_element(element)
100 , m_image(0)
101 , m_firedBeforeLoad(true)
102 , m_firedLoad(true)
103 , m_imageComplete(true)
104 , m_loadManually(false)
105 {
106 }
107
~ImageLoader()108 ImageLoader::~ImageLoader()
109 {
110 if (m_image)
111 m_image->removeClient(this);
112
113 ASSERT(!m_firedBeforeLoad || !beforeLoadEventSender().hasPendingEvents(this));
114 if (!m_firedBeforeLoad)
115 beforeLoadEventSender().cancelEvent(this);
116
117 ASSERT(!m_firedLoad || !loadEventSender().hasPendingEvents(this));
118 if (!m_firedLoad)
119 loadEventSender().cancelEvent(this);
120 }
121
setImage(CachedImage * newImage)122 void ImageLoader::setImage(CachedImage* newImage)
123 {
124 ASSERT(m_failedLoadURL.isEmpty());
125 CachedImage* oldImage = m_image.get();
126 if (newImage != oldImage) {
127 m_image = newImage;
128 if (!m_firedBeforeLoad) {
129 beforeLoadEventSender().cancelEvent(this);
130 m_firedBeforeLoad = true;
131 }
132 if (!m_firedLoad) {
133 loadEventSender().cancelEvent(this);
134 m_firedLoad = true;
135 }
136 m_imageComplete = true;
137 if (newImage)
138 newImage->addClient(this);
139 if (oldImage)
140 oldImage->removeClient(this);
141 }
142
143 if (RenderImageResource* imageResource = renderImageResource())
144 imageResource->resetAnimation();
145 }
146
updateFromElement()147 void ImageLoader::updateFromElement()
148 {
149 // If we're not making renderers for the page, then don't load images. We don't want to slow
150 // down the raw HTML parsing case by loading images we don't intend to display.
151 Document* document = m_element->document();
152 if (!document->renderer())
153 return;
154
155 AtomicString attr = m_element->getAttribute(m_element->imageSourceAttributeName());
156
157 if (attr == m_failedLoadURL)
158 return;
159
160 // Do not load any image if the 'src' attribute is missing or if it is
161 // an empty string referring to a local file. The latter condition is
162 // a quirk that preserves old behavior that Dashboard widgets
163 // need (<rdar://problem/5994621>).
164 CachedImage* newImage = 0;
165 if (!(attr.isNull() || (attr.isEmpty() && document->baseURI().isLocalFile()))) {
166 if (m_loadManually) {
167 bool autoLoadOtherImages = document->cachedResourceLoader()->autoLoadImages();
168 document->cachedResourceLoader()->setAutoLoadImages(false);
169 newImage = new CachedImage(sourceURI(attr));
170 newImage->setLoading(true);
171 newImage->setOwningCachedResourceLoader(document->cachedResourceLoader());
172 document->cachedResourceLoader()->m_documentResources.set(newImage->url(), newImage);
173 document->cachedResourceLoader()->setAutoLoadImages(autoLoadOtherImages);
174 } else
175 newImage = document->cachedResourceLoader()->requestImage(sourceURI(attr));
176
177 // If we do not have an image here, it means that a cross-site
178 // violation occurred.
179 m_failedLoadURL = !newImage ? attr : AtomicString();
180 }
181
182 CachedImage* oldImage = m_image.get();
183 if (newImage != oldImage) {
184 if (!m_firedBeforeLoad)
185 beforeLoadEventSender().cancelEvent(this);
186 if (!m_firedLoad)
187 loadEventSender().cancelEvent(this);
188
189 m_image = newImage;
190 m_firedBeforeLoad = !newImage;
191 m_firedLoad = !newImage;
192 m_imageComplete = !newImage;
193
194 if (newImage) {
195 newImage->addClient(this);
196 if (!m_element->document()->hasListenerType(Document::BEFORELOAD_LISTENER))
197 dispatchPendingBeforeLoadEvent();
198 else
199 beforeLoadEventSender().dispatchEventSoon(this);
200 }
201 if (oldImage)
202 oldImage->removeClient(this);
203 }
204
205 if (RenderImageResource* imageResource = renderImageResource())
206 imageResource->resetAnimation();
207 }
208
updateFromElementIgnoringPreviousError()209 void ImageLoader::updateFromElementIgnoringPreviousError()
210 {
211 // Clear previous error.
212 m_failedLoadURL = AtomicString();
213 updateFromElement();
214 }
215
notifyFinished(CachedResource * resource)216 void ImageLoader::notifyFinished(CachedResource* resource)
217 {
218 ASSERT(m_failedLoadURL.isEmpty());
219 ASSERT(resource == m_image.get());
220
221 m_imageComplete = true;
222 if (haveFiredBeforeLoadEvent())
223 updateRenderer();
224
225 if (m_firedLoad)
226 return;
227
228 if (resource->wasCanceled())
229 return;
230
231 loadEventSender().dispatchEventSoon(this);
232 }
233
renderImageResource()234 RenderImageResource* ImageLoader::renderImageResource()
235 {
236 RenderObject* renderer = m_element->renderer();
237
238 if (!renderer)
239 return 0;
240
241 if (renderer->isImage())
242 return toRenderImage(renderer)->imageResource();
243
244 #if ENABLE(SVG)
245 if (renderer->isSVGImage())
246 return toRenderSVGImage(renderer)->imageResource();
247 #endif
248
249 #if ENABLE(VIDEO)
250 if (renderer->isVideo())
251 return toRenderVideo(renderer)->imageResource();
252 #endif
253
254 return 0;
255 }
256
updateRenderer()257 void ImageLoader::updateRenderer()
258 {
259 RenderImageResource* imageResource = renderImageResource();
260
261 if (!imageResource)
262 return;
263
264 // Only update the renderer if it doesn't have an image or if what we have
265 // is a complete image. This prevents flickering in the case where a dynamic
266 // change is happening between two images.
267 CachedImage* cachedImage = imageResource->cachedImage();
268 if (m_image != cachedImage && (m_imageComplete || !cachedImage))
269 imageResource->setCachedImage(m_image.get());
270 }
271
dispatchPendingBeforeLoadEvent()272 void ImageLoader::dispatchPendingBeforeLoadEvent()
273 {
274 if (m_firedBeforeLoad)
275 return;
276 if (!m_image)
277 return;
278 if (!m_element->document()->attached())
279 return;
280 m_firedBeforeLoad = true;
281 if (m_element->dispatchBeforeLoadEvent(m_image->url())) {
282 updateRenderer();
283 return;
284 }
285 if (m_image) {
286 m_image->removeClient(this);
287 m_image = 0;
288 }
289 loadEventSender().cancelEvent(this);
290
291 if (m_element->hasTagName(HTMLNames::objectTag))
292 static_cast<HTMLObjectElement*>(m_element)->renderFallbackContent();
293 }
294
dispatchPendingLoadEvent()295 void ImageLoader::dispatchPendingLoadEvent()
296 {
297 if (m_firedLoad)
298 return;
299 if (!m_image)
300 return;
301 if (!m_element->document()->attached())
302 return;
303 m_firedLoad = true;
304 dispatchLoadEvent();
305 }
306
dispatchPendingBeforeLoadEvents()307 void ImageLoader::dispatchPendingBeforeLoadEvents()
308 {
309 beforeLoadEventSender().dispatchPendingEvents();
310 }
311
dispatchPendingLoadEvents()312 void ImageLoader::dispatchPendingLoadEvents()
313 {
314 loadEventSender().dispatchPendingEvents();
315 }
316
elementWillMoveToNewOwnerDocument()317 void ImageLoader::elementWillMoveToNewOwnerDocument()
318 {
319 setImage(0);
320 }
321
ImageEventSender(const AtomicString & eventType)322 ImageEventSender::ImageEventSender(const AtomicString& eventType)
323 : m_eventType(eventType)
324 , m_timer(this, &ImageEventSender::timerFired)
325 {
326 }
327
dispatchEventSoon(ImageLoader * loader)328 void ImageEventSender::dispatchEventSoon(ImageLoader* loader)
329 {
330 m_dispatchSoonList.append(loader);
331 if (!m_timer.isActive())
332 m_timer.startOneShot(0);
333 }
334
cancelEvent(ImageLoader * loader)335 void ImageEventSender::cancelEvent(ImageLoader* loader)
336 {
337 // Remove instances of this loader from both lists.
338 // Use loops because we allow multiple instances to get into the lists.
339 size_t size = m_dispatchSoonList.size();
340 for (size_t i = 0; i < size; ++i) {
341 if (m_dispatchSoonList[i] == loader)
342 m_dispatchSoonList[i] = 0;
343 }
344 size = m_dispatchingList.size();
345 for (size_t i = 0; i < size; ++i) {
346 if (m_dispatchingList[i] == loader)
347 m_dispatchingList[i] = 0;
348 }
349 if (m_dispatchSoonList.isEmpty())
350 m_timer.stop();
351 }
352
dispatchPendingEvents()353 void ImageEventSender::dispatchPendingEvents()
354 {
355 // Need to avoid re-entering this function; if new dispatches are
356 // scheduled before the parent finishes processing the list, they
357 // will set a timer and eventually be processed.
358 if (!m_dispatchingList.isEmpty())
359 return;
360
361 m_timer.stop();
362
363 m_dispatchSoonList.checkConsistency();
364
365 m_dispatchingList.swap(m_dispatchSoonList);
366 size_t size = m_dispatchingList.size();
367 for (size_t i = 0; i < size; ++i) {
368 if (ImageLoader* loader = m_dispatchingList[i]) {
369 m_dispatchingList[i] = 0;
370 if (m_eventType == eventNames().beforeloadEvent)
371 loader->dispatchPendingBeforeLoadEvent();
372 else
373 loader->dispatchPendingLoadEvent();
374 }
375 }
376 m_dispatchingList.clear();
377 }
378
timerFired(Timer<ImageEventSender> *)379 void ImageEventSender::timerFired(Timer<ImageEventSender>*)
380 {
381 dispatchPendingEvents();
382 }
383
384 }
385