Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/loader/ImageLoader.cpp
Warning:line 294, column 81
Call argument for parameter 'mapped' is uncounted and unsafe

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple arm64-apple-ios26.2.0-simulator -O3 -Wundef-prefix=TARGET_OS_ -Werror=undef-prefix -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name UnifiedSource281.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=osx -analyzer-checker=security.insecureAPI.decodeValueOfObjCType -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -ffp-contract=on -fno-rounding-math -target-sdk-version=26.2 -fdepfile-entry=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/SDKSettings.json -fcompatibility-qualified-id-block-type-checking -fvisibility-inlines-hidden-static-local-var -fno-modulemap-allow-subdirectory-search -fdefine-target-os-macros -enable-tlsdesc -target-cpu apple-m1 -target-feature +v8.4a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +ccpp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +flagm -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -dwarf-ext-refs -fmodule-format=obj -debug-info-kind=standalone -dwarf-version=5 -debugger-tuning=lldb -fdebug-compilation-dir=/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -target-linker-version 1230.1 -fcoverage-compilation-dir=/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -resource-dir /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21 -dependency-file /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource281.d -skip-unused-modulemap-deps -MT dependencies -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-generated-files.hmap -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-project-headers.hmap -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/PAL/ThirdParty/dav1d/include -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/usr/include -iframework /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/System/Library/Frameworks -iframework /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/System/Library/PrivateFrameworks -iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/Frameworks -include-pch /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PrecompiledHeaders/SharedPrecompiledHeaders/11721353671419897710/WebCorePrefix.h.gch -D _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -D NDEBUG -D BUILDING_WEBKIT -D GL_SILENCE_DEPRECATION=1 -D GLES_SILENCE_DEPRECATION=1 -D __clang_analyzer__ -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-own-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-all-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/include -I PAL -I ForwardingHeaders -I /usr/include/libxslt -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include/libxml2 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/WebKitAdditions -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/WebKitAdditions -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/usr/local/include -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webrtc -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webrtc -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webm -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webm -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/PAL.build/DerivedSources -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/Release-iphonesimulator/PAL.build/DerivedSources -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources-normal/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator -I/usr/local/include -stdlib=libc++ -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include/c++/v1 -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include -internal-isystem /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21/include -internal-externc-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/Frameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/SubFrameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/Library/Frameworks -Wno-trigraphs -Werror -Wno-missing-field-initializers -Wmissing-prototypes -Wunreachable-code -Wnon-virtual-dtor -Wno-overloaded-virtual -Wno-exit-time-destructors -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wsign-compare -Wno-shorten-64-to-32 -Wnewline-eof -Wno-c++11-extensions -Wno-implicit-fallthrough -Wdeprecated-declarations -Winvalid-offsetof -Wno-sign-conversion -Winfinite-recursion -Wmove -Wcomma -Wblock-capture-autoreleasing -Wstrict-prototypes -Wrange-loop-analysis -Wno-semicolon-before-method-body -Wall -Wc99-designator -Wconditional-uninitialized -Wextra -Wdeprecated-enum-enum-conversion -Wdeprecated-enum-float-conversion -Wenum-float-conversion -Wfinal-dtor-non-final-class -Wformat=2 -Wmisleading-indentation -Wpointer-to-int-cast -Wreorder-init-list -Wundef -Wunused-but-set-variable -Wvla -Werror=unguarded-availability -Wno-elaborated-enum-base -Wcast-qual -Wchar-subscripts -Wextra-tokens -Winit-self -Wmissing-noreturn -Wpacked -Wpointer-arith -Wredundant-decls -Wwrite-strings -Wexit-time-destructors -Wglobal-constructors -Wtautological-compare -Wimplicit-fallthrough -Wvla -Wno-unknown-warning-option -Wliteral-conversion -Wthread-safety -Wno-profile-instr-out-of-date -Wno-profile-instr-unprofiled -Wno-backend-plugin -Wunsafe-buffer-usage -std=c++2b -fdeprecated-macro -ferror-limit 19 -fmacro-backtrace-limit=0 -fvisibility=hidden -fvisibility-inlines-hidden -stack-protector 1 -fblocks -fencode-extended-block-signature -fno-rtti -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fno-cxx-modules -fno-implicit-modules -fskip-odr-check-in-gmf -fpascal-strings -fmax-type-align=16 -fdiagnostics-show-note-include-stack -vectorize-loops -vectorize-slp -fsafe-buffer-usage-suggestions -analyzer-output=html -analyzer-config report-in-main-source-file=true -analyzer-config nullability:NoDiagnoseCallsToSystemHeaders=true -analyzer-checker optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker security.insecureAPI.UncheckedReturn -analyzer-checker security.insecureAPI.getpw -analyzer-checker security.insecureAPI.gets -analyzer-checker security.insecureAPI.mkstemp -analyzer-checker security.insecureAPI.mktemp -analyzer-disable-checker security.insecureAPI.rand -analyzer-disable-checker security.insecureAPI.strcpy -analyzer-checker security.insecureAPI.vfork -analyzer-disable-checker alpha,apiModeling,core,cplusplus,deadcode,debug,fuchsia,nullability,optin,osx,security,unix,webkit -analyzer-checker alpha.webkit.ForwardDeclChecker,alpha.webkit.MemoryUnsafeCastChecker,alpha.webkit.NoDeleteChecker,alpha.webkit.NoUncheckedPtrMemberChecker,alpha.webkit.NoUnretainedMemberChecker,alpha.webkit.RetainPtrCtorAdoptChecker,alpha.webkit.UncheckedCallArgsChecker,alpha.webkit.UncheckedLocalVarsChecker,alpha.webkit.UncountedCallArgsChecker,alpha.webkit.UncountedLocalVarsChecker,alpha.webkit.UnretainedCallArgsChecker,alpha.webkit.UnretainedLambdaCapturesChecker,alpha.webkit.UnretainedLocalVarsChecker,webkit.NoUncountedMemberChecker,webkit.RefCntblBaseVirtualDtor,webkit.UncountedLambdaCapturesChecker -analyzer-config max-nodes=10000000 -analyzer-config verbose-report-filename=true -fdwarf2-cfi-asm -o /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource281.plist -x c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore/unified-sources/UnifiedSource281.cpp
1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2004-2025 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 "ArchiveResource.h"
26#include "BitmapImage.h"
27#include "CachedImage.h"
28#include "CachedResourceRequest.h"
29#include "Chrome.h"
30#include "ChromeClient.h"
31#include "ContainerNodeInlines.h"
32#include "CookieJar.h"
33#include "CrossOriginAccessControl.h"
34#include "DocumentLoader.h"
35#include "DocumentPage.h"
36#include "DocumentResourceLoader.h"
37#include "DocumentSecurityOrigin.h"
38#include "DocumentView.h"
39#include "ElementInlines.h"
40#include "Event.h"
41#include "EventNames.h"
42#include "EventSender.h"
43#include "FrameDestructionObserverInlines.h"
44#include "FrameLoader.h"
45#include "HTMLImageElement.h"
46#include "HTMLNames.h"
47#include "HTMLObjectElement.h"
48#include "HTMLPlugInElement.h"
49#include "HTMLSrcsetParser.h"
50#include "InspectorInstrumentation.h"
51#include "JSDOMPromiseDeferred.h"
52#include "LazyLoadImageObserver.h"
53#include "LegacyRenderSVGImage.h"
54#include "LocalFrame.h"
55#include "Logging.h"
56#include "MemoryCache.h"
57#include "Page.h"
58#include "RenderImage.h"
59#include "RenderSVGImage.h"
60#include "Settings.h"
61#include <wtf/NeverDestroyed.h>
62#include <wtf/Scope.h>
63#include <wtf/text/MakeString.h>
64#include <wtf/text/TextStream.h>
65
66#if ENABLE(VIDEO)(defined 1 && 1)
67#include "RenderVideo.h"
68#endif
69
70#if ENABLE(SPATIAL_IMAGE_CONTROLS)(defined ENABLE_SPATIAL_IMAGE_CONTROLS && ENABLE_SPATIAL_IMAGE_CONTROLS
)
71#include "SpatialImageControls.h"
72#endif
73
74#if ASSERT_ENABLED0
75// ImageLoader objects are allocated as members of other objects, so generic pointer check would always fail.
76namespace WTF {
77
78template<> struct ValueCheck<WebCore::ImageLoader*> {
79 typedef WebCore::ImageLoader* TraitType;
80 static void checkConsistency(const WebCore::ImageLoader* p)
81 {
82 if (!p)
83 return;
84 ValueCheck<WebCore::Element*>::checkConsistency(&p->element());
85 }
86};
87
88} // namespace WTF
89#endif // ASSERT_ENABLED
90
91namespace WebCore {
92
93#if !LOG_DISABLED!0
94static TextStream& operator<<(TextStream& ts, LazyImageLoadState state)
95{
96 switch (state) {
97 case LazyImageLoadState::None: ts << "None"_s; break;
98 case LazyImageLoadState::Deferred: ts << "Deferred"_s; break;
99 case LazyImageLoadState::LoadImmediately: ts << "LoadImmediately"_s; break;
100 case LazyImageLoadState::FullImage: ts << "FullImage"_s; break;
101 }
102
103 return ts;
104}
105
106static TextStream& operator<<(TextStream& ts, ImageLoading loading)
107{
108 switch (loading) {
109 case ImageLoading::Immediate: ts << "Immediate"_s; break;
110 case ImageLoading::DeferredUntilVisible: ts << "DeferredUntilVisible"_s; break;
111 }
112
113 return ts;
114}
115#endif // !LOG_DISABLED
116
117static ImageEventSender& loadEventSender()
118{
119 static NeverDestroyed<ImageEventSender> sender;
120 return sender;
121}
122
123static inline bool NODELETE[[clang::annotate_type("webkit.nodelete")]] pageIsBeingDismissed(Document& document)
124{
125 auto* frame = document.frame();
126 return frame && frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None;
127}
128
129// https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data:list-of-available-images
130static bool canReuseFromListOfAvailableImages(const CachedResourceRequest& request, Document& document)
131{
132 RefPtr resource = MemoryCache::singleton().resourceForRequest(request.resourceRequest(), protect(document.page())->sessionID());
133 if (!resource || resource->stillNeedsLoad() || resource->isPreloaded())
134 return false;
135
136 if (resource->options().mode == FetchOptions::Mode::Cors && !protect(document.securityOrigin())->isSameOriginAs(*protect(resource->origin())))
137 return false;
138
139 if (resource->options().mode != request.options().mode || resource->options().credentials != request.options().credentials)
140 return false;
141
142 return true;
143}
144
145ImageLoader::ImageLoader(Element& element)
146 : m_element(element)
147 , m_derefElementTimer(*this, &ImageLoader::timerFired)
148 , m_hasPendingBeforeLoadEvent(false)
149 , m_hasPendingLoadEvent(false)
150 , m_hasPendingErrorEvent(false)
151 , m_imageComplete(true)
152 , m_loadManually(false)
153 , m_elementIsProtected(false)
154{
155}
156
157ImageLoader::~ImageLoader()
158{
159 if (RefPtr image = m_image.get())
160 image->removeClient(*this);
161
162 ASSERT(m_hasPendingLoadEvent || m_hasPendingErrorEvent || !loadEventSender().hasPendingEvents(*this))((void)0);
163 if (m_hasPendingLoadEvent || m_hasPendingErrorEvent)
164 loadEventSender().cancelEvent(*this);
165}
166
167void ImageLoader::ref() const
168{
169 m_element->ref();
170}
171
172void ImageLoader::deref() const
173{
174 m_element->deref();
175}
176
177void ImageLoader::clearImage()
178{
179 clearImageWithoutConsideringPendingLoadEvent();
180
181 // Only consider updating the protection ref-count of the Element immediately before returning
182 // from this function as doing so might result in the destruction of this ImageLoader.
183 updatedHasPendingEvent();
184}
185
186void ImageLoader::clearImageWithoutConsideringPendingLoadEvent()
187{
188 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " clearImageWithoutConsideringPendingLoadEvent")((void)0);
189
190 ASSERT(m_failedLoadURL.isEmpty())((void)0);
191 if (RefPtr oldImage = m_image.get()) {
192 m_image = nullptr;
193 m_hasPendingBeforeLoadEvent = false;
194 if (m_hasPendingLoadEvent || m_hasPendingErrorEvent) {
195 loadEventSender().cancelEvent(*this);
196 m_hasPendingLoadEvent = m_hasPendingErrorEvent = false;
197 }
198 m_imageComplete = true;
199 oldImage->removeClient(*this);
200 }
201
202 if (CheckedPtr imageResource = renderImageResource())
203 imageResource->resetAnimation();
204}
205
206void ImageLoader::updateFromElement(RelevantMutation relevantMutation)
207{
208 // This is implementing https://html.spec.whatwg.org/#update-the-image-data
209 //
210 // If we're not making renderers for the page, then don't load images. We don't want to slow
211 // down the raw HTML parsing case by loading images we don't intend to display.
212 Ref element = this->element();
213 Ref document = element->document();
214 if (!document->hasLivingRenderTree())
215 return;
216
217 AtomString attr = element->imageSourceURL();
218
219 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " updateFromElement, current URL is " << attr)((void)0);
220
221 // Avoid loading a URL we already failed to load.
222 if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
223 return;
224
225 CachedResourceHandle<CachedImage> newImage;
226
227 // Do not load any image if the 'src' attribute is missing.
228 if (attr.isNull()) {
229 didUpdateCachedImage(relevantMutation, WTF::move(newImage));
230 return;
231 }
232
233 // Fire an error event if the URL contains only whitespace.
234 if (StringView(attr).containsOnly<isASCIIWhitespace<char16_t>>()) {
235 m_failedLoadURL = attr;
236 m_hasPendingErrorEvent = true;
237 loadEventSender().dispatchEventSoon(*this, eventNames().errorEvent);
238 didUpdateCachedImage(relevantMutation, WTF::move(newImage));
239 return;
240 }
241
242 // Set up resource loading options.
243 ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
244 auto loadingForElementInShadowTree = element->isInUserAgentShadowTree() || m_elementIsUserAgentShadowRootResource;
245 options.contentSecurityPolicyImposition = loadingForElementInShadowTree ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
246 options.shouldEnableContentExtensionsCheck = loadingForElementInShadowTree ? ShouldEnableContentExtensionsCheck::No : ShouldEnableContentExtensionsCheck::Yes;
247 options.loadedFromPluginElement = is<HTMLPlugInElement>(element) ? LoadedFromPluginElement::Yes : LoadedFromPluginElement::No;
248 options.sameOriginDataURLFlag = SameOriginDataURLFlag::Set;
249 options.serviceWorkersMode = is<HTMLPlugInElement>(element) ? ServiceWorkersMode::None : ServiceWorkersMode::All;
250 RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element);
251 if (imageElement) {
252 options.referrerPolicy = imageElement->referrerPolicy();
253 options.fetchPriority = imageElement->fetchPriority();
254 if (imageElement->usesSrcsetOrPicture())
255 options.initiator = Initiator::Imageset;
256 }
257
258 auto crossOriginAttribute = element->attributeWithoutSynchronization(HTMLNames::crossoriginAttr);
259
260 // Use URL from original request for same URL loads in order to preserve the original base URL.
261 URL imageURL;
262 if (m_image && attr == m_pendingURL)
263 imageURL = m_image->url();
264 else {
265 if (imageElement) {
266 // It is possible that attributes are bulk-set via Element::parserSetAttributes. In that case, it is possible that attribute vectors are already configured,
267 // but corresponding attributeChanged is not called yet. This causes inconsistency in HTMLImageElement. Eventually, we will get the consistent state, but
268 // if "src" attributeChanged is not called yet, imageURL can be invalid and it does not work well for ResourceRequest.
269 // In this case, we should behave same as attr.isNull().
270 imageURL = imageElement->currentURL();
271 if (imageURL.isNull()) {
272 didUpdateCachedImage(relevantMutation, WTF::move(newImage));
273 return;
274 }
275 } else
276 imageURL = document->completeURL(attr);
277 m_pendingURL = attr;
278 }
279 ResourceRequest resourceRequest(WTF::move(imageURL));
280 resourceRequest.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(element));
281
282 auto request = createPotentialAccessControlRequest(WTF::move(resourceRequest), WTF::move(options), document, crossOriginAttribute);
283 request.setInitiator(element);
284
285 if (m_loadManually) {
286 Ref cachedResourceLoader = document->cachedResourceLoader();
287 bool autoLoadOtherImages = cachedResourceLoader->autoLoadImages();
288 cachedResourceLoader->setAutoLoadImages(false);
289 RefPtr page = m_element->document().page();
290 // FIXME: We shouldn't do an explicit `new` here.
291 newImage = adoptRef(*new CachedImage(WTF::move(request), page->sessionID(), &page->cookieJar()));
292 newImage->setStatus(CachedResource::Pending);
293 newImage->setLoading(true);
294 cachedResourceLoader->m_documentResources.set(newImage->url().string(), *newImage);
Call argument for parameter 'mapped' is uncounted and unsafe
295 cachedResourceLoader->setAutoLoadImages(autoLoadOtherImages);
296 } else {
297#if !LOG_DISABLED!0
298 auto oldState = m_lazyImageLoadState;
299#endif
300 if (m_lazyImageLoadState == LazyImageLoadState::None && imageElement) {
301 if (imageElement->isLazyLoadable() && document->settings().lazyImageLoadingEnabled() && !canReuseFromListOfAvailableImages(request, document)) {
302 m_lazyImageLoadState = LazyImageLoadState::Deferred;
303 request.setIgnoreForRequestCount(true);
304 }
305 }
306 auto imageLoading = (m_lazyImageLoadState == LazyImageLoadState::Deferred) ? ImageLoading::DeferredUntilVisible : ImageLoading::Immediate;
307 newImage = protect(document->cachedResourceLoader())->requestImage(WTF::move(request), imageLoading).value_or(nullptr);
308 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " updateFromElement " << element.get() << " - state changed from " << oldState << " to " << m_lazyImageLoadState << ", loading is " << imageLoading << " new image " << newImage.get())((void)0);
309 }
310
311#if ENABLE(MULTI_REPRESENTATION_HEIC)(defined ENABLE_MULTI_REPRESENTATION_HEIC && ENABLE_MULTI_REPRESENTATION_HEIC
)
312 // Adaptive image glyphs need to load both the high fidelity HEIC and the
313 // fallback PNG resource, as both resources are treated as an atomic unit.
314 if (imageElement && imageElement->isMultiRepresentationHEIC()) {
315 auto fallbackURL = imageElement->getURLAttribute(HTMLNames::srcAttr);
316 if (!fallbackURL.isNull()) {
317 ResourceLoaderOptions fallbackOptions = CachedResourceLoader::defaultCachedResourceOptions();
318 fallbackOptions.contentSecurityPolicyImposition = loadingForElementInShadowTree ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
319 fallbackOptions.sameOriginDataURLFlag = SameOriginDataURLFlag::Set;
320
321 ResourceRequest fallbackResourceRequest(WTF::move(fallbackURL));
322 fallbackResourceRequest.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(*imageElement));
323
324 auto fallbackRequest = createPotentialAccessControlRequest(WTF::move(fallbackResourceRequest), WTF::move(fallbackOptions), document, crossOriginAttribute);
325 fallbackRequest.setInitiator(*imageElement);
326
327 protect(document->cachedResourceLoader())->requestImage(WTF::move(fallbackRequest));
328 }
329 }
330#endif
331
332 // If we do not have an image here, it means that a cross-site
333 // violation occurred, or that the image was blocked via Content
334 // Security Policy, or the page is being dismissed. Trigger an
335 // error event if the page is not being dismissed.
336 if (!newImage && !pageIsBeingDismissed(document)) {
337 m_failedLoadURL = attr;
338 m_hasPendingErrorEvent = true;
339 loadEventSender().dispatchEventSoon(*this, eventNames().errorEvent);
340 } else
341 clearFailedLoadURL();
342
343 didUpdateCachedImage(relevantMutation, WTF::move(newImage));
344}
345
346void ImageLoader::didUpdateCachedImage(RelevantMutation relevantMutation, CachedResourceHandle<CachedImage>&& newImage)
347{
348 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " didUpdateCachedImage " << newImage.get())((void)0);
349
350 Ref document = element().document();
351
352 RefPtr oldImage = m_image.get();
353 if (newImage.get() != oldImage.get() || relevantMutation == RelevantMutation::Yes) {
354 LOG_WITH_STREAM(LazyLoading, stream << " switching from old image " << oldImage.get() << " to image " << newImage.get() << " " << (newImage ? newImage->url() : URL()))((void)0);
355
356 m_hasPendingBeforeLoadEvent = false;
357 if (m_hasPendingLoadEvent) {
358 loadEventSender().cancelEvent(*this, eventNames().loadEvent);
359 m_hasPendingLoadEvent = false;
360 }
361
362 // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
363 // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
364 // this load and we should not cancel the event.
365 // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
366 if (m_hasPendingErrorEvent && newImage) {
367 loadEventSender().cancelEvent(*this, eventNames().errorEvent);
368 m_hasPendingErrorEvent = false;
369 }
370
371 m_image = newImage;
372 m_hasPendingBeforeLoadEvent = !document->isImageDocument() && newImage;
373 m_hasPendingLoadEvent = newImage;
374 m_imageComplete = !newImage;
375
376 if (newImage) {
377 if (!document->isImageDocument())
378 dispatchPendingBeforeLoadEvent();
379 else
380 updateRenderer();
381
382 if (m_lazyImageLoadState == LazyImageLoadState::Deferred)
383 LazyLoadImageObserver::observe(protect(element()));
384
385 // If newImage is cached, addClient() will result in the load event
386 // being queued to fire.
387 newImage->addClient(*this);
388 } else
389 resetLazyImageLoading(protect(element().document()));
390
391 if (oldImage) {
392 oldImage->removeClient(*this);
393 updateRenderer();
394 }
395 }
396
397 if (CheckedPtr imageResource = renderImageResource())
398 imageResource->resetAnimation();
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
405void ImageLoader::updateFromElementIgnoringPreviousError(RelevantMutation relevantMutation)
406{
407 clearFailedLoadURL();
408 updateFromElement(relevantMutation);
409}
410
411void ImageLoader::updateFromElementIgnoringPreviousErrorToSameValue()
412{
413 if (!m_image || !m_image->allowsCaching() || !m_failedLoadURL.isEmpty() || element().document().activeServiceWorker()) {
414 updateFromElementIgnoringPreviousError(RelevantMutation::Yes);
415 return;
416 }
417 if (m_hasPendingLoadEvent || !m_pendingURL.isEmpty())
418 return;
419 ASSERT(m_image)((void)0);
420 m_hasPendingLoadEvent = true;
421 notifyFinished(*protect(image()), NetworkLoadMetrics { });
422}
423
424static inline void resolvePromises(Vector<Ref<DeferredPromise>>& promises)
425{
426 ASSERT(!promises.isEmpty())((void)0);
427 auto promisesToBeResolved = std::exchange(promises, { });
428 for (auto& promise : promisesToBeResolved)
429 promise->resolve();
430}
431
432static inline void rejectPromises(Vector<Ref<DeferredPromise>>& promises, ASCIILiteral message)
433{
434 ASSERT(!promises.isEmpty())((void)0);
435 auto promisesToBeRejected = std::exchange(promises, { });
436 for (auto& promise : promisesToBeRejected)
437 promise->reject(Exception { ExceptionCode::EncodingError, message });
438}
439
440inline void ImageLoader::resolveDecodePromises()
441{
442 resolvePromises(m_decodingPromises);
443}
444
445inline void ImageLoader::rejectDecodePromises(ASCIILiteral message)
446{
447 rejectPromises(m_decodingPromises, message);
448}
449
450void ImageLoader::notifyFinished(CachedResource& resource, const NetworkLoadMetrics&, LoadWillContinueInAnotherProcess)
451{
452 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " notifyFinished - hasPendingLoadEvent " << m_hasPendingLoadEvent)((void)0);
453
454 ASSERT(m_failedLoadURL.isEmpty())((void)0);
455 ASSERT_UNUSED(resource, &resource == m_image.get())((void)resource);
456
457 m_pendingURL = { };
458
459 if (isDeferred()) {
460 LazyLoadImageObserver::unobserve(protect(element()), protect(document()));
461 m_lazyImageLoadState = LazyImageLoadState::FullImage;
462 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " notifyFinished() for element " << element() << " setting lazy load state to " << m_lazyImageLoadState)((void)0);
463 }
464
465 if (!m_hasPendingLoadEvent) {
466 setImageCompleteAndMaybeUpdateRenderer();
467 return;
468 }
469
470 if (m_image->resourceError().isAccessControl()) {
471 setImageCompleteAndMaybeUpdateRenderer();
472
473 auto imageURL = m_image->url();
474 clearImageWithoutConsideringPendingLoadEvent();
475
476 m_hasPendingErrorEvent = true;
477 loadEventSender().dispatchEventSoon(*this, eventNames().errorEvent);
478
479 auto message = makeString("Cannot load image "_s, imageURL.string(), " due to access control checks."_s);
480 protect(document())->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
481
482 if (hasPendingDecodePromises())
483 rejectDecodePromises("Access control error."_s);
484
485 ASSERT(!m_hasPendingLoadEvent)((void)0);
486
487 // Only consider updating the protection ref-count of the Element immediately before returning
488 // from this function as doing so might result in the destruction of this ImageLoader.
489 updatedHasPendingEvent();
490 return;
491 }
492
493 if (m_image->wasCanceled()) {
494 setImageCompleteAndMaybeUpdateRenderer();
495
496 if (hasPendingDecodePromises())
497 rejectDecodePromises("Loading was canceled."_s);
498 m_hasPendingLoadEvent = false;
499 // Only consider updating the protection ref-count of the Element immediately before returning
500 // from this function as doing so might result in the destruction of this ImageLoader.
501 updatedHasPendingEvent();
502 return;
503 }
504
505 protect(m_image->image())->subresourcesAreFinished(protect(document()).ptr(), [this, protectedThis = Ref { *this }]() mutable {
506 // It is technically possible state changed underneath us.
507 if (!m_hasPendingLoadEvent)
508 return;
509
510 setImageCompleteAndMaybeUpdateRenderer();
511
512 if (hasPendingDecodePromises())
513 decode();
514 loadEventSender().dispatchEventSoon(*this, eventNames().loadEvent);
515
516#if ENABLE(QUICKLOOK_FULLSCREEN)(defined ENABLE_QUICKLOOK_FULLSCREEN && ENABLE_QUICKLOOK_FULLSCREEN
)
517 if (RefPtr page = element().document().page())
518 page->chrome().client().updateImageSource(protect(element()).get());
519#endif
520
521#if ENABLE(SPATIAL_IMAGE_CONTROLS)(defined ENABLE_SPATIAL_IMAGE_CONTROLS && ENABLE_SPATIAL_IMAGE_CONTROLS
)
522 if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element()))
523 SpatialImageControls::updateSpatialImageControls(*imageElement);
524#endif
525 });
526}
527
528RenderImageResource* ImageLoader::renderImageResource()
529{
530 CheckedPtr renderer = element().renderer();
531 if (!renderer)
532 return nullptr;
533
534 // We don't return style generated image because it doesn't belong to the ImageLoader.
535 // See <https://bugs.webkit.org/show_bug.cgi?id=42840>
536 if (auto* renderImage = dynamicDowncast<RenderImage>(*renderer); renderImage && !renderImage->isGeneratedContent())
537 return &renderImage->imageResource();
538
539 if (auto* svgImage = dynamicDowncast<LegacyRenderSVGImage>(*renderer))
540 return &svgImage->imageResource();
541
542 if (auto* svgImage = dynamicDowncast<RenderSVGImage>(*renderer))
543 return &svgImage->imageResource();
544
545#if ENABLE(VIDEO)(defined 1 && 1)
546 if (auto* renderVideo = dynamicDowncast<RenderVideo>(*renderer))
547 return &renderVideo->imageResource();
548#endif
549
550 return nullptr;
551}
552
553void ImageLoader::updateRenderer()
554{
555 CheckedPtr imageResource = renderImageResource();
556
557 if (!imageResource)
558 return;
559
560 // Only update the renderer if it doesn't have an image or if what we have
561 // is a complete image. This prevents flickering in the case where a dynamic
562 // change is happening between two images.
563 CachedImage* cachedImage = imageResource->cachedImage();
564 if (m_image != cachedImage && (m_imageComplete || !cachedImage))
565 imageResource->setCachedImage(protect(m_image));
566}
567
568void ImageLoader::setImageCompleteAndMaybeUpdateRenderer()
569{
570 m_imageComplete = true;
571 if (!hasPendingBeforeLoadEvent())
572 updateRenderer();
573}
574
575void ImageLoader::updatedHasPendingEvent()
576{
577 // If an Element that does image loading is removed from the DOM the load/error event for the image is still observable.
578 // As long as the ImageLoader is actively loading, the Element itself needs to be ref'ed to keep it from being
579 // destroyed by DOM manipulation or garbage collection.
580 // If such an Element wishes for the load to stop when removed from the DOM it needs to stop the ImageLoader explicitly.
581 bool wasProtected = m_elementIsProtected;
582 m_elementIsProtected = hasPendingActivity();
583 if (wasProtected == m_elementIsProtected)
584 return;
585
586 if (m_elementIsProtected) {
587 if (m_derefElementTimer.isActive())
588 m_derefElementTimer.stop();
589 else
590 m_protectedElement = element();
591 } else {
592 ASSERT(!m_derefElementTimer.isActive())((void)0);
593 m_derefElementTimer.startOneShot(0_s);
594 }
595}
596
597void ImageLoader::decode(Ref<DeferredPromise>&& promise)
598{
599 m_decodingPromises.append(WTF::move(promise));
600
601 if (!element().document().window()) {
602 rejectDecodePromises("Inactive document."_s);
603 return;
604 }
605
606 auto attr = protect(element())->imageSourceURL();
607 if (StringView(attr).containsOnly<isASCIIWhitespace<char16_t>>()) {
608 rejectDecodePromises("Missing source URL."_s);
609 return;
610 }
611
612 if (m_imageComplete)
613 decode();
614}
615
616void ImageLoader::decode()
617{
618 ASSERT(hasPendingDecodePromises())((void)0);
619
620 if (!element().document().window()) {
621 rejectDecodePromises("Inactive document."_s);
622 return;
623 }
624
625 if (!m_image || !m_image->image() || m_image->errorOccurred()) {
626 rejectDecodePromises("Loading error."_s);
627 return;
628 }
629
630 RefPtr bitmapImage = dynamicDowncast<BitmapImage>(m_image->image());
631 if (!bitmapImage) {
632 resolveDecodePromises();
633 return;
634 }
635
636 bitmapImage->decode([promises = WTF::move(m_decodingPromises)](DecodingStatus decodingStatus) mutable {
637 ASSERT(decodingStatus != DecodingStatus::Decoding)((void)0);
638 if (decodingStatus == DecodingStatus::Invalid)
639 rejectPromises(promises, "Decoding error."_s);
640 else
641 resolvePromises(promises);
642 });
643}
644
645void ImageLoader::timerFired()
646{
647 m_protectedElement = nullptr;
648}
649
650bool ImageLoader::hasPendingActivity() const
651{
652 // Because of lazy image loading, an image's load may be deferred indefinitely. To avoid leaking the element, we only
653 // protect it once the load has actually started.
654 bool imageWillBeLoadedLater = m_image && !m_image->isLoading() && m_image->stillNeedsLoad();
655 return (m_hasPendingLoadEvent && !imageWillBeLoadedLater) || m_hasPendingErrorEvent;
656}
657
658void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender, const AtomString& eventType)
659{
660 ASSERT_UNUSED(eventSender, eventSender == &loadEventSender())((void)eventSender);
661 if (eventType == eventNames().loadEvent)
662 dispatchPendingLoadEvent();
663 if (eventType == eventNames().errorEvent)
664 dispatchPendingErrorEvent();
665}
666
667void ImageLoader::dispatchPendingBeforeLoadEvent()
668{
669 if (!m_hasPendingBeforeLoadEvent)
670 return;
671 if (!m_image)
672 return;
673 if (!element().document().hasLivingRenderTree())
674 return;
675 m_hasPendingBeforeLoadEvent = false;
676 if (!element().isConnected())
677 return;
678 updateRenderer();
679}
680
681void ImageLoader::dispatchPendingLoadEvent()
682{
683 if (!m_hasPendingLoadEvent)
684 return;
685 if (!m_image)
686 return;
687 m_hasPendingLoadEvent = false;
688 if (element().document().hasLivingRenderTree())
689 dispatchLoadEvent();
690
691 // Only consider updating the protection ref-count of the Element immediately before returning
692 // from this function as doing so might result in the destruction of this ImageLoader.
693 updatedHasPendingEvent();
694}
695
696void ImageLoader::dispatchPendingErrorEvent()
697{
698 if (!m_hasPendingErrorEvent)
699 return;
700 m_hasPendingErrorEvent = false;
701 loadEventSender().cancelEvent(*this, eventNames().errorEvent);
702 if (element().document().hasLivingRenderTree())
703 protect(element())->dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No));
704
705 // Only consider updating the protection ref-count of the Element immediately before returning
706 // from this function as doing so might result in the destruction of this ImageLoader.
707 updatedHasPendingEvent();
708}
709
710void ImageLoader::dispatchPendingLoadEvents(Page* page)
711{
712 loadEventSender().dispatchPendingEvents(page);
713}
714
715void ImageLoader::elementDidMoveToNewDocument(Document& oldDocument)
716{
717 clearFailedLoadURL();
718 clearImage();
719 resetLazyImageLoading(oldDocument);
720}
721
722inline void ImageLoader::clearFailedLoadURL()
723{
724 m_failedLoadURL = nullAtom();
725}
726
727void ImageLoader::loadDeferredImage()
728{
729 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " loadDeferredImage - state is " << m_lazyImageLoadState)((void)0);
730
731 if (m_lazyImageLoadState != LazyImageLoadState::Deferred)
732 return;
733 m_lazyImageLoadState = LazyImageLoadState::LoadImmediately;
734 updateFromElement(RelevantMutation::No);
735}
736
737void ImageLoader::resetLazyImageLoading(Document& document)
738{
739 LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " resetLazyImageLoading - state is " << m_lazyImageLoadState)((void)0);
740
741 if (isDeferred())
742 LazyLoadImageObserver::unobserve(protect(element()), document);
743 m_lazyImageLoadState = LazyImageLoadState::None;
744}
745
746VisibleInViewportState ImageLoader::imageVisibleInViewport(const Document& document) const
747{
748 if (&element().document() != &document)
749 return VisibleInViewportState::No;
750
751 CheckedPtr renderReplaced = dynamicDowncast<RenderReplaced>(element().renderer());
752 return renderReplaced && renderReplaced->isContentLikelyVisibleInViewport() ? VisibleInViewportState::Yes : VisibleInViewportState::No;
753}
754
755bool ImageLoader::shouldIgnoreCandidateWhenLoadingFromArchive(const ImageCandidate& candidate) const
756{
757#if ENABLE(WEB_ARCHIVE)(defined 1 && 1) || ENABLE(MHTML)(defined 0 && 0)
758 if (candidate.originAttribute == ImageCandidate::SrcOrigin)
759 return false;
760
761 Ref document = element().document();
762 RefPtr loader = document->loader();
763 if (!loader || !loader->hasArchiveResourceCollection())
764 return false;
765
766 auto candidateURL = URL { protect(element())->resolveURLStringIfNeeded(candidate.string.toString()) };
767 if (loader->archiveResourceForURL(candidateURL))
768 return false;
769
770 RefPtr page = document->page();
771 return !page || !page->allowsLoadFromURL(candidateURL, MainFrameMainResource::No);
772#else
773 UNUSED_PARAM(candidate)(void)candidate;
774 return false;
775#endif
776}
777
778} // namespace WebCore