imgLoader.h (20357B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_image_imgLoader_h 8 #define mozilla_image_imgLoader_h 9 10 #include "mozilla/CORSMode.h" 11 #include "mozilla/Maybe.h" 12 #include "mozilla/Mutex.h" 13 #include "mozilla/EnumSet.h" 14 #include "mozilla/UniquePtr.h" 15 16 #include "imgILoader.h" 17 #include "imgICache.h" 18 #include "nsWeakReference.h" 19 #include "nsIContentSniffer.h" 20 #include "nsRefPtrHashtable.h" 21 #include "nsTHashSet.h" 22 #include "nsExpirationTracker.h" 23 #include "ImageCacheKey.h" 24 #include "imgRequest.h" 25 #include "nsIProgressEventSink.h" 26 #include "nsIChannel.h" 27 #include "nsIThreadRetargetableStreamListener.h" 28 #include "imgIRequest.h" 29 #include "mozilla/dom/CacheExpirationTime.h" 30 31 class imgLoader; 32 class imgRequestProxy; 33 class imgINotificationObserver; 34 class nsILoadGroup; 35 class imgCacheExpirationTracker; 36 class imgMemoryReporter; 37 38 namespace mozilla { 39 namespace dom { 40 class Document; 41 enum class FetchPriority : uint8_t; 42 } // namespace dom 43 } // namespace mozilla 44 45 class imgCacheEntry { 46 public: 47 NS_INLINE_DECL_REFCOUNTING(imgCacheEntry) 48 49 imgCacheEntry(imgLoader* loader, imgRequest* request, 50 bool aForcePrincipalCheck); 51 52 uint32_t GetDataSize() const { return mDataSize; } 53 void SetDataSize(uint32_t aDataSize) { 54 int32_t oldsize = mDataSize; 55 mDataSize = aDataSize; 56 UpdateCache(mDataSize - oldsize); 57 } 58 59 int32_t GetTouchedTime() const { return mTouchedTime; } 60 void SetTouchedTime(int32_t time) { 61 mTouchedTime = time; 62 Touch(/* updateTime = */ false); 63 } 64 65 uint32_t GetLoadTime() const { return mLoadTime; } 66 67 void UpdateLoadTime(); 68 69 const CacheExpirationTime& GetExpiryTime() const { return mExpiryTime; } 70 71 void AccumulateExpiryTime(const CacheExpirationTime& aExpiryTime, 72 bool aForceTouch = false) { 73 if (aExpiryTime.IsNever()) { 74 if (aForceTouch) { 75 Touch(); 76 } 77 return; 78 } 79 if (mExpiryTime.IsNever() || aExpiryTime.IsShorterThan(mExpiryTime)) { 80 mExpiryTime = aExpiryTime; 81 Touch(); 82 } else { 83 if (aForceTouch) { 84 Touch(); 85 } 86 } 87 } 88 89 bool GetMustValidate() const { return mMustValidate; } 90 void SetMustValidate(bool aValidate) { 91 mMustValidate = aValidate; 92 Touch(); 93 } 94 95 already_AddRefed<imgRequest> GetRequest() const { 96 RefPtr<imgRequest> req = mRequest; 97 return req.forget(); 98 } 99 100 bool Evicted() const { return mEvicted; } 101 102 nsExpirationState* GetExpirationState() { return &mExpirationState; } 103 104 bool HasNoProxies() const { return mHasNoProxies; } 105 106 bool ForcePrincipalCheck() const { return mForcePrincipalCheck; } 107 108 bool HasNotified() const { return mHasNotified; } 109 void SetHasNotified() { 110 MOZ_ASSERT(!mHasNotified); 111 mHasNotified = true; 112 } 113 114 imgLoader* Loader() const { return mLoader; } 115 116 private: // methods 117 friend class imgLoader; 118 friend class imgCacheQueue; 119 void Touch(bool updateTime = true); 120 void UpdateCache(int32_t diff = 0); 121 void SetEvicted(bool evict) { mEvicted = evict; } 122 void SetHasNoProxies(bool hasNoProxies); 123 124 // Private, unimplemented copy constructor. 125 imgCacheEntry(const imgCacheEntry&); 126 ~imgCacheEntry(); 127 128 private: // data 129 imgLoader* mLoader; 130 RefPtr<imgRequest> mRequest; 131 uint32_t mDataSize; 132 int32_t mTouchedTime; 133 uint32_t mLoadTime; 134 CacheExpirationTime mExpiryTime; 135 nsExpirationState mExpirationState; 136 bool mMustValidate : 1; 137 bool mEvicted : 1; 138 bool mHasNoProxies : 1; 139 bool mForcePrincipalCheck : 1; 140 bool mHasNotified : 1; 141 }; 142 143 #define NS_IMGLOADER_CID \ 144 {/* c1354898-e3fe-4602-88a7-c4520c21cb4e */ \ 145 0xc1354898, \ 146 0xe3fe, \ 147 0x4602, \ 148 {0x88, 0xa7, 0xc4, 0x52, 0x0c, 0x21, 0xcb, 0x4e}} 149 150 class imgCacheQueue { 151 public: 152 imgCacheQueue(); 153 void Remove(imgCacheEntry*); 154 void Push(imgCacheEntry*); 155 void MarkDirty(); 156 bool IsDirty(); 157 already_AddRefed<imgCacheEntry> Pop(); 158 void Refresh(); 159 uint32_t GetSize() const; 160 void UpdateSize(int32_t diff); 161 uint32_t GetNumElements() const; 162 bool Contains(imgCacheEntry* aEntry) const; 163 typedef nsTArray<RefPtr<imgCacheEntry>> queueContainer; 164 typedef queueContainer::iterator iterator; 165 typedef queueContainer::const_iterator const_iterator; 166 167 iterator begin(); 168 const_iterator begin() const; 169 iterator end(); 170 const_iterator end() const; 171 172 private: 173 queueContainer mQueue; 174 bool mDirty; 175 uint32_t mSize; 176 }; 177 178 enum class AcceptedMimeTypes : uint8_t { 179 IMAGES, 180 IMAGES_AND_DOCUMENTS, 181 }; 182 183 class imgLoader final : public imgILoader, 184 public nsIContentSniffer, 185 public imgICache, 186 public nsSupportsWeakReference, 187 public nsIObserver { 188 virtual ~imgLoader(); 189 190 public: 191 using ImageCacheKey = mozilla::image::ImageCacheKey; 192 using imgCacheTable = 193 nsRefPtrHashtable<nsGenericHashKey<ImageCacheKey>, imgCacheEntry>; 194 using imgSet = nsTHashSet<imgRequest*>; 195 using Mutex = mozilla::Mutex; 196 197 NS_DECL_ISUPPORTS 198 NS_DECL_IMGILOADER 199 NS_DECL_NSICONTENTSNIFFER 200 NS_DECL_IMGICACHE 201 NS_DECL_NSIOBSERVER 202 203 /** 204 * Get the normal image loader instance that is used by gecko code, creating 205 * it if necessary. 206 */ 207 static imgLoader* NormalLoader(); 208 209 /** 210 * Get the Private Browsing image loader instance that is used by gecko code, 211 * creating it if necessary. 212 */ 213 static imgLoader* PrivateBrowsingLoader(); 214 215 /** 216 * Gecko code should use NormalLoader() or PrivateBrowsingLoader() to get the 217 * appropriate image loader. 218 * 219 * This constructor is public because the XPCOM module code that creates 220 * instances of "@mozilla.org/image/loader;1" / "@mozilla.org/image/cache;1" 221 * for nsIComponentManager.createInstance()/nsIServiceManager.getService() 222 * calls (now only made by add-ons) needs access to it. 223 * 224 * XXX We would like to get rid of the nsIServiceManager.getService (and 225 * nsIComponentManager.createInstance) method of creating imgLoader objects, 226 * but there are add-ons that are still using it. These add-ons don't 227 * actually do anything useful with the loaders that they create since nobody 228 * who creates an imgLoader using this method actually QIs to imgILoader and 229 * loads images. They all just QI to imgICache and either call clearCache() 230 * or findEntryProperties(). Since they're doing this on an imgLoader that 231 * has never loaded images, these calls are useless. It seems likely that 232 * the code that is doing this is just legacy code left over from a time when 233 * there was only one imgLoader instance for the entire process. (Nowadays 234 * the correct method to get an imgILoader/imgICache is to call 235 * imgITools::getImgCacheForDocument/imgITools::getImgLoaderForDocument.) 236 * All the same, even though what these add-ons are doing is a no-op, 237 * removing the nsIServiceManager.getService method of creating/getting an 238 * imgLoader objects would cause an exception in these add-ons that could 239 * break things. 240 */ 241 imgLoader(); 242 nsresult Init(); 243 244 /** 245 * Clear cache that matches the specified filters. 246 * If called on the parent process, clear cache from all processes. 247 * If called in the content process, clear cache within the process. 248 * 249 * @param aPrivateLoader 250 * If specified and true, clear private loader. 251 * If specified and false, clear normal loader. 252 * If not specified, clear both loaders. 253 * Has no effect with aPrincipal. 254 * @param aChrome 255 * If specified and true, clear chrome cache. 256 * If specified and false, clear content cache. 257 * If not specified, clear both. 258 * Has no effect with aPrincipal, aSchemelessSite or aURL. 259 * @param aPrincipal 260 * If specified, clear cache from the same origin and the same 261 * originAttributes of the passed principal. 262 * Exclusive with aSchemelessSite and aURL. 263 * @param aSchemelessSite 264 * If specified, clear cache which match the the given site. 265 * If this is specified, aPattern should also be specified. 266 * Exclusive with aPrincipal and aURL. 267 * @param aPattern 268 * The pattern used with aSchemelessSite. 269 * @param aURL 270 * If specified, clear cache for given URL. 271 * Exclusive with aPrincipal and aschemelesssite. 272 */ 273 static nsresult ClearCache( 274 mozilla::Maybe<bool> aPrivateLoader = mozilla::Nothing(), 275 mozilla::Maybe<bool> aChrome = mozilla::Nothing(), 276 const mozilla::Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal = 277 mozilla::Nothing(), 278 const mozilla::Maybe<nsCString>& aSchemelessSite = mozilla::Nothing(), 279 const mozilla::Maybe<mozilla::OriginAttributesPattern>& aPattern = 280 mozilla::Nothing(), 281 const mozilla::Maybe<nsCString>& aURL = mozilla::Nothing()); 282 283 bool IsImageAvailable(nsIURI*, nsIPrincipal* aTriggeringPrincipal, 284 mozilla::CORSMode, mozilla::dom::Document*); 285 286 [[nodiscard]] nsresult LoadImage( 287 nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo, 288 nsIPrincipal* aLoadingPrincipal, uint64_t aRequestContextID, 289 nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver, 290 nsINode* aContext, mozilla::dom::Document* aLoadingDocument, 291 nsLoadFlags aLoadFlags, nsISupports* aCacheKey, 292 nsContentPolicyType aContentPolicyType, const nsAString& initiatorType, 293 bool aUseUrgentStartForChannel, bool aLinkPreload, 294 uint64_t aEarlyHintPreloaderId, 295 mozilla::dom::FetchPriority aFetchPriority, imgRequestProxy** _retval); 296 297 [[nodiscard]] nsresult LoadImageWithChannel( 298 nsIChannel* channel, imgINotificationObserver* aObserver, 299 mozilla::dom::Document* aLoadingDocument, nsIStreamListener** listener, 300 imgRequestProxy** _retval); 301 302 static nsresult GetMimeTypeFromContent(const char* aContents, 303 uint32_t aLength, 304 nsACString& aContentType); 305 306 /** 307 * Returns true if the given mime type may be interpreted as an image. 308 * 309 * Some MIME types may be interpreted as both images and documents. (At the 310 * moment only "image/svg+xml" falls into this category, but there may be more 311 * in the future.) Callers which want this function to return true for such 312 * MIME types should pass AcceptedMimeTypes::IMAGES_AND_DOCUMENTS for 313 * @aAccept. 314 * 315 * @param aMimeType The MIME type to evaluate. 316 * @param aAcceptedMimeTypes Which kinds of MIME types to treat as images. 317 */ 318 static bool SupportImageWithMimeType( 319 const nsACString&, AcceptedMimeTypes aAccept = AcceptedMimeTypes::IMAGES); 320 321 static void GlobalInit(); // for use by the factory 322 static void Shutdown(); // for use by the factory 323 static void ShutdownMemoryReporter(); 324 325 enum class ClearOption { 326 ChromeOnly, 327 ContentOnly, 328 UnusedOnly, 329 }; 330 using ClearOptions = mozilla::EnumSet<ClearOption>; 331 nsresult ClearImageCache(ClearOptions = {}); 332 void MinimizeCache() { ClearImageCache({ClearOption::UnusedOnly}); } 333 334 nsresult InitCache(); 335 336 bool RemoveFromCache(const ImageCacheKey& aKey); 337 338 // Enumeration describing if a given entry is in the cache queue or not. 339 // There are some cases we know the entry is definitely not in the queue. 340 enum class QueueState { MaybeExists, AlreadyRemoved }; 341 342 bool RemoveFromCache(imgCacheEntry* entry, 343 QueueState aQueueState = QueueState::MaybeExists); 344 345 bool PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* aEntry); 346 347 void AddToUncachedImages(imgRequest* aRequest); 348 void RemoveFromUncachedImages(imgRequest* aRequest); 349 350 // Returns true if we should prefer evicting cache entry |two| over cache 351 // entry |one|. 352 // This mixes units in the worst way, but provides reasonable results. 353 inline static bool CompareCacheEntries(const RefPtr<imgCacheEntry>& one, 354 const RefPtr<imgCacheEntry>& two) { 355 if (!one) { 356 return false; 357 } 358 if (!two) { 359 return true; 360 } 361 362 const double sizeweight = 1.0 - sCacheTimeWeight; 363 364 // We want large, old images to be evicted first (depending on their 365 // relative weights). Since a larger time is actually newer, we subtract 366 // time's weight, so an older image has a larger weight. 367 double oneweight = double(one->GetDataSize()) * sizeweight - 368 double(one->GetTouchedTime()) * sCacheTimeWeight; 369 double twoweight = double(two->GetDataSize()) * sizeweight - 370 double(two->GetTouchedTime()) * sCacheTimeWeight; 371 372 return oneweight < twoweight; 373 } 374 375 void VerifyCacheSizes(); 376 377 nsresult RemoveEntriesInternal( 378 const mozilla::Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal, 379 const mozilla::Maybe<nsCString>& aSchemelessSite, 380 const mozilla::Maybe<mozilla::OriginAttributesPattern>& aPattern, 381 const mozilla::Maybe<nsCString>& aURL); 382 383 // The image loader maintains a hash table of all imgCacheEntries. However, 384 // only some of them will be evicted from the cache: those who have no 385 // imgRequestProxies watching their imgRequests. 386 // 387 // Once an imgRequest has no imgRequestProxies, it should notify us by 388 // calling HasNoObservers(), and null out its cache entry pointer. 389 // 390 // Upon having a proxy start observing again, it should notify us by calling 391 // HasObservers(). The request's cache entry will be re-set before this 392 // happens, by calling imgRequest::SetCacheEntry() when an entry with no 393 // observers is re-requested. 394 bool SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry); 395 bool SetHasProxies(imgRequest* aRequest); 396 397 private: // methods 398 static already_AddRefed<imgLoader> CreateImageLoader(); 399 400 bool ValidateEntry(imgCacheEntry* aEntry, nsIURI* aURI, 401 nsIURI* aInitialDocumentURI, 402 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup, 403 imgINotificationObserver* aObserver, 404 mozilla::dom::Document* aLoadingDocument, 405 nsLoadFlags aLoadFlags, 406 nsContentPolicyType aLoadPolicyType, 407 bool aCanMakeNewChannel, bool* aNewChannelCreated, 408 imgRequestProxy** aProxyRequest, 409 nsIPrincipal* aTriggeringPrincipal, mozilla::CORSMode, 410 bool aLinkPreload, uint64_t aEarlyHintPreloaderId, 411 mozilla::dom::FetchPriority aFetchPriority); 412 413 bool ValidateRequestWithNewChannel( 414 imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI, 415 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup, 416 imgINotificationObserver* aObserver, 417 mozilla::dom::Document* aLoadingDocument, uint64_t aInnerWindowId, 418 nsLoadFlags aLoadFlags, nsContentPolicyType aContentPolicyType, 419 imgRequestProxy** aProxyRequest, nsIPrincipal* aLoadingPrincipal, 420 mozilla::CORSMode, bool aLinkPreload, uint64_t aEarlyHintPreloaderId, 421 mozilla::dom::FetchPriority aFetchPriority, bool* aNewChannelCreated); 422 423 void NotifyObserversForCachedImage( 424 imgCacheEntry* aEntry, imgRequest* request, nsIURI* aURI, 425 nsIReferrerInfo* aReferrerInfo, mozilla::dom::Document* aLoadingDocument, 426 nsIPrincipal* aTriggeringPrincipal, mozilla::CORSMode, 427 uint64_t aEarlyHintPreloaderId, 428 mozilla::dom::FetchPriority aFetchPriority); 429 // aURI may be different from imgRequest's URI in the case of blob URIs, as we 430 // can share requests with different URIs. 431 nsresult CreateNewProxyForRequest(imgRequest* aRequest, nsIURI* aURI, 432 nsILoadGroup* aLoadGroup, 433 mozilla::dom::Document* aLoadingDocument, 434 imgINotificationObserver* aObserver, 435 nsLoadFlags aLoadFlags, 436 imgRequestProxy** _retval); 437 438 nsresult EvictEntries(bool aChromeOnly); 439 440 void CacheEntriesChanged(int32_t aSizeDiff); 441 void CheckCacheLimits(); 442 443 private: // data 444 friend class imgCacheEntry; 445 friend class imgMemoryReporter; 446 447 imgCacheTable mCache; 448 imgCacheQueue mCacheQueue; 449 450 // Hash set of every imgRequest for this loader that isn't in mCache or 451 // mChromeCache. The union over all imgLoader's of mCache, mChromeCache, and 452 // mUncachedImages should be every imgRequest that is alive. These are weak 453 // pointers so we rely on the imgRequest destructor to remove itself. 454 imgSet mUncachedImages MOZ_GUARDED_BY(mUncachedImagesMutex); 455 // The imgRequest can have refs to them held on non-main thread, so we need 456 // a mutex because we modify the uncached images set from the imgRequest 457 // destructor. 458 Mutex mUncachedImagesMutex; 459 460 static double sCacheTimeWeight; 461 static uint32_t sCacheMaxSize; 462 static imgMemoryReporter* sMemReporter; 463 464 mozilla::UniquePtr<imgCacheExpirationTracker> mCacheTracker; 465 bool mRespectPrivacy; 466 }; 467 468 /** 469 * proxy stream listener class used to handle multipart/x-mixed-replace 470 */ 471 472 #include "nsCOMPtr.h" 473 #include "nsIStreamListener.h" 474 #include "nsIThreadRetargetableStreamListener.h" 475 476 class ProxyListener : public nsIThreadRetargetableStreamListener { 477 public: 478 explicit ProxyListener(nsIStreamListener* dest); 479 480 /* additional members */ 481 NS_DECL_THREADSAFE_ISUPPORTS 482 NS_DECL_NSISTREAMLISTENER 483 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER 484 NS_DECL_NSIREQUESTOBSERVER 485 486 private: 487 virtual ~ProxyListener(); 488 489 nsCOMPtr<nsIStreamListener> mDestListener; 490 }; 491 492 /** 493 * A class that implements nsIProgressEventSink and forwards all calls to it to 494 * the original notification callbacks of the channel. Also implements 495 * nsIInterfaceRequestor and gives out itself for nsIProgressEventSink calls, 496 * and forwards everything else to the channel's notification callbacks. 497 */ 498 class nsProgressNotificationProxy final : public nsIProgressEventSink, 499 public nsIChannelEventSink, 500 public nsIInterfaceRequestor { 501 public: 502 nsProgressNotificationProxy(nsIChannel* channel, imgIRequest* proxy) 503 : mImageRequest(proxy) { 504 channel->GetNotificationCallbacks(getter_AddRefs(mOriginalCallbacks)); 505 } 506 507 NS_DECL_ISUPPORTS 508 NS_DECL_NSIPROGRESSEVENTSINK 509 NS_DECL_NSICHANNELEVENTSINK 510 NS_DECL_NSIINTERFACEREQUESTOR 511 private: 512 ~nsProgressNotificationProxy() = default; 513 514 nsCOMPtr<nsIInterfaceRequestor> mOriginalCallbacks; 515 nsCOMPtr<nsIRequest> mImageRequest; 516 }; 517 518 /** 519 * validate checker 520 */ 521 522 #include "nsCOMArray.h" 523 524 class imgCacheValidator : public nsIThreadRetargetableStreamListener, 525 public nsIChannelEventSink, 526 public nsIInterfaceRequestor, 527 public nsIAsyncVerifyRedirectCallback { 528 public: 529 imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader, 530 imgRequest* aRequest, mozilla::dom::Document* aDocument, 531 uint64_t aInnerWindowId, 532 bool forcePrincipalCheckForCacheEntry); 533 534 void AddProxy(imgRequestProxy* aProxy); 535 void RemoveProxy(imgRequestProxy* aProxy); 536 537 NS_DECL_THREADSAFE_ISUPPORTS 538 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER 539 NS_DECL_NSISTREAMLISTENER 540 NS_DECL_NSIREQUESTOBSERVER 541 NS_DECL_NSICHANNELEVENTSINK 542 NS_DECL_NSIINTERFACEREQUESTOR 543 NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK 544 545 private: 546 void UpdateProxies(bool aCancelRequest, bool aSyncNotify); 547 virtual ~imgCacheValidator(); 548 549 nsCOMPtr<nsIStreamListener> mDestListener; 550 RefPtr<nsProgressNotificationProxy> mProgressProxy; 551 nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; 552 nsCOMPtr<nsIChannel> mRedirectChannel; 553 554 RefPtr<imgRequest> mRequest; 555 AutoTArray<RefPtr<imgRequestProxy>, 4> mProxies; 556 557 RefPtr<imgRequest> mNewRequest; 558 RefPtr<imgCacheEntry> mNewEntry; 559 560 RefPtr<mozilla::dom::Document> mDocument; 561 uint64_t mInnerWindowId; 562 563 imgLoader* mImgLoader; 564 565 bool mHadInsecureRedirect; 566 }; 567 568 #endif // mozilla_image_imgLoader_h