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 "ImageLoader.h"
25 #include "CachedImage.h"
26 #include "CachedResourceLoader.h"
27 #include "CachedResourceRequest.h"
28 #include "CrossOriginAccessControl.h"
32 #include "EventSender.h"
34 #include "HTMLNames.h"
35 #include "HTMLObjectElement.h"
36 #include "HTMLParserIdioms.h"
37 #include "RenderImage.h"
38 #include "ScriptCallStack.h"
39 #include "SecurityOrigin.h"
42 #include "RenderSVGImage.h"
45 #include "RenderVideo.h"
49 // ImageLoader objects are allocated as members of other objects, so generic pointer check would always fail.
52 template<> struct ValueCheck<WebCore::ImageLoader*> {
53 typedef WebCore::ImageLoader* TraitType;
54 static void checkConsistency(const WebCore::ImageLoader* p)
59 ValueCheck<WebCore::Element*>::checkConsistency(p->element());
68 static ImageEventSender& beforeLoadEventSender()
70 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().beforeloadEvent));
74 static ImageEventSender& loadEventSender()
76 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().loadEvent));
80 static ImageEventSender& errorEventSender()
82 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().errorEvent));
86 static inline bool pageIsBeingDismissed(Document& document)
88 Frame* frame = document.frame();
89 return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal;
92 ImageLoader::ImageLoader(Element* element)
95 , m_derefElementTimer(this, &ImageLoader::timerFired)
96 , m_hasPendingBeforeLoadEvent(false)
97 , m_hasPendingLoadEvent(false)
98 , m_hasPendingErrorEvent(false)
99 , m_imageComplete(true)
100 , m_loadManually(false)
101 , m_elementIsProtected(false)
105 ImageLoader::~ImageLoader()
108 m_image->removeClient(this);
110 ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(this));
111 if (m_hasPendingBeforeLoadEvent)
112 beforeLoadEventSender().cancelEvent(this);
114 ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
115 if (m_hasPendingLoadEvent)
116 loadEventSender().cancelEvent(this);
118 ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
119 if (m_hasPendingErrorEvent)
120 errorEventSender().cancelEvent(this);
122 // If the ImageLoader is being destroyed but it is still protecting its image-loading Element,
123 // remove that protection here.
124 if (m_elementIsProtected)
128 void ImageLoader::setImage(CachedImage* newImage)
130 setImageWithoutConsideringPendingLoadEvent(newImage);
132 // Only consider updating the protection ref-count of the Element immediately before returning
133 // from this function as doing so might result in the destruction of this ImageLoader.
134 updatedHasPendingEvent();
137 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(CachedImage* newImage)
139 ASSERT(m_failedLoadURL.isEmpty());
140 CachedImage* oldImage = m_image.get();
141 if (newImage != oldImage) {
143 if (m_hasPendingBeforeLoadEvent) {
144 beforeLoadEventSender().cancelEvent(this);
145 m_hasPendingBeforeLoadEvent = false;
147 if (m_hasPendingLoadEvent) {
148 loadEventSender().cancelEvent(this);
149 m_hasPendingLoadEvent = false;
151 if (m_hasPendingErrorEvent) {
152 errorEventSender().cancelEvent(this);
153 m_hasPendingErrorEvent = false;
155 m_imageComplete = true;
157 newImage->addClient(this);
159 oldImage->removeClient(this);
162 if (RenderImageResource* imageResource = renderImageResource())
163 imageResource->resetAnimation();
166 void ImageLoader::updateFromElement()
168 // If we're not making renderers for the page, then don't load images. We don't want to slow
169 // down the raw HTML parsing case by loading images we don't intend to display.
170 Document& document = m_element->document();
171 if (!document.hasLivingRenderTree())
174 AtomicString attr = m_element->imageSourceURL();
176 if (attr == m_failedLoadURL)
179 // Do not load any image if the 'src' attribute is missing or if it is
181 CachedResourceHandle<CachedImage> newImage = 0;
182 if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
183 CachedResourceRequest request(ResourceRequest(document.completeURL(sourceURI(attr))));
184 request.setInitiator(element());
186 String crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
187 if (!crossOriginMode.isNull()) {
188 StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
189 updateRequestForAccessControl(request.mutableResourceRequest(), document.securityOrigin(), allowCredentials);
192 if (m_loadManually) {
193 bool autoLoadOtherImages = document.cachedResourceLoader()->autoLoadImages();
194 document.cachedResourceLoader()->setAutoLoadImages(false);
195 newImage = new CachedImage(request.resourceRequest());
196 newImage->setLoading(true);
197 newImage->setOwningCachedResourceLoader(document.cachedResourceLoader());
198 document.cachedResourceLoader()->m_documentResources.set(newImage->url(), newImage.get());
199 document.cachedResourceLoader()->setAutoLoadImages(autoLoadOtherImages);
201 newImage = document.cachedResourceLoader()->requestImage(request);
203 // If we do not have an image here, it means that a cross-site
204 // violation occurred, or that the image was blocked via Content
205 // Security Policy, or the page is being dismissed. Trigger an
206 // error event if the page is not being dismissed.
207 if (!newImage && !pageIsBeingDismissed(document)) {
208 m_failedLoadURL = attr;
209 m_hasPendingErrorEvent = true;
210 errorEventSender().dispatchEventSoon(this);
212 clearFailedLoadURL();
213 } else if (!attr.isNull()) {
214 // Fire an error event if the url is empty.
215 m_failedLoadURL = attr;
216 m_hasPendingErrorEvent = true;
217 errorEventSender().dispatchEventSoon(this);
220 CachedImage* oldImage = m_image.get();
221 if (newImage != oldImage) {
222 if (m_hasPendingBeforeLoadEvent) {
223 beforeLoadEventSender().cancelEvent(this);
224 m_hasPendingBeforeLoadEvent = false;
226 if (m_hasPendingLoadEvent) {
227 loadEventSender().cancelEvent(this);
228 m_hasPendingLoadEvent = false;
231 // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
232 // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
233 // this load and we should not cancel the event.
234 // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
235 if (m_hasPendingErrorEvent && newImage) {
236 errorEventSender().cancelEvent(this);
237 m_hasPendingErrorEvent = false;
241 m_hasPendingBeforeLoadEvent = !document.isImageDocument() && newImage;
242 m_hasPendingLoadEvent = newImage;
243 m_imageComplete = !newImage;
246 if (!document.isImageDocument()) {
247 if (!document.hasListenerType(Document::BEFORELOAD_LISTENER))
248 dispatchPendingBeforeLoadEvent();
250 beforeLoadEventSender().dispatchEventSoon(this);
254 // If newImage is cached, addClient() will result in the load event
255 // being queued to fire. Ensure this happens after beforeload is
257 newImage->addClient(this);
260 oldImage->removeClient(this);
263 if (RenderImageResource* imageResource = renderImageResource())
264 imageResource->resetAnimation();
266 // Only consider updating the protection ref-count of the Element immediately before returning
267 // from this function as doing so might result in the destruction of this ImageLoader.
268 updatedHasPendingEvent();
271 void ImageLoader::updateFromElementIgnoringPreviousError()
273 clearFailedLoadURL();
277 void ImageLoader::notifyFinished(CachedResource* resource)
279 ASSERT(m_failedLoadURL.isEmpty());
280 ASSERT(resource == m_image.get());
282 m_imageComplete = true;
283 if (!hasPendingBeforeLoadEvent())
286 if (!m_hasPendingLoadEvent)
289 if (m_element->fastHasAttribute(HTMLNames::crossoriginAttr)
290 && !m_element->document().securityOrigin()->canRequest(image()->response().url())
291 && !resource->passesAccessControlCheck(m_element->document().securityOrigin())) {
293 setImageWithoutConsideringPendingLoadEvent(0);
295 m_hasPendingErrorEvent = true;
296 errorEventSender().dispatchEventSoon(this);
298 DEFINE_STATIC_LOCAL(String, consoleMessage, (ASCIILiteral("Cross-origin image load denied by Cross-Origin Resource Sharing policy.")));
299 m_element->document().addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, consoleMessage);
301 ASSERT(!m_hasPendingLoadEvent);
303 // Only consider updating the protection ref-count of the Element immediately before returning
304 // from this function as doing so might result in the destruction of this ImageLoader.
305 updatedHasPendingEvent();
309 if (resource->wasCanceled()) {
310 m_hasPendingLoadEvent = false;
311 // Only consider updating the protection ref-count of the Element immediately before returning
312 // from this function as doing so might result in the destruction of this ImageLoader.
313 updatedHasPendingEvent();
317 loadEventSender().dispatchEventSoon(this);
320 RenderImageResource* ImageLoader::renderImageResource()
322 auto renderer = m_element->renderer();
326 // We don't return style generated image because it doesn't belong to the ImageLoader.
327 // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
328 if (renderer->isImage() && !toRenderImage(*renderer).isGeneratedContent())
329 return &toRenderImage(*renderer).imageResource();
332 if (renderer->isSVGImage())
333 return &toRenderSVGImage(renderer)->imageResource();
337 if (renderer->isVideo())
338 return &toRenderVideo(*renderer).imageResource();
344 void ImageLoader::updateRenderer()
346 RenderImageResource* imageResource = renderImageResource();
351 // Only update the renderer if it doesn't have an image or if what we have
352 // is a complete image. This prevents flickering in the case where a dynamic
353 // change is happening between two images.
354 CachedImage* cachedImage = imageResource->cachedImage();
355 if (m_image != cachedImage && (m_imageComplete || !cachedImage))
356 imageResource->setCachedImage(m_image.get());
359 void ImageLoader::updatedHasPendingEvent()
361 // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
362 // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
363 // destroyed by DOM manipulation or garbage collection.
364 // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
365 bool wasProtected = m_elementIsProtected;
366 m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
367 if (wasProtected == m_elementIsProtected)
370 if (m_elementIsProtected) {
371 if (m_derefElementTimer.isActive())
372 m_derefElementTimer.stop();
376 ASSERT(!m_derefElementTimer.isActive());
377 m_derefElementTimer.startOneShot(0);
381 void ImageLoader::timerFired(Timer<ImageLoader>&)
386 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
388 ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
389 const AtomicString& eventType = eventSender->eventType();
390 if (eventType == eventNames().beforeloadEvent)
391 dispatchPendingBeforeLoadEvent();
392 if (eventType == eventNames().loadEvent)
393 dispatchPendingLoadEvent();
394 if (eventType == eventNames().errorEvent)
395 dispatchPendingErrorEvent();
398 void ImageLoader::dispatchPendingBeforeLoadEvent()
400 if (!m_hasPendingBeforeLoadEvent)
404 if (!m_element->document().hasLivingRenderTree())
406 m_hasPendingBeforeLoadEvent = false;
407 if (m_element->dispatchBeforeLoadEvent(m_image->url())) {
412 m_image->removeClient(this);
416 loadEventSender().cancelEvent(this);
417 m_hasPendingLoadEvent = false;
419 if (isHTMLObjectElement(m_element))
420 toHTMLObjectElement(m_element)->renderFallbackContent();
422 // Only consider updating the protection ref-count of the Element immediately before returning
423 // from this function as doing so might result in the destruction of this ImageLoader.
424 updatedHasPendingEvent();
427 void ImageLoader::dispatchPendingLoadEvent()
429 if (!m_hasPendingLoadEvent)
433 m_hasPendingLoadEvent = false;
434 if (m_element->document().hasLivingRenderTree())
437 // Only consider updating the protection ref-count of the Element immediately before returning
438 // from this function as doing so might result in the destruction of this ImageLoader.
439 updatedHasPendingEvent();
442 void ImageLoader::dispatchPendingErrorEvent()
444 if (!m_hasPendingErrorEvent)
446 m_hasPendingErrorEvent = false;
447 if (m_element->document().hasLivingRenderTree())
448 m_element->dispatchEvent(Event::create(eventNames().errorEvent, false, false));
450 // Only consider updating the protection ref-count of the Element immediately before returning
451 // from this function as doing so might result in the destruction of this ImageLoader.
452 updatedHasPendingEvent();
455 void ImageLoader::dispatchPendingBeforeLoadEvents()
457 beforeLoadEventSender().dispatchPendingEvents();
460 void ImageLoader::dispatchPendingLoadEvents()
462 loadEventSender().dispatchPendingEvents();
465 void ImageLoader::dispatchPendingErrorEvents()
467 errorEventSender().dispatchPendingEvents();
470 void ImageLoader::elementDidMoveToNewDocument()
472 clearFailedLoadURL();
476 inline void ImageLoader::clearFailedLoadURL()
478 m_failedLoadURL = AtomicString();