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/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 46namespace blink { 47 48static ImageEventSender& loadEventSender() 49{ 50 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load)); 51 return sender; 52} 53 54static ImageEventSender& errorEventSender() 55{ 56 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error)); 57 return sender; 58} 59 60static inline bool pageIsBeingDismissed(Document* document) 61{ 62 return document->pageDismissalEventBeingDispatched() != Document::NoDismissal; 63} 64 65static 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 75class ImageLoader::Task : public blink::WebThread::Task { 76public: 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 107private: 108 ImageLoader* m_loader; 109 BypassMainWorldBehavior m_shouldBypassMainWorldCSP; 110 WeakPtrFactory<Task> m_weakFactory; 111 UpdateFromElementBehavior m_updateBehavior; 112}; 113 114ImageLoader::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 128ImageLoader::~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 148void ImageLoader::trace(Visitor* visitor) 149{ 150 visitor->trace(m_element); 151} 152 153void 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 162void 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 188static 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 198ResourcePtr<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 209inline void ImageLoader::crossSiteOrCSPViolationOccured(AtomicString imageSourceURL) 210{ 211 m_failedLoadURL = imageSourceURL; 212 m_hasPendingErrorEvent = true; 213 errorEventSender().dispatchEventSoon(this); 214} 215 216inline void ImageLoader::clearFailedLoadURL() 217{ 218 m_failedLoadURL = AtomicString(); 219} 220 221inline 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 229void 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 319void 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 344KURL 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 361bool 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 371void 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 407RenderImageResource* 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 428void 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 443void 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 465void ImageLoader::timerFired(Timer<ImageLoader>*) 466{ 467 m_keepAlive.clear(); 468} 469 470void 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 481void 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 496void 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 510void 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 523void 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 533void ImageLoader::removeClient(ImageLoaderClient* client) 534{ 535 willRemoveClient(*client); 536 m_clients.remove(client); 537} 538 539void ImageLoader::dispatchPendingLoadEvents() 540{ 541 loadEventSender().dispatchPendingEvents(); 542} 543 544void ImageLoader::dispatchPendingErrorEvents() 545{ 546 errorEventSender().dispatchPendingEvents(); 547} 548 549void ImageLoader::elementDidMoveToNewDocument() 550{ 551 if (m_loadDelayCounter) 552 m_loadDelayCounter->documentChanged(m_element->document()); 553 clearFailedLoadURL(); 554 setImage(0); 555} 556 557void 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) 574ImageLoader::ImageLoaderClientRemover::~ImageLoaderClientRemover() 575{ 576 m_loader.willRemoveClient(m_client); 577} 578#endif 579 580} 581