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