| OLD | NEW |
| (Empty) |
| 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 reserv
ed. | |
| 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 "sky/engine/core/loader/ImageLoader.h" | |
| 23 | |
| 24 #include "sky/engine/core/dom/Document.h" | |
| 25 #include "sky/engine/core/dom/Element.h" | |
| 26 #include "sky/engine/core/dom/IncrementLoadEventDelayCount.h" | |
| 27 #include "sky/engine/core/dom/Microtask.h" | |
| 28 #include "sky/engine/core/events/Event.h" | |
| 29 #include "sky/engine/core/events/EventSender.h" | |
| 30 #include "sky/engine/core/fetch/FetchRequest.h" | |
| 31 #include "sky/engine/core/fetch/MemoryCache.h" | |
| 32 #include "sky/engine/core/fetch/ResourceFetcher.h" | |
| 33 #include "sky/engine/core/frame/LocalFrame.h" | |
| 34 #include "sky/engine/core/html/parser/HTMLParserIdioms.h" | |
| 35 #include "sky/engine/platform/Logging.h" | |
| 36 #include "sky/engine/public/platform/WebURLRequest.h" | |
| 37 | |
| 38 namespace blink { | |
| 39 | |
| 40 static ImageEventSender& loadEventSender() | |
| 41 { | |
| 42 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::load)); | |
| 43 return sender; | |
| 44 } | |
| 45 | |
| 46 static ImageEventSender& errorEventSender() | |
| 47 { | |
| 48 DEFINE_STATIC_LOCAL(ImageEventSender, sender, (EventTypeNames::error)); | |
| 49 return sender; | |
| 50 } | |
| 51 | |
| 52 static inline bool pageIsBeingDismissed(Document* document) | |
| 53 { | |
| 54 return document->pageDismissalEventBeingDispatched() != Document::NoDismissa
l; | |
| 55 } | |
| 56 | |
| 57 class ImageLoader::Task : public blink::WebThread::Task { | |
| 58 public: | |
| 59 static PassOwnPtr<Task> create(ImageLoader* loader, UpdateFromElementBehavio
r updateBehavior) | |
| 60 { | |
| 61 return adoptPtr(new Task(loader, updateBehavior)); | |
| 62 } | |
| 63 | |
| 64 Task(ImageLoader* loader, UpdateFromElementBehavior updateBehavior) | |
| 65 : m_loader(loader) | |
| 66 , m_weakFactory(this) | |
| 67 , m_updateBehavior(updateBehavior) | |
| 68 { | |
| 69 } | |
| 70 | |
| 71 virtual void run() override | |
| 72 { | |
| 73 if (m_loader) { | |
| 74 m_loader->doUpdateFromElement(m_updateBehavior); | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 void clearLoader() | |
| 79 { | |
| 80 m_loader = 0; | |
| 81 } | |
| 82 | |
| 83 WeakPtr<Task> createWeakPtr() | |
| 84 { | |
| 85 return m_weakFactory.createWeakPtr(); | |
| 86 } | |
| 87 | |
| 88 private: | |
| 89 ImageLoader* m_loader; | |
| 90 WeakPtrFactory<Task> m_weakFactory; | |
| 91 UpdateFromElementBehavior m_updateBehavior; | |
| 92 }; | |
| 93 | |
| 94 ImageLoader::ImageLoader(Element* element) | |
| 95 : m_element(element) | |
| 96 , m_image(0) | |
| 97 , m_derefElementTimer(this, &ImageLoader::timerFired) | |
| 98 , m_hasPendingLoadEvent(false) | |
| 99 , m_hasPendingErrorEvent(false) | |
| 100 , m_imageComplete(true) | |
| 101 , m_elementIsProtected(false) | |
| 102 , m_highPriorityClientCount(0) | |
| 103 { | |
| 104 WTF_LOG(Timers, "new ImageLoader %p", this); | |
| 105 } | |
| 106 | |
| 107 ImageLoader::~ImageLoader() | |
| 108 { | |
| 109 WTF_LOG(Timers, "~ImageLoader %p; m_hasPendingLoadEvent=%d, m_hasPendingErro
rEvent=%d", | |
| 110 this, m_hasPendingLoadEvent, m_hasPendingErrorEvent); | |
| 111 | |
| 112 if (m_pendingTask) | |
| 113 m_pendingTask->clearLoader(); | |
| 114 | |
| 115 if (m_image) | |
| 116 m_image->removeClient(this); | |
| 117 | |
| 118 ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this)); | |
| 119 if (m_hasPendingLoadEvent) | |
| 120 loadEventSender().cancelEvent(this); | |
| 121 | |
| 122 ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this))
; | |
| 123 if (m_hasPendingErrorEvent) | |
| 124 errorEventSender().cancelEvent(this); | |
| 125 } | |
| 126 | |
| 127 void ImageLoader::setImage(ImageResource* newImage) | |
| 128 { | |
| 129 setImageWithoutConsideringPendingLoadEvent(newImage); | |
| 130 | |
| 131 // Only consider updating the protection ref-count of the Element immediatel
y before returning | |
| 132 // from this function as doing so might result in the destruction of this Im
ageLoader. | |
| 133 updatedHasPendingEvent(); | |
| 134 } | |
| 135 | |
| 136 void ImageLoader::setImageWithoutConsideringPendingLoadEvent(ImageResource* newI
mage) | |
| 137 { | |
| 138 ASSERT(m_failedLoadURL.isEmpty()); | |
| 139 ImageResource* oldImage = m_image.get(); | |
| 140 if (newImage != oldImage) { | |
| 141 sourceImageChanged(); | |
| 142 m_image = newImage; | |
| 143 if (m_hasPendingLoadEvent) { | |
| 144 loadEventSender().cancelEvent(this); | |
| 145 m_hasPendingLoadEvent = false; | |
| 146 } | |
| 147 if (m_hasPendingErrorEvent) { | |
| 148 errorEventSender().cancelEvent(this); | |
| 149 m_hasPendingErrorEvent = false; | |
| 150 } | |
| 151 m_imageComplete = true; | |
| 152 if (newImage) | |
| 153 newImage->addClient(this); | |
| 154 if (oldImage) | |
| 155 oldImage->removeClient(this); | |
| 156 } | |
| 157 } | |
| 158 | |
| 159 inline void ImageLoader::crossSiteOrCSPViolationOccured(AtomicString imageSource
URL) | |
| 160 { | |
| 161 m_failedLoadURL = imageSourceURL; | |
| 162 m_hasPendingErrorEvent = true; | |
| 163 errorEventSender().dispatchEventSoon(this); | |
| 164 } | |
| 165 | |
| 166 inline void ImageLoader::clearFailedLoadURL() | |
| 167 { | |
| 168 m_failedLoadURL = AtomicString(); | |
| 169 } | |
| 170 | |
| 171 inline void ImageLoader::enqueueImageLoadingMicroTask(UpdateFromElementBehavior
updateBehavior) | |
| 172 { | |
| 173 OwnPtr<Task> task = Task::create(this, updateBehavior); | |
| 174 m_pendingTask = task->createWeakPtr(); | |
| 175 Microtask::enqueueMicrotask(task.release()); | |
| 176 m_loadDelayCounter = IncrementLoadEventDelayCount::create(m_element->documen
t()); | |
| 177 } | |
| 178 | |
| 179 void ImageLoader::doUpdateFromElement(UpdateFromElementBehavior updateBehavior) | |
| 180 { | |
| 181 // FIXME: According to | |
| 182 // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-cont
ent.html#the-img-element:the-img-element-55 | |
| 183 // When "update image" is called due to environment changes and the load fai
ls, onerror should not be called. | |
| 184 // That is currently not the case. | |
| 185 // | |
| 186 // We don't need to call clearLoader here: Either we were called from the | |
| 187 // task, or our caller updateFromElement cleared the task's loader (and set | |
| 188 // m_pendingTask to null). | |
| 189 m_pendingTask.clear(); | |
| 190 // Make sure to only decrement the count when we exit this function | |
| 191 OwnPtr<IncrementLoadEventDelayCount> loadDelayCounter; | |
| 192 loadDelayCounter.swap(m_loadDelayCounter); | |
| 193 | |
| 194 Document& document = m_element->document(); | |
| 195 if (!document.isActive()) | |
| 196 return; | |
| 197 | |
| 198 AtomicString imageSourceURL = m_element->imageSourceURL(); | |
| 199 KURL url = imageSourceToKURL(imageSourceURL); | |
| 200 ResourcePtr<ImageResource> newImage = 0; | |
| 201 if (!url.isNull()) { | |
| 202 // Unlike raw <img>, we block mixed content inside of <picture> or <img
srcset>. | |
| 203 ResourceLoaderOptions resourceLoaderOptions = ResourceFetcher::defaultRe
sourceOptions(); | |
| 204 ResourceRequest resourceRequest(url); | |
| 205 FetchRequest request(ResourceRequest(url), element()->localName(), resou
rceLoaderOptions); | |
| 206 | |
| 207 newImage = document.fetcher()->fetchImage(request); | |
| 208 | |
| 209 if (!newImage && !pageIsBeingDismissed(&document)) | |
| 210 crossSiteOrCSPViolationOccured(imageSourceURL); | |
| 211 else | |
| 212 clearFailedLoadURL(); | |
| 213 } else if (!imageSourceURL.isNull()) { | |
| 214 // Fire an error event if the url string is not empty, but the KURL is. | |
| 215 m_hasPendingErrorEvent = true; | |
| 216 errorEventSender().dispatchEventSoon(this); | |
| 217 } | |
| 218 | |
| 219 ImageResource* oldImage = m_image.get(); | |
| 220 if (newImage != oldImage) { | |
| 221 sourceImageChanged(); | |
| 222 | |
| 223 if (m_hasPendingLoadEvent) { | |
| 224 loadEventSender().cancelEvent(this); | |
| 225 m_hasPendingLoadEvent = false; | |
| 226 } | |
| 227 | |
| 228 // Cancel error events that belong to the previous load, which is now ca
ncelled by changing the src attribute. | |
| 229 // If newImage is null and m_hasPendingErrorEvent is true, we know the e
rror event has been just posted by | |
| 230 // this load and we should not cancel the event. | |
| 231 // FIXME: If both previous load and this one got blocked with an error,
we can receive one error event instead of two. | |
| 232 if (m_hasPendingErrorEvent && newImage) { | |
| 233 errorEventSender().cancelEvent(this); | |
| 234 m_hasPendingErrorEvent = false; | |
| 235 } | |
| 236 | |
| 237 m_image = newImage; | |
| 238 m_hasPendingLoadEvent = newImage; | |
| 239 m_imageComplete = !newImage; | |
| 240 | |
| 241 updateRenderer(); | |
| 242 // If newImage exists and is cached, addClient() will result in the load
event | |
| 243 // being queued to fire. Ensure this happens after beforeload is dispatc
hed. | |
| 244 if (newImage) | |
| 245 newImage->addClient(this); | |
| 246 | |
| 247 if (oldImage) | |
| 248 oldImage->removeClient(this); | |
| 249 } | |
| 250 | |
| 251 // Only consider updating the protection ref-count of the Element immediatel
y before returning | |
| 252 // from this function as doing so might result in the destruction of this Im
ageLoader. | |
| 253 updatedHasPendingEvent(); | |
| 254 } | |
| 255 | |
| 256 void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior, Lo
adType loadType) | |
| 257 { | |
| 258 AtomicString imageSourceURL = m_element->imageSourceURL(); | |
| 259 | |
| 260 if (updateBehavior == UpdateIgnorePreviousError) | |
| 261 clearFailedLoadURL(); | |
| 262 | |
| 263 if (!m_failedLoadURL.isEmpty() && imageSourceURL == m_failedLoadURL) | |
| 264 return; | |
| 265 | |
| 266 // If we have a pending task, we have to clear it -- either we're | |
| 267 // now loading immediately, or we need to reset the task's state. | |
| 268 if (m_pendingTask) { | |
| 269 m_pendingTask->clearLoader(); | |
| 270 m_pendingTask.clear(); | |
| 271 } | |
| 272 | |
| 273 KURL url = imageSourceToKURL(imageSourceURL); | |
| 274 if (imageSourceURL.isNull() || url.isNull() || shouldLoadImmediately(url, lo
adType)) { | |
| 275 doUpdateFromElement(updateBehavior); | |
| 276 return; | |
| 277 } | |
| 278 enqueueImageLoadingMicroTask(updateBehavior); | |
| 279 } | |
| 280 | |
| 281 KURL ImageLoader::imageSourceToKURL(AtomicString imageSourceURL) const | |
| 282 { | |
| 283 KURL url; | |
| 284 | |
| 285 // Don't load images for inactive documents. We don't want to slow down the | |
| 286 // raw HTML parsing case by loading images we don't intend to display. | |
| 287 Document& document = m_element->document(); | |
| 288 if (!document.isActive()) | |
| 289 return url; | |
| 290 | |
| 291 // Do not load any image if the 'src' attribute is missing or if it is | |
| 292 // an empty string. | |
| 293 if (!imageSourceURL.isNull() && !stripLeadingAndTrailingHTMLSpaces(imageSour
ceURL).isEmpty()) | |
| 294 url = document.completeURL(sourceURI(imageSourceURL)); | |
| 295 return url; | |
| 296 } | |
| 297 | |
| 298 bool ImageLoader::shouldLoadImmediately(const KURL& url, LoadType loadType) cons
t | |
| 299 { | |
| 300 return url.protocolIsData() | |
| 301 || memoryCache()->resourceForURL(url) | |
| 302 || loadType == ForceLoadImmediately; | |
| 303 } | |
| 304 | |
| 305 void ImageLoader::notifyFinished(Resource* resource) | |
| 306 { | |
| 307 WTF_LOG(Timers, "ImageLoader::notifyFinished %p; m_hasPendingLoadEvent=%d", | |
| 308 this, m_hasPendingLoadEvent); | |
| 309 | |
| 310 ASSERT(m_failedLoadURL.isEmpty()); | |
| 311 ASSERT(resource == m_image.get()); | |
| 312 | |
| 313 m_imageComplete = true; | |
| 314 updateRenderer(); | |
| 315 | |
| 316 if (!m_hasPendingLoadEvent) | |
| 317 return; | |
| 318 | |
| 319 if (resource->errorOccurred()) { | |
| 320 loadEventSender().cancelEvent(this); | |
| 321 m_hasPendingLoadEvent = false; | |
| 322 | |
| 323 m_hasPendingErrorEvent = true; | |
| 324 errorEventSender().dispatchEventSoon(this); | |
| 325 | |
| 326 // Only consider updating the protection ref-count of the Element immedi
ately before returning | |
| 327 // from this function as doing so might result in the destruction of thi
s ImageLoader. | |
| 328 updatedHasPendingEvent(); | |
| 329 return; | |
| 330 } | |
| 331 if (resource->wasCanceled()) { | |
| 332 m_hasPendingLoadEvent = false; | |
| 333 // Only consider updating the protection ref-count of the Element immedi
ately before returning | |
| 334 // from this function as doing so might result in the destruction of thi
s ImageLoader. | |
| 335 updatedHasPendingEvent(); | |
| 336 return; | |
| 337 } | |
| 338 loadEventSender().dispatchEventSoon(this); | |
| 339 } | |
| 340 | |
| 341 void ImageLoader::updateRenderer() | |
| 342 { | |
| 343 } | |
| 344 | |
| 345 void ImageLoader::updatedHasPendingEvent() | |
| 346 { | |
| 347 // If an Element that does image loading is removed from the DOM the load/er
ror event for the image is still observable. | |
| 348 // As long as the ImageLoader is actively loading, the Element itself needs
to be ref'ed to keep it from being | |
| 349 // destroyed by DOM manipulation or garbage collection. | |
| 350 // If such an Element wishes for the load to stop when removed from the DOM
it needs to stop the ImageLoader explicitly. | |
| 351 bool wasProtected = m_elementIsProtected; | |
| 352 m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent; | |
| 353 if (wasProtected == m_elementIsProtected) | |
| 354 return; | |
| 355 | |
| 356 if (m_elementIsProtected) { | |
| 357 if (m_derefElementTimer.isActive()) | |
| 358 m_derefElementTimer.stop(); | |
| 359 else | |
| 360 m_keepAlive = m_element; | |
| 361 } else { | |
| 362 ASSERT(!m_derefElementTimer.isActive()); | |
| 363 m_derefElementTimer.startOneShot(0, FROM_HERE); | |
| 364 } | |
| 365 } | |
| 366 | |
| 367 void ImageLoader::timerFired(Timer<ImageLoader>*) | |
| 368 { | |
| 369 m_keepAlive.clear(); | |
| 370 } | |
| 371 | |
| 372 void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender) | |
| 373 { | |
| 374 WTF_LOG(Timers, "ImageLoader::dispatchPendingEvent %p", this); | |
| 375 ASSERT(eventSender == &loadEventSender() || eventSender == &errorEventSender
()); | |
| 376 const AtomicString& eventType = eventSender->eventType(); | |
| 377 if (eventType == EventTypeNames::load) | |
| 378 dispatchPendingLoadEvent(); | |
| 379 if (eventType == EventTypeNames::error) | |
| 380 dispatchPendingErrorEvent(); | |
| 381 } | |
| 382 | |
| 383 void ImageLoader::dispatchPendingLoadEvent() | |
| 384 { | |
| 385 if (!m_hasPendingLoadEvent) | |
| 386 return; | |
| 387 if (!m_image) | |
| 388 return; | |
| 389 m_hasPendingLoadEvent = false; | |
| 390 if (element()->document().frame()) | |
| 391 dispatchLoadEvent(); | |
| 392 | |
| 393 // Only consider updating the protection ref-count of the Element immediatel
y before returning | |
| 394 // from this function as doing so might result in the destruction of this Im
ageLoader. | |
| 395 updatedHasPendingEvent(); | |
| 396 } | |
| 397 | |
| 398 void ImageLoader::dispatchPendingErrorEvent() | |
| 399 { | |
| 400 if (!m_hasPendingErrorEvent) | |
| 401 return; | |
| 402 m_hasPendingErrorEvent = false; | |
| 403 | |
| 404 if (element()->document().frame()) | |
| 405 element()->dispatchEvent(Event::create(EventTypeNames::error)); | |
| 406 | |
| 407 // Only consider updating the protection ref-count of the Element immediatel
y before returning | |
| 408 // from this function as doing so might result in the destruction of this Im
ageLoader. | |
| 409 updatedHasPendingEvent(); | |
| 410 } | |
| 411 | |
| 412 void ImageLoader::addClient(ImageLoaderClient* client) | |
| 413 { | |
| 414 if (client->requestsHighLiveResourceCachePriority()) { | |
| 415 if (m_image && !m_highPriorityClientCount++) | |
| 416 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropert
yChange, MemoryCacheLiveResourcePriorityHigh); | |
| 417 } | |
| 418 m_clients.add(client); | |
| 419 } | |
| 420 | |
| 421 void ImageLoader::willRemoveClient(ImageLoaderClient& client) | |
| 422 { | |
| 423 if (client.requestsHighLiveResourceCachePriority()) { | |
| 424 ASSERT(m_highPriorityClientCount); | |
| 425 m_highPriorityClientCount--; | |
| 426 if (m_image && !m_highPriorityClientCount) | |
| 427 memoryCache()->updateDecodedResource(m_image.get(), UpdateForPropert
yChange, MemoryCacheLiveResourcePriorityLow); | |
| 428 } | |
| 429 } | |
| 430 | |
| 431 void ImageLoader::removeClient(ImageLoaderClient* client) | |
| 432 { | |
| 433 willRemoveClient(*client); | |
| 434 m_clients.remove(client); | |
| 435 } | |
| 436 | |
| 437 void ImageLoader::dispatchPendingLoadEvents() | |
| 438 { | |
| 439 loadEventSender().dispatchPendingEvents(); | |
| 440 } | |
| 441 | |
| 442 void ImageLoader::dispatchPendingErrorEvents() | |
| 443 { | |
| 444 errorEventSender().dispatchPendingEvents(); | |
| 445 } | |
| 446 | |
| 447 void ImageLoader::elementDidMoveToNewDocument() | |
| 448 { | |
| 449 if (m_loadDelayCounter) | |
| 450 m_loadDelayCounter->documentChanged(m_element->document()); | |
| 451 clearFailedLoadURL(); | |
| 452 setImage(0); | |
| 453 } | |
| 454 | |
| 455 void ImageLoader::sourceImageChanged() | |
| 456 { | |
| 457 HashSet<ImageLoaderClient*>::iterator end = m_clients.end(); | |
| 458 for (HashSet<ImageLoaderClient*>::iterator it = m_clients.begin(); it != end
; ++it) { | |
| 459 ImageLoaderClient* handle = *it; | |
| 460 handle->notifyImageSourceChanged(); | |
| 461 } | |
| 462 } | |
| 463 | |
| 464 } | |
| OLD | NEW |