Source code

Revision control

Other Tools

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 <algorithm>
11
12
#include "ImageLogging.h"
13
#include "imgLoader.h"
14
15
#include "mozilla/Attributes.h"
16
#include "mozilla/ClearOnShutdown.h"
17
#include "mozilla/Move.h"
18
#include "mozilla/NullPrincipal.h"
19
#include "mozilla/Preferences.h"
20
#include "mozilla/StaticPrefs_image.h"
21
#include "mozilla/StaticPrefs_network.h"
22
#include "mozilla/ChaosMode.h"
23
#include "mozilla/LoadInfo.h"
24
25
#include "nsImageModule.h"
26
#include "imgRequestProxy.h"
27
28
#include "nsCOMPtr.h"
29
30
#include "nsContentPolicyUtils.h"
31
#include "nsContentUtils.h"
32
#include "nsNetUtil.h"
33
#include "nsNetCID.h"
34
#include "nsIProtocolHandler.h"
35
#include "nsMimeTypes.h"
36
#include "nsStreamUtils.h"
37
#include "nsIHttpChannel.h"
38
#include "nsICacheInfoChannel.h"
39
#include "nsIClassOfService.h"
40
#include "nsIInterfaceRequestor.h"
41
#include "nsIInterfaceRequestorUtils.h"
42
#include "nsIProgressEventSink.h"
43
#include "nsIChannelEventSink.h"
44
#include "nsIAsyncVerifyRedirectCallback.h"
45
#include "nsIFileURL.h"
46
#include "nsIFile.h"
47
#include "nsCRT.h"
48
#include "nsINetworkPredictor.h"
49
#include "nsReadableUtils.h"
50
#include "mozilla/dom/ContentParent.h"
51
#include "mozilla/dom/nsMixedContentBlocker.h"
52
#include "mozilla/image/ImageMemoryReporter.h"
53
#include "mozilla/layers/CompositorManagerChild.h"
54
55
#include "nsIApplicationCache.h"
56
#include "nsIApplicationCacheContainer.h"
57
58
#include "nsIMemoryReporter.h"
59
#include "DecoderFactory.h"
60
#include "Image.h"
61
#include "prtime.h"
62
#include "ReferrerInfo.h"
63
64
// we want to explore making the document own the load group
65
// so we can associate the document URI with the load group.
66
// until this point, we have an evil hack:
67
#include "nsIHttpChannelInternal.h"
68
#include "nsILoadContext.h"
69
#include "nsILoadGroupChild.h"
70
#include "nsIDocShell.h"
71
72
using namespace mozilla;
73
using namespace mozilla::dom;
74
using namespace mozilla::image;
75
using namespace mozilla::net;
76
77
MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf)
78
79
class imgMemoryReporter final : public nsIMemoryReporter {
80
~imgMemoryReporter() = default;
81
82
public:
83
NS_DECL_ISUPPORTS
84
85
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
86
nsISupports* aData, bool aAnonymize) override {
87
MOZ_ASSERT(NS_IsMainThread());
88
89
layers::CompositorManagerChild* manager =
90
CompositorManagerChild::GetInstance();
91
if (!manager || !StaticPrefs::image_mem_debug_reporting()) {
92
layers::SharedSurfacesMemoryReport sharedSurfaces;
93
FinishCollectReports(aHandleReport, aData, aAnonymize, sharedSurfaces);
94
return NS_OK;
95
}
96
97
RefPtr<imgMemoryReporter> self(this);
98
nsCOMPtr<nsIHandleReportCallback> handleReport(aHandleReport);
99
nsCOMPtr<nsISupports> data(aData);
100
manager->SendReportSharedSurfacesMemory(
101
[=](layers::SharedSurfacesMemoryReport aReport) {
102
self->FinishCollectReports(handleReport, data, aAnonymize, aReport);
103
},
104
[=](mozilla::ipc::ResponseRejectReason&& aReason) {
105
layers::SharedSurfacesMemoryReport sharedSurfaces;
106
self->FinishCollectReports(handleReport, data, aAnonymize,
107
sharedSurfaces);
108
});
109
return NS_OK;
110
}
111
112
void FinishCollectReports(
113
nsIHandleReportCallback* aHandleReport, nsISupports* aData,
114
bool aAnonymize, layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
115
nsTArray<ImageMemoryCounter> chrome;
116
nsTArray<ImageMemoryCounter> content;
117
nsTArray<ImageMemoryCounter> uncached;
118
119
for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
120
for (auto iter = mKnownLoaders[i]->mChromeCache.Iter(); !iter.Done();
121
iter.Next()) {
122
imgCacheEntry* entry = iter.UserData();
123
RefPtr<imgRequest> req = entry->GetRequest();
124
RecordCounterForRequest(req, &chrome, !entry->HasNoProxies());
125
}
126
for (auto iter = mKnownLoaders[i]->mCache.Iter(); !iter.Done();
127
iter.Next()) {
128
imgCacheEntry* entry = iter.UserData();
129
RefPtr<imgRequest> req = entry->GetRequest();
130
RecordCounterForRequest(req, &content, !entry->HasNoProxies());
131
}
132
MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex);
133
for (auto iter = mKnownLoaders[i]->mUncachedImages.Iter(); !iter.Done();
134
iter.Next()) {
135
nsPtrHashKey<imgRequest>* entry = iter.Get();
136
RefPtr<imgRequest> req = entry->GetKey();
137
RecordCounterForRequest(req, &uncached, req->HasConsumers());
138
}
139
}
140
141
// Note that we only need to anonymize content image URIs.
142
143
ReportCounterArray(aHandleReport, aData, chrome, "images/chrome",
144
/* aAnonymize */ false, aSharedSurfaces);
145
146
ReportCounterArray(aHandleReport, aData, content, "images/content",
147
aAnonymize, aSharedSurfaces);
148
149
// Uncached images may be content or chrome, so anonymize them.
150
ReportCounterArray(aHandleReport, aData, uncached, "images/uncached",
151
aAnonymize, aSharedSurfaces);
152
153
// Report any shared surfaces that were not merged with the surface cache.
154
ImageMemoryReporter::ReportSharedSurfaces(aHandleReport, aData,
155
aSharedSurfaces);
156
157
nsCOMPtr<nsIMemoryReporterManager> imgr =
158
do_GetService("@mozilla.org/memory-reporter-manager;1");
159
if (imgr) {
160
imgr->EndReport();
161
}
162
}
163
164
static int64_t ImagesContentUsedUncompressedDistinguishedAmount() {
165
size_t n = 0;
166
for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length();
167
i++) {
168
for (auto iter = imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Iter();
169
!iter.Done(); iter.Next()) {
170
imgCacheEntry* entry = iter.UserData();
171
if (entry->HasNoProxies()) {
172
continue;
173
}
174
175
RefPtr<imgRequest> req = entry->GetRequest();
176
RefPtr<image::Image> image = req->GetImage();
177
if (!image) {
178
continue;
179
}
180
181
// Both this and EntryImageSizes measure
182
// images/content/raster/used/decoded memory. This function's
183
// measurement is secondary -- the result doesn't go in the "explicit"
184
// tree -- so we use moz_malloc_size_of instead of ImagesMallocSizeOf to
185
// prevent DMD from seeing it reported twice.
186
SizeOfState state(moz_malloc_size_of);
187
ImageMemoryCounter counter(image, state, /* aIsUsed = */ true);
188
189
n += counter.Values().DecodedHeap();
190
n += counter.Values().DecodedNonHeap();
191
}
192
}
193
return n;
194
}
195
196
void RegisterLoader(imgLoader* aLoader) {
197
mKnownLoaders.AppendElement(aLoader);
198
}
199
200
void UnregisterLoader(imgLoader* aLoader) {
201
mKnownLoaders.RemoveElement(aLoader);
202
}
203
204
private:
205
nsTArray<imgLoader*> mKnownLoaders;
206
207
struct MemoryTotal {
208
MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter) {
209
if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) {
210
if (aImageCounter.IsUsed()) {
211
mUsedRasterCounter += aImageCounter.Values();
212
} else {
213
mUnusedRasterCounter += aImageCounter.Values();
214
}
215
} else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) {
216
if (aImageCounter.IsUsed()) {
217
mUsedVectorCounter += aImageCounter.Values();
218
} else {
219
mUnusedVectorCounter += aImageCounter.Values();
220
}
221
} else {
222
MOZ_CRASH("Unexpected image type");
223
}
224
225
return *this;
226
}
227
228
const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; }
229
const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; }
230
const MemoryCounter& UsedVector() const { return mUsedVectorCounter; }
231
const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; }
232
233
private:
234
MemoryCounter mUsedRasterCounter;
235
MemoryCounter mUnusedRasterCounter;
236
MemoryCounter mUsedVectorCounter;
237
MemoryCounter mUnusedVectorCounter;
238
};
239
240
// Reports all images of a single kind, e.g. all used chrome images.
241
void ReportCounterArray(nsIHandleReportCallback* aHandleReport,
242
nsISupports* aData,
243
nsTArray<ImageMemoryCounter>& aCounterArray,
244
const char* aPathPrefix, bool aAnonymize,
245
layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
246
MemoryTotal summaryTotal;
247
MemoryTotal nonNotableTotal;
248
249
// Report notable images, and compute total and non-notable aggregate sizes.
250
for (uint32_t i = 0; i < aCounterArray.Length(); i++) {
251
ImageMemoryCounter& counter = aCounterArray[i];
252
253
if (aAnonymize) {
254
counter.URI().Truncate();
255
counter.URI().AppendPrintf("<anonymized-%u>", i);
256
} else {
257
// The URI could be an extremely long data: URI. Truncate if needed.
258
static const size_t max = 256;
259
if (counter.URI().Length() > max) {
260
counter.URI().Truncate(max);
261
counter.URI().AppendLiteral(" (truncated)");
262
}
263
counter.URI().ReplaceChar('/', '\\');
264
}
265
266
summaryTotal += counter;
267
268
if (counter.IsNotable() || StaticPrefs::image_mem_debug_reporting()) {
269
ReportImage(aHandleReport, aData, aPathPrefix, counter,
270
aSharedSurfaces);
271
} else {
272
ImageMemoryReporter::TrimSharedSurfaces(counter, aSharedSurfaces);
273
nonNotableTotal += counter;
274
}
275
}
276
277
// Report non-notable images in aggregate.
278
ReportTotal(aHandleReport, aData, /* aExplicit = */ true, aPathPrefix,
279
"<non-notable images>/", nonNotableTotal);
280
281
// Report a summary in aggregate, outside of the explicit tree.
282
ReportTotal(aHandleReport, aData, /* aExplicit = */ false, aPathPrefix, "",
283
summaryTotal);
284
}
285
286
static void ReportImage(nsIHandleReportCallback* aHandleReport,
287
nsISupports* aData, const char* aPathPrefix,
288
const ImageMemoryCounter& aCounter,
289
layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
290
nsAutoCString pathPrefix(NS_LITERAL_CSTRING("explicit/"));
291
pathPrefix.Append(aPathPrefix);
292
pathPrefix.Append(aCounter.Type() == imgIContainer::TYPE_RASTER
293
? "/raster/"
294
: "/vector/");
295
pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
296
pathPrefix.AppendLiteral("image(");
297
pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
298
pathPrefix.AppendLiteral("x");
299
pathPrefix.AppendInt(aCounter.IntrinsicSize().height);
300
pathPrefix.AppendLiteral(", ");
301
302
if (aCounter.URI().IsEmpty()) {
303
pathPrefix.AppendLiteral("<unknown URI>");
304
} else {
305
pathPrefix.Append(aCounter.URI());
306
}
307
308
pathPrefix.AppendLiteral(")/");
309
310
ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter, aSharedSurfaces);
311
312
ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values());
313
}
314
315
static void ReportSurfaces(
316
nsIHandleReportCallback* aHandleReport, nsISupports* aData,
317
const nsACString& aPathPrefix, const ImageMemoryCounter& aCounter,
318
layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
319
for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
320
nsAutoCString surfacePathPrefix(aPathPrefix);
321
if (counter.IsLocked()) {
322
surfacePathPrefix.AppendLiteral("locked/");
323
} else {
324
surfacePathPrefix.AppendLiteral("unlocked/");
325
}
326
if (counter.IsFactor2()) {
327
surfacePathPrefix.AppendLiteral("factor2/");
328
}
329
if (counter.CannotSubstitute()) {
330
surfacePathPrefix.AppendLiteral("cannot_substitute/");
331
}
332
surfacePathPrefix.AppendLiteral("surface(");
333
surfacePathPrefix.AppendInt(counter.Key().Size().width);
334
surfacePathPrefix.AppendLiteral("x");
335
surfacePathPrefix.AppendInt(counter.Key().Size().height);
336
337
if (counter.Values().ExternalHandles() > 0) {
338
surfacePathPrefix.AppendLiteral(", handles:");
339
surfacePathPrefix.AppendInt(
340
uint32_t(counter.Values().ExternalHandles()));
341
}
342
343
ImageMemoryReporter::AppendSharedSurfacePrefix(surfacePathPrefix, counter,
344
aSharedSurfaces);
345
346
if (counter.Type() == SurfaceMemoryCounterType::NORMAL) {
347
PlaybackType playback = counter.Key().Playback();
348
if (playback == PlaybackType::eAnimated) {
349
if (StaticPrefs::image_mem_debug_reporting()) {
350
surfacePathPrefix.AppendPrintf(
351
" (animation %4u)", uint32_t(counter.Values().FrameIndex()));
352
} else {
353
surfacePathPrefix.AppendLiteral(" (animation)");
354
}
355
}
356
357
if (counter.Key().Flags() != DefaultSurfaceFlags()) {
358
surfacePathPrefix.AppendLiteral(", flags:");
359
surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
360
/* aRadix = */ 16);
361
}
362
363
if (counter.Key().SVGContext()) {
364
const SVGImageContext& context = counter.Key().SVGContext().ref();
365
surfacePathPrefix.AppendLiteral(", svgContext:[ ");
366
if (context.GetViewportSize()) {
367
const CSSIntSize& size = context.GetViewportSize().ref();
368
surfacePathPrefix.AppendLiteral("viewport=(");
369
surfacePathPrefix.AppendInt(size.width);
370
surfacePathPrefix.AppendLiteral("x");
371
surfacePathPrefix.AppendInt(size.height);
372
surfacePathPrefix.AppendLiteral(") ");
373
}
374
if (context.GetPreserveAspectRatio()) {
375
nsAutoString aspect;
376
context.GetPreserveAspectRatio()->ToString(aspect);
377
surfacePathPrefix.AppendLiteral("preserveAspectRatio=(");
378
LossyAppendUTF16toASCII(aspect, surfacePathPrefix);
379
surfacePathPrefix.AppendLiteral(") ");
380
}
381
if (context.GetContextPaint()) {
382
const SVGEmbeddingContextPaint* paint = context.GetContextPaint();
383
surfacePathPrefix.AppendLiteral("contextPaint=(");
384
if (paint->GetFill()) {
385
surfacePathPrefix.AppendLiteral(" fill=");
386
surfacePathPrefix.AppendInt(paint->GetFill()->ToABGR(), 16);
387
}
388
if (paint->GetFillOpacity()) {
389
surfacePathPrefix.AppendLiteral(" fillOpa=");
390
surfacePathPrefix.AppendFloat(paint->GetFillOpacity());
391
}
392
if (paint->GetStroke()) {
393
surfacePathPrefix.AppendLiteral(" stroke=");
394
surfacePathPrefix.AppendInt(paint->GetStroke()->ToABGR(), 16);
395
}
396
if (paint->GetStrokeOpacity()) {
397
surfacePathPrefix.AppendLiteral(" strokeOpa=");
398
surfacePathPrefix.AppendFloat(paint->GetStrokeOpacity());
399
}
400
surfacePathPrefix.AppendLiteral(" ) ");
401
}
402
surfacePathPrefix.AppendLiteral("]");
403
}
404
} else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) {
405
surfacePathPrefix.AppendLiteral(", compositing frame");
406
} else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING_PREV) {
407
surfacePathPrefix.AppendLiteral(", compositing prev frame");
408
} else {
409
MOZ_ASSERT_UNREACHABLE("Unknown counter type");
410
}
411
412
surfacePathPrefix.AppendLiteral(")/");
413
414
ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values());
415
}
416
}
417
418
static void ReportTotal(nsIHandleReportCallback* aHandleReport,
419
nsISupports* aData, bool aExplicit,
420
const char* aPathPrefix, const char* aPathInfix,
421
const MemoryTotal& aTotal) {
422
nsAutoCString pathPrefix;
423
if (aExplicit) {
424
pathPrefix.AppendLiteral("explicit/");
425
}
426
pathPrefix.Append(aPathPrefix);
427
428
nsAutoCString rasterUsedPrefix(pathPrefix);
429
rasterUsedPrefix.AppendLiteral("/raster/used/");
430
rasterUsedPrefix.Append(aPathInfix);
431
ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster());
432
433
nsAutoCString rasterUnusedPrefix(pathPrefix);
434
rasterUnusedPrefix.AppendLiteral("/raster/unused/");
435
rasterUnusedPrefix.Append(aPathInfix);
436
ReportValues(aHandleReport, aData, rasterUnusedPrefix,
437
aTotal.UnusedRaster());
438
439
nsAutoCString vectorUsedPrefix(pathPrefix);
440
vectorUsedPrefix.AppendLiteral("/vector/used/");
441
vectorUsedPrefix.Append(aPathInfix);
442
ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector());
443
444
nsAutoCString vectorUnusedPrefix(pathPrefix);
445
vectorUnusedPrefix.AppendLiteral("/vector/unused/");
446
vectorUnusedPrefix.Append(aPathInfix);
447
ReportValues(aHandleReport, aData, vectorUnusedPrefix,
448
aTotal.UnusedVector());
449
}
450
451
static void ReportValues(nsIHandleReportCallback* aHandleReport,
452
nsISupports* aData, const nsACString& aPathPrefix,
453
const MemoryCounter& aCounter) {
454
ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter);
455
456
ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "decoded-heap",
457
"Decoded image data which is stored on the heap.",
458
aCounter.DecodedHeap());
459
460
ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
461
"decoded-nonheap",
462
"Decoded image data which isn't stored on the heap.",
463
aCounter.DecodedNonHeap());
464
}
465
466
static void ReportSourceValue(nsIHandleReportCallback* aHandleReport,
467
nsISupports* aData,
468
const nsACString& aPathPrefix,
469
const MemoryCounter& aCounter) {
470
ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "source",
471
"Raster image source data and vector image documents.",
472
aCounter.Source());
473
}
474
475
static void ReportValue(nsIHandleReportCallback* aHandleReport,
476
nsISupports* aData, int32_t aKind,
477
const nsACString& aPathPrefix,
478
const char* aPathSuffix, const char* aDescription,
479
size_t aValue) {
480
if (aValue == 0) {
481
return;
482
}
483
484
nsAutoCString desc(aDescription);
485
nsAutoCString path(aPathPrefix);
486
path.Append(aPathSuffix);
487
488
aHandleReport->Callback(EmptyCString(), path, aKind, UNITS_BYTES, aValue,
489
desc, aData);
490
}
491
492
static void RecordCounterForRequest(imgRequest* aRequest,
493
nsTArray<ImageMemoryCounter>* aArray,
494
bool aIsUsed) {
495
RefPtr<image::Image> image = aRequest->GetImage();
496
if (!image) {
497
return;
498
}
499
500
SizeOfState state(ImagesMallocSizeOf);
501
ImageMemoryCounter counter(image, state, aIsUsed);
502
503
aArray->AppendElement(std::move(counter));
504
}
505
};
506
507
NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
508
509
NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, nsIProgressEventSink,
510
nsIChannelEventSink, nsIInterfaceRequestor)
511
512
NS_IMETHODIMP
513
nsProgressNotificationProxy::OnProgress(nsIRequest* request, nsISupports* ctxt,
514
int64_t progress, int64_t progressMax) {
515
nsCOMPtr<nsILoadGroup> loadGroup;
516
request->GetLoadGroup(getter_AddRefs(loadGroup));
517
518
nsCOMPtr<nsIProgressEventSink> target;
519
NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
520
NS_GET_IID(nsIProgressEventSink),
521
getter_AddRefs(target));
522
if (!target) {
523
return NS_OK;
524
}
525
return target->OnProgress(mImageRequest, ctxt, progress, progressMax);
526
}
527
528
NS_IMETHODIMP
529
nsProgressNotificationProxy::OnStatus(nsIRequest* request, nsISupports* ctxt,
530
nsresult status,
531
const char16_t* statusArg) {
532
nsCOMPtr<nsILoadGroup> loadGroup;
533
request->GetLoadGroup(getter_AddRefs(loadGroup));
534
535
nsCOMPtr<nsIProgressEventSink> target;
536
NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
537
NS_GET_IID(nsIProgressEventSink),
538
getter_AddRefs(target));
539
if (!target) {
540
return NS_OK;
541
}
542
return target->OnStatus(mImageRequest, ctxt, status, statusArg);
543
}
544
545
NS_IMETHODIMP
546
nsProgressNotificationProxy::AsyncOnChannelRedirect(
547
nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
548
nsIAsyncVerifyRedirectCallback* cb) {
549
// Tell the original original callbacks about it too
550
nsCOMPtr<nsILoadGroup> loadGroup;
551
newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
552
nsCOMPtr<nsIChannelEventSink> target;
553
NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
554
NS_GET_IID(nsIChannelEventSink),
555
getter_AddRefs(target));
556
if (!target) {
557
cb->OnRedirectVerifyCallback(NS_OK);
558
return NS_OK;
559
}
560
561
// Delegate to |target| if set, reusing |cb|
562
return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
563
}
564
565
NS_IMETHODIMP
566
nsProgressNotificationProxy::GetInterface(const nsIID& iid, void** result) {
567
if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
568
*result = static_cast<nsIProgressEventSink*>(this);
569
NS_ADDREF_THIS();
570
return NS_OK;
571
}
572
if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
573
*result = static_cast<nsIChannelEventSink*>(this);
574
NS_ADDREF_THIS();
575
return NS_OK;
576
}
577
if (mOriginalCallbacks) {
578
return mOriginalCallbacks->GetInterface(iid, result);
579
}
580
return NS_NOINTERFACE;
581
}
582
583
static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry,
584
imgLoader* aLoader, const ImageCacheKey& aKey,
585
imgRequest** aRequest, imgCacheEntry** aEntry) {
586
RefPtr<imgRequest> request = new imgRequest(aLoader, aKey);
587
RefPtr<imgCacheEntry> entry =
588
new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
589
aLoader->AddToUncachedImages(request);
590
request.forget(aRequest);
591
entry.forget(aEntry);
592
}
593
594
static bool ShouldRevalidateEntry(imgCacheEntry* aEntry, nsLoadFlags aFlags,
595
bool aHasExpired) {
596
bool bValidateEntry = false;
597
598
if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) {
599
return false;
600
}
601
602
if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
603
bValidateEntry = true;
604
} else if (aEntry->GetMustValidate()) {
605
bValidateEntry = true;
606
} else if (aHasExpired) {
607
// The cache entry has expired... Determine whether the stale cache
608
// entry can be used without validation...
609
if (aFlags &
610
(nsIRequest::VALIDATE_NEVER | nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
611
// VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
612
// entries to be used unless they have been explicitly marked to
613
// indicate that revalidation is necessary.
614
bValidateEntry = false;
615
616
} else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
617
// LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
618
// the entry must be revalidated.
619
bValidateEntry = true;
620
}
621
}
622
623
return bValidateEntry;
624
}
625
626
/* Call content policies on cached images that went through a redirect */
627
static bool ShouldLoadCachedImage(imgRequest* aImgRequest,
628
nsISupports* aLoadingContext,
629
nsIPrincipal* aTriggeringPrincipal,
630
nsContentPolicyType aPolicyType,
631
bool aSendCSPViolationReports) {
632
/* Call content policies on cached images - Bug 1082837
633
* Cached images are keyed off of the first uri in a redirect chain.
634
* Hence content policies don't get a chance to test the intermediate hops
635
* or the final desitnation. Here we test the final destination using
636
* mFinalURI off of the imgRequest and passing it into content policies.
637
* For Mixed Content Blocker, we do an additional check to determine if any
638
* of the intermediary hops went through an insecure redirect with the
639
* mHadInsecureRedirect flag
640
*/
641
bool insecureRedirect = aImgRequest->HadInsecureRedirect();
642
nsCOMPtr<nsIURI> contentLocation;
643
aImgRequest->GetFinalURI(getter_AddRefs(contentLocation));
644
nsresult rv;
645
646
nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aLoadingContext);
647
nsCOMPtr<nsIPrincipal> loadingPrincipal =
648
requestingNode ? requestingNode->NodePrincipal() : aTriggeringPrincipal;
649
// If there is no context and also no triggeringPrincipal, then we use a fresh
650
// nullPrincipal as the loadingPrincipal because we can not create a loadinfo
651
// without a valid loadingPrincipal.
652
if (!loadingPrincipal) {
653
loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
654
}
655
656
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
657
loadingPrincipal, aTriggeringPrincipal, requestingNode,
658
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, aPolicyType);
659
660
secCheckLoadInfo->SetSendCSPViolationEvents(aSendCSPViolationReports);
661
662
int16_t decision = nsIContentPolicy::REJECT_REQUEST;
663
rv = NS_CheckContentLoadPolicy(contentLocation, secCheckLoadInfo,
664
EmptyCString(), // mime guess
665
&decision, nsContentUtils::GetContentPolicy());
666
if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
667
return false;
668
}
669
670
// We call all Content Policies above, but we also have to call mcb
671
// individually to check the intermediary redirect hops are secure.
672
if (insecureRedirect) {
673
// Bug 1314356: If the image ended up in the cache upgraded by HSTS and the
674
// page uses upgrade-inscure-requests it had an insecure redirect
675
// (http->https). We need to invalidate the image and reload it because
676
// mixed content blocker only bails if upgrade-insecure-requests is set on
677
// the doc and the resource load is http: which would result in an incorrect
678
// mixed content warning.
679
nsCOMPtr<nsIDocShell> docShell =
680
NS_CP_GetDocShellFromContext(aLoadingContext);
681
if (docShell) {
682
Document* document = docShell->GetDocument();
683
if (document && document->GetUpgradeInsecureRequests(false)) {
684
return false;
685
}
686
}
687
688
if (!nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
689
// Set the requestingLocation from the aTriggeringPrincipal.
690
nsCOMPtr<nsIURI> requestingLocation;
691
if (aTriggeringPrincipal) {
692
rv = aTriggeringPrincipal->GetURI(getter_AddRefs(requestingLocation));
693
NS_ENSURE_SUCCESS(rv, false);
694
}
695
696
// reset the decision for mixed content blocker check
697
decision = nsIContentPolicy::REJECT_REQUEST;
698
rv = nsMixedContentBlocker::ShouldLoad(
699
insecureRedirect, aPolicyType, contentLocation, requestingLocation,
700
aLoadingContext,
701
EmptyCString(), // mime guess
702
aTriggeringPrincipal, &decision);
703
if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
704
return false;
705
}
706
}
707
}
708
709
return true;
710
}
711
712
// Returns true if this request is compatible with the given CORS mode on the
713
// given loading principal, and false if the request may not be reused due
714
// to CORS. Also checks the Referrer Policy, since requests with different
715
// referrers/policies may generate different responses.
716
static bool ValidateSecurityInfo(imgRequest* request, bool forcePrincipalCheck,
717
int32_t corsmode,
718
nsIPrincipal* triggeringPrincipal,
719
nsISupports* aCX,
720
nsContentPolicyType aPolicyType,
721
nsIReferrerInfo* aReferrerInfo) {
722
// If the referrer policy doesn't match, we can't use this request.
723
// XXX: Note that we only validate referrer policy, not referrerInfo object.
724
// We should do with referrerInfo object, but it will cause us to use more
725
// resources in the common case (the same policies but different original
726
// referrers).
727
// XXX: this will return false if an image has different referrer attributes,
728
// i.e. we currently don't use the cached image but reload the image with
729
// the new referrer policy bug 1174921
730
ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty;
731
if (aReferrerInfo) {
732
referrerPolicy = aReferrerInfo->ReferrerPolicy();
733
}
734
735
ReferrerPolicy requestReferrerPolicy = ReferrerPolicy::_empty;
736
if (request->GetReferrerInfo()) {
737
requestReferrerPolicy = request->GetReferrerInfo()->ReferrerPolicy();
738
}
739
740
if (referrerPolicy != requestReferrerPolicy) {
741
return false;
742
}
743
744
// If the entry's CORS mode doesn't match, or the CORS mode matches but the
745
// document principal isn't the same, we can't use this request.
746
if (request->GetCORSMode() != corsmode) {
747
return false;
748
}
749
if (request->GetCORSMode() != imgIRequest::CORS_NONE || forcePrincipalCheck) {
750
nsCOMPtr<nsIPrincipal> otherprincipal = request->GetTriggeringPrincipal();
751
752
// If we previously had a principal, but we don't now, we can't use this
753
// request.
754
if (otherprincipal && !triggeringPrincipal) {
755
return false;
756
}
757
758
if (otherprincipal && triggeringPrincipal) {
759
bool equals = false;
760
otherprincipal->Equals(triggeringPrincipal, &equals);
761
if (!equals) {
762
return false;
763
}
764
}
765
}
766
767
// Content Policy Check on Cached Images
768
return ShouldLoadCachedImage(request, aCX, triggeringPrincipal, aPolicyType,
769
/* aSendCSPViolationReports */ false);
770
}
771
772
static nsresult NewImageChannel(
773
nsIChannel** aResult,
774
// If aForcePrincipalCheckForCacheEntry is true, then we will
775
// force a principal check even when not using CORS before
776
// assuming we have a cache hit on a cache entry that we
777
// create for this channel. This is an out param that should
778
// be set to true if this channel ends up depending on
779
// aTriggeringPrincipal and false otherwise.
780
bool* aForcePrincipalCheckForCacheEntry, nsIURI* aURI,
781
nsIURI* aInitialDocumentURI, int32_t aCORSMode,
782
nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
783
const nsCString& aAcceptHeader, nsLoadFlags aLoadFlags,
784
nsContentPolicyType aPolicyType, nsIPrincipal* aTriggeringPrincipal,
785
nsISupports* aRequestingContext, bool aRespectPrivacy) {
786
MOZ_ASSERT(aResult);
787
788
nsresult rv;
789
nsCOMPtr<nsIHttpChannel> newHttpChannel;
790
791
nsCOMPtr<nsIInterfaceRequestor> callbacks;
792
793
if (aLoadGroup) {
794
// Get the notification callbacks from the load group for the new channel.
795
//
796
// XXX: This is not exactly correct, because the network request could be
797
// referenced by multiple windows... However, the new channel needs
798
// something. So, using the 'first' notification callbacks is better
799
// than nothing...
800
//
801
aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
802
}
803
804
// Pass in a nullptr loadgroup because this is the underlying network
805
// request. This request may be referenced by several proxy image requests
806
// (possibly in different documents).
807
// If all of the proxy requests are canceled then this request should be
808
// canceled too.
809
//
810
811
nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aRequestingContext);
812
813
nsSecurityFlags securityFlags =
814
aCORSMode == imgIRequest::CORS_NONE
815
? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
816
: nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
817
if (aCORSMode == imgIRequest::CORS_ANONYMOUS) {
818
securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
819
} else if (aCORSMode == imgIRequest::CORS_USE_CREDENTIALS) {
820
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
821
}
822
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
823
824
// Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a
825
// node and a principal. This is for things like background images that are
826
// specified by user stylesheets, where the document is being styled, but
827
// the principal is that of the user stylesheet.
828
if (requestingNode && aTriggeringPrincipal) {
829
rv = NS_NewChannelWithTriggeringPrincipal(aResult, aURI, requestingNode,
830
aTriggeringPrincipal,
831
securityFlags, aPolicyType,
832
nullptr, // PerformanceStorage
833
nullptr, // loadGroup
834
callbacks, aLoadFlags);
835
836
if (NS_FAILED(rv)) {
837
return rv;
838
}
839
840
if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
841
// If this is a favicon loading, we will use the originAttributes from the
842
// triggeringPrincipal as the channel's originAttributes. This allows the
843
// favicon loading from XUL will use the correct originAttributes.
844
845
nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
846
rv = loadInfo->SetOriginAttributes(
847
aTriggeringPrincipal->OriginAttributesRef());
848
}
849
} else {
850
// either we are loading something inside a document, in which case
851
// we should always have a requestingNode, or we are loading something
852
// outside a document, in which case the triggeringPrincipal and
853
// triggeringPrincipal should always be the systemPrincipal.
854
// However, there are exceptions: one is Notifications which create a
855
// channel in the parent process in which case we can't get a
856
// requestingNode.
857
rv = NS_NewChannel(aResult, aURI, nsContentUtils::GetSystemPrincipal(),
858
securityFlags, aPolicyType,
859
nullptr, // nsICookieSettings
860
nullptr, // PerformanceStorage
861
nullptr, // loadGroup
862
callbacks, aLoadFlags);
863
864
if (NS_FAILED(rv)) {
865
return rv;
866
}
867
868
// Use the OriginAttributes from the loading principal, if one is available,
869
// and adjust the private browsing ID based on what kind of load the caller
870
// has asked us to perform.
871
OriginAttributes attrs;
872
if (aTriggeringPrincipal) {
873
attrs = aTriggeringPrincipal->OriginAttributesRef();
874
}
875
attrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0;
876
877
nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
878
rv = loadInfo->SetOriginAttributes(attrs);
879
}
880
881
if (NS_FAILED(rv)) {
882
return rv;
883
}
884
885
// only inherit if we have a principal
886
*aForcePrincipalCheckForCacheEntry =
887
aTriggeringPrincipal && nsContentUtils::ChannelShouldInheritPrincipal(
888
aTriggeringPrincipal, aURI,
889
/* aInheritForAboutBlank */ false,
890
/* aForceInherit */ false);
891
892
// Initialize HTTP-specific attributes
893
newHttpChannel = do_QueryInterface(*aResult);
894
if (newHttpChannel) {
895
rv = newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
896
aAcceptHeader, false);
897
MOZ_ASSERT(NS_SUCCEEDED(rv));
898
899
nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
900
do_QueryInterface(newHttpChannel);
901
NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
902
rv = httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
903
MOZ_ASSERT(NS_SUCCEEDED(rv));
904
if (aReferrerInfo) {
905
DebugOnly<nsresult> rv = newHttpChannel->SetReferrerInfo(aReferrerInfo);
906
MOZ_ASSERT(NS_SUCCEEDED(rv));
907
}
908
}
909
910
// Image channels are loaded by default with reduced priority.
911
nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
912
if (p) {
913
uint32_t priority = nsISupportsPriority::PRIORITY_LOW;
914
915
if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
916
++priority; // further reduce priority for background loads
917
}
918
919
p->AdjustPriority(priority);
920
}
921
922
// Create a new loadgroup for this new channel, using the old group as
923
// the parent. The indirection keeps the channel insulated from cancels,
924
// but does allow a way for this revalidation to be associated with at
925
// least one base load group for scheduling/caching purposes.
926
927
nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
928
nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
929
if (childLoadGroup) {
930
childLoadGroup->SetParentLoadGroup(aLoadGroup);
931
}
932
(*aResult)->SetLoadGroup(loadGroup);
933
934
return NS_OK;
935
}
936
937
/* static */
938
uint32_t imgCacheEntry::SecondsFromPRTime(PRTime prTime) {
939
return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC));
940
}
941
942
imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request,
943
bool forcePrincipalCheck)
944
: mLoader(loader),
945
mRequest(request),
946
mDataSize(0),
947
mTouchedTime(SecondsFromPRTime(PR_Now())),
948
mLoadTime(SecondsFromPRTime(PR_Now())),
949
mExpiryTime(0),
950
mMustValidate(false),
951
// We start off as evicted so we don't try to update the cache.
952
// PutIntoCache will set this to false.
953
mEvicted(true),
954
mHasNoProxies(true),
955
mForcePrincipalCheck(forcePrincipalCheck) {}
956
957
imgCacheEntry::~imgCacheEntry() {
958
LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()");
959
}
960
961
void imgCacheEntry::Touch(bool updateTime /* = true */) {
962
LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");
963
964
if (updateTime) {
965
mTouchedTime = SecondsFromPRTime(PR_Now());
966
}
967
968
UpdateCache();
969
}
970
971
void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) {
972
// Don't update the cache if we've been removed from it or it doesn't care
973
// about our size or usage.
974
if (!Evicted() && HasNoProxies()) {
975
mLoader->CacheEntriesChanged(mRequest->IsChrome(), diff);
976
}
977
}
978
979
void imgCacheEntry::UpdateLoadTime() {
980
mLoadTime = SecondsFromPRTime(PR_Now());
981
}
982
983
void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) {
984
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
985
if (hasNoProxies) {
986
LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true", "uri",
987
mRequest->CacheKey().URI());
988
} else {
989
LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false",
990
"uri", mRequest->CacheKey().URI());
991
}
992
}
993
994
mHasNoProxies = hasNoProxies;
995
}
996
997
imgCacheQueue::imgCacheQueue() : mDirty(false), mSize(0) {}
998
999
void imgCacheQueue::UpdateSize(int32_t diff) { mSize += diff; }
1000
1001
uint32_t imgCacheQueue::GetSize() const { return mSize; }
1002
1003
void imgCacheQueue::Remove(imgCacheEntry* entry) {
1004
uint64_t index = mQueue.IndexOf(entry);
1005
if (index == queueContainer::NoIndex) {
1006
return;
1007
}
1008
1009
mSize -= mQueue[index]->GetDataSize();
1010
1011
// If the queue is clean and this is the first entry,
1012
// then we can efficiently remove the entry without
1013
// dirtying the sort order.
1014
if (!IsDirty() && index == 0) {
1015
std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1016
mQueue.RemoveLastElement();
1017
return;
1018
}
1019
1020
// Remove from the middle of the list. This potentially
1021
// breaks the binary heap sort order.
1022
mQueue.RemoveElementAt(index);
1023
1024
// If we only have one entry or the queue is empty, though,
1025
// then the sort order is still effectively good. Simply
1026
// refresh the list to clear the dirty flag.
1027
if (mQueue.Length() <= 1) {
1028
Refresh();
1029
return;
1030
}
1031
1032
// Otherwise we must mark the queue dirty and potentially
1033
// trigger an expensive sort later.
1034
MarkDirty();
1035
}
1036
1037
void imgCacheQueue::Push(imgCacheEntry* entry) {
1038
mSize += entry->GetDataSize();
1039
1040
RefPtr<imgCacheEntry> refptr(entry);
1041
mQueue.AppendElement(std::move(refptr));
1042
// If we're not dirty already, then we can efficiently add this to the
1043
// binary heap immediately. This is only O(log n).
1044
if (!IsDirty()) {
1045
std::push_heap(mQueue.begin(), mQueue.end(),
1046
imgLoader::CompareCacheEntries);
1047
}
1048
}
1049
1050
already_AddRefed<imgCacheEntry> imgCacheQueue::Pop() {
1051
if (mQueue.IsEmpty()) {
1052
return nullptr;
1053
}
1054
if (IsDirty()) {
1055
Refresh();
1056
}
1057
1058
std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1059
RefPtr<imgCacheEntry> entry = mQueue.PopLastElement();
1060
1061
mSize -= entry->GetDataSize();
1062
return entry.forget();
1063
}
1064
1065
void imgCacheQueue::Refresh() {
1066
// Resort the list. This is an O(3 * n) operation and best avoided
1067
// if possible.
1068
std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1069
mDirty = false;
1070
}
1071
1072
void imgCacheQueue::MarkDirty() { mDirty = true; }
1073
1074
bool imgCacheQueue::IsDirty() { return mDirty; }
1075
1076
uint32_t imgCacheQueue::GetNumElements() const { return mQueue.Length(); }
1077
1078
bool imgCacheQueue::Contains(imgCacheEntry* aEntry) const {
1079
return mQueue.Contains(aEntry);
1080
}
1081
1082
imgCacheQueue::iterator imgCacheQueue::begin() { return mQueue.begin(); }
1083
1084
imgCacheQueue::const_iterator imgCacheQueue::begin() const {
1085
return mQueue.begin();
1086
}
1087
1088
imgCacheQueue::iterator imgCacheQueue::end() { return mQueue.end(); }
1089
1090
imgCacheQueue::const_iterator imgCacheQueue::end() const {
1091
return mQueue.end();
1092
}
1093
1094
nsresult imgLoader::CreateNewProxyForRequest(
1095
imgRequest* aRequest, nsILoadGroup* aLoadGroup, Document* aLoadingDocument,
1096
imgINotificationObserver* aObserver, nsLoadFlags aLoadFlags,
1097
imgRequestProxy** _retval) {
1098
LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest",
1099
"imgRequest", aRequest);
1100
1101
/* XXX If we move decoding onto separate threads, we should save off the
1102
calling thread here and pass it off to |proxyRequest| so that it call
1103
proxy calls to |aObserver|.
1104
*/
1105
1106
RefPtr<imgRequestProxy> proxyRequest = new imgRequestProxy();
1107
1108
/* It is important to call |SetLoadFlags()| before calling |Init()| because
1109
|Init()| adds the request to the loadgroup.
1110
*/
1111
proxyRequest->SetLoadFlags(aLoadFlags);
1112
1113
nsCOMPtr<nsIURI> uri;
1114
aRequest->GetURI(getter_AddRefs(uri));
1115
1116
// init adds itself to imgRequest's list of observers
1117
nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aLoadingDocument, uri,
1118
aObserver);
1119
if (NS_WARN_IF(NS_FAILED(rv))) {
1120
return rv;
1121
}
1122
1123
proxyRequest.forget(_retval);
1124
return NS_OK;
1125
}
1126
1127
class imgCacheExpirationTracker final
1128
: public nsExpirationTracker<imgCacheEntry, 3> {
1129
enum { TIMEOUT_SECONDS = 10 };
1130
1131
public:
1132
imgCacheExpirationTracker();
1133
1134
protected:
1135
void NotifyExpired(imgCacheEntry* entry) override;
1136
};
1137
1138
imgCacheExpirationTracker::imgCacheExpirationTracker()
1139
: nsExpirationTracker<imgCacheEntry, 3>(
1140
TIMEOUT_SECONDS * 1000, "imgCacheExpirationTracker",
1141
SystemGroup::EventTargetFor(TaskCategory::Other)) {}
1142
1143
void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry) {
1144
// Hold on to a reference to this entry, because the expiration tracker
1145
// mechanism doesn't.
1146
RefPtr<imgCacheEntry> kungFuDeathGrip(entry);
1147
1148
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1149
RefPtr<imgRequest> req = entry->GetRequest();
1150
if (req) {
1151
LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheExpirationTracker::NotifyExpired",
1152
"entry", req->CacheKey().URI());
1153
}
1154
}
1155
1156
// We can be called multiple times on the same entry. Don't do work multiple
1157
// times.
1158
if (!entry->Evicted()) {
1159
entry->Loader()->RemoveFromCache(entry);
1160
}
1161
1162
entry->Loader()->VerifyCacheSizes();
1163
}
1164
1165
///////////////////////////////////////////////////////////////////////////////
1166
// imgLoader
1167
///////////////////////////////////////////////////////////////////////////////
1168
1169
double imgLoader::sCacheTimeWeight;
1170
uint32_t imgLoader::sCacheMaxSize;
1171
imgMemoryReporter* imgLoader::sMemReporter;
1172
1173
NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache,
1174
nsISupportsWeakReference, nsIObserver)
1175
1176
static imgLoader* gNormalLoader = nullptr;
1177
static imgLoader* gPrivateBrowsingLoader = nullptr;
1178
1179
/* static */
1180
already_AddRefed<imgLoader> imgLoader::CreateImageLoader() {
1181
// In some cases, such as xpctests, XPCOM modules are not automatically
1182
// initialized. We need to make sure that our module is initialized before
1183
// we hand out imgLoader instances and code starts using them.
1184
mozilla::image::EnsureModuleInitialized();
1185
1186
RefPtr<imgLoader> loader = new imgLoader();
1187
loader->Init();
1188
1189
return loader.forget();
1190
}
1191
1192
imgLoader* imgLoader::NormalLoader() {
1193
if (!gNormalLoader) {
1194
gNormalLoader = CreateImageLoader().take();
1195
}
1196
return gNormalLoader;
1197
}
1198
1199
imgLoader* imgLoader::PrivateBrowsingLoader() {
1200
if (!gPrivateBrowsingLoader) {
1201
gPrivateBrowsingLoader = CreateImageLoader().take();
1202
gPrivateBrowsingLoader->RespectPrivacyNotifications();
1203
}
1204
return gPrivateBrowsingLoader;
1205
}
1206
1207
imgLoader::imgLoader()
1208
: mUncachedImagesMutex("imgLoader::UncachedImages"),
1209
mRespectPrivacy(false) {
1210
sMemReporter->AddRef();
1211
sMemReporter->RegisterLoader(this);
1212
}
1213
1214
imgLoader::~imgLoader() {
1215
ClearChromeImageCache();
1216
ClearImageCache();
1217
{
1218
// If there are any of our imgRequest's left they are in the uncached
1219
// images set, so clear their pointer to us.
1220
MutexAutoLock lock(mUncachedImagesMutex);
1221
for (auto iter = mUncachedImages.Iter(); !iter.Done(); iter.Next()) {
1222
nsPtrHashKey<imgRequest>* entry = iter.Get();
1223
RefPtr<imgRequest> req = entry->GetKey();
1224
req->ClearLoader();
1225
}
1226
}
1227
sMemReporter->UnregisterLoader(this);
1228
sMemReporter->Release();
1229
}
1230
1231
void imgLoader::VerifyCacheSizes() {
1232
#ifdef DEBUG
1233
if (!mCacheTracker) {
1234
return;
1235
}
1236
1237
uint32_t cachesize = mCache.Count() + mChromeCache.Count();
1238
uint32_t queuesize =
1239
mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
1240
uint32_t trackersize = 0;
1241
for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get());
1242
it.Next();) {
1243
trackersize++;
1244
}
1245
MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!");
1246
MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!");
1247
#endif
1248
}
1249
1250
imgLoader::imgCacheTable& imgLoader::GetCache(bool aForChrome) {
1251
return aForChrome ? mChromeCache : mCache;
1252
}
1253
1254
imgLoader::imgCacheTable& imgLoader::GetCache(const ImageCacheKey& aKey) {
1255
return GetCache(aKey.IsChrome());
1256
}
1257
1258
imgCacheQueue& imgLoader::GetCacheQueue(bool aForChrome) {
1259
return aForChrome ? mChromeCacheQueue : mCacheQueue;
1260
}
1261
1262
imgCacheQueue& imgLoader::GetCacheQueue(const ImageCacheKey& aKey) {
1263
return GetCacheQueue(aKey.IsChrome());
1264
}
1265
1266
void imgLoader::GlobalInit() {
1267
sCacheTimeWeight = StaticPrefs::image_cache_timeweight_AtStartup() / 1000.0;
1268
int32_t cachesize = StaticPrefs::image_cache_size_AtStartup();
1269
sCacheMaxSize = cachesize > 0 ? cachesize : 0;
1270
1271
sMemReporter = new imgMemoryReporter();
1272
RegisterStrongAsyncMemoryReporter(sMemReporter);
1273
RegisterImagesContentUsedUncompressedDistinguishedAmount(
1274
imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
1275
}
1276
1277
void imgLoader::ShutdownMemoryReporter() {
1278
UnregisterImagesContentUsedUncompressedDistinguishedAmount();
1279
UnregisterStrongMemoryReporter(sMemReporter);
1280
}
1281
1282
nsresult imgLoader::InitCache() {
1283
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1284
if (!os) {
1285
return NS_ERROR_FAILURE;
1286
}
1287
1288
os->AddObserver(this, "memory-pressure", false);
1289
os->AddObserver(this, "chrome-flush-caches", false);
1290
os->AddObserver(this, "last-pb-context-exited", false);
1291
os->AddObserver(this, "profile-before-change", false);
1292
os->AddObserver(this, "xpcom-shutdown", false);
1293
1294
mCacheTracker = MakeUnique<imgCacheExpirationTracker>();
1295
1296
return NS_OK;
1297
}
1298
1299
nsresult imgLoader::Init() {
1300
InitCache();
1301
1302
ReadAcceptHeaderPref();
1303
1304
Preferences::AddWeakObserver(this, "image.http.accept");
1305
1306
return NS_OK;
1307
}
1308
1309
NS_IMETHODIMP
1310
imgLoader::RespectPrivacyNotifications() {
1311
mRespectPrivacy = true;
1312
return NS_OK;
1313
}
1314
1315
NS_IMETHODIMP
1316
imgLoader::Observe(nsISupports* aSubject, const char* aTopic,
1317
const char16_t* aData) {
1318
// We listen for pref change notifications...
1319
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1320
if (!NS_strcmp(aData, u"image.http.accept")) {
1321
ReadAcceptHeaderPref();
1322
}
1323
1324
} else if (strcmp(aTopic, "memory-pressure") == 0) {
1325
MinimizeCaches();
1326
} else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1327
MinimizeCaches();
1328
ClearChromeImageCache();
1329
} else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
1330
if (mRespectPrivacy) {
1331
ClearImageCache();
1332
ClearChromeImageCache();
1333
}
1334
} else if (strcmp(aTopic, "profile-before-change") == 0) {
1335
mCacheTracker = nullptr;
1336
} else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
1337
mCacheTracker = nullptr;
1338
ShutdownMemoryReporter();
1339
1340
} else {
1341
// (Nothing else should bring us here)
1342
MOZ_ASSERT(0, "Invalid topic received");
1343
}
1344
1345
return NS_OK;
1346
}
1347
1348
void imgLoader::ReadAcceptHeaderPref() {
1349
nsAutoCString accept;
1350
nsresult rv = Preferences::GetCString("image.http.accept", accept);
1351
if (NS_SUCCEEDED(rv)) {
1352
mAcceptHeader = accept;
1353
} else {
1354
mAcceptHeader =
1355
IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5";
1356
}
1357
}
1358
1359
NS_IMETHODIMP
1360
imgLoader::ClearCache(bool chrome) {
1361
if (XRE_IsParentProcess()) {
1362
bool privateLoader = this == gPrivateBrowsingLoader;
1363
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
1364
Unused << cp->SendClearImageCache(privateLoader, chrome);
1365
}
1366
}
1367
1368
if (chrome) {
1369
return ClearChromeImageCache();
1370
}
1371
return ClearImageCache();
1372
}
1373
1374
NS_IMETHODIMP
1375
imgLoader::RemoveEntriesFromPrincipal(nsIPrincipal* aPrincipal) {
1376
nsAutoString origin;
1377
nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, origin);
1378
if (NS_WARN_IF(NS_FAILED(rv))) {
1379
return rv;
1380
}
1381
1382
AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1383
1384
imgCacheTable& cache =
1385
GetCache(nsContentUtils::IsSystemPrincipal(aPrincipal));
1386
for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) {
1387
auto& key = iter.Key();
1388
1389
if (key.OriginAttributesRef() !=
1390
BasePrincipal::Cast(aPrincipal)->OriginAttributesRef()) {
1391
continue;
1392
}
1393
1394
nsAutoString imageOrigin;
1395
nsresult rv = nsContentUtils::GetUTFOrigin(key.URI(), imageOrigin);
1396
if (NS_WARN_IF(NS_FAILED(rv))) {
1397
continue;
1398
}
1399
1400
if (imageOrigin == origin) {
1401
entriesToBeRemoved.AppendElement(iter.Data());
1402
}
1403
}
1404
1405
for (auto& entry : entriesToBeRemoved) {
1406
if (!RemoveFromCache(entry)) {
1407
NS_WARNING(
1408
"Couldn't remove an entry from the cache in "
1409
"RemoveEntriesFromPrincipal()\n");
1410
}
1411
}
1412
1413
return NS_OK;
1414
}
1415
1416
NS_IMETHODIMP
1417
imgLoader::RemoveEntry(nsIURI* aURI, Document* aDoc) {
1418
if (aURI) {
1419
OriginAttributes attrs;
1420
if (aDoc) {
1421
nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1422
if (principal) {
1423
attrs = principal->OriginAttributesRef();
1424
}
1425
}
1426
1427
ImageCacheKey key(aURI, attrs, aDoc);
1428
if (RemoveFromCache(key)) {
1429
return NS_OK;
1430
}
1431
}
1432
return NS_ERROR_NOT_AVAILABLE;
1433
}
1434
1435
NS_IMETHODIMP
1436
imgLoader::FindEntryProperties(nsIURI* uri, Document* aDoc,
1437
nsIProperties** _retval) {
1438
*_retval = nullptr;
1439
1440
OriginAttributes attrs;
1441
if (aDoc) {
1442
nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1443
if (principal) {
1444
attrs = principal->OriginAttributesRef();
1445
}
1446
}
1447
1448
ImageCacheKey key(uri, attrs, aDoc);
1449
imgCacheTable& cache = GetCache(key);
1450
1451
RefPtr<imgCacheEntry> entry;
1452
if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1453
if (mCacheTracker && entry->HasNoProxies()) {
1454
mCacheTracker->MarkUsed(entry);
1455
}
1456
1457
RefPtr<imgRequest> request = entry->GetRequest();
1458
if (request) {
1459
nsCOMPtr<nsIProperties> properties = request->Properties();
1460
properties.forget(_retval);
1461
}
1462
}
1463
1464
return NS_OK;
1465
}
1466
1467
NS_IMETHODIMP_(void)
1468
imgLoader::ClearCacheForControlledDocument(Document* aDoc) {
1469
MOZ_ASSERT(aDoc);
1470
AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1471
imgCacheTable& cache = GetCache(false);
1472
for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) {
1473
auto& key = iter.Key();
1474
if (key.ControlledDocument() == aDoc) {
1475
entriesToBeRemoved.AppendElement(iter.Data());
1476
}
1477
}
1478
for (auto& entry : entriesToBeRemoved) {
1479
if (!RemoveFromCache(entry)) {
1480
NS_WARNING(
1481
"Couldn't remove an entry from the cache in "
1482
"ClearCacheForControlledDocument()\n");
1483
}
1484
}
1485
}
1486
1487
void imgLoader::Shutdown() {
1488
NS_IF_RELEASE(gNormalLoader);
1489
gNormalLoader = nullptr;
1490
NS_IF_RELEASE(gPrivateBrowsingLoader);
1491
gPrivateBrowsingLoader = nullptr;
1492
}
1493
1494
nsresult imgLoader::ClearChromeImageCache() {
1495
return EvictEntries(mChromeCache);
1496
}
1497
1498
nsresult imgLoader::ClearImageCache() { return EvictEntries(mCache); }
1499
1500
void imgLoader::MinimizeCaches() {
1501
EvictEntries(mCacheQueue);
1502
EvictEntries(mChromeCacheQueue);
1503
}
1504
1505
bool imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) {
1506
imgCacheTable& cache = GetCache(aKey);
1507
1508
LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::PutIntoCache", "uri",
1509
aKey.URI());
1510
1511
// Check to see if this request already exists in the cache. If so, we'll
1512
// replace the old version.
1513
RefPtr<imgCacheEntry> tmpCacheEntry;
1514
if (cache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
1515
MOZ_LOG(
1516
gImgLog, LogLevel::Debug,
1517
("[this=%p] imgLoader::PutIntoCache -- Element already in the cache",
1518
nullptr));
1519
RefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();
1520
1521
// If it already exists, and we're putting the same key into the cache, we
1522
// should remove the old version.
1523
MOZ_LOG(gImgLog, LogLevel::Debug,
1524
("[this=%p] imgLoader::PutIntoCache -- Replacing cached element",
1525
nullptr));
1526
1527
RemoveFromCache(aKey);
1528
} else {
1529
MOZ_LOG(gImgLog, LogLevel::Debug,
1530
("[this=%p] imgLoader::PutIntoCache --"
1531
" Element NOT already in the cache",
1532
nullptr));
1533
}
1534
1535
cache.Put(aKey, entry);
1536
1537
// We can be called to resurrect an evicted entry.
1538
if (entry->Evicted()) {
1539
entry->SetEvicted(false);
1540
}
1541
1542
// If we're resurrecting an entry with no proxies, put it back in the
1543
// tracker and queue.
1544
if (entry->HasNoProxies()) {
1545
nsresult addrv = NS_OK;
1546
1547
if (mCacheTracker) {
1548
addrv = mCacheTracker->AddObject(entry);
1549
}
1550
1551
if (NS_SUCCEEDED(addrv)) {
1552
imgCacheQueue& queue = GetCacheQueue(aKey);
1553
queue.Push(entry);
1554
}
1555
}
1556
1557
RefPtr<imgRequest> request = entry->GetRequest();
1558
request->SetIsInCache(true);
1559
RemoveFromUncachedImages(request);
1560
1561
return true;
1562
}
1563
1564
bool imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry) {
1565
LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasNoProxies", "uri",
1566
aRequest->CacheKey().URI());
1567
1568
aEntry->SetHasNoProxies(true);
1569
1570
if (aEntry->Evicted()) {
1571
return false;
1572
}
1573
1574
imgCacheQueue& queue = GetCacheQueue(aRequest->IsChrome());
1575
1576
nsresult addrv = NS_OK;
1577
1578
if (mCacheTracker) {
1579
addrv = mCacheTracker->AddObject(aEntry);
1580
}
1581
1582
if (NS_SUCCEEDED(addrv)) {
1583
queue.Push(aEntry);
1584
}
1585
1586
imgCacheTable& cache = GetCache(aRequest->IsChrome());
1587
CheckCacheLimits(cache, queue);
1588
1589
return true;
1590
}
1591
1592
bool imgLoader::SetHasProxies(imgRequest* aRequest) {
1593
VerifyCacheSizes();
1594
1595
const ImageCacheKey& key = aRequest->CacheKey();
1596
imgCacheTable& cache = GetCache(key);
1597
1598
LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasProxies", "uri",
1599
key.URI());
1600
1601
RefPtr<imgCacheEntry> entry;
1602
if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1603
// Make sure the cache entry is for the right request
1604
RefPtr<imgRequest> entryRequest = entry->GetRequest();
1605
if (entryRequest == aRequest && entry->HasNoProxies()) {
1606
imgCacheQueue& queue = GetCacheQueue(key);
1607
queue.Remove(entry);
1608
1609
if (mCacheTracker) {
1610
mCacheTracker->RemoveObject(entry);
1611
}
1612
1613
entry->SetHasNoProxies(false);
1614
1615
return true;
1616
}
1617
}
1618
1619
return false;
1620
}
1621
1622
void imgLoader::CacheEntriesChanged(bool aForChrome,
1623
int32_t aSizeDiff /* = 0 */) {
1624
imgCacheQueue& queue = GetCacheQueue(aForChrome);
1625
// We only need to dirty the queue if there is any sorting
1626
// taking place. Empty or single-entry lists can't become
1627
// dirty.
1628
if (queue.GetNumElements() > 1) {
1629
queue.MarkDirty();
1630
}
1631
queue.UpdateSize(aSizeDiff);
1632
}
1633
1634
void imgLoader::CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue) {
1635
if (queue.GetNumElements() == 0) {
1636
NS_ASSERTION(queue.GetSize() == 0,
1637
"imgLoader::CheckCacheLimits -- incorrect cache size");
1638
}
1639
1640
// Remove entries from the cache until we're back at our desired max size.
1641
while (queue.GetSize() > sCacheMaxSize) {
1642
// Remove the first entry in the queue.
1643
RefPtr<imgCacheEntry> entry(queue.Pop());
1644
1645
NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
1646
1647
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1648
RefPtr<imgRequest> req = entry->GetRequest();
1649
if (req) {
1650
LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::CheckCacheLimits",
1651
"entry", req->CacheKey().URI());
1652
}
1653
}
1654
1655
if (entry) {
1656
// We just popped this entry from the queue, so pass AlreadyRemoved
1657
// to avoid searching the queue again in RemoveFromCache.
1658
RemoveFromCache(entry, QueueState::AlreadyRemoved);
1659
}
1660
}
1661
}
1662
1663
bool imgLoader::ValidateRequestWithNewChannel(
1664
imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI,
1665
nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
1666
imgINotificationObserver* aObserver, nsISupports* aCX,
1667
Document* aLoadingDocument, uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
1668
nsContentPolicyType aLoadPolicyType, imgRequestProxy** aProxyRequest,
1669
nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode,
1670
bool* aNewChannelCreated) {
1671
// now we need to insert a new channel request object in between the real
1672
// request and the proxy that basically delays loading the image until it
1673
// gets a 304 or figures out that this needs to be a new request
1674
1675
nsresult rv;
1676
1677
// If we're currently in the middle of validating this request, just hand
1678
// back a proxy to it; the required work will be done for us.
1679
if (request->GetValidator()) {
1680
rv = CreateNewProxyForRequest(request, aLoadGroup, aLoadingDocument,
1681
aObserver, aLoadFlags, aProxyRequest);
1682
if (NS_FAILED(rv)) {
1683
return false;
1684
}
1685
1686
if (*aProxyRequest) {
1687
imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
1688
1689
// We will send notifications from imgCacheValidator::OnStartRequest().
1690
// In the mean time, we must defer notifications because we are added to
1691
// the imgRequest's proxy list, and we can get extra notifications
1692
// resulting from methods such as StartDecoding(). See bug 579122.
1693
proxy->MarkValidating();
1694
1695
// Attach the proxy without notifying
1696
request->GetValidator()->AddProxy(proxy);
1697
}
1698
1699
return NS_SUCCEEDED(rv);
1700
}
1701
// We will rely on Necko to cache this request when it's possible, and to
1702
// tell imgCacheValidator::OnStartRequest whether the request came from its
1703
// cache.
1704
nsCOMPtr<nsIChannel> newChannel;
1705
bool forcePrincipalCheck;
1706
rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
1707
aInitialDocumentURI, aCORSMode, aReferrerInfo,
1708
aLoadGroup, mAcceptHeader, aLoadFlags, aLoadPolicyType,
1709
aTriggeringPrincipal, aCX, mRespectPrivacy);
1710
if (NS_FAILED(rv)) {
1711
return false;
1712
}
1713
1714
if (aNewChannelCreated) {
1715
*aNewChannelCreated = true;
1716
}
1717
1718
RefPtr<imgRequestProxy> req;
1719
rv = CreateNewProxyForRequest(request, aLoadGroup, aLoadingDocument,
1720
aObserver, aLoadFlags, getter_AddRefs(req));
1721
if (NS_FAILED(rv)) {
1722
return false;
1723
}
1724
1725
// Make sure that OnStatus/OnProgress calls have the right request set...
1726
RefPtr<nsProgressNotificationProxy> progressproxy =
1727
new nsProgressNotificationProxy(newChannel, req);
1728
if (!progressproxy) {
1729
return false;
1730
}
1731
1732
RefPtr<imgCacheValidator> hvc = new imgCacheValidator(
1733
progressproxy, this, request, aCX, aInnerWindowId, forcePrincipalCheck);
1734
1735
// Casting needed here to get past multiple inheritance.
1736
nsCOMPtr<nsIStreamListener> listener =
1737
do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc));
1738
NS_ENSURE_TRUE(listener, false);
1739
1740
// We must set the notification callbacks before setting up the
1741
// CORS listener, because that's also interested inthe
1742
// notification callbacks.
1743
newChannel->SetNotificationCallbacks(hvc);
1744
1745
request->SetValidator(hvc);
1746
1747
// We will send notifications from imgCacheValidator::OnStartRequest().
1748
// In the mean time, we must defer notifications because we are added to
1749
// the imgRequest's proxy list, and we can get extra notifications
1750
// resulting from methods such as StartDecoding(). See bug 579122.
1751
req->MarkValidating();
1752
1753
// Add the proxy without notifying
1754
hvc->AddProxy(req);
1755
1756
mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
1757
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1758
aLoadGroup);
1759
rv = newChannel->AsyncOpen(listener);
1760
if (NS_WARN_IF(NS_FAILED(rv))) {
1761
req->CancelAndForgetObserver(rv);
1762
return false;
1763
}
1764
1765
req.forget(aProxyRequest);
1766
return true;