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