imgLoader.cpp (122911B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 // Undefine windows version of LoadImage because our code uses that name. 8 #include "mozilla/ScopeExit.h" 9 #include "nsIChildChannel.h" 10 #include "nsIThreadRetargetableStreamListener.h" 11 #undef LoadImage 12 13 #include "imgLoader.h" 14 15 #include <algorithm> 16 #include <utility> 17 18 #include "DecoderFactory.h" 19 #include "Image.h" 20 #include "ImageLogging.h" 21 #include "ReferrerInfo.h" 22 #include "imgRequestProxy.h" 23 #include "mozilla/BasePrincipal.h" 24 #include "mozilla/ChaosMode.h" 25 #include "mozilla/ClearOnShutdown.h" 26 #include "mozilla/LoadInfo.h" 27 #include "mozilla/NullPrincipal.h" 28 #include "mozilla/Preferences.h" 29 #include "mozilla/ProfilerLabels.h" 30 #include "mozilla/StaticPrefs_image.h" 31 #include "mozilla/StaticPrefs_network.h" 32 #include "mozilla/StoragePrincipalHelper.h" 33 #include "mozilla/Maybe.h" 34 #include "mozilla/SharedSubResourceCache.h" 35 #include "mozilla/dom/CacheExpirationTime.h" 36 #include "mozilla/dom/ContentParent.h" 37 #include "mozilla/dom/FetchPriority.h" 38 #include "mozilla/dom/nsMixedContentBlocker.h" 39 #include "mozilla/image/ImageMemoryReporter.h" 40 #include "mozilla/layers/CompositorManagerChild.h" 41 #include "nsCOMPtr.h" 42 #include "nsCRT.h" 43 #include "nsComponentManagerUtils.h" 44 #include "nsContentPolicyUtils.h" 45 #include "nsContentSecurityManager.h" 46 #include "nsContentUtils.h" 47 #include "nsHttpChannel.h" 48 #include "nsIAsyncVerifyRedirectCallback.h" 49 #include "nsICacheInfoChannel.h" 50 #include "nsIChannelEventSink.h" 51 #include "nsIClassOfService.h" 52 #include "nsIEffectiveTLDService.h" 53 #include "nsIFile.h" 54 #include "nsIFileURL.h" 55 #include "nsIHttpChannel.h" 56 #include "nsIInterfaceRequestor.h" 57 #include "nsIInterfaceRequestorUtils.h" 58 #include "nsIMemoryReporter.h" 59 #include "nsIProgressEventSink.h" 60 #include "nsIProtocolHandler.h" 61 #include "nsImageModule.h" 62 #include "nsMediaSniffer.h" 63 #include "nsMimeTypes.h" 64 #include "nsNetCID.h" 65 #include "nsNetUtil.h" 66 #include "nsProxyRelease.h" 67 #include "nsQueryObject.h" 68 #include "nsReadableUtils.h" 69 #include "nsStreamUtils.h" 70 #include "prtime.h" 71 72 // we want to explore making the document own the load group 73 // so we can associate the document URI with the load group. 74 // until this point, we have an evil hack: 75 #include "nsIHttpChannelInternal.h" 76 #include "nsILoadGroupChild.h" 77 #include "nsIDocShell.h" 78 79 using namespace mozilla; 80 using namespace mozilla::dom; 81 using namespace mozilla::image; 82 using namespace mozilla::net; 83 84 MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf) 85 86 class imgMemoryReporter final : public nsIMemoryReporter { 87 ~imgMemoryReporter() = default; 88 89 public: 90 NS_DECL_ISUPPORTS 91 92 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, 93 nsISupports* aData, bool aAnonymize) override { 94 MOZ_ASSERT(NS_IsMainThread()); 95 96 layers::CompositorManagerChild* manager = 97 mozilla::layers::CompositorManagerChild::GetInstance(); 98 if (!manager || !StaticPrefs::image_mem_debug_reporting()) { 99 layers::SharedSurfacesMemoryReport sharedSurfaces; 100 FinishCollectReports(aHandleReport, aData, aAnonymize, sharedSurfaces); 101 return NS_OK; 102 } 103 104 RefPtr<imgMemoryReporter> self(this); 105 nsCOMPtr<nsIHandleReportCallback> handleReport(aHandleReport); 106 nsCOMPtr<nsISupports> data(aData); 107 manager->SendReportSharedSurfacesMemory( 108 [=](layers::SharedSurfacesMemoryReport aReport) { 109 self->FinishCollectReports(handleReport, data, aAnonymize, aReport); 110 }, 111 [=](mozilla::ipc::ResponseRejectReason&& aReason) { 112 layers::SharedSurfacesMemoryReport sharedSurfaces; 113 self->FinishCollectReports(handleReport, data, aAnonymize, 114 sharedSurfaces); 115 }); 116 return NS_OK; 117 } 118 119 void FinishCollectReports( 120 nsIHandleReportCallback* aHandleReport, nsISupports* aData, 121 bool aAnonymize, layers::SharedSurfacesMemoryReport& aSharedSurfaces) { 122 nsTArray<ImageMemoryCounter> chrome; 123 nsTArray<ImageMemoryCounter> content; 124 nsTArray<ImageMemoryCounter> uncached; 125 126 for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) { 127 for (imgCacheEntry* entry : mKnownLoaders[i]->mCache.Values()) { 128 RefPtr<imgRequest> req = entry->GetRequest(); 129 RecordCounterForRequest(req, &content, !entry->HasNoProxies()); 130 } 131 MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex); 132 for (RefPtr<imgRequest> req : mKnownLoaders[i]->mUncachedImages) { 133 RecordCounterForRequest(req, &uncached, req->HasConsumers()); 134 } 135 } 136 137 // Note that we only need to anonymize content image URIs. 138 139 ReportCounterArray(aHandleReport, aData, chrome, "images/chrome", 140 /* aAnonymize */ false, aSharedSurfaces); 141 142 ReportCounterArray(aHandleReport, aData, content, "images/content", 143 aAnonymize, aSharedSurfaces); 144 145 // Uncached images may be content or chrome, so anonymize them. 146 ReportCounterArray(aHandleReport, aData, uncached, "images/uncached", 147 aAnonymize, aSharedSurfaces); 148 149 // Report any shared surfaces that were not merged with the surface cache. 150 ImageMemoryReporter::ReportSharedSurfaces(aHandleReport, aData, 151 aSharedSurfaces); 152 153 nsCOMPtr<nsIMemoryReporterManager> imgr = 154 do_GetService("@mozilla.org/memory-reporter-manager;1"); 155 if (imgr) { 156 imgr->EndReport(); 157 } 158 } 159 160 static int64_t ImagesContentUsedUncompressedDistinguishedAmount() { 161 size_t n = 0; 162 for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length(); 163 i++) { 164 nsTArray<RefPtr<imgCacheEntry>> entries( 165 imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Count()); 166 167 for (imgCacheEntry* entry : 168 imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Values()) { 169 entries.AppendElement(entry); 170 } 171 for (imgCacheEntry* entry : entries) { 172 if (entry->HasNoProxies()) { 173 continue; 174 } 175 176 RefPtr<imgRequest> req = entry->GetRequest(); 177 RefPtr<image::Image> image = req->GetImage(); 178 if (!image) { 179 continue; 180 } 181 182 // Both this and EntryImageSizes measure 183 // images/content/raster/used/decoded memory. This function's 184 // measurement is secondary -- the result doesn't go in the "explicit" 185 // tree -- so we use moz_malloc_size_of instead of ImagesMallocSizeOf to 186 // prevent DMD from seeing it reported twice. 187 SizeOfState state(moz_malloc_size_of); 188 ImageMemoryCounter counter(req, image, state, /* aIsUsed = */ true); 189 190 n += counter.Values().DecodedHeap(); 191 n += counter.Values().DecodedNonHeap(); 192 n += counter.Values().DecodedUnknown(); 193 } 194 } 195 return n; 196 } 197 198 void RegisterLoader(imgLoader* aLoader) { 199 mKnownLoaders.AppendElement(aLoader); 200 } 201 202 void UnregisterLoader(imgLoader* aLoader) { 203 mKnownLoaders.RemoveElement(aLoader); 204 } 205 206 private: 207 nsTArray<imgLoader*> mKnownLoaders; 208 209 struct MemoryTotal { 210 MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter) { 211 if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) { 212 if (aImageCounter.IsUsed()) { 213 mUsedRasterCounter += aImageCounter.Values(); 214 } else { 215 mUnusedRasterCounter += aImageCounter.Values(); 216 } 217 } else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) { 218 if (aImageCounter.IsUsed()) { 219 mUsedVectorCounter += aImageCounter.Values(); 220 } else { 221 mUnusedVectorCounter += aImageCounter.Values(); 222 } 223 } else if (aImageCounter.Type() == imgIContainer::TYPE_REQUEST) { 224 // Nothing to do, we did not get to the point of having an image. 225 } else { 226 MOZ_CRASH("Unexpected image type"); 227 } 228 229 return *this; 230 } 231 232 const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; } 233 const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; } 234 const MemoryCounter& UsedVector() const { return mUsedVectorCounter; } 235 const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; } 236 237 private: 238 MemoryCounter mUsedRasterCounter; 239 MemoryCounter mUnusedRasterCounter; 240 MemoryCounter mUsedVectorCounter; 241 MemoryCounter mUnusedVectorCounter; 242 }; 243 244 // Reports all images of a single kind, e.g. all used chrome images. 245 void ReportCounterArray(nsIHandleReportCallback* aHandleReport, 246 nsISupports* aData, 247 nsTArray<ImageMemoryCounter>& aCounterArray, 248 const char* aPathPrefix, bool aAnonymize, 249 layers::SharedSurfacesMemoryReport& aSharedSurfaces) { 250 MemoryTotal summaryTotal; 251 MemoryTotal nonNotableTotal; 252 253 // Report notable images, and compute total and non-notable aggregate sizes. 254 for (uint32_t i = 0; i < aCounterArray.Length(); i++) { 255 ImageMemoryCounter& counter = aCounterArray[i]; 256 257 if (aAnonymize) { 258 counter.URI().Truncate(); 259 counter.URI().AppendPrintf("<anonymized-%u>", i); 260 } else { 261 // The URI could be an extremely long data: URI. Truncate if needed. 262 static const size_t max = 256; 263 if (counter.URI().Length() > max) { 264 counter.URI().Truncate(max); 265 counter.URI().AppendLiteral(" (truncated)"); 266 } 267 counter.URI().ReplaceChar('/', '\\'); 268 } 269 270 summaryTotal += counter; 271 272 if (counter.IsNotable() || StaticPrefs::image_mem_debug_reporting()) { 273 ReportImage(aHandleReport, aData, aPathPrefix, counter, 274 aSharedSurfaces); 275 } else { 276 ImageMemoryReporter::TrimSharedSurfaces(counter, aSharedSurfaces); 277 nonNotableTotal += counter; 278 } 279 } 280 281 // Report non-notable images in aggregate. 282 ReportTotal(aHandleReport, aData, /* aExplicit = */ true, aPathPrefix, 283 "<non-notable images>/", nonNotableTotal); 284 285 // Report a summary in aggregate, outside of the explicit tree. 286 ReportTotal(aHandleReport, aData, /* aExplicit = */ false, aPathPrefix, "", 287 summaryTotal); 288 } 289 290 static void ReportImage(nsIHandleReportCallback* aHandleReport, 291 nsISupports* aData, const char* aPathPrefix, 292 const ImageMemoryCounter& aCounter, 293 layers::SharedSurfacesMemoryReport& aSharedSurfaces) { 294 nsAutoCString pathPrefix("explicit/"_ns); 295 pathPrefix.Append(aPathPrefix); 296 297 switch (aCounter.Type()) { 298 case imgIContainer::TYPE_RASTER: 299 pathPrefix.AppendLiteral("/raster/"); 300 break; 301 case imgIContainer::TYPE_VECTOR: 302 pathPrefix.AppendLiteral("/vector/"); 303 break; 304 case imgIContainer::TYPE_REQUEST: 305 pathPrefix.AppendLiteral("/request/"); 306 break; 307 default: 308 pathPrefix.AppendLiteral("/unknown="); 309 pathPrefix.AppendInt(aCounter.Type()); 310 pathPrefix.AppendLiteral("/"); 311 break; 312 } 313 314 pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/"); 315 if (aCounter.IsValidating()) { 316 pathPrefix.AppendLiteral("validating/"); 317 } 318 if (aCounter.HasError()) { 319 pathPrefix.AppendLiteral("err/"); 320 } 321 322 pathPrefix.AppendLiteral("progress="); 323 pathPrefix.AppendInt(aCounter.Progress(), 16); 324 pathPrefix.AppendLiteral("/"); 325 326 pathPrefix.AppendLiteral("image("); 327 pathPrefix.AppendInt(aCounter.IntrinsicSize().width); 328 pathPrefix.AppendLiteral("x"); 329 pathPrefix.AppendInt(aCounter.IntrinsicSize().height); 330 pathPrefix.AppendLiteral(", "); 331 332 if (aCounter.URI().IsEmpty()) { 333 pathPrefix.AppendLiteral("<unknown URI>"); 334 } else { 335 pathPrefix.Append(aCounter.URI()); 336 } 337 338 pathPrefix.AppendLiteral(")/"); 339 340 ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter, aSharedSurfaces); 341 342 ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values()); 343 } 344 345 static void ReportSurfaces( 346 nsIHandleReportCallback* aHandleReport, nsISupports* aData, 347 const nsACString& aPathPrefix, const ImageMemoryCounter& aCounter, 348 layers::SharedSurfacesMemoryReport& aSharedSurfaces) { 349 for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) { 350 nsAutoCString surfacePathPrefix(aPathPrefix); 351 switch (counter.Type()) { 352 case SurfaceMemoryCounterType::NORMAL: 353 if (counter.IsLocked()) { 354 surfacePathPrefix.AppendLiteral("locked/"); 355 } else { 356 surfacePathPrefix.AppendLiteral("unlocked/"); 357 } 358 if (counter.IsFactor2()) { 359 surfacePathPrefix.AppendLiteral("factor2/"); 360 } 361 if (counter.CannotSubstitute()) { 362 surfacePathPrefix.AppendLiteral("cannot_substitute/"); 363 } 364 break; 365 case SurfaceMemoryCounterType::CONTAINER: 366 surfacePathPrefix.AppendLiteral("container/"); 367 break; 368 default: 369 MOZ_ASSERT_UNREACHABLE("Unknown counter type"); 370 break; 371 } 372 373 surfacePathPrefix.AppendLiteral("types="); 374 surfacePathPrefix.AppendInt(counter.Values().SurfaceTypes(), 16); 375 surfacePathPrefix.AppendLiteral("/surface("); 376 surfacePathPrefix.AppendInt(counter.Key().Size().width); 377 surfacePathPrefix.AppendLiteral("x"); 378 surfacePathPrefix.AppendInt(counter.Key().Size().height); 379 380 if (!counter.IsFinished()) { 381 surfacePathPrefix.AppendLiteral(", incomplete"); 382 } 383 384 if (counter.Values().ExternalHandles() > 0) { 385 surfacePathPrefix.AppendLiteral(", handles:"); 386 surfacePathPrefix.AppendInt( 387 uint32_t(counter.Values().ExternalHandles())); 388 } 389 390 ImageMemoryReporter::AppendSharedSurfacePrefix(surfacePathPrefix, counter, 391 aSharedSurfaces); 392 393 PlaybackType playback = counter.Key().Playback(); 394 if (playback == PlaybackType::eAnimated) { 395 if (StaticPrefs::image_mem_debug_reporting()) { 396 surfacePathPrefix.AppendPrintf( 397 " (animation %4u)", uint32_t(counter.Values().FrameIndex())); 398 } else { 399 surfacePathPrefix.AppendLiteral(" (animation)"); 400 } 401 } 402 403 if (counter.Key().Flags() != DefaultSurfaceFlags()) { 404 surfacePathPrefix.AppendLiteral(", flags:"); 405 surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()), 406 /* aRadix = */ 16); 407 } 408 409 if (counter.Key().Region()) { 410 const ImageIntRegion& region = counter.Key().Region().ref(); 411 const gfx::IntRect& rect = region.Rect(); 412 surfacePathPrefix.AppendLiteral(", region:[ rect=("); 413 surfacePathPrefix.AppendInt(rect.x); 414 surfacePathPrefix.AppendLiteral(","); 415 surfacePathPrefix.AppendInt(rect.y); 416 surfacePathPrefix.AppendLiteral(") "); 417 surfacePathPrefix.AppendInt(rect.width); 418 surfacePathPrefix.AppendLiteral("x"); 419 surfacePathPrefix.AppendInt(rect.height); 420 if (region.IsRestricted()) { 421 const gfx::IntRect& restrict = region.Restriction(); 422 if (restrict == rect) { 423 surfacePathPrefix.AppendLiteral(", restrict=rect"); 424 } else { 425 surfacePathPrefix.AppendLiteral(", restrict=("); 426 surfacePathPrefix.AppendInt(restrict.x); 427 surfacePathPrefix.AppendLiteral(","); 428 surfacePathPrefix.AppendInt(restrict.y); 429 surfacePathPrefix.AppendLiteral(") "); 430 surfacePathPrefix.AppendInt(restrict.width); 431 surfacePathPrefix.AppendLiteral("x"); 432 surfacePathPrefix.AppendInt(restrict.height); 433 } 434 } 435 if (region.GetExtendMode() != gfx::ExtendMode::CLAMP) { 436 surfacePathPrefix.AppendLiteral(", extendMode="); 437 surfacePathPrefix.AppendInt(int32_t(region.GetExtendMode())); 438 } 439 surfacePathPrefix.AppendLiteral("]"); 440 } 441 442 const SVGImageContext& context = counter.Key().SVGContext(); 443 surfacePathPrefix.AppendLiteral(", svgContext:[ "); 444 if (context.GetViewportSize()) { 445 const CSSIntSize& size = context.GetViewportSize().ref(); 446 surfacePathPrefix.AppendLiteral("viewport=("); 447 surfacePathPrefix.AppendInt(size.width); 448 surfacePathPrefix.AppendLiteral("x"); 449 surfacePathPrefix.AppendInt(size.height); 450 surfacePathPrefix.AppendLiteral(") "); 451 } 452 if (context.GetPreserveAspectRatio()) { 453 nsAutoString aspect; 454 context.GetPreserveAspectRatio()->ToString(aspect); 455 surfacePathPrefix.AppendLiteral("preserveAspectRatio=("); 456 LossyAppendUTF16toASCII(aspect, surfacePathPrefix); 457 surfacePathPrefix.AppendLiteral(") "); 458 } 459 if (auto scheme = context.GetColorScheme()) { 460 surfacePathPrefix.AppendLiteral("colorScheme="); 461 surfacePathPrefix.AppendInt(int32_t(*scheme)); 462 surfacePathPrefix.AppendLiteral(" "); 463 } 464 if (context.GetContextPaint()) { 465 const SVGEmbeddingContextPaint* paint = context.GetContextPaint(); 466 surfacePathPrefix.AppendLiteral("contextPaint=("); 467 if (paint->GetFill()) { 468 surfacePathPrefix.AppendLiteral(" fill="); 469 surfacePathPrefix.AppendInt(paint->GetFill()->ToABGR(), 16); 470 } 471 if (paint->GetFillOpacity() != 1.0) { 472 surfacePathPrefix.AppendLiteral(" fillOpa="); 473 surfacePathPrefix.AppendFloat(paint->GetFillOpacity()); 474 } 475 if (paint->GetStroke()) { 476 surfacePathPrefix.AppendLiteral(" stroke="); 477 surfacePathPrefix.AppendInt(paint->GetStroke()->ToABGR(), 16); 478 } 479 if (paint->GetStrokeOpacity() != 1.0) { 480 surfacePathPrefix.AppendLiteral(" strokeOpa="); 481 surfacePathPrefix.AppendFloat(paint->GetStrokeOpacity()); 482 } 483 surfacePathPrefix.AppendLiteral(" ) "); 484 } 485 surfacePathPrefix.AppendLiteral("]"); 486 487 surfacePathPrefix.AppendLiteral(")/"); 488 489 ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values()); 490 } 491 } 492 493 static void ReportTotal(nsIHandleReportCallback* aHandleReport, 494 nsISupports* aData, bool aExplicit, 495 const char* aPathPrefix, const char* aPathInfix, 496 const MemoryTotal& aTotal) { 497 nsAutoCString pathPrefix; 498 if (aExplicit) { 499 pathPrefix.AppendLiteral("explicit/"); 500 } 501 pathPrefix.Append(aPathPrefix); 502 503 nsAutoCString rasterUsedPrefix(pathPrefix); 504 rasterUsedPrefix.AppendLiteral("/raster/used/"); 505 rasterUsedPrefix.Append(aPathInfix); 506 ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster()); 507 508 nsAutoCString rasterUnusedPrefix(pathPrefix); 509 rasterUnusedPrefix.AppendLiteral("/raster/unused/"); 510 rasterUnusedPrefix.Append(aPathInfix); 511 ReportValues(aHandleReport, aData, rasterUnusedPrefix, 512 aTotal.UnusedRaster()); 513 514 nsAutoCString vectorUsedPrefix(pathPrefix); 515 vectorUsedPrefix.AppendLiteral("/vector/used/"); 516 vectorUsedPrefix.Append(aPathInfix); 517 ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector()); 518 519 nsAutoCString vectorUnusedPrefix(pathPrefix); 520 vectorUnusedPrefix.AppendLiteral("/vector/unused/"); 521 vectorUnusedPrefix.Append(aPathInfix); 522 ReportValues(aHandleReport, aData, vectorUnusedPrefix, 523 aTotal.UnusedVector()); 524 } 525 526 static void ReportValues(nsIHandleReportCallback* aHandleReport, 527 nsISupports* aData, const nsACString& aPathPrefix, 528 const MemoryCounter& aCounter) { 529 ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter); 530 531 ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "decoded-heap", 532 "Decoded image data which is stored on the heap.", 533 aCounter.DecodedHeap()); 534 535 ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix, 536 "decoded-nonheap", 537 "Decoded image data which isn't stored on the heap.", 538 aCounter.DecodedNonHeap()); 539 540 // We don't know for certain whether or not it is on the heap, so let's 541 // just report it as non-heap for reporting purposes. 542 ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix, 543 "decoded-unknown", 544 "Decoded image data which is unknown to be on the heap or not.", 545 aCounter.DecodedUnknown()); 546 } 547 548 static void ReportSourceValue(nsIHandleReportCallback* aHandleReport, 549 nsISupports* aData, 550 const nsACString& aPathPrefix, 551 const MemoryCounter& aCounter) { 552 ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "source", 553 "Raster image source data and vector image documents.", 554 aCounter.Source()); 555 } 556 557 static void ReportValue(nsIHandleReportCallback* aHandleReport, 558 nsISupports* aData, int32_t aKind, 559 const nsACString& aPathPrefix, 560 const char* aPathSuffix, const char* aDescription, 561 size_t aValue) { 562 if (aValue == 0) { 563 return; 564 } 565 566 nsAutoCString desc(aDescription); 567 nsAutoCString path(aPathPrefix); 568 path.Append(aPathSuffix); 569 570 aHandleReport->Callback(""_ns, path, aKind, UNITS_BYTES, aValue, desc, 571 aData); 572 } 573 574 static void RecordCounterForRequest(imgRequest* aRequest, 575 nsTArray<ImageMemoryCounter>* aArray, 576 bool aIsUsed) { 577 SizeOfState state(ImagesMallocSizeOf); 578 RefPtr<image::Image> image = aRequest->GetImage(); 579 if (image) { 580 ImageMemoryCounter counter(aRequest, image, state, aIsUsed); 581 aArray->AppendElement(std::move(counter)); 582 } else { 583 // We can at least record some information about the image from the 584 // request, and mark it as not knowing the image type yet. 585 ImageMemoryCounter counter(aRequest, state, aIsUsed); 586 aArray->AppendElement(std::move(counter)); 587 } 588 } 589 }; 590 591 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter) 592 593 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, nsIProgressEventSink, 594 nsIChannelEventSink, nsIInterfaceRequestor) 595 596 NS_IMETHODIMP 597 nsProgressNotificationProxy::OnProgress(nsIRequest* request, int64_t progress, 598 int64_t progressMax) { 599 nsCOMPtr<nsILoadGroup> loadGroup; 600 request->GetLoadGroup(getter_AddRefs(loadGroup)); 601 602 nsCOMPtr<nsIProgressEventSink> target; 603 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup, 604 NS_GET_IID(nsIProgressEventSink), 605 getter_AddRefs(target)); 606 if (!target) { 607 return NS_OK; 608 } 609 return target->OnProgress(mImageRequest, progress, progressMax); 610 } 611 612 NS_IMETHODIMP 613 nsProgressNotificationProxy::OnStatus(nsIRequest* request, nsresult status, 614 const char16_t* statusArg) { 615 nsCOMPtr<nsILoadGroup> loadGroup; 616 request->GetLoadGroup(getter_AddRefs(loadGroup)); 617 618 nsCOMPtr<nsIProgressEventSink> target; 619 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup, 620 NS_GET_IID(nsIProgressEventSink), 621 getter_AddRefs(target)); 622 if (!target) { 623 return NS_OK; 624 } 625 return target->OnStatus(mImageRequest, status, statusArg); 626 } 627 628 NS_IMETHODIMP 629 nsProgressNotificationProxy::AsyncOnChannelRedirect( 630 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags, 631 nsIAsyncVerifyRedirectCallback* cb) { 632 // Tell the original original callbacks about it too 633 nsCOMPtr<nsILoadGroup> loadGroup; 634 newChannel->GetLoadGroup(getter_AddRefs(loadGroup)); 635 nsCOMPtr<nsIChannelEventSink> target; 636 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup, 637 NS_GET_IID(nsIChannelEventSink), 638 getter_AddRefs(target)); 639 if (!target) { 640 cb->OnRedirectVerifyCallback(NS_OK); 641 return NS_OK; 642 } 643 644 // Delegate to |target| if set, reusing |cb| 645 return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb); 646 } 647 648 NS_IMETHODIMP 649 nsProgressNotificationProxy::GetInterface(const nsIID& iid, void** result) { 650 if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) { 651 *result = static_cast<nsIProgressEventSink*>(this); 652 NS_ADDREF_THIS(); 653 return NS_OK; 654 } 655 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { 656 *result = static_cast<nsIChannelEventSink*>(this); 657 NS_ADDREF_THIS(); 658 return NS_OK; 659 } 660 if (mOriginalCallbacks) { 661 return mOriginalCallbacks->GetInterface(iid, result); 662 } 663 return NS_NOINTERFACE; 664 } 665 666 static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, 667 imgLoader* aLoader, const ImageCacheKey& aKey, 668 imgRequest** aRequest, imgCacheEntry** aEntry) { 669 RefPtr<imgRequest> request = new imgRequest(aLoader, aKey); 670 RefPtr<imgCacheEntry> entry = 671 new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry); 672 aLoader->AddToUncachedImages(request); 673 request.forget(aRequest); 674 entry.forget(aEntry); 675 } 676 677 static bool ShouldRevalidateEntry(imgCacheEntry* aEntry, nsLoadFlags aFlags, 678 bool aHasExpired) { 679 if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) { 680 return false; 681 } 682 if (aFlags & nsIRequest::VALIDATE_ALWAYS) { 683 return true; 684 } 685 if (aEntry->GetMustValidate()) { 686 return true; 687 } 688 if (aHasExpired) { 689 // The cache entry has expired... Determine whether the stale cache 690 // entry can be used without validation... 691 if (aFlags & (nsIRequest::LOAD_FROM_CACHE | nsIRequest::VALIDATE_NEVER | 692 nsIRequest::VALIDATE_ONCE_PER_SESSION)) { 693 // LOAD_FROM_CACHE, VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow 694 // stale cache entries to be used unless they have been explicitly marked 695 // to indicate that revalidation is necessary. 696 return false; 697 } 698 // Entry is expired, revalidate. 699 return true; 700 } 701 return false; 702 } 703 704 /* Call content policies on cached images that went through a redirect */ 705 static bool ShouldLoadCachedImage(imgRequest* aImgRequest, 706 Document* aLoadingDocument, 707 nsIPrincipal* aTriggeringPrincipal, 708 nsContentPolicyType aPolicyType, 709 bool aSendCSPViolationReports) { 710 /* Call content policies on cached images - Bug 1082837 711 * Cached images are keyed off of the first uri in a redirect chain. 712 * Hence content policies don't get a chance to test the intermediate hops 713 * or the final destination. Here we test the final destination using 714 * mFinalURI off of the imgRequest and passing it into content policies. 715 * For Mixed Content Blocker, we do an additional check to determine if any 716 * of the intermediary hops went through an insecure redirect with the 717 * mHadInsecureRedirect flag 718 */ 719 bool insecureRedirect = aImgRequest->HadInsecureRedirect(); 720 nsCOMPtr<nsIURI> contentLocation; 721 aImgRequest->GetFinalURI(getter_AddRefs(contentLocation)); 722 nsresult rv; 723 724 nsCOMPtr<nsIPrincipal> loadingPrincipal = 725 aLoadingDocument ? aLoadingDocument->NodePrincipal() 726 : aTriggeringPrincipal; 727 // If there is no context and also no triggeringPrincipal, then we use a fresh 728 // nullPrincipal as the loadingPrincipal because we can not create a loadinfo 729 // without a valid loadingPrincipal. 730 if (!loadingPrincipal) { 731 loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes(); 732 } 733 734 Result<RefPtr<LoadInfo>, nsresult> maybeLoadInfo = LoadInfo::Create( 735 loadingPrincipal, aTriggeringPrincipal, aLoadingDocument, 736 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, aPolicyType); 737 if (NS_WARN_IF(maybeLoadInfo.isErr())) { 738 return false; 739 } 740 RefPtr<LoadInfo> secCheckLoadInfo = maybeLoadInfo.unwrap(); 741 secCheckLoadInfo->SetSendCSPViolationEvents(aSendCSPViolationReports); 742 743 int16_t decision = nsIContentPolicy::REJECT_REQUEST; 744 rv = NS_CheckContentLoadPolicy(contentLocation, secCheckLoadInfo, &decision, 745 nsContentUtils::GetContentPolicy()); 746 if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) { 747 return false; 748 } 749 750 // We call all Content Policies above, but we also have to call mcb 751 // individually to check the intermediary redirect hops are secure. 752 if (insecureRedirect) { 753 // Bug 1314356: If the image ended up in the cache upgraded by HSTS and the 754 // page uses upgrade-inscure-requests it had an insecure redirect 755 // (http->https). We need to invalidate the image and reload it because 756 // mixed content blocker only bails if upgrade-insecure-requests is set on 757 // the doc and the resource load is http: which would result in an incorrect 758 // mixed content warning. 759 nsCOMPtr<nsIDocShell> docShell = 760 NS_CP_GetDocShellFromContext(ToSupports(aLoadingDocument)); 761 if (docShell) { 762 Document* document = docShell->GetDocument(); 763 if (document && document->GetUpgradeInsecureRequests(false)) { 764 return false; 765 } 766 } 767 768 if (!aTriggeringPrincipal || !aTriggeringPrincipal->IsSystemPrincipal()) { 769 // reset the decision for mixed content blocker check 770 decision = nsIContentPolicy::REJECT_REQUEST; 771 rv = nsMixedContentBlocker::ShouldLoad(insecureRedirect, contentLocation, 772 secCheckLoadInfo, 773 true, // aReportError 774 &decision); 775 if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) { 776 return false; 777 } 778 } 779 } 780 781 return true; 782 } 783 784 // Returns true if this request is compatible with the given CORS mode on the 785 // given loading principal, and false if the request may not be reused due 786 // to CORS. 787 static bool ValidateCORSMode(imgRequest* aRequest, bool aForcePrincipalCheck, 788 CORSMode aCORSMode, 789 nsIPrincipal* aTriggeringPrincipal) { 790 // If the entry's CORS mode doesn't match, or the CORS mode matches but the 791 // document principal isn't the same, we can't use this request. 792 if (aRequest->GetCORSMode() != aCORSMode) { 793 return false; 794 } 795 796 if (aRequest->GetCORSMode() != CORS_NONE || aForcePrincipalCheck) { 797 nsCOMPtr<nsIPrincipal> otherprincipal = aRequest->GetTriggeringPrincipal(); 798 799 // If we previously had a principal, but we don't now, we can't use this 800 // request. 801 if (otherprincipal && !aTriggeringPrincipal) { 802 return false; 803 } 804 805 if (otherprincipal && aTriggeringPrincipal && 806 !otherprincipal->Equals(aTriggeringPrincipal)) { 807 return false; 808 } 809 } 810 811 return true; 812 } 813 814 static bool ValidateSecurityInfo(imgRequest* aRequest, 815 bool aForcePrincipalCheck, CORSMode aCORSMode, 816 nsIPrincipal* aTriggeringPrincipal, 817 Document* aLoadingDocument, 818 nsContentPolicyType aPolicyType) { 819 if (!ValidateCORSMode(aRequest, aForcePrincipalCheck, aCORSMode, 820 aTriggeringPrincipal)) { 821 return false; 822 } 823 // Content Policy Check on Cached Images 824 return ShouldLoadCachedImage(aRequest, aLoadingDocument, aTriggeringPrincipal, 825 aPolicyType, 826 /* aSendCSPViolationReports */ false); 827 } 828 829 static void AdjustPriorityForImages(nsIChannel* aChannel, 830 nsLoadFlags aLoadFlags, 831 FetchPriority aFetchPriority) { 832 // Image channels are loaded by default with reduced priority. 833 if (nsCOMPtr<nsISupportsPriority> supportsPriority = 834 do_QueryInterface(aChannel)) { 835 int32_t priority = nsISupportsPriority::PRIORITY_LOW; 836 837 // Adjust priority according to fetchpriorty attribute. 838 if (StaticPrefs::network_fetchpriority_enabled()) { 839 priority += FETCH_PRIORITY_ADJUSTMENT_FOR(images, aFetchPriority); 840 } 841 842 // Further reduce priority for background loads 843 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { 844 ++priority; 845 } 846 847 supportsPriority->AdjustPriority(priority); 848 } 849 850 if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) { 851 cos->SetFetchPriorityDOM(aFetchPriority); 852 } 853 } 854 855 static nsresult NewImageChannel( 856 nsIChannel** aResult, 857 // If aForcePrincipalCheckForCacheEntry is true, then we will 858 // force a principal check even when not using CORS before 859 // assuming we have a cache hit on a cache entry that we 860 // create for this channel. This is an out param that should 861 // be set to true if this channel ends up depending on 862 // aTriggeringPrincipal and false otherwise. 863 bool* aForcePrincipalCheckForCacheEntry, nsIURI* aURI, 864 nsIURI* aInitialDocumentURI, CORSMode aCORSMode, 865 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup, 866 nsLoadFlags aLoadFlags, nsContentPolicyType aPolicyType, 867 nsIPrincipal* aTriggeringPrincipal, nsINode* aRequestingNode, 868 bool aRespectPrivacy, uint64_t aEarlyHintPreloaderId, 869 FetchPriority aFetchPriority) { 870 MOZ_ASSERT(aResult); 871 872 nsresult rv; 873 nsCOMPtr<nsIHttpChannel> newHttpChannel; 874 875 nsCOMPtr<nsIInterfaceRequestor> callbacks; 876 877 if (aLoadGroup) { 878 // Get the notification callbacks from the load group for the new channel. 879 // 880 // XXX: This is not exactly correct, because the network request could be 881 // referenced by multiple windows... However, the new channel needs 882 // something. So, using the 'first' notification callbacks is better 883 // than nothing... 884 // 885 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); 886 } 887 888 // Pass in a nullptr loadgroup because this is the underlying network 889 // request. This request may be referenced by several proxy image requests 890 // (possibly in different documents). 891 // If all of the proxy requests are canceled then this request should be 892 // canceled too. 893 // 894 895 nsSecurityFlags securityFlags = 896 nsContentSecurityManager::ComputeSecurityFlags( 897 aCORSMode, nsContentSecurityManager::CORSSecurityMapping:: 898 CORS_NONE_MAPS_TO_INHERITED_CONTEXT); 899 900 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME; 901 902 // Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a 903 // node and a principal. This is for things like background images that are 904 // specified by user stylesheets, where the document is being styled, but 905 // the principal is that of the user stylesheet. 906 if (aRequestingNode && aTriggeringPrincipal) { 907 rv = NS_NewChannelWithTriggeringPrincipal(aResult, aURI, aRequestingNode, 908 aTriggeringPrincipal, 909 securityFlags, aPolicyType, 910 nullptr, // PerformanceStorage 911 nullptr, // loadGroup 912 callbacks, aLoadFlags); 913 914 if (NS_FAILED(rv)) { 915 return rv; 916 } 917 918 if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) { 919 // If this is a favicon loading, we will use the originAttributes from the 920 // triggeringPrincipal as the channel's originAttributes. This allows the 921 // favicon loading from XUL will use the correct originAttributes. 922 923 nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo(); 924 rv = loadInfo->SetOriginAttributes( 925 aTriggeringPrincipal->OriginAttributesRef()); 926 } 927 } else { 928 // either we are loading something inside a document, in which case 929 // we should always have a requestingNode, or we are loading something 930 // outside a document, in which case the triggeringPrincipal should be the 931 // systemPrincipal. However, there are exceptions: one is Notifications 932 // which create a channel in the parent process in which case we can't get a 933 // requestingNode though we might have a valid triggeringPrincipal. 934 rv = NS_NewChannel(aResult, aURI, 935 aTriggeringPrincipal 936 ? aTriggeringPrincipal 937 : nsContentUtils::GetSystemPrincipal(), 938 securityFlags, aPolicyType, 939 nullptr, // nsICookieJarSettings 940 nullptr, // PerformanceStorage 941 nullptr, // loadGroup 942 callbacks, aLoadFlags); 943 944 if (NS_FAILED(rv)) { 945 return rv; 946 } 947 948 // Use the OriginAttributes from the loading principal, if one is available, 949 // and adjust the private browsing ID based on what kind of load the caller 950 // has asked us to perform. 951 OriginAttributes attrs; 952 if (aTriggeringPrincipal) { 953 attrs = aTriggeringPrincipal->OriginAttributesRef(); 954 } 955 attrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0; 956 957 nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo(); 958 rv = loadInfo->SetOriginAttributes(attrs); 959 } 960 961 if (NS_FAILED(rv)) { 962 return rv; 963 } 964 965 // only inherit if we have a principal 966 *aForcePrincipalCheckForCacheEntry = 967 aTriggeringPrincipal && nsContentUtils::ChannelShouldInheritPrincipal( 968 aTriggeringPrincipal, aURI, 969 /* aInheritForAboutBlank */ false, 970 /* aForceInherit */ false); 971 972 // Initialize HTTP-specific attributes 973 newHttpChannel = do_QueryInterface(*aResult); 974 if (newHttpChannel) { 975 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = 976 do_QueryInterface(newHttpChannel); 977 NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED); 978 rv = httpChannelInternal->SetDocumentURI(aInitialDocumentURI); 979 MOZ_ASSERT(NS_SUCCEEDED(rv)); 980 if (aReferrerInfo) { 981 DebugOnly<nsresult> rv = newHttpChannel->SetReferrerInfo(aReferrerInfo); 982 MOZ_ASSERT(NS_SUCCEEDED(rv)); 983 } 984 985 if (aEarlyHintPreloaderId) { 986 rv = httpChannelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId); 987 NS_ENSURE_SUCCESS(rv, rv); 988 } 989 } 990 991 AdjustPriorityForImages(*aResult, aLoadFlags, aFetchPriority); 992 993 // Create a new loadgroup for this new channel, using the old group as 994 // the parent. The indirection keeps the channel insulated from cancels, 995 // but does allow a way for this revalidation to be associated with at 996 // least one base load group for scheduling/caching purposes. 997 998 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); 999 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup); 1000 if (childLoadGroup) { 1001 childLoadGroup->SetParentLoadGroup(aLoadGroup); 1002 } 1003 (*aResult)->SetLoadGroup(loadGroup); 1004 1005 return NS_OK; 1006 } 1007 1008 static uint32_t SecondsFromPRTime(PRTime aTime) { 1009 return nsContentUtils::SecondsFromPRTime(aTime); 1010 } 1011 1012 /* static */ 1013 imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request, 1014 bool forcePrincipalCheck) 1015 : mLoader(loader), 1016 mRequest(request), 1017 mDataSize(0), 1018 mTouchedTime(SecondsFromPRTime(PR_Now())), 1019 mLoadTime(SecondsFromPRTime(PR_Now())), 1020 mExpiryTime(CacheExpirationTime::Never()), 1021 mMustValidate(false), 1022 // We start off as evicted so we don't try to update the cache. 1023 // PutIntoCache will set this to false. 1024 mEvicted(true), 1025 mHasNoProxies(true), 1026 mForcePrincipalCheck(forcePrincipalCheck), 1027 mHasNotified(false) {} 1028 1029 imgCacheEntry::~imgCacheEntry() { 1030 LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()"); 1031 } 1032 1033 void imgCacheEntry::Touch(bool updateTime /* = true */) { 1034 LOG_SCOPE(gImgLog, "imgCacheEntry::Touch"); 1035 1036 if (updateTime) { 1037 mTouchedTime = SecondsFromPRTime(PR_Now()); 1038 } 1039 1040 UpdateCache(); 1041 } 1042 1043 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) { 1044 // Don't update the cache if we've been removed from it or it doesn't care 1045 // about our size or usage. 1046 if (!Evicted() && HasNoProxies()) { 1047 mLoader->CacheEntriesChanged(diff); 1048 } 1049 } 1050 1051 void imgCacheEntry::UpdateLoadTime() { 1052 mLoadTime = SecondsFromPRTime(PR_Now()); 1053 } 1054 1055 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) { 1056 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { 1057 if (hasNoProxies) { 1058 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true", "uri", 1059 mRequest->CacheKey().URI()); 1060 } else { 1061 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false", 1062 "uri", mRequest->CacheKey().URI()); 1063 } 1064 } 1065 1066 mHasNoProxies = hasNoProxies; 1067 } 1068 1069 imgCacheQueue::imgCacheQueue() : mDirty(false), mSize(0) {} 1070 1071 void imgCacheQueue::UpdateSize(int32_t diff) { mSize += diff; } 1072 1073 uint32_t imgCacheQueue::GetSize() const { return mSize; } 1074 1075 void imgCacheQueue::Remove(imgCacheEntry* entry) { 1076 uint64_t index = mQueue.IndexOf(entry); 1077 if (index == queueContainer::NoIndex) { 1078 return; 1079 } 1080 1081 mSize -= mQueue[index]->GetDataSize(); 1082 1083 // If the queue is clean and this is the first entry, 1084 // then we can efficiently remove the entry without 1085 // dirtying the sort order. 1086 if (!IsDirty() && index == 0) { 1087 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); 1088 mQueue.RemoveLastElement(); 1089 return; 1090 } 1091 1092 // Remove from the middle of the list. This potentially 1093 // breaks the binary heap sort order. 1094 mQueue.RemoveElementAt(index); 1095 1096 // If we only have one entry or the queue is empty, though, 1097 // then the sort order is still effectively good. Simply 1098 // refresh the list to clear the dirty flag. 1099 if (mQueue.Length() <= 1) { 1100 Refresh(); 1101 return; 1102 } 1103 1104 // Otherwise we must mark the queue dirty and potentially 1105 // trigger an expensive sort later. 1106 MarkDirty(); 1107 } 1108 1109 void imgCacheQueue::Push(imgCacheEntry* entry) { 1110 mSize += entry->GetDataSize(); 1111 1112 RefPtr<imgCacheEntry> refptr(entry); 1113 mQueue.AppendElement(std::move(refptr)); 1114 // If we're not dirty already, then we can efficiently add this to the 1115 // binary heap immediately. This is only O(log n). 1116 if (!IsDirty()) { 1117 std::push_heap(mQueue.begin(), mQueue.end(), 1118 imgLoader::CompareCacheEntries); 1119 } 1120 } 1121 1122 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop() { 1123 if (mQueue.IsEmpty()) { 1124 return nullptr; 1125 } 1126 if (IsDirty()) { 1127 Refresh(); 1128 } 1129 1130 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); 1131 RefPtr<imgCacheEntry> entry = mQueue.PopLastElement(); 1132 1133 mSize -= entry->GetDataSize(); 1134 return entry.forget(); 1135 } 1136 1137 void imgCacheQueue::Refresh() { 1138 // Resort the list. This is an O(3 * n) operation and best avoided 1139 // if possible. 1140 std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); 1141 mDirty = false; 1142 } 1143 1144 void imgCacheQueue::MarkDirty() { mDirty = true; } 1145 1146 bool imgCacheQueue::IsDirty() { return mDirty; } 1147 1148 uint32_t imgCacheQueue::GetNumElements() const { return mQueue.Length(); } 1149 1150 bool imgCacheQueue::Contains(imgCacheEntry* aEntry) const { 1151 return mQueue.Contains(aEntry); 1152 } 1153 1154 imgCacheQueue::iterator imgCacheQueue::begin() { return mQueue.begin(); } 1155 1156 imgCacheQueue::const_iterator imgCacheQueue::begin() const { 1157 return mQueue.begin(); 1158 } 1159 1160 imgCacheQueue::iterator imgCacheQueue::end() { return mQueue.end(); } 1161 1162 imgCacheQueue::const_iterator imgCacheQueue::end() const { 1163 return mQueue.end(); 1164 } 1165 1166 nsresult imgLoader::CreateNewProxyForRequest( 1167 imgRequest* aRequest, nsIURI* aURI, nsILoadGroup* aLoadGroup, 1168 Document* aLoadingDocument, imgINotificationObserver* aObserver, 1169 nsLoadFlags aLoadFlags, imgRequestProxy** _retval) { 1170 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest", 1171 "imgRequest", aRequest); 1172 1173 /* XXX If we move decoding onto separate threads, we should save off the 1174 calling thread here and pass it off to |proxyRequest| so that it call 1175 proxy calls to |aObserver|. 1176 */ 1177 1178 RefPtr<imgRequestProxy> proxyRequest = new imgRequestProxy(); 1179 1180 /* It is important to call |SetLoadFlags()| before calling |Init()| because 1181 |Init()| adds the request to the loadgroup. 1182 */ 1183 proxyRequest->SetLoadFlags(aLoadFlags); 1184 1185 // init adds itself to imgRequest's list of observers 1186 nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aURI, aObserver); 1187 if (NS_WARN_IF(NS_FAILED(rv))) { 1188 return rv; 1189 } 1190 1191 proxyRequest.forget(_retval); 1192 return NS_OK; 1193 } 1194 1195 class imgCacheExpirationTracker final 1196 : public nsExpirationTracker<imgCacheEntry, 3> { 1197 enum { TIMEOUT_SECONDS = 10 }; 1198 1199 public: 1200 imgCacheExpirationTracker(); 1201 1202 protected: 1203 void NotifyExpired(imgCacheEntry* entry) override; 1204 }; 1205 1206 imgCacheExpirationTracker::imgCacheExpirationTracker() 1207 : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000, 1208 "imgCacheExpirationTracker"_ns) {} 1209 1210 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry) { 1211 // Hold on to a reference to this entry, because the expiration tracker 1212 // mechanism doesn't. 1213 RefPtr<imgCacheEntry> kungFuDeathGrip(entry); 1214 1215 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { 1216 RefPtr<imgRequest> req = entry->GetRequest(); 1217 if (req) { 1218 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheExpirationTracker::NotifyExpired", 1219 "entry", req->CacheKey().URI()); 1220 } 1221 } 1222 1223 // We can be called multiple times on the same entry. Don't do work multiple 1224 // times. 1225 if (!entry->Evicted()) { 1226 entry->Loader()->RemoveFromCache(entry); 1227 } 1228 1229 entry->Loader()->VerifyCacheSizes(); 1230 } 1231 1232 /////////////////////////////////////////////////////////////////////////////// 1233 // imgLoader 1234 /////////////////////////////////////////////////////////////////////////////// 1235 1236 double imgLoader::sCacheTimeWeight; 1237 uint32_t imgLoader::sCacheMaxSize; 1238 imgMemoryReporter* imgLoader::sMemReporter; 1239 1240 NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache, 1241 nsISupportsWeakReference, nsIObserver) 1242 1243 static imgLoader* gNormalLoader = nullptr; 1244 static imgLoader* gPrivateBrowsingLoader = nullptr; 1245 1246 /* static */ 1247 already_AddRefed<imgLoader> imgLoader::CreateImageLoader() { 1248 // In some cases, such as xpctests, XPCOM modules are not automatically 1249 // initialized. We need to make sure that our module is initialized before 1250 // we hand out imgLoader instances and code starts using them. 1251 mozilla::image::EnsureModuleInitialized(); 1252 1253 RefPtr<imgLoader> loader = new imgLoader(); 1254 loader->Init(); 1255 1256 return loader.forget(); 1257 } 1258 1259 imgLoader* imgLoader::NormalLoader() { 1260 if (!gNormalLoader) { 1261 gNormalLoader = CreateImageLoader().take(); 1262 } 1263 return gNormalLoader; 1264 } 1265 1266 imgLoader* imgLoader::PrivateBrowsingLoader() { 1267 if (!gPrivateBrowsingLoader) { 1268 gPrivateBrowsingLoader = CreateImageLoader().take(); 1269 gPrivateBrowsingLoader->RespectPrivacyNotifications(); 1270 } 1271 return gPrivateBrowsingLoader; 1272 } 1273 1274 imgLoader::imgLoader() 1275 : mUncachedImagesMutex("imgLoader::UncachedImages"), 1276 mRespectPrivacy(false) { 1277 sMemReporter->AddRef(); 1278 sMemReporter->RegisterLoader(this); 1279 } 1280 1281 imgLoader::~imgLoader() { 1282 ClearImageCache(); 1283 { 1284 // If there are any of our imgRequest's left they are in the uncached 1285 // images set, so clear their pointer to us. 1286 MutexAutoLock lock(mUncachedImagesMutex); 1287 for (RefPtr<imgRequest> req : mUncachedImages) { 1288 req->ClearLoader(); 1289 } 1290 } 1291 sMemReporter->UnregisterLoader(this); 1292 sMemReporter->Release(); 1293 } 1294 1295 void imgLoader::VerifyCacheSizes() { 1296 #ifdef DEBUG 1297 if (!mCacheTracker) { 1298 return; 1299 } 1300 1301 uint32_t cachesize = mCache.Count(); 1302 uint32_t queuesize = mCacheQueue.GetNumElements(); 1303 uint32_t trackersize = 0; 1304 for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get()); 1305 it.Next();) { 1306 trackersize++; 1307 } 1308 MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!"); 1309 MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!"); 1310 #endif 1311 } 1312 1313 void imgLoader::GlobalInit() { 1314 sCacheTimeWeight = StaticPrefs::image_cache_timeweight_AtStartup() / 1000.0; 1315 int32_t cachesize = StaticPrefs::image_cache_size_AtStartup(); 1316 sCacheMaxSize = cachesize > 0 ? cachesize : 0; 1317 1318 sMemReporter = new imgMemoryReporter(); 1319 RegisterStrongAsyncMemoryReporter(sMemReporter); 1320 RegisterImagesContentUsedUncompressedDistinguishedAmount( 1321 imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount); 1322 } 1323 1324 void imgLoader::ShutdownMemoryReporter() { 1325 UnregisterImagesContentUsedUncompressedDistinguishedAmount(); 1326 UnregisterStrongMemoryReporter(sMemReporter); 1327 } 1328 1329 nsresult imgLoader::InitCache() { 1330 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1331 if (!os) { 1332 return NS_ERROR_FAILURE; 1333 } 1334 1335 os->AddObserver(this, "memory-pressure", false); 1336 os->AddObserver(this, "chrome-flush-caches", false); 1337 os->AddObserver(this, "last-pb-context-exited", false); 1338 os->AddObserver(this, "profile-before-change", false); 1339 os->AddObserver(this, "xpcom-shutdown", false); 1340 1341 mCacheTracker = MakeUnique<imgCacheExpirationTracker>(); 1342 1343 return NS_OK; 1344 } 1345 1346 nsresult imgLoader::Init() { 1347 InitCache(); 1348 1349 return NS_OK; 1350 } 1351 1352 NS_IMETHODIMP 1353 imgLoader::RespectPrivacyNotifications() { 1354 mRespectPrivacy = true; 1355 return NS_OK; 1356 } 1357 1358 NS_IMETHODIMP 1359 imgLoader::Observe(nsISupports* aSubject, const char* aTopic, 1360 const char16_t* aData) { 1361 if (strcmp(aTopic, "memory-pressure") == 0) { 1362 MinimizeCache(); 1363 } else if (strcmp(aTopic, "chrome-flush-caches") == 0) { 1364 MinimizeCache(); 1365 ClearImageCache({ClearOption::ChromeOnly}); 1366 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) { 1367 if (mRespectPrivacy) { 1368 ClearImageCache(); 1369 } 1370 } else if (strcmp(aTopic, "profile-before-change") == 0) { 1371 mCacheTracker = nullptr; 1372 } else if (strcmp(aTopic, "xpcom-shutdown") == 0) { 1373 mCacheTracker = nullptr; 1374 ShutdownMemoryReporter(); 1375 1376 } else { 1377 // (Nothing else should bring us here) 1378 MOZ_ASSERT(0, "Invalid topic received"); 1379 } 1380 1381 return NS_OK; 1382 } 1383 1384 NS_IMETHODIMP 1385 imgLoader::ClearCache(JS::Handle<JS::Value> aChrome) { 1386 nsresult rv = NS_OK; 1387 1388 Maybe<bool> chrome = 1389 aChrome.isBoolean() ? Some(aChrome.toBoolean()) : Nothing(); 1390 if (XRE_IsParentProcess()) { 1391 bool privateLoader = this == gPrivateBrowsingLoader; 1392 rv = ClearCache(Some(privateLoader), chrome, Nothing(), Nothing(), 1393 Nothing()); 1394 1395 if (this == gNormalLoader || this == gPrivateBrowsingLoader) { 1396 return rv; 1397 } 1398 1399 // NOTE: There can be other loaders created with 1400 // Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader). 1401 // If ClearCache is called on them, the above static ClearCache 1402 // doesn't handle it, and ClearImageCache needs to be called on 1403 // the current instance. 1404 // The favicon handling and some tests can create such loaders. 1405 } 1406 1407 ClearOptions options; 1408 if (chrome) { 1409 if (*chrome) { 1410 options += ClearOption::ChromeOnly; 1411 } else { 1412 options += ClearOption::ContentOnly; 1413 } 1414 } 1415 nsresult rv2 = ClearImageCache(options); 1416 1417 if (NS_FAILED(rv)) { 1418 return rv; 1419 } 1420 return rv2; 1421 } 1422 1423 /*static */ 1424 nsresult imgLoader::ClearCache( 1425 mozilla::Maybe<bool> aPrivateLoader /* = mozilla::Nothing() */, 1426 mozilla::Maybe<bool> aChrome /* = mozilla::Nothing() */, 1427 const mozilla::Maybe<nsCOMPtr<nsIPrincipal>>& 1428 aPrincipal /* = mozilla::Nothing() */, 1429 const mozilla::Maybe<nsCString>& aSchemelessSite /* = mozilla::Nothing() */, 1430 const mozilla::Maybe<mozilla::OriginAttributesPattern>& 1431 aPattern /* = mozilla::Nothing() */, 1432 const mozilla::Maybe<nsCString>& aURL /* = mozilla::Nothing() */) { 1433 if (XRE_IsParentProcess()) { 1434 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { 1435 (void)cp->SendClearImageCache(aPrivateLoader, aChrome, aPrincipal, 1436 aSchemelessSite, aPattern, aURL); 1437 } 1438 } 1439 1440 if (aPrincipal) { 1441 imgLoader* loader; 1442 if ((*aPrincipal)->OriginAttributesRef().IsPrivateBrowsing()) { 1443 loader = imgLoader::PrivateBrowsingLoader(); 1444 } else { 1445 loader = imgLoader::NormalLoader(); 1446 } 1447 1448 loader->RemoveEntriesInternal(aPrincipal, Nothing(), Nothing(), Nothing()); 1449 return NS_OK; 1450 } 1451 1452 if (aSchemelessSite) { 1453 if (!aPrivateLoader || !*aPrivateLoader) { 1454 nsresult rv = imgLoader::NormalLoader()->RemoveEntriesInternal( 1455 Nothing(), aSchemelessSite, aPattern, Nothing()); 1456 NS_ENSURE_SUCCESS(rv, rv); 1457 } 1458 if (!aPrivateLoader || *aPrivateLoader) { 1459 nsresult rv = imgLoader::PrivateBrowsingLoader()->RemoveEntriesInternal( 1460 Nothing(), aSchemelessSite, aPattern, Nothing()); 1461 NS_ENSURE_SUCCESS(rv, rv); 1462 } 1463 return NS_OK; 1464 } 1465 1466 if (aURL) { 1467 nsCOMPtr<nsIURI> uri; 1468 nsresult rv = NS_NewURI(getter_AddRefs(uri), *aURL); 1469 NS_ENSURE_SUCCESS(rv, rv); 1470 1471 if (!aPrivateLoader || !*aPrivateLoader) { 1472 nsresult rv = imgLoader::NormalLoader()->RemoveEntry(uri, nullptr); 1473 NS_ENSURE_SUCCESS(rv, rv); 1474 } 1475 if (!aPrivateLoader || *aPrivateLoader) { 1476 nsresult rv = 1477 imgLoader::PrivateBrowsingLoader()->RemoveEntry(uri, nullptr); 1478 NS_ENSURE_SUCCESS(rv, rv); 1479 } 1480 return NS_OK; 1481 } 1482 1483 ClearOptions options; 1484 if (aChrome) { 1485 if (*aChrome) { 1486 options += ClearOption::ChromeOnly; 1487 } else { 1488 options += ClearOption::ContentOnly; 1489 } 1490 } 1491 1492 if (!aPrivateLoader || !*aPrivateLoader) { 1493 nsresult rv = imgLoader::NormalLoader()->ClearImageCache(options); 1494 NS_ENSURE_SUCCESS(rv, rv); 1495 } 1496 if (!aPrivateLoader || *aPrivateLoader) { 1497 nsresult rv = imgLoader::PrivateBrowsingLoader()->ClearImageCache(options); 1498 NS_ENSURE_SUCCESS(rv, rv); 1499 } 1500 return NS_OK; 1501 } 1502 1503 NS_IMETHODIMP 1504 imgLoader::RemoveEntriesFromPrincipalInAllProcesses(nsIPrincipal* aPrincipal) { 1505 if (!XRE_IsParentProcess()) { 1506 return NS_ERROR_NOT_AVAILABLE; 1507 } 1508 1509 nsCOMPtr<nsIPrincipal> principal = aPrincipal; 1510 return ClearCache(Nothing(), Nothing(), Some(principal)); 1511 } 1512 1513 NS_IMETHODIMP 1514 imgLoader::RemoveEntriesFromSiteInAllProcesses( 1515 const nsACString& aSchemelessSite, 1516 JS::Handle<JS::Value> aOriginAttributesPattern, JSContext* aCx) { 1517 if (!XRE_IsParentProcess()) { 1518 return NS_ERROR_NOT_AVAILABLE; 1519 } 1520 1521 OriginAttributesPattern pattern; 1522 if (!aOriginAttributesPattern.isObject() || 1523 !pattern.Init(aCx, aOriginAttributesPattern)) { 1524 return NS_ERROR_INVALID_ARG; 1525 } 1526 1527 return ClearCache(Nothing(), Nothing(), Nothing(), 1528 Some(nsCString(aSchemelessSite)), Some(pattern)); 1529 } 1530 1531 nsresult imgLoader::RemoveEntriesInternal( 1532 const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal, 1533 const Maybe<nsCString>& aSchemelessSite, 1534 const Maybe<OriginAttributesPattern>& aPattern, 1535 const mozilla::Maybe<nsCString>& aURL) { 1536 // Can only clear by either principal or site + pattern. 1537 if ((!aPrincipal && !aSchemelessSite && !aURL) || 1538 (aPrincipal && aSchemelessSite) || (aPrincipal && aURL) || 1539 (aSchemelessSite && aURL) || 1540 aSchemelessSite.isSome() != aPattern.isSome()) { 1541 return NS_ERROR_INVALID_ARG; 1542 } 1543 1544 AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved; 1545 1546 // For base domain we only clear the non-chrome cache. 1547 for (const auto& entry : mCache) { 1548 const auto& key = entry.GetKey(); 1549 1550 if (SharedSubResourceCacheUtils::ShouldClearEntry( 1551 key.URI(), key.LoaderPrincipal(), key.PartitionPrincipal(), 1552 Nothing(), aPrincipal, aSchemelessSite, aPattern, aURL)) { 1553 entriesToBeRemoved.AppendElement(entry.GetData()); 1554 } 1555 } 1556 1557 for (auto& entry : entriesToBeRemoved) { 1558 if (!RemoveFromCache(entry)) { 1559 NS_WARNING( 1560 "Couldn't remove an entry from the cache in " 1561 "RemoveEntriesInternal()\n"); 1562 } 1563 } 1564 1565 return NS_OK; 1566 } 1567 1568 constexpr auto AllCORSModes() { 1569 return MakeInclusiveEnumeratedRange(kFirstCORSMode, kLastCORSMode); 1570 } 1571 1572 NS_IMETHODIMP 1573 imgLoader::RemoveEntry(nsIURI* aURI, Document* aDoc) { 1574 if (!aURI) { 1575 return NS_OK; 1576 } 1577 for (auto corsMode : AllCORSModes()) { 1578 ImageCacheKey key(aURI, corsMode, aDoc); 1579 RemoveFromCache(key); 1580 } 1581 return NS_OK; 1582 } 1583 1584 NS_IMETHODIMP 1585 imgLoader::FindEntryProperties(nsIURI* uri, Document* aDoc, 1586 nsIProperties** _retval) { 1587 *_retval = nullptr; 1588 1589 for (auto corsMode : AllCORSModes()) { 1590 ImageCacheKey key(uri, corsMode, aDoc); 1591 RefPtr<imgCacheEntry> entry; 1592 if (!mCache.Get(key, getter_AddRefs(entry)) || !entry) { 1593 continue; 1594 } 1595 if (mCacheTracker && entry->HasNoProxies()) { 1596 mCacheTracker->MarkUsed(entry); 1597 } 1598 RefPtr<imgRequest> request = entry->GetRequest(); 1599 if (request) { 1600 nsCOMPtr<nsIProperties> properties = request->Properties(); 1601 properties.forget(_retval); 1602 return NS_OK; 1603 } 1604 } 1605 return NS_OK; 1606 } 1607 1608 NS_IMETHODIMP_(void) 1609 imgLoader::ClearCacheForControlledDocument(Document* aDoc) { 1610 MOZ_ASSERT(aDoc); 1611 AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved; 1612 for (const auto& entry : mCache) { 1613 const auto& key = entry.GetKey(); 1614 if (key.ControlledDocument() == aDoc) { 1615 entriesToBeRemoved.AppendElement(entry.GetData()); 1616 } 1617 } 1618 for (auto& entry : entriesToBeRemoved) { 1619 if (!RemoveFromCache(entry)) { 1620 NS_WARNING( 1621 "Couldn't remove an entry from the cache in " 1622 "ClearCacheForControlledDocument()\n"); 1623 } 1624 } 1625 } 1626 1627 void imgLoader::Shutdown() { 1628 NS_IF_RELEASE(gNormalLoader); 1629 gNormalLoader = nullptr; 1630 NS_IF_RELEASE(gPrivateBrowsingLoader); 1631 gPrivateBrowsingLoader = nullptr; 1632 } 1633 1634 bool imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) { 1635 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::PutIntoCache", "uri", 1636 aKey.URI()); 1637 1638 // Check to see if this request already exists in the cache. If so, we'll 1639 // replace the old version. 1640 RefPtr<imgCacheEntry> tmpCacheEntry; 1641 if (mCache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) { 1642 MOZ_LOG( 1643 gImgLog, LogLevel::Debug, 1644 ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", 1645 nullptr)); 1646 RefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest(); 1647 1648 // If it already exists, and we're putting the same key into the cache, we 1649 // should remove the old version. 1650 MOZ_LOG(gImgLog, LogLevel::Debug, 1651 ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element", 1652 nullptr)); 1653 1654 RemoveFromCache(aKey); 1655 } else { 1656 MOZ_LOG(gImgLog, LogLevel::Debug, 1657 ("[this=%p] imgLoader::PutIntoCache --" 1658 " Element NOT already in the cache", 1659 nullptr)); 1660 } 1661 1662 mCache.InsertOrUpdate(aKey, RefPtr{entry}); 1663 1664 // We can be called to resurrect an evicted entry. 1665 if (entry->Evicted()) { 1666 entry->SetEvicted(false); 1667 } 1668 1669 // If we're resurrecting an entry with no proxies, put it back in the 1670 // tracker and queue. 1671 if (entry->HasNoProxies()) { 1672 nsresult addrv = NS_OK; 1673 1674 if (mCacheTracker) { 1675 addrv = mCacheTracker->AddObject(entry); 1676 } 1677 1678 if (NS_SUCCEEDED(addrv)) { 1679 mCacheQueue.Push(entry); 1680 } 1681 } 1682 1683 RefPtr<imgRequest> request = entry->GetRequest(); 1684 request->SetIsInCache(true); 1685 RemoveFromUncachedImages(request); 1686 1687 return true; 1688 } 1689 1690 bool imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry) { 1691 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasNoProxies", "uri", 1692 aRequest->CacheKey().URI()); 1693 1694 aEntry->SetHasNoProxies(true); 1695 1696 if (aEntry->Evicted()) { 1697 return false; 1698 } 1699 1700 nsresult addrv = NS_OK; 1701 1702 if (mCacheTracker) { 1703 addrv = mCacheTracker->AddObject(aEntry); 1704 } 1705 1706 if (NS_SUCCEEDED(addrv)) { 1707 mCacheQueue.Push(aEntry); 1708 } 1709 1710 return true; 1711 } 1712 1713 bool imgLoader::SetHasProxies(imgRequest* aRequest) { 1714 VerifyCacheSizes(); 1715 1716 const ImageCacheKey& key = aRequest->CacheKey(); 1717 1718 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasProxies", "uri", 1719 key.URI()); 1720 1721 RefPtr<imgCacheEntry> entry; 1722 if (mCache.Get(key, getter_AddRefs(entry)) && entry) { 1723 // Make sure the cache entry is for the right request 1724 RefPtr<imgRequest> entryRequest = entry->GetRequest(); 1725 if (entryRequest == aRequest && entry->HasNoProxies()) { 1726 mCacheQueue.Remove(entry); 1727 1728 if (mCacheTracker) { 1729 mCacheTracker->RemoveObject(entry); 1730 } 1731 1732 entry->SetHasNoProxies(false); 1733 1734 return true; 1735 } 1736 } 1737 1738 return false; 1739 } 1740 1741 void imgLoader::CacheEntriesChanged(int32_t aSizeDiff /* = 0 */) { 1742 // We only need to dirty the queue if there is any sorting 1743 // taking place. Empty or single-entry lists can't become 1744 // dirty. 1745 if (mCacheQueue.GetNumElements() > 1) { 1746 mCacheQueue.MarkDirty(); 1747 } 1748 mCacheQueue.UpdateSize(aSizeDiff); 1749 } 1750 1751 void imgLoader::CheckCacheLimits() { 1752 if (mCacheQueue.GetNumElements() == 0) { 1753 NS_ASSERTION(mCacheQueue.GetSize() == 0, 1754 "imgLoader::CheckCacheLimits -- incorrect cache size"); 1755 } 1756 1757 // Remove entries from the cache until we're back at our desired max size. 1758 while (mCacheQueue.GetSize() > sCacheMaxSize) { 1759 // Remove the first entry in the queue. 1760 RefPtr<imgCacheEntry> entry(mCacheQueue.Pop()); 1761 1762 NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer"); 1763 1764 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { 1765 RefPtr<imgRequest> req = entry->GetRequest(); 1766 if (req) { 1767 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::CheckCacheLimits", 1768 "entry", req->CacheKey().URI()); 1769 } 1770 } 1771 1772 if (entry) { 1773 // We just popped this entry from the queue, so pass AlreadyRemoved 1774 // to avoid searching the queue again in RemoveFromCache. 1775 RemoveFromCache(entry, QueueState::AlreadyRemoved); 1776 } 1777 } 1778 } 1779 1780 bool imgLoader::ValidateRequestWithNewChannel( 1781 imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI, 1782 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup, 1783 imgINotificationObserver* aObserver, Document* aLoadingDocument, 1784 uint64_t aInnerWindowId, nsLoadFlags aLoadFlags, 1785 nsContentPolicyType aLoadPolicyType, imgRequestProxy** aProxyRequest, 1786 nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode, bool aLinkPreload, 1787 uint64_t aEarlyHintPreloaderId, FetchPriority aFetchPriority, 1788 bool* aNewChannelCreated) { 1789 // now we need to insert a new channel request object in between the real 1790 // request and the proxy that basically delays loading the image until it 1791 // gets a 304 or figures out that this needs to be a new request 1792 1793 nsresult rv; 1794 1795 // If we're currently in the middle of validating this request, just hand 1796 // back a proxy to it; the required work will be done for us. 1797 if (imgCacheValidator* validator = request->GetValidator()) { 1798 rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument, 1799 aObserver, aLoadFlags, aProxyRequest); 1800 if (NS_FAILED(rv)) { 1801 return false; 1802 } 1803 1804 if (*aProxyRequest) { 1805 imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest); 1806 1807 // We will send notifications from imgCacheValidator::OnStartRequest(). 1808 // In the mean time, we must defer notifications because we are added to 1809 // the imgRequest's proxy list, and we can get extra notifications 1810 // resulting from methods such as StartDecoding(). See bug 579122. 1811 proxy->MarkValidating(); 1812 1813 if (aLinkPreload) { 1814 MOZ_ASSERT(aLoadingDocument); 1815 auto preloadKey = PreloadHashKey::CreateAsImage( 1816 aURI, aTriggeringPrincipal, aCORSMode); 1817 proxy->NotifyOpen(preloadKey, aLoadingDocument, true); 1818 } 1819 1820 // Attach the proxy without notifying 1821 validator->AddProxy(proxy); 1822 } 1823 1824 return true; 1825 } 1826 // We will rely on Necko to cache this request when it's possible, and to 1827 // tell imgCacheValidator::OnStartRequest whether the request came from its 1828 // cache. 1829 nsCOMPtr<nsIChannel> newChannel; 1830 bool forcePrincipalCheck; 1831 rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI, 1832 aInitialDocumentURI, aCORSMode, aReferrerInfo, 1833 aLoadGroup, aLoadFlags, aLoadPolicyType, 1834 aTriggeringPrincipal, aLoadingDocument, mRespectPrivacy, 1835 aEarlyHintPreloaderId, aFetchPriority); 1836 if (NS_FAILED(rv)) { 1837 return false; 1838 } 1839 1840 if (aNewChannelCreated) { 1841 *aNewChannelCreated = true; 1842 } 1843 1844 RefPtr<imgRequestProxy> req; 1845 rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument, 1846 aObserver, aLoadFlags, getter_AddRefs(req)); 1847 if (NS_FAILED(rv)) { 1848 return false; 1849 } 1850 1851 // Make sure that OnStatus/OnProgress calls have the right request set... 1852 RefPtr<nsProgressNotificationProxy> progressproxy = 1853 new nsProgressNotificationProxy(newChannel, req); 1854 if (!progressproxy) { 1855 return false; 1856 } 1857 1858 RefPtr<imgCacheValidator> hvc = 1859 new imgCacheValidator(progressproxy, this, request, aLoadingDocument, 1860 aInnerWindowId, forcePrincipalCheck); 1861 1862 // Casting needed here to get past multiple inheritance. 1863 nsCOMPtr<nsIStreamListener> listener = 1864 static_cast<nsIThreadRetargetableStreamListener*>(hvc); 1865 NS_ENSURE_TRUE(listener, false); 1866 1867 // We must set the notification callbacks before setting up the 1868 // CORS listener, because that's also interested inthe 1869 // notification callbacks. 1870 newChannel->SetNotificationCallbacks(hvc); 1871 1872 request->SetValidator(hvc); 1873 1874 // We will send notifications from imgCacheValidator::OnStartRequest(). 1875 // In the mean time, we must defer notifications because we are added to 1876 // the imgRequest's proxy list, and we can get extra notifications 1877 // resulting from methods such as StartDecoding(). See bug 579122. 1878 req->MarkValidating(); 1879 1880 if (aLinkPreload) { 1881 MOZ_ASSERT(aLoadingDocument); 1882 auto preloadKey = 1883 PreloadHashKey::CreateAsImage(aURI, aTriggeringPrincipal, aCORSMode); 1884 req->NotifyOpen(preloadKey, aLoadingDocument, true); 1885 } 1886 1887 // Add the proxy without notifying 1888 hvc->AddProxy(req); 1889 1890 rv = newChannel->AsyncOpen(listener); 1891 if (NS_WARN_IF(NS_FAILED(rv))) { 1892 req->CancelAndForgetObserver(rv); 1893 // This will notify any current or future <link preload> tags. Pass the 1894 // non-open channel so that we can read loadinfo and referrer info of that 1895 // channel. 1896 req->NotifyStart(newChannel); 1897 // Use the non-channel overload of this method to force the notification to 1898 // happen. The preload request has not been assigned a channel. 1899 req->NotifyStop(rv); 1900 return false; 1901 } 1902 1903 req.forget(aProxyRequest); 1904 return true; 1905 } 1906 1907 void imgLoader::NotifyObserversForCachedImage( 1908 imgCacheEntry* aEntry, imgRequest* request, nsIURI* aURI, 1909 nsIReferrerInfo* aReferrerInfo, Document* aLoadingDocument, 1910 nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode, 1911 uint64_t aEarlyHintPreloaderId, FetchPriority aFetchPriority) { 1912 if (aEntry->HasNotified()) { 1913 return; 1914 } 1915 1916 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 1917 1918 if (!obsService->HasObservers("http-on-resource-cache-response")) { 1919 return; 1920 } 1921 1922 aEntry->SetHasNotified(); 1923 1924 nsCOMPtr<nsIChannel> newChannel; 1925 bool forcePrincipalCheck; 1926 nsresult rv = NewImageChannel( 1927 getter_AddRefs(newChannel), &forcePrincipalCheck, aURI, nullptr, 1928 aCORSMode, aReferrerInfo, nullptr, 0, 1929 nsIContentPolicy::TYPE_INTERNAL_IMAGE, aTriggeringPrincipal, 1930 aLoadingDocument, mRespectPrivacy, aEarlyHintPreloaderId, aFetchPriority); 1931 if (NS_FAILED(rv)) { 1932 return; 1933 } 1934 1935 RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(newChannel); 1936 if (httpBaseChannel) { 1937 httpBaseChannel->SetDummyChannelForCachedResource(); 1938 newChannel->SetContentType(nsDependentCString(request->GetMimeType())); 1939 RefPtr<mozilla::image::Image> image = request->GetImage(); 1940 if (image) { 1941 newChannel->SetContentLength(aEntry->GetDataSize()); 1942 } 1943 obsService->NotifyObservers(newChannel, "http-on-resource-cache-response", 1944 nullptr); 1945 } 1946 } 1947 1948 bool imgLoader::ValidateEntry( 1949 imgCacheEntry* aEntry, nsIURI* aURI, nsIURI* aInitialDocumentURI, 1950 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup, 1951 imgINotificationObserver* aObserver, Document* aLoadingDocument, 1952 nsLoadFlags aLoadFlags, nsContentPolicyType aLoadPolicyType, 1953 bool aCanMakeNewChannel, bool* aNewChannelCreated, 1954 imgRequestProxy** aProxyRequest, nsIPrincipal* aTriggeringPrincipal, 1955 CORSMode aCORSMode, bool aLinkPreload, uint64_t aEarlyHintPreloaderId, 1956 FetchPriority aFetchPriority) { 1957 LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry"); 1958 1959 // If the expiration time is zero, then the request has not gotten far enough 1960 // to know when it will expire, or we know it will never expire (see 1961 // nsContentUtils::GetSubresourceCacheValidationInfo). 1962 bool hasExpired = aEntry->GetExpiryTime().IsExpired(); 1963 1964 // Special treatment for file URLs - aEntry has expired if file has changed 1965 if (nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI)) { 1966 uint32_t lastModTime = aEntry->GetLoadTime(); 1967 nsCOMPtr<nsIFile> theFile; 1968 if (NS_SUCCEEDED(fileUrl->GetFile(getter_AddRefs(theFile)))) { 1969 PRTime fileLastMod; 1970 if (NS_SUCCEEDED(theFile->GetLastModifiedTime(&fileLastMod))) { 1971 // nsIFile uses millisec, NSPR usec. 1972 fileLastMod *= 1000; 1973 hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime; 1974 } 1975 } 1976 } 1977 1978 RefPtr<imgRequest> request(aEntry->GetRequest()); 1979 1980 if (!request) { 1981 return false; 1982 } 1983 1984 if (!ValidateSecurityInfo(request, aEntry->ForcePrincipalCheck(), aCORSMode, 1985 aTriggeringPrincipal, aLoadingDocument, 1986 aLoadPolicyType)) { 1987 return false; 1988 } 1989 1990 // data URIs are immutable and by their nature can't leak data, so we can 1991 // just return true in that case. Doing so would mean that shift-reload 1992 // doesn't reload data URI documents/images though (which is handy for 1993 // debugging during gecko development) so we make an exception in that case. 1994 if (aURI->SchemeIs("data") && !(aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)) { 1995 return true; 1996 } 1997 1998 bool validateRequest = false; 1999 2000 if (!request->CanReuseWithoutValidation(aLoadingDocument)) { 2001 // If we would need to revalidate this entry, but we're being told to 2002 // bypass the cache, we don't allow this entry to be used. 2003 if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) { 2004 return false; 2005 } 2006 2007 if (MOZ_UNLIKELY(ChaosMode::isActive(ChaosFeature::ImageCache))) { 2008 if (ChaosMode::randomUint32LessThan(4) < 1) { 2009 return false; 2010 } 2011 } 2012 2013 // Determine whether the cache aEntry must be revalidated... 2014 validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired); 2015 2016 MOZ_LOG(gImgLog, LogLevel::Debug, 2017 ("imgLoader::ValidateEntry validating cache entry. " 2018 "validateRequest = %d", 2019 validateRequest)); 2020 } else if (!aLoadingDocument && MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { 2021 MOZ_LOG(gImgLog, LogLevel::Debug, 2022 ("imgLoader::ValidateEntry BYPASSING cache validation for %s " 2023 "because of NULL loading document", 2024 aURI->GetSpecOrDefault().get())); 2025 } 2026 2027 // If the original request is still transferring don't kick off a validation 2028 // network request because it is a bit silly to issue a validation request if 2029 // the original request hasn't even finished yet. So just return true 2030 // indicating the caller can create a new proxy for the request and use it as 2031 // is. 2032 // This is an optimization but it's also required for correctness. If we don't 2033 // do this then when firing the load complete notification for the original 2034 // request that can unblock load for the document and then spin the event loop 2035 // (see the stack in bug 1641682) which then the OnStartRequest for the 2036 // validation request can fire which can call UpdateProxies and can sync 2037 // notify on the progress tracker about all existing state, which includes 2038 // load complete, so we fire a second load complete notification for the 2039 // image. 2040 // In addition, we want to validate if the original request encountered 2041 // an error for two reasons. The first being if the error was a network error 2042 // then trying to re-fetch the image might succeed. The second is more 2043 // complicated. We decide if we should fire the load or error event for img 2044 // elements depending on if the image has error in its status at the time when 2045 // the load complete notification is received, and we set error status on an 2046 // image if it encounters a network error or a decode error with no real way 2047 // to tell them apart. So if we load an image that will produce a decode error 2048 // the first time we will usually fire the load event, and then decode enough 2049 // to encounter the decode error and set the error status on the image. The 2050 // next time we reference the image in the same document the load complete 2051 // notification is replayed and this time the error status from the decode is 2052 // already present so we fire the error event instead of the load event. This 2053 // is a bug (bug 1645576) that we should fix. In order to avoid that bug in 2054 // some cases (specifically the cases when we hit this code and try to 2055 // validate the request) we make sure to validate. This avoids the bug because 2056 // when the load complete notification arrives the proxy is marked as 2057 // validating so it lies about its status and returns nothing. 2058 const bool requestComplete = [&] { 2059 RefPtr<ProgressTracker> tracker; 2060 RefPtr<mozilla::image::Image> image = request->GetImage(); 2061 if (image) { 2062 tracker = image->GetProgressTracker(); 2063 } else { 2064 tracker = request->GetProgressTracker(); 2065 } 2066 return tracker && 2067 tracker->GetProgress() & (FLAG_LOAD_COMPLETE | FLAG_HAS_ERROR); 2068 }(); 2069 2070 if (!requestComplete) { 2071 return true; 2072 } 2073 2074 if (validateRequest && aCanMakeNewChannel) { 2075 LOG_SCOPE(gImgLog, "imgLoader::ValidateRequest |cache hit| must validate"); 2076 2077 uint64_t innerWindowID = 2078 aLoadingDocument ? aLoadingDocument->InnerWindowID() : 0; 2079 return ValidateRequestWithNewChannel( 2080 request, aURI, aInitialDocumentURI, aReferrerInfo, aLoadGroup, 2081 aObserver, aLoadingDocument, innerWindowID, aLoadFlags, aLoadPolicyType, 2082 aProxyRequest, aTriggeringPrincipal, aCORSMode, aLinkPreload, 2083 aEarlyHintPreloaderId, aFetchPriority, aNewChannelCreated); 2084 } 2085 2086 if (!validateRequest) { 2087 NotifyObserversForCachedImage( 2088 aEntry, request, aURI, aReferrerInfo, aLoadingDocument, 2089 aTriggeringPrincipal, aCORSMode, aEarlyHintPreloaderId, aFetchPriority); 2090 } 2091 2092 return !validateRequest; 2093 } 2094 2095 bool imgLoader::RemoveFromCache(const ImageCacheKey& aKey) { 2096 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache", "uri", 2097 aKey.URI()); 2098 RefPtr<imgCacheEntry> entry; 2099 mCache.Remove(aKey, getter_AddRefs(entry)); 2100 if (entry) { 2101 MOZ_ASSERT(!entry->Evicted(), "Evicting an already-evicted cache entry!"); 2102 2103 // Entries with no proxies are in the tracker. 2104 if (entry->HasNoProxies()) { 2105 if (mCacheTracker) { 2106 mCacheTracker->RemoveObject(entry); 2107 } 2108 mCacheQueue.Remove(entry); 2109 } 2110 2111 entry->SetEvicted(true); 2112 2113 RefPtr<imgRequest> request = entry->GetRequest(); 2114 request->SetIsInCache(false); 2115 AddToUncachedImages(request); 2116 2117 return true; 2118 } 2119 return false; 2120 } 2121 2122 bool imgLoader::RemoveFromCache(imgCacheEntry* entry, QueueState aQueueState) { 2123 LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry"); 2124 2125 RefPtr<imgRequest> request = entry->GetRequest(); 2126 if (request) { 2127 const ImageCacheKey& key = request->CacheKey(); 2128 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache", 2129 "entry's uri", key.URI()); 2130 2131 mCache.Remove(key); 2132 2133 if (entry->HasNoProxies()) { 2134 LOG_STATIC_FUNC(gImgLog, 2135 "imgLoader::RemoveFromCache removing from tracker"); 2136 if (mCacheTracker) { 2137 mCacheTracker->RemoveObject(entry); 2138 } 2139 // Only search the queue to remove the entry if its possible it might 2140 // be in the queue. If we know its not in the queue this would be 2141 // wasted work. 2142 MOZ_ASSERT_IF(aQueueState == QueueState::AlreadyRemoved, 2143 !mCacheQueue.Contains(entry)); 2144 if (aQueueState == QueueState::MaybeExists) { 2145 mCacheQueue.Remove(entry); 2146 } 2147 } 2148 2149 entry->SetEvicted(true); 2150 request->SetIsInCache(false); 2151 AddToUncachedImages(request); 2152 2153 return true; 2154 } 2155 2156 return false; 2157 } 2158 2159 nsresult imgLoader::ClearImageCache(ClearOptions aOptions) { 2160 const bool chromeOnly = aOptions.contains(ClearOption::ChromeOnly); 2161 const bool contentOnly = aOptions.contains(ClearOption::ContentOnly); 2162 const auto ShouldRemove = [&](imgCacheEntry* aEntry) { 2163 if (chromeOnly || contentOnly) { 2164 RefPtr<imgRequest> request = aEntry->GetRequest(); 2165 if (!request) { 2166 return false; 2167 } 2168 nsIURI* uri = request->CacheKey().URI(); 2169 bool isChrome = uri->SchemeIs("chrome") || uri->SchemeIs("resource"); 2170 if (chromeOnly && !isChrome) { 2171 return false; 2172 } 2173 if (contentOnly && isChrome) { 2174 return false; 2175 } 2176 } 2177 return true; 2178 }; 2179 if (aOptions.contains(ClearOption::UnusedOnly)) { 2180 LOG_STATIC_FUNC(gImgLog, "imgLoader::ClearImageCache queue"); 2181 // We have to make a temporary, since RemoveFromCache removes the element 2182 // from the queue, invalidating iterators. 2183 nsTArray<RefPtr<imgCacheEntry>> entries(mCacheQueue.GetNumElements()); 2184 for (auto& entry : mCacheQueue) { 2185 if (ShouldRemove(entry)) { 2186 entries.AppendElement(entry); 2187 } 2188 } 2189 2190 // Iterate in reverse order to minimize array copying. 2191 for (auto& entry : entries) { 2192 if (!RemoveFromCache(entry)) { 2193 return NS_ERROR_FAILURE; 2194 } 2195 } 2196 2197 MOZ_ASSERT(chromeOnly || contentOnly || mCacheQueue.GetNumElements() == 0); 2198 return NS_OK; 2199 } 2200 2201 LOG_STATIC_FUNC(gImgLog, "imgLoader::ClearImageCache table"); 2202 // We have to make a temporary, since RemoveFromCache removes the element 2203 // from the queue, invalidating iterators. 2204 const auto entries = 2205 ToTArray<nsTArray<RefPtr<imgCacheEntry>>>(mCache.Values()); 2206 for (const auto& entry : entries) { 2207 if (!ShouldRemove(entry)) { 2208 continue; 2209 } 2210 if (!RemoveFromCache(entry)) { 2211 return NS_ERROR_FAILURE; 2212 } 2213 } 2214 MOZ_ASSERT(chromeOnly || contentOnly || mCache.IsEmpty()); 2215 return NS_OK; 2216 } 2217 2218 void imgLoader::AddToUncachedImages(imgRequest* aRequest) { 2219 MutexAutoLock lock(mUncachedImagesMutex); 2220 mUncachedImages.Insert(aRequest); 2221 } 2222 2223 void imgLoader::RemoveFromUncachedImages(imgRequest* aRequest) { 2224 MutexAutoLock lock(mUncachedImagesMutex); 2225 mUncachedImages.Remove(aRequest); 2226 } 2227 2228 #define LOAD_FLAGS_CACHE_MASK \ 2229 (nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FROM_CACHE) 2230 2231 #define LOAD_FLAGS_VALIDATE_MASK \ 2232 (nsIRequest::VALIDATE_ALWAYS | nsIRequest::VALIDATE_NEVER | \ 2233 nsIRequest::VALIDATE_ONCE_PER_SESSION) 2234 2235 NS_IMETHODIMP 2236 imgLoader::LoadImageXPCOM( 2237 nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo, 2238 nsIPrincipal* aTriggeringPrincipal, nsILoadGroup* aLoadGroup, 2239 imgINotificationObserver* aObserver, Document* aLoadingDocument, 2240 nsLoadFlags aLoadFlags, nsISupports* aCacheKey, 2241 nsContentPolicyType aContentPolicyType, imgIRequest** _retval) { 2242 // Optional parameter, so defaults to 0 (== TYPE_INVALID) 2243 if (!aContentPolicyType) { 2244 aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE; 2245 } 2246 imgRequestProxy* proxy; 2247 nsresult rv = 2248 LoadImage(aURI, aInitialDocumentURI, aReferrerInfo, aTriggeringPrincipal, 2249 0, aLoadGroup, aObserver, aLoadingDocument, aLoadingDocument, 2250 aLoadFlags, aCacheKey, aContentPolicyType, u""_ns, 2251 /* aUseUrgentStartForChannel */ false, /* aListPreload */ false, 2252 0, FetchPriority::Auto, &proxy); 2253 *_retval = proxy; 2254 return rv; 2255 } 2256 2257 static void MakeRequestStaticIfNeeded( 2258 Document* aLoadingDocument, imgRequestProxy** aProxyAboutToGetReturned) { 2259 if (!aLoadingDocument || !aLoadingDocument->IsStaticDocument()) { 2260 return; 2261 } 2262 2263 if (!*aProxyAboutToGetReturned) { 2264 return; 2265 } 2266 2267 RefPtr<imgRequestProxy> proxy = dont_AddRef(*aProxyAboutToGetReturned); 2268 *aProxyAboutToGetReturned = nullptr; 2269 2270 RefPtr<imgRequestProxy> staticProxy = 2271 proxy->GetStaticRequest(aLoadingDocument); 2272 if (staticProxy != proxy) { 2273 proxy->CancelAndForgetObserver(NS_BINDING_ABORTED); 2274 proxy = std::move(staticProxy); 2275 } 2276 proxy.forget(aProxyAboutToGetReturned); 2277 } 2278 2279 bool imgLoader::IsImageAvailable(nsIURI* aURI, 2280 nsIPrincipal* aTriggeringPrincipal, 2281 CORSMode aCORSMode, Document* aDocument) { 2282 ImageCacheKey key(aURI, aCORSMode, aDocument); 2283 RefPtr<imgCacheEntry> entry; 2284 if (!mCache.Get(key, getter_AddRefs(entry)) || !entry) { 2285 return false; 2286 } 2287 RefPtr<imgRequest> request = entry->GetRequest(); 2288 if (!request) { 2289 return false; 2290 } 2291 if (nsCOMPtr<nsILoadGroup> docLoadGroup = aDocument->GetDocumentLoadGroup()) { 2292 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; 2293 docLoadGroup->GetLoadFlags(&requestFlags); 2294 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { 2295 // If we're bypassing the cache, treat the image as not available. 2296 return false; 2297 } 2298 } 2299 return ValidateCORSMode(request, false, aCORSMode, aTriggeringPrincipal); 2300 } 2301 2302 nsresult imgLoader::LoadImage( 2303 nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo, 2304 nsIPrincipal* aTriggeringPrincipal, uint64_t aRequestContextID, 2305 nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver, 2306 nsINode* aContext, Document* aLoadingDocument, nsLoadFlags aLoadFlags, 2307 nsISupports* aCacheKey, nsContentPolicyType aContentPolicyType, 2308 const nsAString& initiatorType, bool aUseUrgentStartForChannel, 2309 bool aLinkPreload, uint64_t aEarlyHintPreloaderId, 2310 FetchPriority aFetchPriority, imgRequestProxy** _retval) { 2311 VerifyCacheSizes(); 2312 2313 NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer"); 2314 2315 if (!aURI) { 2316 return NS_ERROR_NULL_POINTER; 2317 } 2318 2319 auto makeStaticIfNeeded = mozilla::MakeScopeExit( 2320 [&] { MakeRequestStaticIfNeeded(aLoadingDocument, _retval); }); 2321 2322 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("imgLoader::LoadImage", NETWORK, 2323 aURI->GetSpecOrDefault()); 2324 2325 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", aURI); 2326 2327 *_retval = nullptr; 2328 2329 RefPtr<imgRequest> request; 2330 2331 nsresult rv; 2332 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; 2333 2334 #ifdef DEBUG 2335 bool isPrivate = false; 2336 2337 if (aLoadingDocument) { 2338 isPrivate = aLoadingDocument->IsInPrivateBrowsing(); 2339 } else if (aLoadGroup) { 2340 isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadGroup); 2341 } 2342 MOZ_ASSERT(isPrivate == mRespectPrivacy); 2343 2344 if (aLoadingDocument) { 2345 // The given load group should match that of the document if given. If 2346 // that isn't the case, then we need to add more plumbing to ensure we 2347 // block the document as well. 2348 nsCOMPtr<nsILoadGroup> docLoadGroup = 2349 aLoadingDocument->GetDocumentLoadGroup(); 2350 MOZ_ASSERT(docLoadGroup == aLoadGroup); 2351 } 2352 #endif 2353 2354 // Get the default load flags from the loadgroup (if possible)... 2355 if (aLoadGroup) { 2356 aLoadGroup->GetLoadFlags(&requestFlags); 2357 } 2358 // 2359 // Merge the default load flags with those passed in via aLoadFlags. 2360 // Currently, *only* the caching, validation and background load flags 2361 // are merged... 2362 // 2363 // The flags in aLoadFlags take precedence over the default flags! 2364 // 2365 if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) { 2366 // Override the default caching flags... 2367 requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) | 2368 (aLoadFlags & LOAD_FLAGS_CACHE_MASK); 2369 } 2370 if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) { 2371 // Override the default validation flags... 2372 requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) | 2373 (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK); 2374 } 2375 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { 2376 // Propagate background loading... 2377 requestFlags |= nsIRequest::LOAD_BACKGROUND; 2378 } 2379 2380 if (aLinkPreload) { 2381 // Set background loading if it is <link rel=preload> 2382 requestFlags |= nsIRequest::LOAD_BACKGROUND; 2383 } 2384 2385 CORSMode corsmode = CORS_NONE; 2386 if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) { 2387 corsmode = CORS_ANONYMOUS; 2388 } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) { 2389 corsmode = CORS_USE_CREDENTIALS; 2390 } 2391 2392 // Look in the preloaded images of loading document first. 2393 if (!aLinkPreload && aLoadingDocument) { 2394 // All Early Hints preloads are Link preloads, therefore we don't have a 2395 // Early Hints preload here 2396 MOZ_ASSERT(!aEarlyHintPreloaderId); 2397 auto key = 2398 PreloadHashKey::CreateAsImage(aURI, aTriggeringPrincipal, corsmode); 2399 if (RefPtr<PreloaderBase> preload = 2400 aLoadingDocument->Preloads().LookupPreload(key)) { 2401 RefPtr<imgRequestProxy> proxy = do_QueryObject(preload); 2402 MOZ_ASSERT(proxy); 2403 2404 MOZ_LOG(gImgLog, LogLevel::Debug, 2405 ("[this=%p] imgLoader::LoadImage -- preloaded [proxy=%p]" 2406 " [document=%p]\n", 2407 this, proxy.get(), aLoadingDocument)); 2408 2409 // Removing the preload for this image to be in parity with Chromium. Any 2410 // following regular image request will be reloaded using the regular 2411 // path: image cache, http cache, network. Any following `<link 2412 // rel=preload as=image>` will start a new image preload that can be 2413 // satisfied from http cache or network. 2414 // 2415 // There is a spec discussion for "preload cache", see 2416 // https://github.com/w3c/preload/issues/97. And it is also not clear how 2417 // preload image interacts with list of available images, see 2418 // https://github.com/whatwg/html/issues/4474. 2419 proxy->RemoveSelf(aLoadingDocument); 2420 proxy->NotifyUsage(aLoadingDocument); 2421 2422 imgRequest* request = proxy->GetOwner(); 2423 nsresult rv = 2424 CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument, 2425 aObserver, requestFlags, _retval); 2426 NS_ENSURE_SUCCESS(rv, rv); 2427 2428 imgRequestProxy* newProxy = *_retval; 2429 if (imgCacheValidator* validator = request->GetValidator()) { 2430 newProxy->MarkValidating(); 2431 // Attach the proxy without notifying and this will add us to the load 2432 // group. 2433 validator->AddProxy(newProxy); 2434 } else { 2435 // It's OK to add here even if the request is done. If it is, it'll send 2436 // a OnStopRequest()and the proxy will be removed from the loadgroup in 2437 // imgRequestProxy::OnLoadComplete. 2438 newProxy->AddToLoadGroup(); 2439 newProxy->NotifyListener(); 2440 } 2441 2442 return NS_OK; 2443 } 2444 } 2445 2446 RefPtr<imgCacheEntry> entry; 2447 2448 // Look in the cache for our URI, and then validate it. 2449 // XXX For now ignore aCacheKey. We will need it in the future 2450 // for correctly dealing with image load requests that are a result 2451 // of post data. 2452 ImageCacheKey key(aURI, corsmode, aLoadingDocument); 2453 if (mCache.Get(key, getter_AddRefs(entry)) && entry) { 2454 bool newChannelCreated = false; 2455 if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerInfo, 2456 aLoadGroup, aObserver, aLoadingDocument, requestFlags, 2457 aContentPolicyType, true, &newChannelCreated, _retval, 2458 aTriggeringPrincipal, corsmode, aLinkPreload, 2459 aEarlyHintPreloaderId, aFetchPriority)) { 2460 request = entry->GetRequest(); 2461 2462 // If this entry has no proxies, its request has no reference to the 2463 // entry. 2464 if (entry->HasNoProxies()) { 2465 LOG_FUNC_WITH_PARAM(gImgLog, 2466 "imgLoader::LoadImage() adding proxyless entry", 2467 "uri", key.URI()); 2468 MOZ_ASSERT(!request->HasCacheEntry(), 2469 "Proxyless entry's request has cache entry!"); 2470 request->SetCacheEntry(entry); 2471 2472 if (mCacheTracker && entry->GetExpirationState()->IsTracked()) { 2473 mCacheTracker->MarkUsed(entry); 2474 } 2475 } 2476 2477 entry->Touch(); 2478 2479 if (!newChannelCreated) { 2480 // This is ugly but it's needed to report CSP violations. We have 3 2481 // scenarios: 2482 // - we don't have cache. We are not in this if() stmt. A new channel is 2483 // created and that triggers the CSP checks. 2484 // - We have a cache entry and this is blocked by CSP directives. 2485 DebugOnly<bool> shouldLoad = ShouldLoadCachedImage( 2486 request, aLoadingDocument, aTriggeringPrincipal, aContentPolicyType, 2487 /* aSendCSPViolationReports */ true); 2488 MOZ_ASSERT(shouldLoad); 2489 } 2490 } else { 2491 // We can't use this entry. We'll try to load it off the network, and if 2492 // successful, overwrite the old entry in the cache with a new one. 2493 entry = nullptr; 2494 } 2495 } 2496 2497 // Keep the channel in this scope, so we can adjust its notificationCallbacks 2498 // later when we create the proxy. 2499 nsCOMPtr<nsIChannel> newChannel; 2500 // If we didn't get a cache hit, we need to load from the network. 2501 if (!request) { 2502 LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|"); 2503 2504 bool forcePrincipalCheck; 2505 rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI, 2506 aInitialDocumentURI, corsmode, aReferrerInfo, 2507 aLoadGroup, requestFlags, aContentPolicyType, 2508 aTriggeringPrincipal, aContext, mRespectPrivacy, 2509 aEarlyHintPreloaderId, aFetchPriority); 2510 if (NS_FAILED(rv)) { 2511 return NS_ERROR_FAILURE; 2512 } 2513 2514 MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy); 2515 2516 NewRequestAndEntry(forcePrincipalCheck, this, key, getter_AddRefs(request), 2517 getter_AddRefs(entry)); 2518 2519 MOZ_LOG(gImgLog, LogLevel::Debug, 2520 ("[this=%p] imgLoader::LoadImage -- Created new imgRequest" 2521 " [request=%p]\n", 2522 this, request.get())); 2523 2524 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel)); 2525 if (cos) { 2526 if (aUseUrgentStartForChannel && !aLinkPreload) { 2527 cos->AddClassFlags(nsIClassOfService::UrgentStart); 2528 } 2529 if (StaticPrefs::image_priority_incremental()) { 2530 cos->SetIncremental(true); 2531 } 2532 2533 if (StaticPrefs::network_http_tailing_enabled() && 2534 aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) { 2535 cos->AddClassFlags(nsIClassOfService::Throttleable | 2536 nsIClassOfService::Tail); 2537 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel)); 2538 if (httpChannel) { 2539 (void)httpChannel->SetRequestContextID(aRequestContextID); 2540 } 2541 } 2542 } 2543 2544 nsCOMPtr<nsILoadGroup> channelLoadGroup; 2545 newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup)); 2546 rv = request->Init(aURI, aURI, /* aHadInsecureRedirect = */ false, 2547 channelLoadGroup, newChannel, entry, aLoadingDocument, 2548 aTriggeringPrincipal, corsmode, aReferrerInfo); 2549 if (NS_FAILED(rv)) { 2550 return NS_ERROR_FAILURE; 2551 } 2552 2553 // Add the initiator type for this image load 2554 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel); 2555 if (timedChannel) { 2556 timedChannel->SetInitiatorType(initiatorType); 2557 } 2558 2559 // create the proxy listener 2560 nsCOMPtr<nsIStreamListener> listener = new ProxyListener(request.get()); 2561 2562 MOZ_LOG(gImgLog, LogLevel::Debug, 2563 ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", 2564 this)); 2565 2566 nsresult openRes; 2567 openRes = newChannel->AsyncOpen(listener); 2568 2569 if (NS_FAILED(openRes)) { 2570 MOZ_LOG( 2571 gImgLog, LogLevel::Debug, 2572 ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%" PRIx32 2573 "\n", 2574 this, static_cast<uint32_t>(openRes))); 2575 request->CancelAndAbort(openRes); 2576 return openRes; 2577 } 2578 2579 // Try to add the new request into the cache. 2580 PutIntoCache(key, entry); 2581 } else { 2582 LOG_MSG_WITH_PARAM(gImgLog, "imgLoader::LoadImage |cache hit|", "request", 2583 request); 2584 } 2585 2586 // If we didn't get a proxy when validating the cache entry, we need to 2587 // create one. 2588 if (!*_retval) { 2589 // ValidateEntry() has three return values: "Is valid," "might be valid -- 2590 // validating over network", and "not valid." If we don't have a _retval, 2591 // we know ValidateEntry is not validating over the network, so it's safe 2592 // to SetLoadId here because we know this request is valid for this context. 2593 // 2594 // Note, however, that this doesn't guarantee the behaviour we want (one 2595 // URL maps to the same image on a page) if we load the same image in a 2596 // different tab (see bug 528003), because its load id will get re-set, and 2597 // that'll cause us to validate over the network. 2598 request->SetLoadId(aLoadingDocument); 2599 2600 LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request."); 2601 rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument, 2602 aObserver, requestFlags, _retval); 2603 if (NS_FAILED(rv)) { 2604 return rv; 2605 } 2606 2607 imgRequestProxy* proxy = *_retval; 2608 2609 // Make sure that OnStatus/OnProgress calls have the right request set, if 2610 // we did create a channel here. 2611 if (newChannel) { 2612 nsCOMPtr<nsIInterfaceRequestor> requestor( 2613 new nsProgressNotificationProxy(newChannel, proxy)); 2614 if (!requestor) { 2615 return NS_ERROR_OUT_OF_MEMORY; 2616 } 2617 newChannel->SetNotificationCallbacks(requestor); 2618 } 2619 2620 if (aLinkPreload) { 2621 MOZ_ASSERT(aLoadingDocument); 2622 auto preloadKey = 2623 PreloadHashKey::CreateAsImage(aURI, aTriggeringPrincipal, corsmode); 2624 proxy->NotifyOpen(preloadKey, aLoadingDocument, true); 2625 } 2626 2627 // Note that it's OK to add here even if the request is done. If it is, 2628 // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and 2629 // the proxy will be removed from the loadgroup. 2630 proxy->AddToLoadGroup(); 2631 2632 // If we're loading off the network, explicitly don't notify our proxy, 2633 // because necko (or things called from necko, such as imgCacheValidator) 2634 // are going to call our notifications asynchronously, and we can't make it 2635 // further asynchronous because observers might rely on imagelib completing 2636 // its work between the channel's OnStartRequest and OnStopRequest. 2637 if (!newChannel) { 2638 proxy->NotifyListener(); 2639 } 2640 2641 return rv; 2642 } 2643 2644 NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value"); 2645 2646 return NS_OK; 2647 } 2648 2649 NS_IMETHODIMP 2650 imgLoader::LoadImageWithChannelXPCOM(nsIChannel* channel, 2651 imgINotificationObserver* aObserver, 2652 Document* aLoadingDocument, 2653 nsIStreamListener** listener, 2654 imgIRequest** _retval) { 2655 nsresult result; 2656 imgRequestProxy* proxy; 2657 result = LoadImageWithChannel(channel, aObserver, aLoadingDocument, listener, 2658 &proxy); 2659 *_retval = proxy; 2660 return result; 2661 } 2662 2663 nsresult imgLoader::LoadImageWithChannel(nsIChannel* channel, 2664 imgINotificationObserver* aObserver, 2665 Document* aLoadingDocument, 2666 nsIStreamListener** listener, 2667 imgRequestProxy** _retval) { 2668 NS_ASSERTION(channel, 2669 "imgLoader::LoadImageWithChannel -- NULL channel pointer"); 2670 2671 MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy); 2672 2673 auto makeStaticIfNeeded = mozilla::MakeScopeExit( 2674 [&] { MakeRequestStaticIfNeeded(aLoadingDocument, _retval); }); 2675 2676 LOG_SCOPE(gImgLog, "imgLoader::LoadImageWithChannel"); 2677 RefPtr<imgRequest> request; 2678 2679 nsCOMPtr<nsIURI> uri; 2680 channel->GetURI(getter_AddRefs(uri)); 2681 2682 NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); 2683 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 2684 2685 // TODO: Get a meaningful cors mode from the caller probably? 2686 const auto corsMode = CORS_NONE; 2687 ImageCacheKey key(uri, corsMode, aLoadingDocument); 2688 2689 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; 2690 channel->GetLoadFlags(&requestFlags); 2691 2692 RefPtr<imgCacheEntry> entry; 2693 2694 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { 2695 RemoveFromCache(key); 2696 } else { 2697 // Look in the cache for our URI, and then validate it. 2698 // XXX For now ignore aCacheKey. We will need it in the future 2699 // for correctly dealing with image load requests that are a result 2700 // of post data. 2701 if (mCache.Get(key, getter_AddRefs(entry)) && entry) { 2702 // We don't want to kick off another network load. So we ask 2703 // ValidateEntry to only do validation without creating a new proxy. If 2704 // it says that the entry isn't valid any more, we'll only use the entry 2705 // we're getting if the channel is loading from the cache anyways. 2706 // 2707 // XXX -- should this be changed? it's pretty much verbatim from the old 2708 // code, but seems nonsensical. 2709 // 2710 // Since aCanMakeNewChannel == false, we don't need to pass content policy 2711 // type/principal/etc 2712 2713 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 2714 // if there is a loadInfo, use the right contentType, otherwise 2715 // default to the internal image type 2716 nsContentPolicyType policyType = loadInfo->InternalContentPolicyType(); 2717 2718 if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver, 2719 aLoadingDocument, requestFlags, policyType, false, 2720 nullptr, nullptr, nullptr, corsMode, false, 0, 2721 FetchPriority::Auto)) { 2722 request = entry->GetRequest(); 2723 } else { 2724 nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(channel)); 2725 bool bUseCacheCopy; 2726 2727 if (cacheChan) { 2728 cacheChan->IsFromCache(&bUseCacheCopy); 2729 } else { 2730 bUseCacheCopy = false; 2731 } 2732 2733 if (!bUseCacheCopy) { 2734 entry = nullptr; 2735 } else { 2736 request = entry->GetRequest(); 2737 } 2738 } 2739 2740 if (request && entry) { 2741 // If this entry has no proxies, its request has no reference to 2742 // the entry. 2743 if (entry->HasNoProxies()) { 2744 LOG_FUNC_WITH_PARAM( 2745 gImgLog, 2746 "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri", 2747 key.URI()); 2748 MOZ_ASSERT(!request->HasCacheEntry(), 2749 "Proxyless entry's request has cache entry!"); 2750 request->SetCacheEntry(entry); 2751 2752 if (mCacheTracker && entry->GetExpirationState()->IsTracked()) { 2753 mCacheTracker->MarkUsed(entry); 2754 } 2755 } 2756 } 2757 } 2758 } 2759 2760 nsCOMPtr<nsILoadGroup> loadGroup; 2761 channel->GetLoadGroup(getter_AddRefs(loadGroup)); 2762 2763 #ifdef DEBUG 2764 if (aLoadingDocument) { 2765 // The load group of the channel should always match that of the 2766 // document if given. If that isn't the case, then we need to add more 2767 // plumbing to ensure we block the document as well. 2768 nsCOMPtr<nsILoadGroup> docLoadGroup = 2769 aLoadingDocument->GetDocumentLoadGroup(); 2770 MOZ_ASSERT(docLoadGroup == loadGroup); 2771 } 2772 #endif 2773 2774 // Filter out any load flags not from nsIRequest 2775 requestFlags &= nsIRequest::LOAD_REQUESTMASK; 2776 2777 nsresult rv = NS_OK; 2778 if (request) { 2779 // we have this in our cache already.. cancel the current (document) load 2780 2781 // this should fire an OnStopRequest 2782 channel->Cancel(NS_ERROR_PARSED_DATA_CACHED); 2783 2784 *listener = nullptr; // give them back a null nsIStreamListener 2785 2786 rv = CreateNewProxyForRequest(request, uri, loadGroup, aLoadingDocument, 2787 aObserver, requestFlags, _retval); 2788 static_cast<imgRequestProxy*>(*_retval)->NotifyListener(); 2789 } else { 2790 // We use originalURI here to fulfil the imgIRequest contract on GetURI. 2791 nsCOMPtr<nsIURI> originalURI; 2792 channel->GetOriginalURI(getter_AddRefs(originalURI)); 2793 2794 // XXX(seth): We should be able to just use |key| here, except that |key| is 2795 // constructed above with the *current URI* and not the *original URI*. I'm 2796 // pretty sure this is a bug, and it's preventing us from ever getting a 2797 // cache hit in LoadImageWithChannel when redirects are involved. 2798 ImageCacheKey originalURIKey(originalURI, corsMode, aLoadingDocument); 2799 2800 // Default to doing a principal check because we don't know who 2801 // started that load and whether their principal ended up being 2802 // inherited on the channel. 2803 NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true, this, 2804 originalURIKey, getter_AddRefs(request), 2805 getter_AddRefs(entry)); 2806 2807 // No principal specified here, because we're not passed one. 2808 // In LoadImageWithChannel, the redirects that may have been 2809 // associated with this load would have gone through necko. 2810 // We only have the final URI in ImageLib and hence don't know 2811 // if the request went through insecure redirects. But if it did, 2812 // the necko cache should have handled that (since all necko cache hits 2813 // including the redirects will go through content policy). Hence, we 2814 // can set aHadInsecureRedirect to false here. 2815 rv = request->Init(originalURI, uri, /* aHadInsecureRedirect = */ false, 2816 channel, channel, entry, aLoadingDocument, nullptr, 2817 corsMode, nullptr); 2818 NS_ENSURE_SUCCESS(rv, rv); 2819 2820 RefPtr<ProxyListener> pl = 2821 new ProxyListener(static_cast<nsIStreamListener*>(request.get())); 2822 pl.forget(listener); 2823 2824 // Try to add the new request into the cache. 2825 PutIntoCache(originalURIKey, entry); 2826 2827 rv = CreateNewProxyForRequest(request, originalURI, loadGroup, 2828 aLoadingDocument, aObserver, requestFlags, 2829 _retval); 2830 2831 // Explicitly don't notify our proxy, because we're loading off the 2832 // network, and necko (or things called from necko, such as 2833 // imgCacheValidator) are going to call our notifications asynchronously, 2834 // and we can't make it further asynchronous because observers might rely 2835 // on imagelib completing its work between the channel's OnStartRequest and 2836 // OnStopRequest. 2837 } 2838 2839 if (NS_FAILED(rv)) { 2840 return rv; 2841 } 2842 2843 (*_retval)->AddToLoadGroup(); 2844 return rv; 2845 } 2846 2847 bool imgLoader::SupportImageWithMimeType(const nsACString& aMimeType, 2848 AcceptedMimeTypes aAccept 2849 /* = AcceptedMimeTypes::IMAGES */) { 2850 nsAutoCString mimeType(aMimeType); 2851 ToLowerCase(mimeType); 2852 2853 if (aAccept == AcceptedMimeTypes::IMAGES_AND_DOCUMENTS && 2854 mimeType.EqualsLiteral(IMAGE_SVG_XML)) { 2855 return true; 2856 } 2857 2858 DecoderType type = DecoderFactory::GetDecoderType(mimeType.get()); 2859 return type != DecoderType::UNKNOWN; 2860 } 2861 2862 NS_IMETHODIMP 2863 imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest, 2864 const uint8_t* aContents, uint32_t aLength, 2865 nsACString& aContentType) { 2866 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); 2867 if (channel) { 2868 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 2869 if (loadInfo->GetSkipContentSniffing()) { 2870 return NS_ERROR_NOT_AVAILABLE; 2871 } 2872 } 2873 2874 nsresult rv = 2875 GetMimeTypeFromContent((const char*)aContents, aLength, aContentType); 2876 if (NS_SUCCEEDED(rv) && channel && XRE_IsParentProcess()) { 2877 if (RefPtr<mozilla::net::nsHttpChannel> httpChannel = 2878 do_QueryObject(channel)) { 2879 // If the image type pattern matching algorithm given bytes does not 2880 // return undefined, then disable the further check and allow the 2881 // response. 2882 httpChannel->DisableIsOpaqueResponseAllowedAfterSniffCheck( 2883 mozilla::net::nsHttpChannel::SnifferType::Image); 2884 } 2885 } 2886 2887 return rv; 2888 } 2889 2890 /* static */ 2891 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, 2892 uint32_t aLength, 2893 nsACString& aContentType) { 2894 nsAutoCString detected; 2895 2896 /* Is it a GIF? */ 2897 if (aLength >= 6 && 2898 (!strncmp(aContents, "GIF87a", 6) || !strncmp(aContents, "GIF89a", 6))) { 2899 aContentType.AssignLiteral(IMAGE_GIF); 2900 2901 /* or a PNG? */ 2902 } else if (aLength >= 8 && ((unsigned char)aContents[0] == 0x89 && 2903 (unsigned char)aContents[1] == 0x50 && 2904 (unsigned char)aContents[2] == 0x4E && 2905 (unsigned char)aContents[3] == 0x47 && 2906 (unsigned char)aContents[4] == 0x0D && 2907 (unsigned char)aContents[5] == 0x0A && 2908 (unsigned char)aContents[6] == 0x1A && 2909 (unsigned char)aContents[7] == 0x0A)) { 2910 aContentType.AssignLiteral(IMAGE_PNG); 2911 2912 /* maybe a JPEG (JFIF)? */ 2913 /* JFIF files start with SOI APP0 but older files can start with SOI DQT 2914 * so we test for SOI followed by any marker, i.e. FF D8 FF 2915 * this will also work for SPIFF JPEG files if they appear in the future. 2916 * 2917 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00) 2918 */ 2919 } else if (aLength >= 3 && ((unsigned char)aContents[0]) == 0xFF && 2920 ((unsigned char)aContents[1]) == 0xD8 && 2921 ((unsigned char)aContents[2]) == 0xFF) { 2922 aContentType.AssignLiteral(IMAGE_JPEG); 2923 2924 /* or how about ART? */ 2925 /* ART begins with JG (4A 47). Major version offset 2. 2926 * Minor version offset 3. Offset 4 must be nullptr. 2927 */ 2928 } else if (aLength >= 5 && ((unsigned char)aContents[0]) == 0x4a && 2929 ((unsigned char)aContents[1]) == 0x47 && 2930 ((unsigned char)aContents[4]) == 0x00) { 2931 aContentType.AssignLiteral(IMAGE_ART); 2932 2933 } else if (aLength >= 2 && !strncmp(aContents, "BM", 2)) { 2934 aContentType.AssignLiteral(IMAGE_BMP); 2935 2936 // ICOs always begin with a 2-byte 0 followed by a 2-byte 1. 2937 // CURs begin with 2-byte 0 followed by 2-byte 2. 2938 } else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) || 2939 !memcmp(aContents, "\000\000\002\000", 4))) { 2940 aContentType.AssignLiteral(IMAGE_ICO); 2941 2942 // WebPs always begin with RIFF, a 32-bit length, and WEBP. 2943 } else if (aLength >= 12 && !memcmp(aContents, "RIFF", 4) && 2944 !memcmp(aContents + 8, "WEBP", 4)) { 2945 aContentType.AssignLiteral(IMAGE_WEBP); 2946 2947 } else if (MatchesMP4(reinterpret_cast<const uint8_t*>(aContents), aLength, 2948 detected) && 2949 detected.Equals(IMAGE_AVIF)) { 2950 aContentType.AssignLiteral(IMAGE_AVIF); 2951 } else if ((aLength >= 2 && !memcmp(aContents, "\xFF\x0A", 2)) || 2952 (aLength >= 12 && 2953 !memcmp(aContents, "\x00\x00\x00\x0CJXL \x0D\x0A\x87\x0A", 12))) { 2954 // Each version is for containerless and containerful files respectively. 2955 aContentType.AssignLiteral(IMAGE_JXL); 2956 } else { 2957 /* none of the above? I give up */ 2958 return NS_ERROR_NOT_AVAILABLE; 2959 } 2960 2961 return NS_OK; 2962 } 2963 2964 /** 2965 * proxy stream listener class used to handle multipart/x-mixed-replace 2966 */ 2967 2968 #include "nsIRequest.h" 2969 #include "nsIStreamConverterService.h" 2970 2971 NS_IMPL_ISUPPORTS(ProxyListener, nsIStreamListener, 2972 nsIThreadRetargetableStreamListener, nsIRequestObserver) 2973 2974 ProxyListener::ProxyListener(nsIStreamListener* dest) : mDestListener(dest) {} 2975 2976 ProxyListener::~ProxyListener() = default; 2977 2978 /** nsIRequestObserver methods **/ 2979 2980 NS_IMETHODIMP 2981 ProxyListener::OnStartRequest(nsIRequest* aRequest) { 2982 if (!mDestListener) { 2983 return NS_ERROR_FAILURE; 2984 } 2985 2986 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); 2987 if (channel) { 2988 // We need to set the initiator type for the image load 2989 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel); 2990 if (timedChannel) { 2991 nsAutoString type; 2992 timedChannel->GetInitiatorType(type); 2993 if (type.IsEmpty()) { 2994 timedChannel->SetInitiatorType(u"img"_ns); 2995 } 2996 } 2997 2998 nsAutoCString contentType; 2999 nsresult rv = channel->GetContentType(contentType); 3000 3001 if (!contentType.IsEmpty()) { 3002 /* If multipart/x-mixed-replace content, we'll insert a MIME decoder 3003 in the pipeline to handle the content and pass it along to our 3004 original listener. 3005 */ 3006 if ("multipart/x-mixed-replace"_ns.Equals(contentType)) { 3007 nsCOMPtr<nsIStreamConverterService> convServ( 3008 do_GetService("@mozilla.org/streamConverters;1", &rv)); 3009 if (NS_SUCCEEDED(rv)) { 3010 nsCOMPtr<nsIStreamListener> toListener(mDestListener); 3011 nsCOMPtr<nsIStreamListener> fromListener; 3012 3013 rv = convServ->AsyncConvertData("multipart/x-mixed-replace", "*/*", 3014 toListener, nullptr, 3015 getter_AddRefs(fromListener)); 3016 if (NS_SUCCEEDED(rv)) { 3017 mDestListener = fromListener; 3018 } 3019 } 3020 } 3021 } 3022 } 3023 3024 return mDestListener->OnStartRequest(aRequest); 3025 } 3026 3027 NS_IMETHODIMP 3028 ProxyListener::OnStopRequest(nsIRequest* aRequest, nsresult status) { 3029 if (!mDestListener) { 3030 return NS_ERROR_FAILURE; 3031 } 3032 3033 return mDestListener->OnStopRequest(aRequest, status); 3034 } 3035 3036 /** nsIStreamListener methods **/ 3037 3038 NS_IMETHODIMP 3039 ProxyListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr, 3040 uint64_t sourceOffset, uint32_t count) { 3041 if (!mDestListener) { 3042 return NS_ERROR_FAILURE; 3043 } 3044 3045 return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count); 3046 } 3047 3048 NS_IMETHODIMP 3049 ProxyListener::OnDataFinished(nsresult aStatus) { 3050 if (!mDestListener) { 3051 return NS_ERROR_FAILURE; 3052 } 3053 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = 3054 do_QueryInterface(mDestListener); 3055 if (retargetableListener) { 3056 return retargetableListener->OnDataFinished(aStatus); 3057 } 3058 3059 return NS_OK; 3060 } 3061 3062 /** nsThreadRetargetableStreamListener methods **/ 3063 NS_IMETHODIMP 3064 ProxyListener::CheckListenerChain() { 3065 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); 3066 nsresult rv = NS_OK; 3067 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = 3068 do_QueryInterface(mDestListener, &rv); 3069 if (retargetableListener) { 3070 rv = retargetableListener->CheckListenerChain(); 3071 } 3072 MOZ_LOG( 3073 gImgLog, LogLevel::Debug, 3074 ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%" PRIx32 3075 "]", 3076 (NS_SUCCEEDED(rv) ? "success" : "failure"), this, 3077 (nsIStreamListener*)mDestListener, static_cast<uint32_t>(rv))); 3078 return rv; 3079 } 3080 3081 /** 3082 * http validate class. check a channel for a 304 3083 */ 3084 3085 NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver, 3086 nsIThreadRetargetableStreamListener, nsIChannelEventSink, 3087 nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) 3088 3089 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress, 3090 imgLoader* loader, imgRequest* request, 3091 Document* aDocument, 3092 uint64_t aInnerWindowId, 3093 bool forcePrincipalCheckForCacheEntry) 3094 : mProgressProxy(progress), 3095 mRequest(request), 3096 mDocument(aDocument), 3097 mInnerWindowId(aInnerWindowId), 3098 mImgLoader(loader), 3099 mHadInsecureRedirect(false) { 3100 NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, 3101 mRequest->CacheKey(), getter_AddRefs(mNewRequest), 3102 getter_AddRefs(mNewEntry)); 3103 } 3104 3105 imgCacheValidator::~imgCacheValidator() { 3106 if (mRequest) { 3107 // If something went wrong, and we never unblocked the requests waiting on 3108 // validation, now is our last chance. We will cancel the new request and 3109 // switch the waiting proxies to it. 3110 UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ false); 3111 } 3112 } 3113 3114 void imgCacheValidator::AddProxy(imgRequestProxy* aProxy) { 3115 // aProxy needs to be in the loadgroup since we're validating from 3116 // the network. 3117 aProxy->AddToLoadGroup(); 3118 3119 mProxies.AppendElement(aProxy); 3120 } 3121 3122 void imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy) { 3123 mProxies.RemoveElement(aProxy); 3124 } 3125 3126 void imgCacheValidator::UpdateProxies(bool aCancelRequest, bool aSyncNotify) { 3127 MOZ_ASSERT(mRequest); 3128 3129 // Clear the validator before updating the proxies. The notifications may 3130 // clone an existing request, and its state could be inconsistent. 3131 mRequest->SetValidator(nullptr); 3132 mRequest = nullptr; 3133 3134 // If an error occurred, we will want to cancel the new request, and make the 3135 // validating proxies point to it. Any proxies still bound to the original 3136 // request which are not validating should remain untouched. 3137 if (aCancelRequest) { 3138 MOZ_ASSERT(mNewRequest); 3139 mNewRequest->CancelAndAbort(NS_BINDING_ABORTED); 3140 } 3141 3142 // We have finished validating the request, so we can safely take ownership 3143 // of the proxy list. imgRequestProxy::SyncNotifyListener can mutate the list 3144 // if imgRequestProxy::CancelAndForgetObserver is called by its owner. Note 3145 // that any potential notifications should still be suppressed in 3146 // imgRequestProxy::ChangeOwner because we haven't cleared the validating 3147 // flag yet, and thus they will remain deferred. 3148 AutoTArray<RefPtr<imgRequestProxy>, 4> proxies(std::move(mProxies)); 3149 3150 for (auto& proxy : proxies) { 3151 // First update the state of all proxies before notifying any of them 3152 // to ensure a consistent state (e.g. in case the notification causes 3153 // other proxies to be touched indirectly.) 3154 MOZ_ASSERT(proxy->IsValidating()); 3155 MOZ_ASSERT(proxy->NotificationsDeferred(), 3156 "Proxies waiting on cache validation should be " 3157 "deferring notifications!"); 3158 if (mNewRequest) { 3159 proxy->ChangeOwner(mNewRequest); 3160 } 3161 proxy->ClearValidating(); 3162 } 3163 3164 mNewRequest = nullptr; 3165 mNewEntry = nullptr; 3166 3167 for (auto& proxy : proxies) { 3168 if (aSyncNotify) { 3169 // Notify synchronously, because the caller knows we are already in an 3170 // asynchronously-called function (e.g. OnStartRequest). 3171 proxy->SyncNotifyListener(); 3172 } else { 3173 // Notify asynchronously, because the caller does not know our current 3174 // call state (e.g. ~imgCacheValidator). 3175 proxy->NotifyListener(); 3176 } 3177 } 3178 } 3179 3180 /** nsIRequestObserver methods **/ 3181 3182 NS_IMETHODIMP 3183 imgCacheValidator::OnStartRequest(nsIRequest* aRequest) { 3184 // We may be holding on to a document, so ensure that it's released. 3185 RefPtr<Document> document = mDocument.forget(); 3186 3187 // If for some reason we don't still have an existing request (probably 3188 // because OnStartRequest got delivered more than once), just bail. 3189 if (!mRequest) { 3190 MOZ_ASSERT_UNREACHABLE("OnStartRequest delivered more than once?"); 3191 aRequest->CancelWithReason(NS_BINDING_ABORTED, 3192 "OnStartRequest delivered more than once?"_ns); 3193 return NS_ERROR_FAILURE; 3194 } 3195 3196 // If this request is coming from cache and has the same URI as our 3197 // imgRequest, the request all our proxies are pointing at is valid, and all 3198 // we have to do is tell them to notify their listeners. 3199 nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(aRequest)); 3200 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); 3201 if (cacheChan && channel) { 3202 bool isFromCache = false; 3203 cacheChan->IsFromCache(&isFromCache); 3204 3205 nsCOMPtr<nsIURI> channelURI; 3206 channel->GetURI(getter_AddRefs(channelURI)); 3207 3208 nsCOMPtr<nsIURI> finalURI; 3209 mRequest->GetFinalURI(getter_AddRefs(finalURI)); 3210 3211 bool sameURI = false; 3212 if (channelURI && finalURI) { 3213 channelURI->Equals(finalURI, &sameURI); 3214 } 3215 3216 if (isFromCache && sameURI) { 3217 // We don't need to load this any more. 3218 aRequest->CancelWithReason(NS_BINDING_ABORTED, 3219 "imgCacheValidator::OnStartRequest"_ns); 3220 mNewRequest = nullptr; 3221 3222 // Clear the validator before updating the proxies. The notifications may 3223 // clone an existing request, and its state could be inconsistent. 3224 mRequest->SetLoadId(document); 3225 mRequest->SetInnerWindowID(mInnerWindowId); 3226 UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true); 3227 return NS_OK; 3228 } 3229 } 3230 3231 // We can't load out of cache. We have to create a whole new request for the 3232 // data that's coming in off the channel. 3233 nsCOMPtr<nsIURI> uri; 3234 mRequest->GetURI(getter_AddRefs(uri)); 3235 3236 LOG_MSG_WITH_PARAM(gImgLog, 3237 "imgCacheValidator::OnStartRequest creating new request", 3238 "uri", uri); 3239 3240 CORSMode corsmode = mRequest->GetCORSMode(); 3241 nsCOMPtr<nsIReferrerInfo> referrerInfo = mRequest->GetReferrerInfo(); 3242 nsCOMPtr<nsIPrincipal> triggeringPrincipal = 3243 mRequest->GetTriggeringPrincipal(); 3244 3245 // Doom the old request's cache entry 3246 mRequest->RemoveFromCache(); 3247 3248 // We use originalURI here to fulfil the imgIRequest contract on GetURI. 3249 nsCOMPtr<nsIURI> originalURI; 3250 channel->GetOriginalURI(getter_AddRefs(originalURI)); 3251 nsresult rv = mNewRequest->Init(originalURI, uri, mHadInsecureRedirect, 3252 aRequest, channel, mNewEntry, document, 3253 triggeringPrincipal, corsmode, referrerInfo); 3254 if (NS_FAILED(rv)) { 3255 UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ true); 3256 return rv; 3257 } 3258 3259 mDestListener = new ProxyListener(mNewRequest); 3260 3261 // Try to add the new request into the cache. Note that the entry must be in 3262 // the cache before the proxies' ownership changes, because adding a proxy 3263 // changes the caching behaviour for imgRequests. 3264 mImgLoader->PutIntoCache(mNewRequest->CacheKey(), mNewEntry); 3265 UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true); 3266 return mDestListener->OnStartRequest(aRequest); 3267 } 3268 3269 NS_IMETHODIMP 3270 imgCacheValidator::OnStopRequest(nsIRequest* aRequest, nsresult status) { 3271 // Be sure we've released the document that we may have been holding on to. 3272 mDocument = nullptr; 3273 3274 if (!mDestListener) { 3275 return NS_OK; 3276 } 3277 3278 return mDestListener->OnStopRequest(aRequest, status); 3279 } 3280 3281 /** nsIStreamListener methods **/ 3282 3283 NS_IMETHODIMP 3284 imgCacheValidator::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr, 3285 uint64_t sourceOffset, uint32_t count) { 3286 if (!mDestListener) { 3287 // XXX see bug 113959 3288 uint32_t _retval; 3289 inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval); 3290 return NS_OK; 3291 } 3292 3293 return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count); 3294 } 3295 3296 NS_IMETHODIMP 3297 imgCacheValidator::OnDataFinished(nsresult aStatus) { 3298 if (!mDestListener) { 3299 return NS_ERROR_FAILURE; 3300 } 3301 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = 3302 do_QueryInterface(mDestListener); 3303 if (retargetableListener) { 3304 return retargetableListener->OnDataFinished(aStatus); 3305 } 3306 3307 return NS_OK; 3308 } 3309 3310 /** nsIThreadRetargetableStreamListener methods **/ 3311 3312 NS_IMETHODIMP 3313 imgCacheValidator::CheckListenerChain() { 3314 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); 3315 nsresult rv = NS_OK; 3316 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = 3317 do_QueryInterface(mDestListener, &rv); 3318 if (retargetableListener) { 3319 rv = retargetableListener->CheckListenerChain(); 3320 } 3321 MOZ_LOG( 3322 gImgLog, LogLevel::Debug, 3323 ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %" PRId32 "=%s", 3324 this, static_cast<uint32_t>(rv), 3325 NS_SUCCEEDED(rv) ? "succeeded" : "failed")); 3326 return rv; 3327 } 3328 3329 /** nsIInterfaceRequestor methods **/ 3330 3331 NS_IMETHODIMP 3332 imgCacheValidator::GetInterface(const nsIID& aIID, void** aResult) { 3333 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { 3334 return QueryInterface(aIID, aResult); 3335 } 3336 3337 return mProgressProxy->GetInterface(aIID, aResult); 3338 } 3339 3340 // These functions are materially the same as the same functions in imgRequest. 3341 // We duplicate them because we're verifying whether cache loads are necessary, 3342 // not unconditionally loading. 3343 3344 /** nsIChannelEventSink methods **/ 3345 NS_IMETHODIMP 3346 imgCacheValidator::AsyncOnChannelRedirect( 3347 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags, 3348 nsIAsyncVerifyRedirectCallback* callback) { 3349 // Note all cache information we get from the old channel. 3350 mNewRequest->SetCacheValidation(mNewEntry, oldChannel); 3351 3352 // If the previous URI is a non-HTTPS URI, record that fact for later use by 3353 // security code, which needs to know whether there is an insecure load at any 3354 // point in the redirect chain. 3355 nsCOMPtr<nsIURI> oldURI; 3356 bool schemeLocal = false; 3357 if (NS_FAILED(oldChannel->GetURI(getter_AddRefs(oldURI))) || 3358 NS_FAILED(NS_URIChainHasFlags( 3359 oldURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) || 3360 (!oldURI->SchemeIs("https") && !oldURI->SchemeIs("chrome") && 3361 !schemeLocal)) { 3362 mHadInsecureRedirect = true; 3363 } 3364 3365 // Prepare for callback 3366 mRedirectCallback = callback; 3367 mRedirectChannel = newChannel; 3368 3369 return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags, 3370 this); 3371 } 3372 3373 NS_IMETHODIMP 3374 imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult) { 3375 // If we've already been told to abort, just do so. 3376 if (NS_FAILED(aResult)) { 3377 mRedirectCallback->OnRedirectVerifyCallback(aResult); 3378 mRedirectCallback = nullptr; 3379 mRedirectChannel = nullptr; 3380 return NS_OK; 3381 } 3382 3383 // make sure we have a protocol that returns data rather than opens 3384 // an external application, e.g. mailto: 3385 nsCOMPtr<nsIURI> uri; 3386 mRedirectChannel->GetURI(getter_AddRefs(uri)); 3387 3388 nsresult result = NS_OK; 3389 3390 if (nsContentUtils::IsExternalProtocol(uri)) { 3391 result = NS_ERROR_ABORT; 3392 } 3393 3394 mRedirectCallback->OnRedirectVerifyCallback(result); 3395 mRedirectCallback = nullptr; 3396 mRedirectChannel = nullptr; 3397 return NS_OK; 3398 }