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 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 "CSSHelper.h"
26 #include "CachedImage.h"
27 #include "DocLoader.h"
28 #include "Document.h"
29 #include "Element.h"
30 #include "RenderImage.h"
31
32 namespace WebCore {
33
34 class ImageLoadEventSender {
35 public:
36 ImageLoadEventSender();
37
38 void dispatchLoadEventSoon(ImageLoader*);
39 void cancelLoadEvent(ImageLoader*);
40
41 void dispatchPendingLoadEvents();
42
43 private:
44 ~ImageLoadEventSender();
45
46 void timerFired(Timer<ImageLoadEventSender>*);
47
48 Timer<ImageLoadEventSender> m_timer;
49 Vector<ImageLoader*> m_dispatchSoonList;
50 Vector<ImageLoader*> m_dispatchingList;
51 };
52
loadEventSender()53 static ImageLoadEventSender& loadEventSender()
54 {
55 DEFINE_STATIC_LOCAL(ImageLoadEventSender, sender, ());
56 return sender;
57 }
58
ImageLoader(Element * element)59 ImageLoader::ImageLoader(Element* element)
60 : m_element(element)
61 , m_image(0)
62 , m_firedLoad(true)
63 , m_imageComplete(true)
64 , m_loadManually(false)
65 {
66 }
67
~ImageLoader()68 ImageLoader::~ImageLoader()
69 {
70 if (m_image)
71 m_image->removeClient(this);
72 loadEventSender().cancelLoadEvent(this);
73 }
74
setImage(CachedImage * newImage)75 void ImageLoader::setImage(CachedImage* newImage)
76 {
77 ASSERT(m_failedLoadURL.isEmpty());
78 CachedImage* oldImage = m_image.get();
79 if (newImage != oldImage) {
80 setLoadingImage(newImage);
81 m_firedLoad = true;
82 m_imageComplete = true;
83 if (newImage)
84 newImage->addClient(this);
85 if (oldImage)
86 oldImage->removeClient(this);
87 }
88
89 if (RenderObject* renderer = m_element->renderer()) {
90 if (!renderer->isImage())
91 return;
92
93 toRenderImage(renderer)->resetAnimation();
94 }
95 }
96
setLoadingImage(CachedImage * loadingImage)97 void ImageLoader::setLoadingImage(CachedImage* loadingImage)
98 {
99 m_firedLoad = false;
100 m_imageComplete = false;
101 m_image = loadingImage;
102 }
103
updateFromElement()104 void ImageLoader::updateFromElement()
105 {
106 // If we're not making renderers for the page, then don't load images. We don't want to slow
107 // down the raw HTML parsing case by loading images we don't intend to display.
108 Document* document = m_element->document();
109 if (!document->renderer())
110 return;
111
112 AtomicString attr = m_element->getAttribute(m_element->imageSourceAttributeName());
113
114 if (attr == m_failedLoadURL)
115 return;
116
117 // Do not load any image if the 'src' attribute is missing or if it is
118 // an empty string referring to a local file. The latter condition is
119 // a quirk that preserves old behavior that Dashboard widgets
120 // need (<rdar://problem/5994621>).
121 CachedImage* newImage = 0;
122 if (!(attr.isNull() || (attr.isEmpty() && document->baseURI().isLocalFile()))) {
123 if (m_loadManually) {
124 document->docLoader()->setAutoLoadImages(false);
125 newImage = new CachedImage(sourceURI(attr));
126 newImage->setLoading(true);
127 newImage->setDocLoader(document->docLoader());
128 document->docLoader()->m_documentResources.set(newImage->url(), newImage);
129 } else
130 newImage = document->docLoader()->requestImage(sourceURI(attr));
131
132 // If we do not have an image here, it means that a cross-site
133 // violation occurred.
134 m_failedLoadURL = !newImage ? attr : AtomicString();
135 }
136
137 CachedImage* oldImage = m_image.get();
138 if (newImage != oldImage) {
139 setLoadingImage(newImage);
140 if (newImage)
141 newImage->addClient(this);
142 if (oldImage)
143 oldImage->removeClient(this);
144 }
145
146 if (RenderObject* renderer = m_element->renderer()) {
147 if (!renderer->isImage())
148 return;
149
150 toRenderImage(renderer)->resetAnimation();
151 }
152 }
153
updateFromElementIgnoringPreviousError()154 void ImageLoader::updateFromElementIgnoringPreviousError()
155 {
156 // Clear previous error.
157 m_failedLoadURL = AtomicString();
158 updateFromElement();
159 }
160
notifyFinished(CachedResource *)161 void ImageLoader::notifyFinished(CachedResource*)
162 {
163 ASSERT(m_failedLoadURL.isEmpty());
164 m_imageComplete = true;
165
166 loadEventSender().dispatchLoadEventSoon(this);
167
168 if (RenderObject* renderer = m_element->renderer()) {
169 if (!renderer->isImage())
170 return;
171
172 toRenderImage(renderer)->setCachedImage(m_image.get());
173 }
174 }
175
dispatchPendingLoadEvent()176 void ImageLoader::dispatchPendingLoadEvent()
177 {
178 if (m_firedLoad)
179 return;
180 if (!m_image)
181 return;
182 if (!m_element->document()->attached())
183 return;
184 m_firedLoad = true;
185 dispatchLoadEvent();
186 }
187
dispatchPendingLoadEvents()188 void ImageLoader::dispatchPendingLoadEvents()
189 {
190 loadEventSender().dispatchPendingLoadEvents();
191 }
192
ImageLoadEventSender()193 ImageLoadEventSender::ImageLoadEventSender()
194 : m_timer(this, &ImageLoadEventSender::timerFired)
195 {
196 }
197
dispatchLoadEventSoon(ImageLoader * loader)198 void ImageLoadEventSender::dispatchLoadEventSoon(ImageLoader* loader)
199 {
200 m_dispatchSoonList.append(loader);
201 if (!m_timer.isActive())
202 m_timer.startOneShot(0);
203 }
204
cancelLoadEvent(ImageLoader * loader)205 void ImageLoadEventSender::cancelLoadEvent(ImageLoader* loader)
206 {
207 // Remove instances of this loader from both lists.
208 // Use loops because we allow multiple instances to get into the lists.
209 size_t size = m_dispatchSoonList.size();
210 for (size_t i = 0; i < size; ++i) {
211 if (m_dispatchSoonList[i] == loader)
212 m_dispatchSoonList[i] = 0;
213 }
214 size = m_dispatchingList.size();
215 for (size_t i = 0; i < size; ++i) {
216 if (m_dispatchingList[i] == loader)
217 m_dispatchingList[i] = 0;
218 }
219 if (m_dispatchSoonList.isEmpty())
220 m_timer.stop();
221 }
222
dispatchPendingLoadEvents()223 void ImageLoadEventSender::dispatchPendingLoadEvents()
224 {
225 // Need to avoid re-entering this function; if new dispatches are
226 // scheduled before the parent finishes processing the list, they
227 // will set a timer and eventually be processed.
228 if (!m_dispatchingList.isEmpty())
229 return;
230
231 m_timer.stop();
232
233 m_dispatchingList.swap(m_dispatchSoonList);
234 size_t size = m_dispatchingList.size();
235 for (size_t i = 0; i < size; ++i) {
236 if (ImageLoader* loader = m_dispatchingList[i])
237 loader->dispatchPendingLoadEvent();
238 }
239 m_dispatchingList.clear();
240 }
241
timerFired(Timer<ImageLoadEventSender> *)242 void ImageLoadEventSender::timerFired(Timer<ImageLoadEventSender>*)
243 {
244 dispatchPendingLoadEvents();
245 }
246
247 }
248