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