1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2004-2017 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 "BitmapImage.h"
26#include "CachedImage.h"
27#include "CachedResourceLoader.h"
28#include "CachedResourceRequest.h"
29#include "CrossOriginAccessControl.h"
30#include "Document.h"
31#include "Element.h"
32#include "Event.h"
33#include "EventNames.h"
34#include "EventSender.h"
35#include "Frame.h"
36#include "FrameLoader.h"
37#include "HTMLNames.h"
38#include "HTMLObjectElement.h"
39#include "HTMLParserIdioms.h"
40#include "InspectorInstrumentation.h"
41#include "Page.h"
42#include "RenderImage.h"
43#include "RenderSVGImage.h"
44#include <wtf/NeverDestroyed.h>
45
46#if ENABLE(VIDEO)
47#include "RenderVideo.h"
48#endif
49
50#if !ASSERT_DISABLED
51// ImageLoader objects are allocated as members of other objects, so generic pointer check would always fail.
52namespace WTF {
53
54template<> struct ValueCheck<WebCore::ImageLoader*> {
55 typedef WebCore::ImageLoader* TraitType;
56 static void checkConsistency(const WebCore::ImageLoader* p)
57 {
58 if (!p)
59 return;
60 ValueCheck<WebCore::Element*>::checkConsistency(&p->element());
61 }
62};
63
64}
65#endif
66
67namespace WebCore {
68
69static ImageEventSender& beforeLoadEventSender()
70{
71 static NeverDestroyed<ImageEventSender> sender(eventNames().beforeloadEvent);
72 return sender;
73}
74
75static ImageEventSender& loadEventSender()
76{
77 static NeverDestroyed<ImageEventSender> sender(eventNames().loadEvent);
78 return sender;
79}
80
81static ImageEventSender& errorEventSender()
82{
83 static NeverDestroyed<ImageEventSender> sender(eventNames().errorEvent);
84 return sender;
85}
86
87static inline bool pageIsBeingDismissed(Document& document)
88{
89 Frame* frame = document.frame();
90 return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None;
91}
92
93ImageLoader::ImageLoader(Element& element)
94 : m_element(element)
95 , m_image(nullptr)
96 , m_derefElementTimer(*this, &ImageLoader::timerFired)
97 , m_hasPendingBeforeLoadEvent(false)
98 , m_hasPendingLoadEvent(false)
99 , m_hasPendingErrorEvent(false)
100 , m_imageComplete(true)
101 , m_loadManually(false)
102 , m_elementIsProtected(false)
103{
104}
105
106ImageLoader::~ImageLoader()
107{
108 if (m_image)
109 m_image->removeClient(*this);
110
111 ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(*this));
112 if (m_hasPendingBeforeLoadEvent)
113 beforeLoadEventSender().cancelEvent(*this);
114
115 ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(*this));
116 if (m_hasPendingLoadEvent)
117 loadEventSender().cancelEvent(*this);
118
119 ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(*this));
120 if (m_hasPendingErrorEvent)
121 errorEventSender().cancelEvent(*this);
122}
123
124void ImageLoader::clearImage()
125{
126 clearImageWithoutConsideringPendingLoadEvent();
127
128 // Only consider updating the protection ref-count of the Element immediately before returning
129 // from this function as doing so might result in the destruction of this ImageLoader.
130 updatedHasPendingEvent();
131}
132
133void ImageLoader::clearImageWithoutConsideringPendingLoadEvent()
134{
135 ASSERT(m_failedLoadURL.isEmpty());
136 CachedImage* oldImage = m_image.get();
137 if (oldImage) {
138 m_image = nullptr;
139 if (m_hasPendingBeforeLoadEvent) {
140 beforeLoadEventSender().cancelEvent(*this);
141 m_hasPendingBeforeLoadEvent = false;
142 }
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 (oldImage)
153 oldImage->removeClient(*this);
154 }
155
156 if (RenderImageResource* imageResource = renderImageResource())
157 imageResource->resetAnimation();
158}
159
160void ImageLoader::updateFromElement()
161{
162 // If we're not making renderers for the page, then don't load images. We don't want to slow
163 // down the raw HTML parsing case by loading images we don't intend to display.
164 Document& document = element().document();
165 if (!document.hasLivingRenderTree())
166 return;
167
168 AtomicString attr = element().imageSourceURL();
169
170 // Avoid loading a URL we already failed to load.
171 if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
172 return;
173
174 // Do not load any image if the 'src' attribute is missing or if it is
175 // an empty string.
176 CachedResourceHandle<CachedImage> newImage = nullptr;
177 if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
178 ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
179 options.contentSecurityPolicyImposition = element().isInUserAgentShadowTree() ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
180 options.sameOriginDataURLFlag = SameOriginDataURLFlag::Set;
181
182 auto crossOriginAttribute = element().attributeWithoutSynchronization(HTMLNames::crossoriginAttr);
183
184 ResourceRequest resourceRequest(document.completeURL(sourceURI(attr)));
185 resourceRequest.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(m_element));
186
187 auto request = createPotentialAccessControlRequest(WTFMove(resourceRequest), document, crossOriginAttribute, WTFMove(options));
188 request.setInitiator(element());
189
190 if (m_loadManually) {
191 bool autoLoadOtherImages = document.cachedResourceLoader().autoLoadImages();
192 document.cachedResourceLoader().setAutoLoadImages(false);
193 auto* page = m_element.document().page();
194 newImage = new CachedImage(WTFMove(request), page->sessionID(), &page->cookieJar());
195 newImage->setStatus(CachedResource::Pending);
196 newImage->setLoading(true);
197 document.cachedResourceLoader().m_documentResources.set(newImage->url(), newImage.get());
198 document.cachedResourceLoader().setAutoLoadImages(autoLoadOtherImages);
199 } else
200 newImage = document.cachedResourceLoader().requestImage(WTFMove(request)).value_or(nullptr);
201
202 // If we do not have an image here, it means that a cross-site
203 // violation occurred, or that the image was blocked via Content
204 // Security Policy, or the page is being dismissed. Trigger an
205 // error event if the page is not being dismissed.
206 if (!newImage && !pageIsBeingDismissed(document)) {
207 m_failedLoadURL = attr;
208 m_hasPendingErrorEvent = true;
209 errorEventSender().dispatchEventSoon(*this);
210 } else
211 clearFailedLoadURL();
212 } else if (!attr.isNull()) {
213 // Fire an error event if the url is empty.
214 m_failedLoadURL = attr;
215 m_hasPendingErrorEvent = true;
216 errorEventSender().dispatchEventSoon(*this);
217 }
218
219 CachedImage* oldImage = m_image.get();
220 if (newImage != oldImage) {
221 if (m_hasPendingBeforeLoadEvent) {
222 beforeLoadEventSender().cancelEvent(*this);
223 m_hasPendingBeforeLoadEvent = false;
224 }
225 if (m_hasPendingLoadEvent) {
226 loadEventSender().cancelEvent(*this);
227 m_hasPendingLoadEvent = false;
228 }
229
230 // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
231 // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
232 // this load and we should not cancel the event.
233 // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
234 if (m_hasPendingErrorEvent && newImage) {
235 errorEventSender().cancelEvent(*this);
236 m_hasPendingErrorEvent = false;
237 }
238
239 m_image = newImage;
240 m_hasPendingBeforeLoadEvent = !document.isImageDocument() && newImage;
241 m_hasPendingLoadEvent = newImage;
242 m_imageComplete = !newImage;
243
244 if (newImage) {
245 if (!document.isImageDocument()) {
246 if (!document.hasListenerType(Document::BEFORELOAD_LISTENER))
247 dispatchPendingBeforeLoadEvent();
248 else
249 beforeLoadEventSender().dispatchEventSoon(*this);
250 } else
251 updateRenderer();
252
253 // If newImage is cached, addClient() will result in the load event
254 // being queued to fire. Ensure this happens after beforeload is
255 // dispatched.
256 newImage->addClient(*this);
257 }
258 if (oldImage) {
259 oldImage->removeClient(*this);
260 updateRenderer();
261 }
262 }
263
264 if (RenderImageResource* imageResource = renderImageResource())
265 imageResource->resetAnimation();
266
267 // Only consider updating the protection ref-count of the Element immediately before returning
268 // from this function as doing so might result in the destruction of this ImageLoader.
269 updatedHasPendingEvent();
270}
271
272void ImageLoader::updateFromElementIgnoringPreviousError()
273{
274 clearFailedLoadURL();
275 updateFromElement();
276}
277
278void ImageLoader::notifyFinished(CachedResource& resource)
279{
280 ASSERT(m_failedLoadURL.isEmpty());
281 ASSERT_UNUSED(resource, &resource == m_image.get());
282
283 m_imageComplete = true;
284 if (!hasPendingBeforeLoadEvent())
285 updateRenderer();
286
287 if (!m_hasPendingLoadEvent)
288 return;
289
290 if (m_image->resourceError().isAccessControl()) {
291 URL imageURL = m_image->url();
292
293 clearImageWithoutConsideringPendingLoadEvent();
294
295 m_hasPendingErrorEvent = true;
296 errorEventSender().dispatchEventSoon(*this);
297
298 auto message = makeString("Cannot load image ", imageURL.string(), " due to access control checks.");
299 element().document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
300
301 if (hasPendingDecodePromises())
302 decodeError("Access control error.");
303
304 ASSERT(!m_hasPendingLoadEvent);
305
306 // Only consider updating the protection ref-count of the Element immediately before returning
307 // from this function as doing so might result in the destruction of this ImageLoader.
308 updatedHasPendingEvent();
309 return;
310 }
311
312 if (m_image->wasCanceled()) {
313 if (hasPendingDecodePromises())
314 decodeError("Loading was canceled.");
315 m_hasPendingLoadEvent = false;
316 // Only consider updating the protection ref-count of the Element immediately before returning
317 // from this function as doing so might result in the destruction of this ImageLoader.
318 updatedHasPendingEvent();
319 return;
320 }
321
322 if (hasPendingDecodePromises())
323 decode();
324 loadEventSender().dispatchEventSoon(*this);
325}
326
327RenderImageResource* ImageLoader::renderImageResource()
328{
329 auto* renderer = element().renderer();
330 if (!renderer)
331 return nullptr;
332
333 // We don't return style generated image because it doesn't belong to the ImageLoader.
334 // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
335 if (is<RenderImage>(*renderer) && !downcast<RenderImage>(*renderer).isGeneratedContent())
336 return &downcast<RenderImage>(*renderer).imageResource();
337
338 if (is<RenderSVGImage>(*renderer))
339 return &downcast<RenderSVGImage>(*renderer).imageResource();
340
341#if ENABLE(VIDEO)
342 if (is<RenderVideo>(*renderer))
343 return &downcast<RenderVideo>(*renderer).imageResource();
344#endif
345
346 return nullptr;
347}
348
349void ImageLoader::updateRenderer()
350{
351 RenderImageResource* imageResource = renderImageResource();
352
353 if (!imageResource)
354 return;
355
356 // Only update the renderer if it doesn't have an image or if what we have
357 // is a complete image. This prevents flickering in the case where a dynamic
358 // change is happening between two images.
359 CachedImage* cachedImage = imageResource->cachedImage();
360 if (m_image != cachedImage && (m_imageComplete || !cachedImage))
361 imageResource->setCachedImage(m_image.get());
362}
363
364void ImageLoader::updatedHasPendingEvent()
365{
366 // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
367 // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
368 // destroyed by DOM manipulation or garbage collection.
369 // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
370 bool wasProtected = m_elementIsProtected;
371 m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
372 if (wasProtected == m_elementIsProtected)
373 return;
374
375 if (m_elementIsProtected) {
376 if (m_derefElementTimer.isActive())
377 m_derefElementTimer.stop();
378 else
379 m_protectedElement = &element();
380 } else {
381 ASSERT(!m_derefElementTimer.isActive());
382 m_derefElementTimer.startOneShot(0_s);
383 }
384}
385
386void ImageLoader::decode(Ref<DeferredPromise>&& promise)
387{
388 m_decodingPromises.append(WTFMove(promise));
389
390 if (!element().document().domWindow()) {
391 decodeError("Inactive document.");
392 return;
393 }
394
395 AtomicString attr = element().imageSourceURL();
396 if (stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
397 decodeError("Missing source URL.");
398 return;
399 }
400
401 if (m_imageComplete)
402 decode();
403}
404
405void ImageLoader::decodeError(const char* message)
406{
407 ASSERT(hasPendingDecodePromises());
408 for (auto& promise : m_decodingPromises)
409 promise->reject(Exception { EncodingError, message });
410 m_decodingPromises.clear();
411}
412
413void ImageLoader::decode()
414{
415 ASSERT(hasPendingDecodePromises());
416
417 if (!element().document().domWindow()) {
418 decodeError("Inactive document.");
419 return;
420 }
421
422 if (!m_image || !m_image->image() || m_image->errorOccurred()) {
423 decodeError("Loading error.");
424 return;
425 }
426
427 Image* image = m_image->image();
428 if (!is<BitmapImage>(image)) {
429 decodeError("Invalid image type.");
430 return;
431 }
432
433 auto& bitmapImage = downcast<BitmapImage>(*image);
434 bitmapImage.decode([promises = WTFMove(m_decodingPromises)]() mutable {
435 for (auto& promise : promises)
436 promise->resolve();
437 });
438}
439
440void ImageLoader::timerFired()
441{
442 m_protectedElement = nullptr;
443}
444
445void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
446{
447 ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
448 const AtomicString& eventType = eventSender->eventType();
449 if (eventType == eventNames().beforeloadEvent)
450 dispatchPendingBeforeLoadEvent();
451 if (eventType == eventNames().loadEvent)
452 dispatchPendingLoadEvent();
453 if (eventType == eventNames().errorEvent)
454 dispatchPendingErrorEvent();
455}
456
457void ImageLoader::dispatchPendingBeforeLoadEvent()
458{
459 if (!m_hasPendingBeforeLoadEvent)
460 return;
461 if (!m_image)
462 return;
463 if (!element().document().hasLivingRenderTree())
464 return;
465 m_hasPendingBeforeLoadEvent = false;
466 Ref<Document> originalDocument = element().document();
467 if (element().dispatchBeforeLoadEvent(m_image->url())) {
468 bool didEventListenerDisconnectThisElement = !element().isConnected() || &element().document() != originalDocument.ptr();
469 if (didEventListenerDisconnectThisElement)
470 return;
471
472 updateRenderer();
473 return;
474 }
475 if (m_image) {
476 m_image->removeClient(*this);
477 m_image = nullptr;
478 }
479
480 loadEventSender().cancelEvent(*this);
481 m_hasPendingLoadEvent = false;
482
483 if (is<HTMLObjectElement>(element()))
484 downcast<HTMLObjectElement>(element()).renderFallbackContent();
485
486 // Only consider updating the protection ref-count of the Element immediately before returning
487 // from this function as doing so might result in the destruction of this ImageLoader.
488 updatedHasPendingEvent();
489}
490
491void ImageLoader::dispatchPendingLoadEvent()
492{
493 if (!m_hasPendingLoadEvent)
494 return;
495 if (!m_image)
496 return;
497 m_hasPendingLoadEvent = false;
498 if (element().document().hasLivingRenderTree())
499 dispatchLoadEvent();
500
501 // Only consider updating the protection ref-count of the Element immediately before returning
502 // from this function as doing so might result in the destruction of this ImageLoader.
503 updatedHasPendingEvent();
504}
505
506void ImageLoader::dispatchPendingErrorEvent()
507{
508 if (!m_hasPendingErrorEvent)
509 return;
510 m_hasPendingErrorEvent = false;
511 if (element().document().hasLivingRenderTree())
512 element().dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No));
513
514 // Only consider updating the protection ref-count of the Element immediately before returning
515 // from this function as doing so might result in the destruction of this ImageLoader.
516 updatedHasPendingEvent();
517}
518
519void ImageLoader::dispatchPendingBeforeLoadEvents()
520{
521 beforeLoadEventSender().dispatchPendingEvents();
522}
523
524void ImageLoader::dispatchPendingLoadEvents()
525{
526 loadEventSender().dispatchPendingEvents();
527}
528
529void ImageLoader::dispatchPendingErrorEvents()
530{
531 errorEventSender().dispatchPendingEvents();
532}
533
534void ImageLoader::elementDidMoveToNewDocument()
535{
536 clearFailedLoadURL();
537 clearImage();
538}
539
540inline void ImageLoader::clearFailedLoadURL()
541{
542 m_failedLoadURL = nullAtom();
543}
544
545}
546