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