• 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 "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"
39 
40 namespace WebCore {
41 
beforeLoadEventSender()42 static ImageEventSender& beforeLoadEventSender()
43 {
44     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::beforeload));
45     return sender;
46 }
47 
loadEventSender()48 static ImageEventSender& loadEventSender()
49 {
50     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load));
51     return sender;
52 }
53 
errorEventSender()54 static ImageEventSender& errorEventSender()
55 {
56     DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error));
57     return sender;
58 }
59 
pageIsBeingDismissed(Document * document)60 static inline bool pageIsBeingDismissed(Document* document)
61 {
62     return document->pageDismissalEventBeingDispatched() != Document::NoDismissal;
63 }
64 
ImageLoader(Element * element)65 ImageLoader::ImageLoader(Element* element)
66     : m_element(element)
67     , m_image(0)
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)
76 {
77 }
78 
~ImageLoader()79 ImageLoader::~ImageLoader()
80 {
81     if (m_image)
82         m_image->removeClient(this);
83 
84     ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(this));
85     if (m_hasPendingBeforeLoadEvent)
86         beforeLoadEventSender().cancelEvent(this);
87 
88     ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
89     if (m_hasPendingLoadEvent)
90         loadEventSender().cancelEvent(this);
91 
92     ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
93     if (m_hasPendingErrorEvent)
94         errorEventSender().cancelEvent(this);
95 
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)
99         m_element->deref();
100 }
101 
setImage(ImageResource * newImage)102 void ImageLoader::setImage(ImageResource* newImage)
103 {
104     setImageWithoutConsideringPendingLoadEvent(newImage);
105 
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();
109 }
110 
setImageWithoutConsideringPendingLoadEvent(ImageResource * newImage)111 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage)
112 {
113     ASSERT(m_failedLoadURL.isEmpty());
114     ImageResource* oldImage = m_image.get();
115     if (newImage != oldImage) {
116         sourceImageChanged();
117         m_image = newImage;
118         if (m_hasPendingBeforeLoadEvent) {
119             beforeLoadEventSender().cancelEvent(this);
120             m_hasPendingBeforeLoadEvent = false;
121         }
122         if (m_hasPendingLoadEvent) {
123             loadEventSender().cancelEvent(this);
124             m_hasPendingLoadEvent = false;
125         }
126         if (m_hasPendingErrorEvent) {
127             errorEventSender().cancelEvent(this);
128             m_hasPendingErrorEvent = false;
129         }
130         m_imageComplete = true;
131         if (newImage)
132             newImage->addClient(this);
133         if (oldImage)
134             oldImage->removeClient(this);
135     }
136 
137     if (RenderImageResource* imageResource = renderImageResource())
138         imageResource->resetAnimation();
139 }
140 
updateFromElement()141 void ImageLoader::updateFromElement()
142 {
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())
147         return;
148 
149     AtomicString attr = m_element->imageSourceURL();
150 
151     if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
152         return;
153 
154     // Do not load any image if the 'src' attribute is missing or if it is
155     // an empty string.
156     ResourcePtr<ImageResource> newImage = 0;
157     if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
158         FetchRequest request(ResourceRequest(document.completeURL(sourceURI(attr))), element()->localName());
159 
160         AtomicString crossOriginMode = m_element->fastGetAttribute(HTMLNames::crossoriginAttr);
161         if (!crossOriginMode.isNull()) {
162             StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
163             updateRequestForAccessControl(request.mutableResourceRequest(), document.securityOrigin(), allowCredentials);
164         }
165 
166         if (m_loadManually) {
167             bool autoLoadOtherImages = document.fetcher()->autoLoadImages();
168             document.fetcher()->setAutoLoadImages(false);
169             newImage = new ImageResource(request.resourceRequest());
170             newImage->setLoading(true);
171             document.fetcher()->m_documentResources.set(newImage->url(), newImage.get());
172             document.fetcher()->setAutoLoadImages(autoLoadOtherImages);
173         } else {
174             newImage = document.fetcher()->fetchImage(request);
175         }
176 
177         // If we do not have an image here, it means that a cross-site
178         // violation occurred, or that the image was blocked via Content
179         // Security Policy, or the page is being dismissed. Trigger an
180         // error event if the page is not being dismissed.
181         if (!newImage && !pageIsBeingDismissed(&document)) {
182             m_failedLoadURL = attr;
183             m_hasPendingErrorEvent = true;
184             errorEventSender().dispatchEventSoon(this);
185         } else
186             clearFailedLoadURL();
187     } else if (!attr.isNull()) {
188         // Fire an error event if the url is empty.
189         m_hasPendingErrorEvent = true;
190         errorEventSender().dispatchEventSoon(this);
191     }
192 
193     ImageResource* oldImage = m_image.get();
194     if (newImage != oldImage) {
195         sourceImageChanged();
196 
197         if (m_hasPendingBeforeLoadEvent) {
198             beforeLoadEventSender().cancelEvent(this);
199             m_hasPendingBeforeLoadEvent = false;
200         }
201         if (m_hasPendingLoadEvent) {
202             loadEventSender().cancelEvent(this);
203             m_hasPendingLoadEvent = false;
204         }
205 
206         // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
207         // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
208         // this load and we should not cancel the event.
209         // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
210         if (m_hasPendingErrorEvent && newImage) {
211             errorEventSender().cancelEvent(this);
212             m_hasPendingErrorEvent = false;
213         }
214 
215         m_image = newImage;
216         m_hasPendingBeforeLoadEvent = !m_element->document().isImageDocument() && newImage;
217         m_hasPendingLoadEvent = newImage;
218         m_imageComplete = !newImage;
219 
220         if (newImage) {
221             if (!m_element->document().isImageDocument()) {
222                 if (!m_element->document().hasListenerType(Document::BEFORELOAD_LISTENER))
223                     dispatchPendingBeforeLoadEvent();
224                 else
225                     beforeLoadEventSender().dispatchEventSoon(this);
226             } else
227                 updateRenderer();
228 
229             // If newImage is cached, addClient() will result in the load event
230             // being queued to fire. Ensure this happens after beforeload is
231             // dispatched.
232             newImage->addClient(this);
233         } else {
234             updateRenderer();
235         }
236 
237         if (oldImage)
238             oldImage->removeClient(this);
239     }
240 
241     if (RenderImageResource* imageResource = renderImageResource())
242         imageResource->resetAnimation();
243 
244     // Only consider updating the protection ref-count of the Element immediately before returning
245     // from this function as doing so might result in the destruction of this ImageLoader.
246     updatedHasPendingEvent();
247 }
248 
updateFromElementIgnoringPreviousError()249 void ImageLoader::updateFromElementIgnoringPreviousError()
250 {
251     clearFailedLoadURL();
252     updateFromElement();
253 }
254 
notifyFinished(Resource * resource)255 void ImageLoader::notifyFinished(Resource* resource)
256 {
257     ASSERT(m_failedLoadURL.isEmpty());
258     ASSERT(resource == m_image.get());
259 
260     m_imageComplete = true;
261     if (!hasPendingBeforeLoadEvent())
262         updateRenderer();
263 
264     if (!m_hasPendingLoadEvent)
265         return;
266 
267     if (m_element->fastHasAttribute(HTMLNames::crossoriginAttr)
268         && !m_element->document().securityOrigin()->canRequest(image()->response().url())
269         && !resource->passesAccessControlCheck(m_element->document().securityOrigin())) {
270 
271         setImageWithoutConsideringPendingLoadEvent(0);
272 
273         m_hasPendingErrorEvent = true;
274         errorEventSender().dispatchEventSoon(this);
275 
276         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Cross-origin image load denied by Cross-Origin Resource Sharing policy."));
277         m_element->document().addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, consoleMessage);
278 
279         ASSERT(!m_hasPendingLoadEvent);
280 
281         // Only consider updating the protection ref-count of the Element immediately before returning
282         // from this function as doing so might result in the destruction of this ImageLoader.
283         updatedHasPendingEvent();
284         return;
285     }
286 
287     if (resource->wasCanceled()) {
288         m_hasPendingLoadEvent = false;
289         // Only consider updating the protection ref-count of the Element immediately before returning
290         // from this function as doing so might result in the destruction of this ImageLoader.
291         updatedHasPendingEvent();
292         return;
293     }
294 
295     loadEventSender().dispatchEventSoon(this);
296 }
297 
renderImageResource()298 RenderImageResource* ImageLoader::renderImageResource()
299 {
300     RenderObject* renderer = m_element->renderer();
301 
302     if (!renderer)
303         return 0;
304 
305     // We don't return style generated image because it doesn't belong to the ImageLoader.
306     // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
307     if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent())
308         return toRenderImage(renderer)->imageResource();
309 
310     if (renderer->isSVGImage())
311         return toRenderSVGImage(renderer)->imageResource();
312 
313     if (renderer->isVideo())
314         return toRenderVideo(renderer)->imageResource();
315 
316     return 0;
317 }
318 
updateRenderer()319 void ImageLoader::updateRenderer()
320 {
321     RenderImageResource* imageResource = renderImageResource();
322 
323     if (!imageResource)
324         return;
325 
326     // Only update the renderer if it doesn't have an image or if what we have
327     // is a complete image.  This prevents flickering in the case where a dynamic
328     // change is happening between two images.
329     ImageResource* cachedImage = imageResource->cachedImage();
330     if (m_image != cachedImage && (m_imageComplete || !cachedImage))
331         imageResource->setImageResource(m_image.get());
332 }
333 
updatedHasPendingEvent()334 void ImageLoader::updatedHasPendingEvent()
335 {
336     // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
337     // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
338     // destroyed by DOM manipulation or garbage collection.
339     // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
340     bool wasProtected = m_elementIsProtected;
341     m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
342     if (wasProtected == m_elementIsProtected)
343         return;
344 
345     if (m_elementIsProtected) {
346         if (m_derefElementTimer.isActive())
347             m_derefElementTimer.stop();
348         else
349             m_element->ref();
350     } else {
351         ASSERT(!m_derefElementTimer.isActive());
352         m_derefElementTimer.startOneShot(0);
353     }
354 }
355 
timerFired(Timer<ImageLoader> *)356 void ImageLoader::timerFired(Timer<ImageLoader>*)
357 {
358     m_element->deref();
359 }
360 
dispatchPendingEvent(ImageEventSender * eventSender)361 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
362 {
363     ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
364     const AtomicString& eventType = eventSender->eventType();
365     if (eventType == EventTypeNames::beforeload)
366         dispatchPendingBeforeLoadEvent();
367     if (eventType == EventTypeNames::load)
368         dispatchPendingLoadEvent();
369     if (eventType == EventTypeNames::error)
370         dispatchPendingErrorEvent();
371 }
372 
dispatchPendingBeforeLoadEvent()373 void ImageLoader::dispatchPendingBeforeLoadEvent()
374 {
375     if (!m_hasPendingBeforeLoadEvent)
376         return;
377     if (!m_image)
378         return;
379     if (!m_element->document().frame())
380         return;
381     m_hasPendingBeforeLoadEvent = false;
382     if (m_element->dispatchBeforeLoadEvent(m_image->url().string())) {
383         updateRenderer();
384         return;
385     }
386     if (m_image) {
387         m_image->removeClient(this);
388         m_image = 0;
389     }
390 
391     loadEventSender().cancelEvent(this);
392     m_hasPendingLoadEvent = false;
393 
394     if (m_element->hasTagName(HTMLNames::objectTag))
395         toHTMLObjectElement(m_element)->renderFallbackContent();
396 
397     // Only consider updating the protection ref-count of the Element immediately before returning
398     // from this function as doing so might result in the destruction of this ImageLoader.
399     updatedHasPendingEvent();
400 }
401 
dispatchPendingLoadEvent()402 void ImageLoader::dispatchPendingLoadEvent()
403 {
404     if (!m_hasPendingLoadEvent)
405         return;
406     if (!m_image)
407         return;
408     m_hasPendingLoadEvent = false;
409     if (element()->document().frame())
410         dispatchLoadEvent();
411 
412     // Only consider updating the protection ref-count of the Element immediately before returning
413     // from this function as doing so might result in the destruction of this ImageLoader.
414     updatedHasPendingEvent();
415 }
416 
dispatchPendingErrorEvent()417 void ImageLoader::dispatchPendingErrorEvent()
418 {
419     if (!m_hasPendingErrorEvent)
420         return;
421     m_hasPendingErrorEvent = false;
422     if (element()->document().frame())
423         element()->dispatchEvent(Event::create(EventTypeNames::error));
424 
425     // Only consider updating the protection ref-count of the Element immediately before returning
426     // from this function as doing so might result in the destruction of this ImageLoader.
427     updatedHasPendingEvent();
428 }
429 
addClient(ImageLoaderClient * client)430 void ImageLoader::addClient(ImageLoaderClient* client)
431 {
432     if (client->requestsHighLiveResourceCachePriority()) {
433         if (m_image && !m_highPriorityClientCount++)
434             m_image->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityHigh);
435     }
436     m_clients.add(client);
437 }
removeClient(ImageLoaderClient * client)438 void ImageLoader::removeClient(ImageLoaderClient* client)
439 {
440     if (client->requestsHighLiveResourceCachePriority()) {
441         ASSERT(m_highPriorityClientCount);
442         m_highPriorityClientCount--;
443         if (m_image && !m_highPriorityClientCount)
444             m_image->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityLow);
445     }
446     m_clients.remove(client);
447 }
448 
dispatchPendingBeforeLoadEvents()449 void ImageLoader::dispatchPendingBeforeLoadEvents()
450 {
451     beforeLoadEventSender().dispatchPendingEvents();
452 }
453 
dispatchPendingLoadEvents()454 void ImageLoader::dispatchPendingLoadEvents()
455 {
456     loadEventSender().dispatchPendingEvents();
457 }
458 
dispatchPendingErrorEvents()459 void ImageLoader::dispatchPendingErrorEvents()
460 {
461     errorEventSender().dispatchPendingEvents();
462 }
463 
elementDidMoveToNewDocument()464 void ImageLoader::elementDidMoveToNewDocument()
465 {
466     clearFailedLoadURL();
467     setImage(0);
468 }
469 
sourceImageChanged()470 void ImageLoader::sourceImageChanged()
471 {
472     HashSet<ImageLoaderClient*>::iterator end = m_clients.end();
473     for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) {
474         ImageLoaderClient* handle = *it;
475         handle->notifyImageSourceChanged();
476     }
477 }
478 
clearFailedLoadURL()479 inline void ImageLoader::clearFailedLoadURL()
480 {
481     m_failedLoadURL = AtomicString();
482 }
483 
484 }
485