• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "bindings/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 
44 namespace WebCore {
45 
loadEventSender()46 static ImageEventSender& loadEventSender()
47 {
48     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load));
49     return sender;
50 }
51 
errorEventSender()52 static ImageEventSender& errorEventSender()
53 {
54     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error));
55     return sender;
56 }
57 
pageIsBeingDismissed(Document * document)58 static inline bool pageIsBeingDismissed(Document* document)
59 {
60     return document->pageDismissalEventBeingDispatched() != Document::NoDismissal;
61 }
62 
63 class ImageLoader::Task : public blink::WebThread::Task {
64 public:
Task(ImageLoader * loader)65     Task(ImageLoader* loader)
66         : m_loader(loader)
67         , m_shouldBypassMainWorldContentSecurityPolicy(false)
68         , m_weakFactory(this)
69     {
70         LocalFrame* frame = loader->m_element->document().frame();
71         m_shouldBypassMainWorldContentSecurityPolicy = frame->script().shouldBypassMainWorldContentSecurityPolicy();
72     }
73 
run()74     virtual void run() OVERRIDE
75     {
76         if (m_loader) {
77             m_loader->doUpdateFromElement(m_shouldBypassMainWorldContentSecurityPolicy);
78         }
79     }
80 
clearLoader()81     void clearLoader()
82     {
83         m_loader = 0;
84     }
85 
createWeakPtr()86     WeakPtr<Task> createWeakPtr()
87     {
88         return m_weakFactory.createWeakPtr();
89     }
90 
91 private:
92     ImageLoader* m_loader;
93     bool m_shouldBypassMainWorldContentSecurityPolicy;
94     WeakPtrFactory<Task> m_weakFactory;
95 };
96 
ImageLoader(Element * element)97 ImageLoader::ImageLoader(Element* element)
98     : m_element(element)
99     , m_image(0)
100     , m_derefElementTimer(this, &ImageLoader::timerFired)
101     , m_hasPendingLoadEvent(false)
102     , m_hasPendingErrorEvent(false)
103     , m_imageComplete(true)
104     , m_loadManually(false)
105     , m_elementIsProtected(false)
106     , m_highPriorityClientCount(0)
107 {
108 }
109 
~ImageLoader()110 ImageLoader::~ImageLoader()
111 {
112     if (m_pendingTask)
113         m_pendingTask->clearLoader();
114 
115     if (m_image)
116         m_image->removeClient(this);
117 
118     ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
119     if (m_hasPendingLoadEvent)
120         loadEventSender().cancelEvent(this);
121 
122     ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
123     if (m_hasPendingErrorEvent)
124         errorEventSender().cancelEvent(this);
125 }
126 
trace(Visitor * visitor)127 void ImageLoader::trace(Visitor* visitor)
128 {
129     visitor->trace(m_element);
130 }
131 
setImage(ImageResource * newImage)132 void ImageLoader::setImage(ImageResource* newImage)
133 {
134     setImageWithoutConsideringPendingLoadEvent(newImage);
135 
136     // Only consider updating the protection ref-count of the Element immediately before returning
137     // from this function as doing so might result in the destruction of this ImageLoader.
138     updatedHasPendingEvent();
139 }
140 
setImageWithoutConsideringPendingLoadEvent(ImageResource * newImage)141 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage)
142 {
143     ASSERT(m_failedLoadURL.isEmpty());
144     ImageResource* oldImage = m_image.get();
145     if (newImage != oldImage) {
146         sourceImageChanged();
147         m_image = newImage;
148         if (m_hasPendingLoadEvent) {
149             loadEventSender().cancelEvent(this);
150             m_hasPendingLoadEvent = false;
151         }
152         if (m_hasPendingErrorEvent) {
153             errorEventSender().cancelEvent(this);
154             m_hasPendingErrorEvent = false;
155         }
156         m_imageComplete = true;
157         if (newImage)
158             newImage->addClient(this);
159         if (oldImage)
160             oldImage->removeClient(this);
161     }
162 
163     if (RenderImageResource* imageResource = renderImageResource())
164         imageResource->resetAnimation();
165 }
166 
doUpdateFromElement(bool bypassMainWorldCSP)167 void ImageLoader::doUpdateFromElement(bool bypassMainWorldCSP)
168 {
169     // We don't need to call clearLoader here: Either we were called from the
170     // task, or our caller updateFromElement cleared the task's loader (and set
171     // m_pendingTask to null).
172     m_pendingTask.clear();
173     // Make sure to only decrement the count when we exit this function
174     OwnPtr<IncrementLoadEventDelayCount> delayLoad;
175     delayLoad.swap(m_delayLoad);
176 
177     Document& document = m_element->document();
178     if (!document.isActive())
179         return;
180 
181     AtomicString attr = m_element->imageSourceURL();
182 
183     KURL url = imageURL();
184     ResourcePtr<ImageResource> newImage = 0;
185     if (!url.isNull()) {
186         FetchRequest request(ResourceRequest(url), element()->localName());
187         if (bypassMainWorldCSP)
188             request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy);
189 
190         AtomicString crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
191         if (!crossOriginMode.isNull())
192             request.setCrossOriginAccessControl(document.securityOrigin(), crossOriginMode);
193 
194         if (m_loadManually) {
195             bool autoLoadOtherImages = document.fetcher()->autoLoadImages();
196             document.fetcher()->setAutoLoadImages(false);
197             newImage = new ImageResource(request.resourceRequest());
198             newImage->setLoading(true);
199             document.fetcher()->m_documentResources.set(newImage->url(), newImage.get());
200             document.fetcher()->setAutoLoadImages(autoLoadOtherImages);
201         } else {
202             newImage = document.fetcher()->fetchImage(request);
203         }
204 
205         // If we do not have an image here, it means that a cross-site
206         // violation occurred, or that the image was blocked via Content
207         // Security Policy, or the page is being dismissed. Trigger an
208         // error event if the page is not being dismissed.
209         if (!newImage && !pageIsBeingDismissed(&document)) {
210             m_failedLoadURL = attr;
211             m_hasPendingErrorEvent = true;
212             errorEventSender().dispatchEventSoon(this);
213         } else
214             clearFailedLoadURL();
215     } else if (!attr.isNull()) {
216         // Fire an error event if the url is empty.
217         m_hasPendingErrorEvent = true;
218         errorEventSender().dispatchEventSoon(this);
219     }
220 
221     ImageResource* oldImage = m_image.get();
222     if (newImage != oldImage) {
223         sourceImageChanged();
224 
225         if (m_hasPendingLoadEvent) {
226             loadEventSender().cancelEvent(this);
227             m_hasPendingLoadEvent = false;
228         }
229 
230         // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
231         // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
232         // this load and we should not cancel the event.
233         // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
234         if (m_hasPendingErrorEvent && newImage) {
235             errorEventSender().cancelEvent(this);
236             m_hasPendingErrorEvent = false;
237         }
238 
239         m_image = newImage;
240         m_hasPendingLoadEvent = newImage;
241         m_imageComplete = !newImage;
242 
243         if (newImage) {
244             updateRenderer();
245 
246             // If newImage is cached, addClient() will result in the load event
247             // being queued to fire. Ensure this happens after beforeload is
248             // dispatched.
249             newImage->addClient(this);
250         } else {
251             updateRenderer();
252         }
253 
254         if (oldImage)
255             oldImage->removeClient(this);
256     }
257 
258     if (RenderImageResource* imageResource = renderImageResource())
259         imageResource->resetAnimation();
260 
261     // Only consider updating the protection ref-count of the Element immediately before returning
262     // from this function as doing so might result in the destruction of this ImageLoader.
263     updatedHasPendingEvent();
264 }
265 
updateFromElement(LoadType loadType)266 void ImageLoader::updateFromElement(LoadType loadType)
267 {
268     AtomicString attr = m_element->imageSourceURL();
269 
270     if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
271         return;
272 
273     // If we have a pending task, we have to clear it -- either we're
274     // now loading immediately, or we need to reset the task's state.
275     if (m_pendingTask) {
276         m_pendingTask->clearLoader();
277         m_pendingTask.clear();
278     }
279 
280     KURL url = imageURL();
281     if (!attr.isNull() && !url.isNull()) {
282         bool loadImmediately = shouldLoadImmediately(url) || (loadType == ForceLoadImmediately);
283         if (loadImmediately) {
284             doUpdateFromElement(false);
285         } else {
286             OwnPtr<Task> task = adoptPtr(new Task(this));
287             m_pendingTask = task->createWeakPtr();
288             Microtask::enqueueMicrotask(task.release());
289             m_delayLoad = adoptPtr(new IncrementLoadEventDelayCount(m_element->document()));
290             return;
291         }
292     } else {
293         doUpdateFromElement(false);
294     }
295 }
296 
updateFromElementIgnoringPreviousError()297 void ImageLoader::updateFromElementIgnoringPreviousError()
298 {
299     clearFailedLoadURL();
300     updateFromElement();
301 }
302 
imageURL() const303 KURL ImageLoader::imageURL() const
304 {
305     KURL url;
306 
307     // Don't load images for inactive documents. We don't want to slow down the
308     // raw HTML parsing case by loading images we don't intend to display.
309     Document& document = m_element->document();
310     if (!document.isActive())
311         return url;
312 
313     AtomicString attr = m_element->imageSourceURL();
314 
315     // Do not load any image if the 'src' attribute is missing or if it is
316     // an empty string.
317     if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
318         url = document.completeURL(sourceURI(attr));
319     }
320     return url;
321 }
322 
shouldLoadImmediately(const KURL & url) const323 bool ImageLoader::shouldLoadImmediately(const KURL& url) const
324 {
325     if (m_loadManually)
326         return true;
327     if (isHTMLObjectElement(m_element) || isHTMLEmbedElement(m_element))
328         return true;
329 
330     if (url.protocolIsData())
331         return true;
332     if (memoryCache()->resourceForURL(url))
333         return true;
334     return false;
335 }
336 
notifyFinished(Resource * resource)337 void ImageLoader::notifyFinished(Resource* resource)
338 {
339     ASSERT(m_failedLoadURL.isEmpty());
340     ASSERT(resource == m_image.get());
341 
342     m_imageComplete = true;
343     updateRenderer();
344 
345     if (!m_hasPendingLoadEvent)
346         return;
347 
348     if (resource->errorOccurred()) {
349         loadEventSender().cancelEvent(this);
350         m_hasPendingLoadEvent = false;
351 
352         m_hasPendingErrorEvent = true;
353         errorEventSender().dispatchEventSoon(this);
354 
355         // Only consider updating the protection ref-count of the Element immediately before returning
356         // from this function as doing so might result in the destruction of this ImageLoader.
357         updatedHasPendingEvent();
358         return;
359     }
360     if (resource->wasCanceled()) {
361         m_hasPendingLoadEvent = false;
362         // Only consider updating the protection ref-count of the Element immediately before returning
363         // from this function as doing so might result in the destruction of this ImageLoader.
364         updatedHasPendingEvent();
365         return;
366     }
367     loadEventSender().dispatchEventSoon(this);
368 }
369 
renderImageResource()370 RenderImageResource* ImageLoader::renderImageResource()
371 {
372     RenderObject* renderer = m_element->renderer();
373 
374     if (!renderer)
375         return 0;
376 
377     // We don't return style generated image because it doesn't belong to the ImageLoader.
378     // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
379     if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
380         return toRenderImage(renderer)->imageResource();
381 
382     if (renderer->isSVGImage())
383         return toRenderSVGImage(renderer)->imageResource();
384 
385     if (renderer->isVideo())
386         return toRenderVideo(renderer)->imageResource();
387 
388     return 0;
389 }
390 
updateRenderer()391 void ImageLoader::updateRenderer()
392 {
393     RenderImageResource* imageResource = renderImageResource();
394 
395     if (!imageResource)
396         return;
397 
398     // Only update the renderer if it doesn't have an image or if what we have
399     // is a complete image.  This prevents flickering in the case where a dynamic
400     // change is happening between two images.
401     ImageResource* cachedImage = imageResource->cachedImage();
402     if (m_image != cachedImage && (m_imageComplete || !cachedImage))
403         imageResource->setImageResource(m_image.get());
404 }
405 
updatedHasPendingEvent()406 void ImageLoader::updatedHasPendingEvent()
407 {
408     // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
409     // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
410     // destroyed by DOM manipulation or garbage collection.
411     // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
412     bool wasProtected = m_elementIsProtected;
413     m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
414     if (wasProtected == m_elementIsProtected)
415         return;
416 
417     if (m_elementIsProtected) {
418         if (m_derefElementTimer.isActive())
419             m_derefElementTimer.stop();
420         else
421             m_keepAlive = m_element;
422     } else {
423         ASSERT(!m_derefElementTimer.isActive());
424         m_derefElementTimer.startOneShot(0, FROM_HERE);
425     }
426 }
427 
timerFired(Timer<ImageLoader> *)428 void ImageLoader::timerFired(Timer<ImageLoader>*)
429 {
430     m_keepAlive.clear();
431 }
432 
dispatchPendingEvent(ImageEventSender * eventSender)433 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
434 {
435     ASSERT(eventSender == &loadEventSender() || eventSender == &errorEventSender());
436     const AtomicString& eventType = eventSender->eventType();
437     if (eventType == EventTypeNames::load)
438         dispatchPendingLoadEvent();
439     if (eventType == EventTypeNames::error)
440         dispatchPendingErrorEvent();
441 }
442 
dispatchPendingLoadEvent()443 void ImageLoader::dispatchPendingLoadEvent()
444 {
445     if (!m_hasPendingLoadEvent)
446         return;
447     if (!m_image)
448         return;
449     m_hasPendingLoadEvent = false;
450     if (element()->document().frame())
451         dispatchLoadEvent();
452 
453     // Only consider updating the protection ref-count of the Element immediately before returning
454     // from this function as doing so might result in the destruction of this ImageLoader.
455     updatedHasPendingEvent();
456 }
457 
dispatchPendingErrorEvent()458 void ImageLoader::dispatchPendingErrorEvent()
459 {
460     if (!m_hasPendingErrorEvent)
461         return;
462     m_hasPendingErrorEvent = false;
463 
464     if (element()->document().frame())
465         element()->dispatchEvent(Event::create(EventTypeNames::error));
466 
467     // Only consider updating the protection ref-count of the Element immediately before returning
468     // from this function as doing so might result in the destruction of this ImageLoader.
469     updatedHasPendingEvent();
470 }
471 
addClient(ImageLoaderClient * client)472 void ImageLoader::addClient(ImageLoaderClient* client)
473 {
474     if (client->requestsHighLiveResourceCachePriority()) {
475         if (m_image && !m_highPriorityClientCount++)
476             memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh);
477     }
478 #if ENABLE(OILPAN)
479     m_clients.add(client, adoptPtr(new ImageLoaderClientRemover(*this, *client)));
480 #else
481     m_clients.add(client);
482 #endif
483 }
484 
willRemoveClient(ImageLoaderClient & client)485 void ImageLoader::willRemoveClient(ImageLoaderClient& client)
486 {
487     if (client.requestsHighLiveResourceCachePriority()) {
488         ASSERT(m_highPriorityClientCount);
489         m_highPriorityClientCount--;
490         if (m_image && !m_highPriorityClientCount)
491             memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow);
492     }
493 }
494 
removeClient(ImageLoaderClient * client)495 void ImageLoader::removeClient(ImageLoaderClient* client)
496 {
497     willRemoveClient(*client);
498     m_clients.remove(client);
499 }
500 
dispatchPendingLoadEvents()501 void ImageLoader::dispatchPendingLoadEvents()
502 {
503     loadEventSender().dispatchPendingEvents();
504 }
505 
dispatchPendingErrorEvents()506 void ImageLoader::dispatchPendingErrorEvents()
507 {
508     errorEventSender().dispatchPendingEvents();
509 }
510 
elementDidMoveToNewDocument()511 void ImageLoader::elementDidMoveToNewDocument()
512 {
513     if (m_delayLoad) {
514         m_delayLoad->documentChanged(m_element->document());
515     }
516     clearFailedLoadURL();
517     setImage(0);
518 }
519 
sourceImageChanged()520 void ImageLoader::sourceImageChanged()
521 {
522 #if ENABLE(OILPAN)
523     PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator end = m_clients.end();
524     for (PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator it = m_clients.begin(); it != end; ++it) {
525         it->key->notifyImageSourceChanged();
526     }
527 #else
528     HashSet<ImageLoaderClient*>::iterator end = m_clients.end();
529     for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) {
530         ImageLoaderClient* handle = *it;
531         handle->notifyImageSourceChanged();
532     }
533 #endif
534 }
535 
clearFailedLoadURL()536 inline void ImageLoader::clearFailedLoadURL()
537 {
538     m_failedLoadURL = AtomicString();
539 }
540 
541 #if ENABLE(OILPAN)
~ImageLoaderClientRemover()542 ImageLoader::ImageLoaderClientRemover::~ImageLoaderClientRemover()
543 {
544     m_loader.willRemoveClient(m_client);
545 }
546 #endif
547 
548 }
549