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.
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.
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.
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.
23 #include "core/loader/ImageLoader.h"
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"
42 static ImageEventSender& beforeLoadEventSender()
44 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::beforeload));
48 static ImageEventSender& loadEventSender()
50 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load));
54 static ImageEventSender& errorEventSender()
56 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error));
60 static inline bool pageIsBeingDismissed(Document* document)
62 return document->pageDismissalEventBeingDispatched() != Document::NoDismissal;
65 ImageLoader::ImageLoader(Element* element)
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)
79 ImageLoader::~ImageLoader()
82 m_image->removeClient(this);
84 ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(this));
85 if (m_hasPendingBeforeLoadEvent)
86 beforeLoadEventSender().cancelEvent(this);
88 ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
89 if (m_hasPendingLoadEvent)
90 loadEventSender().cancelEvent(this);
92 ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
93 if (m_hasPendingErrorEvent)
94 errorEventSender().cancelEvent(this);
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)
102 void ImageLoader::setImage(ImageResource* newImage)
104 setImageWithoutConsideringPendingLoadEvent(newImage);
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();
111 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage)
113 ASSERT(m_failedLoadURL.isEmpty());
114 ImageResource* oldImage = m_image.get();
115 if (newImage != oldImage) {
116 sourceImageChanged();
118 if (m_hasPendingBeforeLoadEvent) {
119 beforeLoadEventSender().cancelEvent(this);
120 m_hasPendingBeforeLoadEvent = false;
122 if (m_hasPendingLoadEvent) {
123 loadEventSender().cancelEvent(this);
124 m_hasPendingLoadEvent = false;
126 if (m_hasPendingErrorEvent) {
127 errorEventSender().cancelEvent(this);
128 m_hasPendingErrorEvent = false;
130 m_imageComplete = true;
132 newImage->addClient(this);
134 oldImage->removeClient(this);
137 if (RenderImageResource* imageResource = renderImageResource())
138 imageResource->resetAnimation();
141 void ImageLoader::updateFromElement()
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())
149 AtomicString attr = m_element->imageSourceURL();
151 if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
154 // Do not load any image if the 'src' attribute is missing or if it is
156 ResourcePtr<ImageResource> newImage = 0;
157 if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
158 FetchRequest request(ResourceRequest(document.completeURL(sourceURI(attr))), element()->localName());
160 AtomicString crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
161 if (!crossOriginMode.isNull())
162 request.setCrossOriginAccessControl(document.securityOrigin(), crossOriginMode);
164 if (m_loadManually) {
165 bool autoLoadOtherImages = document.fetcher()->autoLoadImages();
166 document.fetcher()->setAutoLoadImages(false);
167 newImage = new ImageResource(request.resourceRequest());
168 newImage->setLoading(true);
169 document.fetcher()->m_documentResources.set(newImage->url(), newImage.get());
170 document.fetcher()->setAutoLoadImages(autoLoadOtherImages);
172 newImage = document.fetcher()->fetchImage(request);
175 // If we do not have an image here, it means that a cross-site
176 // violation occurred, or that the image was blocked via Content
177 // Security Policy, or the page is being dismissed. Trigger an
178 // error event if the page is not being dismissed.
179 if (!newImage && !pageIsBeingDismissed(&document)) {
180 m_failedLoadURL = attr;
181 m_hasPendingErrorEvent = true;
182 errorEventSender().dispatchEventSoon(this);
184 clearFailedLoadURL();
185 } else if (!attr.isNull()) {
186 // Fire an error event if the url is empty.
187 m_hasPendingErrorEvent = true;
188 errorEventSender().dispatchEventSoon(this);
191 ImageResource* oldImage = m_image.get();
192 if (newImage != oldImage) {
193 sourceImageChanged();
195 if (m_hasPendingBeforeLoadEvent) {
196 beforeLoadEventSender().cancelEvent(this);
197 m_hasPendingBeforeLoadEvent = false;
199 if (m_hasPendingLoadEvent) {
200 loadEventSender().cancelEvent(this);
201 m_hasPendingLoadEvent = false;
204 // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
205 // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
206 // this load and we should not cancel the event.
207 // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
208 if (m_hasPendingErrorEvent && newImage) {
209 errorEventSender().cancelEvent(this);
210 m_hasPendingErrorEvent = false;
214 m_hasPendingBeforeLoadEvent = !m_element->document().isImageDocument() && newImage;
215 m_hasPendingLoadEvent = newImage;
216 m_imageComplete = !newImage;
219 if (!m_element->document().isImageDocument()) {
220 if (!m_element->document().hasListenerType(Document::BEFORELOAD_LISTENER))
221 dispatchPendingBeforeLoadEvent();
223 beforeLoadEventSender().dispatchEventSoon(this);
227 // If newImage is cached, addClient() will result in the load event
228 // being queued to fire. Ensure this happens after beforeload is
230 newImage->addClient(this);
236 oldImage->removeClient(this);
239 if (RenderImageResource* imageResource = renderImageResource())
240 imageResource->resetAnimation();
242 // Only consider updating the protection ref-count of the Element immediately before returning
243 // from this function as doing so might result in the destruction of this ImageLoader.
244 updatedHasPendingEvent();
247 void ImageLoader::updateFromElementIgnoringPreviousError()
249 clearFailedLoadURL();
253 void ImageLoader::notifyFinished(Resource* resource)
255 ASSERT(m_failedLoadURL.isEmpty());
256 ASSERT(resource == m_image.get());
258 m_imageComplete = true;
259 if (!hasPendingBeforeLoadEvent())
262 if (!m_hasPendingLoadEvent)
265 if (resource->errorOccurred()) {
266 loadEventSender().cancelEvent(this);
267 m_hasPendingLoadEvent = false;
269 m_hasPendingErrorEvent = true;
270 errorEventSender().dispatchEventSoon(this);
272 // Only consider updating the protection ref-count of the Element immediately before returning
273 // from this function as doing so might result in the destruction of this ImageLoader.
274 updatedHasPendingEvent();
277 if (resource->wasCanceled()) {
278 m_hasPendingLoadEvent = false;
279 // Only consider updating the protection ref-count of the Element immediately before returning
280 // from this function as doing so might result in the destruction of this ImageLoader.
281 updatedHasPendingEvent();
284 loadEventSender().dispatchEventSoon(this);
287 RenderImageResource* ImageLoader::renderImageResource()
289 RenderObject* renderer = m_element->renderer();
294 // We don't return style generated image because it doesn't belong to the ImageLoader.
295 // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
296 if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
297 return toRenderImage(renderer)->imageResource();
299 if (renderer->isSVGImage())
300 return toRenderSVGImage(renderer)->imageResource();
302 if (renderer->isVideo())
303 return toRenderVideo(renderer)->imageResource();
308 void ImageLoader::updateRenderer()
310 RenderImageResource* imageResource = renderImageResource();
315 // Only update the renderer if it doesn't have an image or if what we have
316 // is a complete image. This prevents flickering in the case where a dynamic
317 // change is happening between two images.
318 ImageResource* cachedImage = imageResource->cachedImage();
319 if (m_image != cachedImage && (m_imageComplete || !cachedImage))
320 imageResource->setImageResource(m_image.get());
323 void ImageLoader::updatedHasPendingEvent()
325 // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
326 // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
327 // destroyed by DOM manipulation or garbage collection.
328 // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
329 bool wasProtected = m_elementIsProtected;
330 m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
331 if (wasProtected == m_elementIsProtected)
334 if (m_elementIsProtected) {
335 if (m_derefElementTimer.isActive())
336 m_derefElementTimer.stop();
340 ASSERT(!m_derefElementTimer.isActive());
341 m_derefElementTimer.startOneShot(0);
345 void ImageLoader::timerFired(Timer<ImageLoader>*)
350 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
352 ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
353 const AtomicString& eventType = eventSender->eventType();
354 if (eventType == EventTypeNames::beforeload)
355 dispatchPendingBeforeLoadEvent();
356 if (eventType == EventTypeNames::load)
357 dispatchPendingLoadEvent();
358 if (eventType == EventTypeNames::error)
359 dispatchPendingErrorEvent();
362 void ImageLoader::dispatchPendingBeforeLoadEvent()
364 if (!m_hasPendingBeforeLoadEvent)
368 if (!m_element->document().frame())
370 m_hasPendingBeforeLoadEvent = false;
371 if (m_element->dispatchBeforeLoadEvent(m_image->url().string())) {
376 m_image->removeClient(this);
380 loadEventSender().cancelEvent(this);
381 m_hasPendingLoadEvent = false;
383 if (m_element->hasTagName(HTMLNames::objectTag))
384 toHTMLObjectElement(m_element)->renderFallbackContent();
386 // Only consider updating the protection ref-count of the Element immediately before returning
387 // from this function as doing so might result in the destruction of this ImageLoader.
388 updatedHasPendingEvent();
391 void ImageLoader::dispatchPendingLoadEvent()
393 if (!m_hasPendingLoadEvent)
397 m_hasPendingLoadEvent = false;
398 if (element()->document().frame())
401 // Only consider updating the protection ref-count of the Element immediately before returning
402 // from this function as doing so might result in the destruction of this ImageLoader.
403 updatedHasPendingEvent();
406 void ImageLoader::dispatchPendingErrorEvent()
408 if (!m_hasPendingErrorEvent)
410 m_hasPendingErrorEvent = false;
411 if (element()->document().frame())
412 element()->dispatchEvent(Event::create(EventTypeNames::error));
414 // Only consider updating the protection ref-count of the Element immediately before returning
415 // from this function as doing so might result in the destruction of this ImageLoader.
416 updatedHasPendingEvent();
419 void ImageLoader::addClient(ImageLoaderClient* client)
421 if (client->requestsHighLiveResourceCachePriority()) {
422 if (m_image && !m_highPriorityClientCount++)
423 m_image->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityHigh);
425 m_clients.add(client);
427 void ImageLoader::removeClient(ImageLoaderClient* client)
429 if (client->requestsHighLiveResourceCachePriority()) {
430 ASSERT(m_highPriorityClientCount);
431 m_highPriorityClientCount--;
432 if (m_image && !m_highPriorityClientCount)
433 m_image->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityLow);
435 m_clients.remove(client);
438 void ImageLoader::dispatchPendingBeforeLoadEvents()
440 beforeLoadEventSender().dispatchPendingEvents();
443 void ImageLoader::dispatchPendingLoadEvents()
445 loadEventSender().dispatchPendingEvents();
448 void ImageLoader::dispatchPendingErrorEvents()
450 errorEventSender().dispatchPendingEvents();
453 void ImageLoader::elementDidMoveToNewDocument()
455 clearFailedLoadURL();
459 void ImageLoader::sourceImageChanged()
461 HashSet<ImageLoaderClient*>::iterator end = m_clients.end();
462 for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) {
463 ImageLoaderClient* handle = *it;
464 handle->notifyImageSourceChanged();
468 inline void ImageLoader::clearFailedLoadURL()
470 m_failedLoadURL = AtomicString();