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 "bindings/core/v8/ScriptController.h"
26 #include "core/dom/Document.h"
27 #include "core/dom/Element.h"
28 #include "core/dom/IncrementLoadEventDelayCount.h"
29 #include "core/dom/Microtask.h"
30 #include "core/events/Event.h"
31 #include "core/events/EventSender.h"
32 #include "core/fetch/CrossOriginAccessControl.h"
33 #include "core/fetch/FetchRequest.h"
34 #include "core/fetch/MemoryCache.h"
35 #include "core/fetch/ResourceFetcher.h"
36 #include "core/frame/LocalFrame.h"
37 #include "core/html/HTMLImageElement.h"
38 #include "core/html/parser/HTMLParserIdioms.h"
39 #include "core/rendering/RenderImage.h"
40 #include "core/rendering/RenderVideo.h"
41 #include "core/rendering/svg/RenderSVGImage.h"
42 #include "platform/Logging.h"
43 #include "platform/weborigin/SecurityOrigin.h"
44 #include "public/platform/WebURLRequest.h"
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 static ImageLoader::BypassMainWorldBehavior shouldBypassMainWorldCSP(ImageLoader* loader)
68 ASSERT(loader->element());
69 ASSERT(loader->element()->document().frame());
70 if (loader->element()->document().frame()->script().shouldBypassMainWorldCSP())
71 return ImageLoader::BypassMainWorldCSP;
72 return ImageLoader::DoNotBypassMainWorldCSP;
75 class ImageLoader::Task : public blink::WebThread::Task {
77 static PassOwnPtr<Task> create(ImageLoader* loader, UpdateFromElementBehavior updateBehavior)
79 return adoptPtr(new Task(loader, updateBehavior));
82 Task(ImageLoader* loader, UpdateFromElementBehavior updateBehavior)
84 , m_shouldBypassMainWorldCSP(shouldBypassMainWorldCSP(loader))
86 , m_updateBehavior(updateBehavior)
90 virtual void run() OVERRIDE
93 m_loader->doUpdateFromElement(m_shouldBypassMainWorldCSP, m_updateBehavior);
102 WeakPtr<Task> createWeakPtr()
104 return m_weakFactory.createWeakPtr();
108 ImageLoader* m_loader;
109 BypassMainWorldBehavior m_shouldBypassMainWorldCSP;
110 WeakPtrFactory<Task> m_weakFactory;
111 UpdateFromElementBehavior m_updateBehavior;
114 ImageLoader::ImageLoader(Element* element)
117 , m_derefElementTimer(this, &ImageLoader::timerFired)
118 , m_hasPendingLoadEvent(false)
119 , m_hasPendingErrorEvent(false)
120 , m_imageComplete(true)
121 , m_loadingImageDocument(false)
122 , m_elementIsProtected(false)
123 , m_highPriorityClientCount(0)
125 WTF_LOG(Timers, "new ImageLoader %p", this);
128 ImageLoader::~ImageLoader()
130 WTF_LOG(Timers, "~ImageLoader %p; m_hasPendingLoadEvent=%d, m_hasPendingErrorEvent=%d",
131 this, m_hasPendingLoadEvent, m_hasPendingErrorEvent);
134 m_pendingTask->clearLoader();
137 m_image->removeClient(this);
139 ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
140 if (m_hasPendingLoadEvent)
141 loadEventSender().cancelEvent(this);
143 ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
144 if (m_hasPendingErrorEvent)
145 errorEventSender().cancelEvent(this);
148 void ImageLoader::trace(Visitor* visitor)
150 visitor->trace(m_element);
153 void ImageLoader::setImage(ImageResource* newImage)
155 setImageWithoutConsideringPendingLoadEvent(newImage);
157 // Only consider updating the protection ref-count of the Element immediately before returning
158 // from this function as doing so might result in the destruction of this ImageLoader.
159 updatedHasPendingEvent();
162 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage)
164 ASSERT(m_failedLoadURL.isEmpty());
165 ImageResource* oldImage = m_image.get();
166 if (newImage != oldImage) {
167 sourceImageChanged();
169 if (m_hasPendingLoadEvent) {
170 loadEventSender().cancelEvent(this);
171 m_hasPendingLoadEvent = false;
173 if (m_hasPendingErrorEvent) {
174 errorEventSender().cancelEvent(this);
175 m_hasPendingErrorEvent = false;
177 m_imageComplete = true;
179 newImage->addClient(this);
181 oldImage->removeClient(this);
184 if (RenderImageResource* imageResource = renderImageResource())
185 imageResource->resetAnimation();
188 static void configureRequest(FetchRequest& request, ImageLoader::BypassMainWorldBehavior bypassBehavior, Element& element)
190 if (bypassBehavior == ImageLoader::BypassMainWorldCSP)
191 request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy);
193 AtomicString crossOriginMode = element.fastGetAttribute(HTMLNames::crossoriginAttr);
194 if (!crossOriginMode.isNull())
195 request.setCrossOriginAccessControl(element.document().securityOrigin(), crossOriginMode);
198 ResourcePtr<ImageResource> ImageLoader::createImageResourceForImageDocument(Document& document, FetchRequest& request)
200 bool autoLoadOtherImages = document.fetcher()->autoLoadImages();
201 document.fetcher()->setAutoLoadImages(false);
202 ResourcePtr<ImageResource> newImage = new ImageResource(request.resourceRequest());
203 newImage->setLoading(true);
204 document.fetcher()->m_documentResources.set(newImage->url(), newImage.get());
205 document.fetcher()->setAutoLoadImages(autoLoadOtherImages);
209 inline void ImageLoader::crossSiteOrCSPViolationOccured(AtomicString imageSourceURL)
211 m_failedLoadURL = imageSourceURL;
212 m_hasPendingErrorEvent = true;
213 errorEventSender().dispatchEventSoon(this);
216 inline void ImageLoader::clearFailedLoadURL()
218 m_failedLoadURL = AtomicString();
221 inline void ImageLoader::enqueueImageLoadingMicroTask(UpdateFromElementBehavior updateBehavior)
223 OwnPtr<Task> task = Task::create(this, updateBehavior);
224 m_pendingTask = task->createWeakPtr();
225 Microtask::enqueueMicrotask(task.release());
226 m_loadDelayCounter = IncrementLoadEventDelayCount::create(m_element->document());
229 void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, UpdateFromElementBehavior updateBehavior)
231 // FIXME: According to
232 // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#the-img-element:the-img-element-55
233 // When "update image" is called due to environment changes and the load fails, onerror should not be called.
234 // That is currently not the case.
236 // We don't need to call clearLoader here: Either we were called from the
237 // task, or our caller updateFromElement cleared the task's loader (and set
238 // m_pendingTask to null).
239 m_pendingTask.clear();
240 // Make sure to only decrement the count when we exit this function
241 OwnPtr<IncrementLoadEventDelayCount> loadDelayCounter;
242 loadDelayCounter.swap(m_loadDelayCounter);
244 Document& document = m_element->document();
245 if (!document.isActive())
248 AtomicString imageSourceURL = m_element->imageSourceURL();
249 KURL url = imageSourceToKURL(imageSourceURL);
250 ResourcePtr<ImageResource> newImage = 0;
252 // Unlike raw <img>, we block mixed content inside of <picture> or <img srcset>.
253 ResourceLoaderOptions resourceLoaderOptions = ResourceFetcher::defaultResourceOptions();
254 ResourceRequest resourceRequest(url);
255 if (isHTMLPictureElement(element()->parentNode()) || !element()->fastGetAttribute(HTMLNames::srcsetAttr).isNull()) {
256 resourceLoaderOptions.mixedContentBlockingTreatment = TreatAsActiveContent;
257 resourceRequest.setRequestContext(WebURLRequest::RequestContextImageSet);
259 FetchRequest request(resourceRequest, element()->localName(), resourceLoaderOptions);
260 configureRequest(request, bypassBehavior, *m_element);
262 if (m_loadingImageDocument)
263 newImage = createImageResourceForImageDocument(document, request);
265 newImage = document.fetcher()->fetchImage(request);
267 if (!newImage && !pageIsBeingDismissed(&document))
268 crossSiteOrCSPViolationOccured(imageSourceURL);
270 clearFailedLoadURL();
271 } else if (!imageSourceURL.isNull()) {
272 // Fire an error event if the url string is not empty, but the KURL is.
273 m_hasPendingErrorEvent = true;
274 errorEventSender().dispatchEventSoon(this);
277 ImageResource* oldImage = m_image.get();
278 if (newImage != oldImage) {
279 sourceImageChanged();
281 if (m_hasPendingLoadEvent) {
282 loadEventSender().cancelEvent(this);
283 m_hasPendingLoadEvent = false;
286 // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
287 // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
288 // this load and we should not cancel the event.
289 // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
290 if (m_hasPendingErrorEvent && newImage) {
291 errorEventSender().cancelEvent(this);
292 m_hasPendingErrorEvent = false;
296 m_hasPendingLoadEvent = newImage;
297 m_imageComplete = !newImage;
300 // If newImage exists and is cached, addClient() will result in the load event
301 // being queued to fire. Ensure this happens after beforeload is dispatched.
303 newImage->addClient(this);
306 oldImage->removeClient(this);
307 } else if (updateBehavior == UpdateSizeChanged && m_element->renderer() && m_element->renderer()->isImage()) {
308 toRenderImage(m_element->renderer())->intrinsicSizeChanged();
311 if (RenderImageResource* imageResource = renderImageResource())
312 imageResource->resetAnimation();
314 // Only consider updating the protection ref-count of the Element immediately before returning
315 // from this function as doing so might result in the destruction of this ImageLoader.
316 updatedHasPendingEvent();
319 void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior, LoadType loadType)
321 AtomicString imageSourceURL = m_element->imageSourceURL();
323 if (updateBehavior == UpdateIgnorePreviousError)
324 clearFailedLoadURL();
326 if (!m_failedLoadURL.isEmpty() && imageSourceURL == m_failedLoadURL)
329 // If we have a pending task, we have to clear it -- either we're
330 // now loading immediately, or we need to reset the task's state.
332 m_pendingTask->clearLoader();
333 m_pendingTask.clear();
336 KURL url = imageSourceToKURL(imageSourceURL);
337 if (imageSourceURL.isNull() || url.isNull() || shouldLoadImmediately(url, loadType)) {
338 doUpdateFromElement(DoNotBypassMainWorldCSP, updateBehavior);
341 enqueueImageLoadingMicroTask(updateBehavior);
344 KURL ImageLoader::imageSourceToKURL(AtomicString imageSourceURL) const
348 // Don't load images for inactive documents. We don't want to slow down the
349 // raw HTML parsing case by loading images we don't intend to display.
350 Document& document = m_element->document();
351 if (!document.isActive())
354 // Do not load any image if the 'src' attribute is missing or if it is
356 if (!imageSourceURL.isNull() && !stripLeadingAndTrailingHTMLSpaces(imageSourceURL).isEmpty())
357 url = document.completeURL(sourceURI(imageSourceURL));
361 bool ImageLoader::shouldLoadImmediately(const KURL& url, LoadType loadType) const
363 return (m_loadingImageDocument
364 || isHTMLObjectElement(m_element)
365 || isHTMLEmbedElement(m_element)
366 || url.protocolIsData()
367 || memoryCache()->resourceForURL(url)
368 || loadType == ForceLoadImmediately);
371 void ImageLoader::notifyFinished(Resource* resource)
373 WTF_LOG(Timers, "ImageLoader::notifyFinished %p; m_hasPendingLoadEvent=%d",
374 this, m_hasPendingLoadEvent);
376 ASSERT(m_failedLoadURL.isEmpty());
377 ASSERT(resource == m_image.get());
379 m_imageComplete = true;
382 if (!m_hasPendingLoadEvent)
385 if (resource->errorOccurred()) {
386 loadEventSender().cancelEvent(this);
387 m_hasPendingLoadEvent = false;
389 m_hasPendingErrorEvent = true;
390 errorEventSender().dispatchEventSoon(this);
392 // Only consider updating the protection ref-count of the Element immediately before returning
393 // from this function as doing so might result in the destruction of this ImageLoader.
394 updatedHasPendingEvent();
397 if (resource->wasCanceled()) {
398 m_hasPendingLoadEvent = false;
399 // Only consider updating the protection ref-count of the Element immediately before returning
400 // from this function as doing so might result in the destruction of this ImageLoader.
401 updatedHasPendingEvent();
404 loadEventSender().dispatchEventSoon(this);
407 RenderImageResource* ImageLoader::renderImageResource()
409 RenderObject* renderer = m_element->renderer();
414 // We don't return style generated image because it doesn't belong to the ImageLoader.
415 // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
416 if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
417 return toRenderImage(renderer)->imageResource();
419 if (renderer->isSVGImage())
420 return toRenderSVGImage(renderer)->imageResource();
422 if (renderer->isVideo())
423 return toRenderVideo(renderer)->imageResource();
428 void ImageLoader::updateRenderer()
430 RenderImageResource* imageResource = renderImageResource();
435 // Only update the renderer if it doesn't have an image or if what we have
436 // is a complete image. This prevents flickering in the case where a dynamic
437 // change is happening between two images.
438 ImageResource* cachedImage = imageResource->cachedImage();
439 if (m_image != cachedImage && (m_imageComplete || !cachedImage))
440 imageResource->setImageResource(m_image.get());
443 void ImageLoader::updatedHasPendingEvent()
445 // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
446 // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
447 // destroyed by DOM manipulation or garbage collection.
448 // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
449 bool wasProtected = m_elementIsProtected;
450 m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
451 if (wasProtected == m_elementIsProtected)
454 if (m_elementIsProtected) {
455 if (m_derefElementTimer.isActive())
456 m_derefElementTimer.stop();
458 m_keepAlive = m_element;
460 ASSERT(!m_derefElementTimer.isActive());
461 m_derefElementTimer.startOneShot(0, FROM_HERE);
465 void ImageLoader::timerFired(Timer<ImageLoader>*)
470 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
472 WTF_LOG(Timers, "ImageLoader::dispatchPendingEvent %p", this);
473 ASSERT(eventSender == &loadEventSender() || eventSender == &errorEventSender());
474 const AtomicString& eventType = eventSender->eventType();
475 if (eventType == EventTypeNames::load)
476 dispatchPendingLoadEvent();
477 if (eventType == EventTypeNames::error)
478 dispatchPendingErrorEvent();
481 void ImageLoader::dispatchPendingLoadEvent()
483 if (!m_hasPendingLoadEvent)
487 m_hasPendingLoadEvent = false;
488 if (element()->document().frame())
491 // Only consider updating the protection ref-count of the Element immediately before returning
492 // from this function as doing so might result in the destruction of this ImageLoader.
493 updatedHasPendingEvent();
496 void ImageLoader::dispatchPendingErrorEvent()
498 if (!m_hasPendingErrorEvent)
500 m_hasPendingErrorEvent = false;
502 if (element()->document().frame())
503 element()->dispatchEvent(Event::create(EventTypeNames::error));
505 // Only consider updating the protection ref-count of the Element immediately before returning
506 // from this function as doing so might result in the destruction of this ImageLoader.
507 updatedHasPendingEvent();
510 void ImageLoader::addClient(ImageLoaderClient* client)
512 if (client->requestsHighLiveResourceCachePriority()) {
513 if (m_image && !m_highPriorityClientCount++)
514 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh);
517 m_clients.add(client, adoptPtr(new ImageLoaderClientRemover(*this, *client)));
519 m_clients.add(client);
523 void ImageLoader::willRemoveClient(ImageLoaderClient& client)
525 if (client.requestsHighLiveResourceCachePriority()) {
526 ASSERT(m_highPriorityClientCount);
527 m_highPriorityClientCount--;
528 if (m_image && !m_highPriorityClientCount)
529 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow);
533 void ImageLoader::removeClient(ImageLoaderClient* client)
535 willRemoveClient(*client);
536 m_clients.remove(client);
539 void ImageLoader::dispatchPendingLoadEvents()
541 loadEventSender().dispatchPendingEvents();
544 void ImageLoader::dispatchPendingErrorEvents()
546 errorEventSender().dispatchPendingEvents();
549 void ImageLoader::elementDidMoveToNewDocument()
551 if (m_loadDelayCounter)
552 m_loadDelayCounter->documentChanged(m_element->document());
553 clearFailedLoadURL();
557 void ImageLoader::sourceImageChanged()
560 PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator end = m_clients.end();
561 for (PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator it = m_clients.begin(); it != end; ++it) {
562 it->key->notifyImageSourceChanged();
565 HashSet<ImageLoaderClient*>::iterator end = m_clients.end();
566 for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) {
567 ImageLoaderClient* handle = *it;
568 handle->notifyImageSourceChanged();
574 ImageLoader::ImageLoaderClientRemover::~ImageLoaderClientRemover()
576 m_loader.willRemoveClient(m_client);