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