Coverage Report

Created: 2018-09-25 14:53

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