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/weborigin/SecurityOrigin.h"
43 #include "public/platform/WebURLRequest.h"
47 static ImageEventSender& loadEventSender()
49 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load));
53 static ImageEventSender& errorEventSender()
55 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error));
59 static inline bool pageIsBeingDismissed(Document* document)
61 return document->pageDismissalEventBeingDispatched() != Document::NoDismissal;
64 static ImageLoader::BypassMainWorldBehavior shouldBypassMainWorldCSP(ImageLoader* loader)
67 ASSERT(loader->element());
68 ASSERT(loader->element()->document().frame());
69 if (loader->element()->document().frame()->script().shouldBypassMainWorldCSP())
70 return ImageLoader::BypassMainWorldCSP;
71 return ImageLoader::DoNotBypassMainWorldCSP;
74 class ImageLoader::Task : public blink::WebThread::Task {
76 static PassOwnPtr<Task> create(ImageLoader* loader, UpdateFromElementBehavior updateBehavior)
78 return adoptPtr(new Task(loader, updateBehavior));
81 Task(ImageLoader* loader, UpdateFromElementBehavior updateBehavior)
83 , m_shouldBypassMainWorldCSP(shouldBypassMainWorldCSP(loader))
85 , m_updateBehavior(updateBehavior)
89 virtual void run() OVERRIDE
92 m_loader->doUpdateFromElement(m_shouldBypassMainWorldCSP, m_updateBehavior);
101 WeakPtr<Task> createWeakPtr()
103 return m_weakFactory.createWeakPtr();
107 ImageLoader* m_loader;
108 BypassMainWorldBehavior m_shouldBypassMainWorldCSP;
109 WeakPtrFactory<Task> m_weakFactory;
110 UpdateFromElementBehavior m_updateBehavior;
113 ImageLoader::ImageLoader(Element* element)
116 , m_derefElementTimer(this, &ImageLoader::timerFired)
117 , m_hasPendingLoadEvent(false)
118 , m_hasPendingErrorEvent(false)
119 , m_imageComplete(true)
120 , m_loadingImageDocument(false)
121 , m_elementIsProtected(false)
122 , m_highPriorityClientCount(0)
126 ImageLoader::~ImageLoader()
129 m_pendingTask->clearLoader();
132 m_image->removeClient(this);
134 ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
135 if (m_hasPendingLoadEvent)
136 loadEventSender().cancelEvent(this);
138 ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
139 if (m_hasPendingErrorEvent)
140 errorEventSender().cancelEvent(this);
143 void ImageLoader::trace(Visitor* visitor)
145 visitor->trace(m_element);
148 void ImageLoader::setImage(ImageResource* newImage)
150 setImageWithoutConsideringPendingLoadEvent(newImage);
152 // Only consider updating the protection ref-count of the Element immediately before returning
153 // from this function as doing so might result in the destruction of this ImageLoader.
154 updatedHasPendingEvent();
157 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage)
159 ASSERT(m_failedLoadURL.isEmpty());
160 ImageResource* oldImage = m_image.get();
161 if (newImage != oldImage) {
162 sourceImageChanged();
164 if (m_hasPendingLoadEvent) {
165 loadEventSender().cancelEvent(this);
166 m_hasPendingLoadEvent = false;
168 if (m_hasPendingErrorEvent) {
169 errorEventSender().cancelEvent(this);
170 m_hasPendingErrorEvent = false;
172 m_imageComplete = true;
174 newImage->addClient(this);
176 oldImage->removeClient(this);
179 if (RenderImageResource* imageResource = renderImageResource())
180 imageResource->resetAnimation();
183 static void configureRequest(FetchRequest& request, ImageLoader::BypassMainWorldBehavior bypassBehavior, Element& element)
185 if (bypassBehavior == ImageLoader::BypassMainWorldCSP)
186 request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy);
188 AtomicString crossOriginMode = element.fastGetAttribute(HTMLNames::crossoriginAttr);
189 if (!crossOriginMode.isNull())
190 request.setCrossOriginAccessControl(element.document().securityOrigin(), crossOriginMode);
193 ResourcePtr<ImageResource> ImageLoader::createImageResourceForImageDocument(Document& document, FetchRequest& request)
195 bool autoLoadOtherImages = document.fetcher()->autoLoadImages();
196 document.fetcher()->setAutoLoadImages(false);
197 ResourcePtr<ImageResource> newImage = new ImageResource(request.resourceRequest());
198 newImage->setLoading(true);
199 document.fetcher()->m_documentResources.set(newImage->url(), newImage.get());
200 document.fetcher()->setAutoLoadImages(autoLoadOtherImages);
204 inline void ImageLoader::crossSiteOrCSPViolationOccured(AtomicString imageSourceURL)
206 m_failedLoadURL = imageSourceURL;
207 m_hasPendingErrorEvent = true;
208 errorEventSender().dispatchEventSoon(this);
211 inline void ImageLoader::clearFailedLoadURL()
213 m_failedLoadURL = AtomicString();
216 inline void ImageLoader::enqueueImageLoadingMicroTask(UpdateFromElementBehavior updateBehavior)
218 OwnPtr<Task> task = Task::create(this, updateBehavior);
219 m_pendingTask = task->createWeakPtr();
220 Microtask::enqueueMicrotask(task.release());
221 m_loadDelayCounter = IncrementLoadEventDelayCount::create(m_element->document());
224 void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, UpdateFromElementBehavior updateBehavior)
226 // We don't need to call clearLoader here: Either we were called from the
227 // task, or our caller updateFromElement cleared the task's loader (and set
228 // m_pendingTask to null).
229 m_pendingTask.clear();
230 // Make sure to only decrement the count when we exit this function
231 OwnPtr<IncrementLoadEventDelayCount> loadDelayCounter;
232 loadDelayCounter.swap(m_loadDelayCounter);
234 Document& document = m_element->document();
235 if (!document.isActive())
238 AtomicString imageSourceURL = m_element->imageSourceURL();
239 KURL url = imageSourceToKURL(imageSourceURL);
240 ResourcePtr<ImageResource> newImage = 0;
242 // Unlike raw <img>, we block mixed content inside of <picture> or <img srcset>.
243 ResourceLoaderOptions resourceLoaderOptions = ResourceFetcher::defaultResourceOptions();
244 ResourceRequest resourceRequest(url);
245 if (isHTMLPictureElement(element()->parentNode()) || !element()->fastGetAttribute(HTMLNames::srcsetAttr).isNull()) {
246 resourceLoaderOptions.mixedContentBlockingTreatment = TreatAsActiveContent;
247 resourceRequest.setRequestContext(WebURLRequest::RequestContextImageSet);
249 FetchRequest request(ResourceRequest(url), element()->localName(), resourceLoaderOptions);
250 configureRequest(request, bypassBehavior, *m_element);
252 if (m_loadingImageDocument)
253 newImage = createImageResourceForImageDocument(document, request);
255 newImage = document.fetcher()->fetchImage(request);
257 if (!newImage && !pageIsBeingDismissed(&document))
258 crossSiteOrCSPViolationOccured(imageSourceURL);
260 clearFailedLoadURL();
261 } else if (!imageSourceURL.isNull()) {
262 // Fire an error event if the url string is not empty, but the KURL is.
263 m_hasPendingErrorEvent = true;
264 errorEventSender().dispatchEventSoon(this);
267 ImageResource* oldImage = m_image.get();
268 if (newImage != oldImage) {
269 sourceImageChanged();
271 if (m_hasPendingLoadEvent) {
272 loadEventSender().cancelEvent(this);
273 m_hasPendingLoadEvent = false;
276 // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
277 // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
278 // this load and we should not cancel the event.
279 // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
280 if (m_hasPendingErrorEvent && newImage) {
281 errorEventSender().cancelEvent(this);
282 m_hasPendingErrorEvent = false;
286 m_hasPendingLoadEvent = newImage;
287 m_imageComplete = !newImage;
290 // If newImage exists and is cached, addClient() will result in the load event
291 // being queued to fire. Ensure this happens after beforeload is dispatched.
293 newImage->addClient(this);
296 oldImage->removeClient(this);
297 } else if (updateBehavior == UpdateSizeChanged && m_element->renderer() && m_element->renderer()->isImage()) {
298 toRenderImage(m_element->renderer())->intrinsicSizeChanged();
301 if (RenderImageResource* imageResource = renderImageResource())
302 imageResource->resetAnimation();
304 // Only consider updating the protection ref-count of the Element immediately before returning
305 // from this function as doing so might result in the destruction of this ImageLoader.
306 updatedHasPendingEvent();
309 void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior, LoadType loadType)
311 AtomicString imageSourceURL = m_element->imageSourceURL();
313 if (updateBehavior == UpdateIgnorePreviousError)
314 clearFailedLoadURL();
316 if (!m_failedLoadURL.isEmpty() && imageSourceURL == m_failedLoadURL)
319 // If we have a pending task, we have to clear it -- either we're
320 // now loading immediately, or we need to reset the task's state.
322 m_pendingTask->clearLoader();
323 m_pendingTask.clear();
326 KURL url = imageSourceToKURL(imageSourceURL);
327 if (imageSourceURL.isNull() || url.isNull() || shouldLoadImmediately(url, loadType)) {
328 doUpdateFromElement(DoNotBypassMainWorldCSP, updateBehavior);
331 enqueueImageLoadingMicroTask(updateBehavior);
334 KURL ImageLoader::imageSourceToKURL(AtomicString imageSourceURL) const
338 // Don't load images for inactive documents. We don't want to slow down the
339 // raw HTML parsing case by loading images we don't intend to display.
340 Document& document = m_element->document();
341 if (!document.isActive())
344 // Do not load any image if the 'src' attribute is missing or if it is
346 if (!imageSourceURL.isNull() && !stripLeadingAndTrailingHTMLSpaces(imageSourceURL).isEmpty())
347 url = document.completeURL(sourceURI(imageSourceURL));
351 bool ImageLoader::shouldLoadImmediately(const KURL& url, LoadType loadType) const
353 return (m_loadingImageDocument
354 || isHTMLObjectElement(m_element)
355 || isHTMLEmbedElement(m_element)
356 || url.protocolIsData()
357 || memoryCache()->resourceForURL(url)
358 || loadType == ForceLoadImmediately);
361 void ImageLoader::notifyFinished(Resource* resource)
363 ASSERT(m_failedLoadURL.isEmpty());
364 ASSERT(resource == m_image.get());
366 m_imageComplete = true;
369 if (!m_hasPendingLoadEvent)
372 if (resource->errorOccurred()) {
373 loadEventSender().cancelEvent(this);
374 m_hasPendingLoadEvent = false;
376 m_hasPendingErrorEvent = true;
377 errorEventSender().dispatchEventSoon(this);
379 // Only consider updating the protection ref-count of the Element immediately before returning
380 // from this function as doing so might result in the destruction of this ImageLoader.
381 updatedHasPendingEvent();
384 if (resource->wasCanceled()) {
385 m_hasPendingLoadEvent = false;
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 loadEventSender().dispatchEventSoon(this);
394 RenderImageResource* ImageLoader::renderImageResource()
396 RenderObject* renderer = m_element->renderer();
401 // We don't return style generated image because it doesn't belong to the ImageLoader.
402 // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
403 if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
404 return toRenderImage(renderer)->imageResource();
406 if (renderer->isSVGImage())
407 return toRenderSVGImage(renderer)->imageResource();
409 if (renderer->isVideo())
410 return toRenderVideo(renderer)->imageResource();
415 void ImageLoader::updateRenderer()
417 RenderImageResource* imageResource = renderImageResource();
422 // Only update the renderer if it doesn't have an image or if what we have
423 // is a complete image. This prevents flickering in the case where a dynamic
424 // change is happening between two images.
425 ImageResource* cachedImage = imageResource->cachedImage();
426 if (m_image != cachedImage && (m_imageComplete || !cachedImage))
427 imageResource->setImageResource(m_image.get());
430 void ImageLoader::updatedHasPendingEvent()
432 // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
433 // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
434 // destroyed by DOM manipulation or garbage collection.
435 // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
436 bool wasProtected = m_elementIsProtected;
437 m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
438 if (wasProtected == m_elementIsProtected)
441 if (m_elementIsProtected) {
442 if (m_derefElementTimer.isActive())
443 m_derefElementTimer.stop();
445 m_keepAlive = m_element;
447 ASSERT(!m_derefElementTimer.isActive());
448 m_derefElementTimer.startOneShot(0, FROM_HERE);
452 void ImageLoader::timerFired(Timer<ImageLoader>*)
457 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
459 ASSERT(eventSender == &loadEventSender() || eventSender == &errorEventSender());
460 const AtomicString& eventType = eventSender->eventType();
461 if (eventType == EventTypeNames::load)
462 dispatchPendingLoadEvent();
463 if (eventType == EventTypeNames::error)
464 dispatchPendingErrorEvent();
467 void ImageLoader::dispatchPendingLoadEvent()
469 if (!m_hasPendingLoadEvent)
473 m_hasPendingLoadEvent = false;
474 if (element()->document().frame())
477 // Only consider updating the protection ref-count of the Element immediately before returning
478 // from this function as doing so might result in the destruction of this ImageLoader.
479 updatedHasPendingEvent();
482 void ImageLoader::dispatchPendingErrorEvent()
484 if (!m_hasPendingErrorEvent)
486 m_hasPendingErrorEvent = false;
488 if (element()->document().frame())
489 element()->dispatchEvent(Event::create(EventTypeNames::error));
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::addClient(ImageLoaderClient* client)
498 if (client->requestsHighLiveResourceCachePriority()) {
499 if (m_image && !m_highPriorityClientCount++)
500 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh);
503 m_clients.add(client, adoptPtr(new ImageLoaderClientRemover(*this, *client)));
505 m_clients.add(client);
509 void ImageLoader::willRemoveClient(ImageLoaderClient& client)
511 if (client.requestsHighLiveResourceCachePriority()) {
512 ASSERT(m_highPriorityClientCount);
513 m_highPriorityClientCount--;
514 if (m_image && !m_highPriorityClientCount)
515 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow);
519 void ImageLoader::removeClient(ImageLoaderClient* client)
521 willRemoveClient(*client);
522 m_clients.remove(client);
525 void ImageLoader::dispatchPendingLoadEvents()
527 loadEventSender().dispatchPendingEvents();
530 void ImageLoader::dispatchPendingErrorEvents()
532 errorEventSender().dispatchPendingEvents();
535 void ImageLoader::elementDidMoveToNewDocument()
537 if (m_loadDelayCounter)
538 m_loadDelayCounter->documentChanged(m_element->document());
539 clearFailedLoadURL();
543 void ImageLoader::sourceImageChanged()
546 PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator end = m_clients.end();
547 for (PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator it = m_clients.begin(); it != end; ++it) {
548 it->key->notifyImageSourceChanged();
551 HashSet<ImageLoaderClient*>::iterator end = m_clients.end();
552 for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) {
553 ImageLoaderClient* handle = *it;
554 handle->notifyImageSourceChanged();
560 ImageLoader::ImageLoaderClientRemover::~ImageLoaderClientRemover()
562 m_loader.willRemoveClient(m_client);