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