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 "bindings/core/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/Logging.h" 43 #include "platform/weborigin/SecurityOrigin.h" 44 #include "public/platform/WebURLRequest.h" 45 46 namespace blink { 47 48 static ImageEventSender& loadEventSender() 49 { 50 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load)); 51 return sender; 52 } 53 54 static ImageEventSender& errorEventSender() 55 { 56 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error)); 57 return sender; 58 } 59 60 static inline bool pageIsBeingDismissed(Document* document) 61 { 62 return document->pageDismissalEventBeingDispatched() != Document::NoDismissal; 63 } 64 65 static ImageLoader::BypassMainWorldBehavior shouldBypassMainWorldCSP(ImageLoader* loader) 66 { 67 ASSERT(loader); 68 ASSERT(loader->element()); 69 ASSERT(loader->element()->document().frame()); 70 if (loader->element()->document().frame()->script().shouldBypassMainWorldCSP()) 71 return ImageLoader::BypassMainWorldCSP; 72 return ImageLoader::DoNotBypassMainWorldCSP; 73 } 74 75 class ImageLoader::Task : public blink::WebThread::Task { 76 public: 77 static PassOwnPtr<Task> create(ImageLoader* loader, UpdateFromElementBehavior updateBehavior) 78 { 79 return adoptPtr(new Task(loader, updateBehavior)); 80 } 81 82 Task(ImageLoader* loader, UpdateFromElementBehavior updateBehavior) 83 : m_loader(loader) 84 , m_shouldBypassMainWorldCSP(shouldBypassMainWorldCSP(loader)) 85 , m_weakFactory(this) 86 , m_updateBehavior(updateBehavior) 87 { 88 } 89 90 virtual void run() OVERRIDE 91 { 92 if (m_loader) { 93 m_loader->doUpdateFromElement(m_shouldBypassMainWorldCSP, m_updateBehavior); 94 } 95 } 96 97 void clearLoader() 98 { 99 m_loader = 0; 100 } 101 102 WeakPtr<Task> createWeakPtr() 103 { 104 return m_weakFactory.createWeakPtr(); 105 } 106 107 private: 108 ImageLoader* m_loader; 109 BypassMainWorldBehavior m_shouldBypassMainWorldCSP; 110 WeakPtrFactory<Task> m_weakFactory; 111 UpdateFromElementBehavior m_updateBehavior; 112 }; 113 114 ImageLoader::ImageLoader(Element* element) 115 : m_element(element) 116 , m_image(0) 117 , m_derefElementTimer(this, &ImageLoader::timerFired) 118 , m_hasPendingLoadEvent(false) 119 , m_hasPendingErrorEvent(false) 120 , m_imageComplete(true) 121 , m_loadingImageDocument(false) 122 , m_elementIsProtected(false) 123 , m_highPriorityClientCount(0) 124 { 125 WTF_LOG(Timers, "new ImageLoader %p", this); 126 } 127 128 ImageLoader::~ImageLoader() 129 { 130 WTF_LOG(Timers, "~ImageLoader %p; m_hasPendingLoadEvent=%d, m_hasPendingErrorEvent=%d", 131 this, m_hasPendingLoadEvent, m_hasPendingErrorEvent); 132 133 if (m_pendingTask) 134 m_pendingTask->clearLoader(); 135 136 if (m_image) 137 m_image->removeClient(this); 138 139 ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this)); 140 if (m_hasPendingLoadEvent) 141 loadEventSender().cancelEvent(this); 142 143 ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this)); 144 if (m_hasPendingErrorEvent) 145 errorEventSender().cancelEvent(this); 146 } 147 148 void ImageLoader::trace(Visitor* visitor) 149 { 150 visitor->trace(m_element); 151 } 152 153 void ImageLoader::setImage(ImageResource* newImage) 154 { 155 setImageWithoutConsideringPendingLoadEvent(newImage); 156 157 // Only consider updating the protection ref-count of the Element immediately before returning 158 // from this function as doing so might result in the destruction of this ImageLoader. 159 updatedHasPendingEvent(); 160 } 161 162 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newImage) 163 { 164 ASSERT(m_failedLoadURL.isEmpty()); 165 ImageResource* oldImage = m_image.get(); 166 if (newImage != oldImage) { 167 sourceImageChanged(); 168 m_image = newImage; 169 if (m_hasPendingLoadEvent) { 170 loadEventSender().cancelEvent(this); 171 m_hasPendingLoadEvent = false; 172 } 173 if (m_hasPendingErrorEvent) { 174 errorEventSender().cancelEvent(this); 175 m_hasPendingErrorEvent = false; 176 } 177 m_imageComplete = true; 178 if (newImage) 179 newImage->addClient(this); 180 if (oldImage) 181 oldImage->removeClient(this); 182 } 183 184 if (RenderImageResource* imageResource = renderImageResource()) 185 imageResource->resetAnimation(); 186 } 187 188 static void configureRequest(FetchRequest& request, ImageLoader::BypassMainWorldBehavior bypassBehavior, Element& element) 189 { 190 if (bypassBehavior == ImageLoader::BypassMainWorldCSP) 191 request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy); 192 193 AtomicString crossOriginMode = element.fastGetAttribute(HTMLNames::crossoriginAttr); 194 if (!crossOriginMode.isNull()) 195 request.setCrossOriginAccessControl(element.document().securityOrigin(), crossOriginMode); 196 } 197 198 ResourcePtr<ImageResource> ImageLoader::createImageResourceForImageDocument(Document& document, FetchRequest& request) 199 { 200 bool autoLoadOtherImages = document.fetcher()->autoLoadImages(); 201 document.fetcher()->setAutoLoadImages(false); 202 ResourcePtr<ImageResource> newImage = new ImageResource(request.resourceRequest()); 203 newImage->setLoading(true); 204 document.fetcher()->m_documentResources.set(newImage->url(), newImage.get()); 205 document.fetcher()->setAutoLoadImages(autoLoadOtherImages); 206 return newImage; 207 } 208 209 inline void ImageLoader::crossSiteOrCSPViolationOccured(AtomicString imageSourceURL) 210 { 211 m_failedLoadURL = imageSourceURL; 212 m_hasPendingErrorEvent = true; 213 errorEventSender().dispatchEventSoon(this); 214 } 215 216 inline void ImageLoader::clearFailedLoadURL() 217 { 218 m_failedLoadURL = AtomicString(); 219 } 220 221 inline void ImageLoader::enqueueImageLoadingMicroTask(UpdateFromElementBehavior updateBehavior) 222 { 223 OwnPtr<Task> task = Task::create(this, updateBehavior); 224 m_pendingTask = task->createWeakPtr(); 225 Microtask::enqueueMicrotask(task.release()); 226 m_loadDelayCounter = IncrementLoadEventDelayCount::create(m_element->document()); 227 } 228 229 void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, UpdateFromElementBehavior updateBehavior) 230 { 231 // FIXME: According to 232 // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#the-img-element:the-img-element-55 233 // When "update image" is called due to environment changes and the load fails, onerror should not be called. 234 // That is currently not the case. 235 // 236 // We don't need to call clearLoader here: Either we were called from the 237 // task, or our caller updateFromElement cleared the task's loader (and set 238 // m_pendingTask to null). 239 m_pendingTask.clear(); 240 // Make sure to only decrement the count when we exit this function 241 OwnPtr<IncrementLoadEventDelayCount> loadDelayCounter; 242 loadDelayCounter.swap(m_loadDelayCounter); 243 244 Document& document = m_element->document(); 245 if (!document.isActive()) 246 return; 247 248 AtomicString imageSourceURL = m_element->imageSourceURL(); 249 KURL url = imageSourceToKURL(imageSourceURL); 250 ResourcePtr<ImageResource> newImage = 0; 251 if (!url.isNull()) { 252 // Unlike raw <img>, we block mixed content inside of <picture> or <img srcset>. 253 ResourceLoaderOptions resourceLoaderOptions = ResourceFetcher::defaultResourceOptions(); 254 ResourceRequest resourceRequest(url); 255 if (isHTMLPictureElement(element()->parentNode()) || !element()->fastGetAttribute(HTMLNames::srcsetAttr).isNull()) { 256 resourceLoaderOptions.mixedContentBlockingTreatment = TreatAsActiveContent; 257 resourceRequest.setRequestContext(WebURLRequest::RequestContextImageSet); 258 } 259 FetchRequest request(resourceRequest, element()->localName(), resourceLoaderOptions); 260 configureRequest(request, bypassBehavior, *m_element); 261 262 if (m_loadingImageDocument) 263 newImage = createImageResourceForImageDocument(document, request); 264 else 265 newImage = document.fetcher()->fetchImage(request); 266 267 if (!newImage && !pageIsBeingDismissed(&document)) 268 crossSiteOrCSPViolationOccured(imageSourceURL); 269 else 270 clearFailedLoadURL(); 271 } else if (!imageSourceURL.isNull()) { 272 // Fire an error event if the url string is not empty, but the KURL is. 273 m_hasPendingErrorEvent = true; 274 errorEventSender().dispatchEventSoon(this); 275 } 276 277 ImageResource* oldImage = m_image.get(); 278 if (newImage != oldImage) { 279 sourceImageChanged(); 280 281 if (m_hasPendingLoadEvent) { 282 loadEventSender().cancelEvent(this); 283 m_hasPendingLoadEvent = false; 284 } 285 286 // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute. 287 // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by 288 // this load and we should not cancel the event. 289 // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two. 290 if (m_hasPendingErrorEvent && newImage) { 291 errorEventSender().cancelEvent(this); 292 m_hasPendingErrorEvent = false; 293 } 294 295 m_image = newImage; 296 m_hasPendingLoadEvent = newImage; 297 m_imageComplete = !newImage; 298 299 updateRenderer(); 300 // If newImage exists and is cached, addClient() will result in the load event 301 // being queued to fire. Ensure this happens after beforeload is dispatched. 302 if (newImage) 303 newImage->addClient(this); 304 305 if (oldImage) 306 oldImage->removeClient(this); 307 } else if (updateBehavior == UpdateSizeChanged && m_element->renderer() && m_element->renderer()->isImage()) { 308 toRenderImage(m_element->renderer())->intrinsicSizeChanged(); 309 } 310 311 if (RenderImageResource* imageResource = renderImageResource()) 312 imageResource->resetAnimation(); 313 314 // Only consider updating the protection ref-count of the Element immediately before returning 315 // from this function as doing so might result in the destruction of this ImageLoader. 316 updatedHasPendingEvent(); 317 } 318 319 void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior, LoadType loadType) 320 { 321 AtomicString imageSourceURL = m_element->imageSourceURL(); 322 323 if (updateBehavior == UpdateIgnorePreviousError) 324 clearFailedLoadURL(); 325 326 if (!m_failedLoadURL.isEmpty() && imageSourceURL == m_failedLoadURL) 327 return; 328 329 // If we have a pending task, we have to clear it -- either we're 330 // now loading immediately, or we need to reset the task's state. 331 if (m_pendingTask) { 332 m_pendingTask->clearLoader(); 333 m_pendingTask.clear(); 334 } 335 336 KURL url = imageSourceToKURL(imageSourceURL); 337 if (imageSourceURL.isNull() || url.isNull() || shouldLoadImmediately(url, loadType)) { 338 doUpdateFromElement(DoNotBypassMainWorldCSP, updateBehavior); 339 return; 340 } 341 enqueueImageLoadingMicroTask(updateBehavior); 342 } 343 344 KURL ImageLoader::imageSourceToKURL(AtomicString imageSourceURL) const 345 { 346 KURL url; 347 348 // Don't load images for inactive documents. We don't want to slow down the 349 // raw HTML parsing case by loading images we don't intend to display. 350 Document& document = m_element->document(); 351 if (!document.isActive()) 352 return url; 353 354 // Do not load any image if the 'src' attribute is missing or if it is 355 // an empty string. 356 if (!imageSourceURL.isNull() && !stripLeadingAndTrailingHTMLSpaces(imageSourceURL).isEmpty()) 357 url = document.completeURL(sourceURI(imageSourceURL)); 358 return url; 359 } 360 361 bool ImageLoader::shouldLoadImmediately(const KURL& url, LoadType loadType) const 362 { 363 return (m_loadingImageDocument 364 || isHTMLObjectElement(m_element) 365 || isHTMLEmbedElement(m_element) 366 || url.protocolIsData() 367 || memoryCache()->resourceForURL(url) 368 || loadType == ForceLoadImmediately); 369 } 370 371 void ImageLoader::notifyFinished(Resource* resource) 372 { 373 WTF_LOG(Timers, "ImageLoader::notifyFinished %p; m_hasPendingLoadEvent=%d", 374 this, m_hasPendingLoadEvent); 375 376 ASSERT(m_failedLoadURL.isEmpty()); 377 ASSERT(resource == m_image.get()); 378 379 m_imageComplete = true; 380 updateRenderer(); 381 382 if (!m_hasPendingLoadEvent) 383 return; 384 385 if (resource->errorOccurred()) { 386 loadEventSender().cancelEvent(this); 387 m_hasPendingLoadEvent = false; 388 389 m_hasPendingErrorEvent = true; 390 errorEventSender().dispatchEventSoon(this); 391 392 // Only consider updating the protection ref-count of the Element immediately before returning 393 // from this function as doing so might result in the destruction of this ImageLoader. 394 updatedHasPendingEvent(); 395 return; 396 } 397 if (resource->wasCanceled()) { 398 m_hasPendingLoadEvent = false; 399 // Only consider updating the protection ref-count of the Element immediately before returning 400 // from this function as doing so might result in the destruction of this ImageLoader. 401 updatedHasPendingEvent(); 402 return; 403 } 404 loadEventSender().dispatchEventSoon(this); 405 } 406 407 RenderImageResource* ImageLoader::renderImageResource() 408 { 409 RenderObject* renderer = m_element->renderer(); 410 411 if (!renderer) 412 return 0; 413 414 // We don't return style generated image because it doesn't belong to the ImageLoader. 415 // See <https://bugs.webkit.org/show_bug.cgi?id=42840> 416 if (renderer->isImage() && !static_cast<RenderImage*>(renderer)->isGeneratedContent()) 417 return toRenderImage(renderer)->imageResource(); 418 419 if (renderer->isSVGImage()) 420 return toRenderSVGImage(renderer)->imageResource(); 421 422 if (renderer->isVideo()) 423 return toRenderVideo(renderer)->imageResource(); 424 425 return 0; 426 } 427 428 void ImageLoader::updateRenderer() 429 { 430 RenderImageResource* imageResource = renderImageResource(); 431 432 if (!imageResource) 433 return; 434 435 // Only update the renderer if it doesn't have an image or if what we have 436 // is a complete image. This prevents flickering in the case where a dynamic 437 // change is happening between two images. 438 ImageResource* cachedImage = imageResource->cachedImage(); 439 if (m_image != cachedImage && (m_imageComplete || !cachedImage)) 440 imageResource->setImageResource(m_image.get()); 441 } 442 443 void ImageLoader::updatedHasPendingEvent() 444 { 445 // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable. 446 // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being 447 // destroyed by DOM manipulation or garbage collection. 448 // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly. 449 bool wasProtected = m_elementIsProtected; 450 m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent; 451 if (wasProtected == m_elementIsProtected) 452 return; 453 454 if (m_elementIsProtected) { 455 if (m_derefElementTimer.isActive()) 456 m_derefElementTimer.stop(); 457 else 458 m_keepAlive = m_element; 459 } else { 460 ASSERT(!m_derefElementTimer.isActive()); 461 m_derefElementTimer.startOneShot(0, FROM_HERE); 462 } 463 } 464 465 void ImageLoader::timerFired(Timer<ImageLoader>*) 466 { 467 m_keepAlive.clear(); 468 } 469 470 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender) 471 { 472 WTF_LOG(Timers, "ImageLoader::dispatchPendingEvent %p", this); 473 ASSERT(eventSender == &loadEventSender() || eventSender == &errorEventSender()); 474 const AtomicString& eventType = eventSender->eventType(); 475 if (eventType == EventTypeNames::load) 476 dispatchPendingLoadEvent(); 477 if (eventType == EventTypeNames::error) 478 dispatchPendingErrorEvent(); 479 } 480 481 void ImageLoader::dispatchPendingLoadEvent() 482 { 483 if (!m_hasPendingLoadEvent) 484 return; 485 if (!m_image) 486 return; 487 m_hasPendingLoadEvent = false; 488 if (element()->document().frame()) 489 dispatchLoadEvent(); 490 491 // Only consider updating the protection ref-count of the Element immediately before returning 492 // from this function as doing so might result in the destruction of this ImageLoader. 493 updatedHasPendingEvent(); 494 } 495 496 void ImageLoader::dispatchPendingErrorEvent() 497 { 498 if (!m_hasPendingErrorEvent) 499 return; 500 m_hasPendingErrorEvent = false; 501 502 if (element()->document().frame()) 503 element()->dispatchEvent(Event::create(EventTypeNames::error)); 504 505 // Only consider updating the protection ref-count of the Element immediately before returning 506 // from this function as doing so might result in the destruction of this ImageLoader. 507 updatedHasPendingEvent(); 508 } 509 510 void ImageLoader::addClient(ImageLoaderClient* client) 511 { 512 if (client->requestsHighLiveResourceCachePriority()) { 513 if (m_image && !m_highPriorityClientCount++) 514 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh); 515 } 516 #if ENABLE(OILPAN) 517 m_clients.add(client, adoptPtr(new ImageLoaderClientRemover(*this, *client))); 518 #else 519 m_clients.add(client); 520 #endif 521 } 522 523 void ImageLoader::willRemoveClient(ImageLoaderClient& client) 524 { 525 if (client.requestsHighLiveResourceCachePriority()) { 526 ASSERT(m_highPriorityClientCount); 527 m_highPriorityClientCount--; 528 if (m_image && !m_highPriorityClientCount) 529 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow); 530 } 531 } 532 533 void ImageLoader::removeClient(ImageLoaderClient* client) 534 { 535 willRemoveClient(*client); 536 m_clients.remove(client); 537 } 538 539 void ImageLoader::dispatchPendingLoadEvents() 540 { 541 loadEventSender().dispatchPendingEvents(); 542 } 543 544 void ImageLoader::dispatchPendingErrorEvents() 545 { 546 errorEventSender().dispatchPendingEvents(); 547 } 548 549 void ImageLoader::elementDidMoveToNewDocument() 550 { 551 if (m_loadDelayCounter) 552 m_loadDelayCounter->documentChanged(m_element->document()); 553 clearFailedLoadURL(); 554 setImage(0); 555 } 556 557 void ImageLoader::sourceImageChanged() 558 { 559 #if ENABLE(OILPAN) 560 PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator end = m_clients.end(); 561 for (PersistentHeapHashMap<WeakMember<ImageLoaderClient>, OwnPtr<ImageLoaderClientRemover> >::iterator it = m_clients.begin(); it != end; ++it) { 562 it->key->notifyImageSourceChanged(); 563 } 564 #else 565 HashSet<ImageLoaderClient*>::iterator end = m_clients.end(); 566 for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end; ++it) { 567 ImageLoaderClient* handle = *it; 568 handle->notifyImageSourceChanged(); 569 } 570 #endif 571 } 572 573 #if ENABLE(OILPAN) 574 ImageLoader::ImageLoaderClientRemover::~ImageLoaderClientRemover() 575 { 576 m_loader.willRemoveClient(m_client); 577 } 578 #endif 579 580 } 581