|
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 #include "mozilla/Attributes.h" |
|
8 #include "mozilla/Preferences.h" |
|
9 #include "mozilla/ClearOnShutdown.h" |
|
10 |
|
11 #include "ImageLogging.h" |
|
12 #include "nsPrintfCString.h" |
|
13 #include "imgLoader.h" |
|
14 #include "imgRequestProxy.h" |
|
15 |
|
16 #include "nsCOMPtr.h" |
|
17 |
|
18 #include "nsContentUtils.h" |
|
19 #include "nsCrossSiteListenerProxy.h" |
|
20 #include "nsNetUtil.h" |
|
21 #include "nsMimeTypes.h" |
|
22 #include "nsStreamUtils.h" |
|
23 #include "nsIHttpChannel.h" |
|
24 #include "nsICachingChannel.h" |
|
25 #include "nsIInterfaceRequestor.h" |
|
26 #include "nsIProgressEventSink.h" |
|
27 #include "nsIChannelEventSink.h" |
|
28 #include "nsIAsyncVerifyRedirectCallback.h" |
|
29 #include "nsIFileURL.h" |
|
30 #include "nsCRT.h" |
|
31 #include "nsIDocument.h" |
|
32 #include "nsINetworkSeer.h" |
|
33 #include "nsIConsoleService.h" |
|
34 |
|
35 #include "nsIApplicationCache.h" |
|
36 #include "nsIApplicationCacheContainer.h" |
|
37 |
|
38 #include "nsIMemoryReporter.h" |
|
39 #include "Image.h" |
|
40 #include "DiscardTracker.h" |
|
41 |
|
42 // we want to explore making the document own the load group |
|
43 // so we can associate the document URI with the load group. |
|
44 // until this point, we have an evil hack: |
|
45 #include "nsIHttpChannelInternal.h" |
|
46 #include "nsILoadContext.h" |
|
47 #include "nsILoadGroupChild.h" |
|
48 |
|
49 using namespace mozilla; |
|
50 using namespace mozilla::image; |
|
51 |
|
52 MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf) |
|
53 |
|
54 class imgMemoryReporter MOZ_FINAL : public nsIMemoryReporter |
|
55 { |
|
56 public: |
|
57 NS_DECL_ISUPPORTS |
|
58 |
|
59 NS_IMETHOD CollectReports(nsIMemoryReporterCallback *callback, |
|
60 nsISupports *closure) |
|
61 { |
|
62 AllSizes chrome; |
|
63 AllSizes content; |
|
64 |
|
65 for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) { |
|
66 mKnownLoaders[i]->mChromeCache.EnumerateRead(EntryAllSizes, &chrome); |
|
67 mKnownLoaders[i]->mCache.EnumerateRead(EntryAllSizes, &content); |
|
68 } |
|
69 |
|
70 #define REPORT(_path, _kind, _amount, _desc) \ |
|
71 do { \ |
|
72 nsresult rv; \ |
|
73 rv = callback->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \ |
|
74 _kind, UNITS_BYTES, _amount, \ |
|
75 NS_LITERAL_CSTRING(_desc), closure); \ |
|
76 NS_ENSURE_SUCCESS(rv, rv); \ |
|
77 } while (0) |
|
78 |
|
79 REPORT("explicit/images/chrome/used/raw", |
|
80 KIND_HEAP, chrome.mUsedRaw, |
|
81 "Memory used by in-use chrome images (compressed data)."); |
|
82 |
|
83 REPORT("explicit/images/chrome/used/uncompressed-heap", |
|
84 KIND_HEAP, chrome.mUsedUncompressedHeap, |
|
85 "Memory used by in-use chrome images (uncompressed data)."); |
|
86 |
|
87 REPORT("explicit/images/chrome/used/uncompressed-nonheap", |
|
88 KIND_NONHEAP, chrome.mUsedUncompressedNonheap, |
|
89 "Memory used by in-use chrome images (uncompressed data)."); |
|
90 |
|
91 REPORT("explicit/images/chrome/unused/raw", |
|
92 KIND_HEAP, chrome.mUnusedRaw, |
|
93 "Memory used by not in-use chrome images (compressed data)."); |
|
94 |
|
95 REPORT("explicit/images/chrome/unused/uncompressed-heap", |
|
96 KIND_HEAP, chrome.mUnusedUncompressedHeap, |
|
97 "Memory used by not in-use chrome images (uncompressed data)."); |
|
98 |
|
99 REPORT("explicit/images/chrome/unused/uncompressed-nonheap", |
|
100 KIND_NONHEAP, chrome.mUnusedUncompressedNonheap, |
|
101 "Memory used by not in-use chrome images (uncompressed data)."); |
|
102 |
|
103 REPORT("explicit/images/content/used/raw", |
|
104 KIND_HEAP, content.mUsedRaw, |
|
105 "Memory used by in-use content images (compressed data)."); |
|
106 |
|
107 REPORT("explicit/images/content/used/uncompressed-heap", |
|
108 KIND_HEAP, content.mUsedUncompressedHeap, |
|
109 "Memory used by in-use content images (uncompressed data)."); |
|
110 |
|
111 REPORT("explicit/images/content/used/uncompressed-nonheap", |
|
112 KIND_NONHEAP, content.mUsedUncompressedNonheap, |
|
113 "Memory used by in-use content images (uncompressed data)."); |
|
114 |
|
115 REPORT("explicit/images/content/unused/raw", |
|
116 KIND_HEAP, content.mUnusedRaw, |
|
117 "Memory used by not in-use content images (compressed data)."); |
|
118 |
|
119 REPORT("explicit/images/content/unused/uncompressed-heap", |
|
120 KIND_HEAP, content.mUnusedUncompressedHeap, |
|
121 "Memory used by not in-use content images (uncompressed data)."); |
|
122 |
|
123 REPORT("explicit/images/content/unused/uncompressed-nonheap", |
|
124 KIND_NONHEAP, content.mUnusedUncompressedNonheap, |
|
125 "Memory used by not in-use content images (uncompressed data)."); |
|
126 |
|
127 #undef REPORT |
|
128 |
|
129 return NS_OK; |
|
130 } |
|
131 |
|
132 static int64_t ImagesContentUsedUncompressedDistinguishedAmount() |
|
133 { |
|
134 size_t n = 0; |
|
135 for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length(); i++) { |
|
136 imgLoader::sMemReporter->mKnownLoaders[i]->mCache.EnumerateRead(EntryUsedUncompressedSize, &n); |
|
137 } |
|
138 return n; |
|
139 } |
|
140 |
|
141 void RegisterLoader(imgLoader* aLoader) |
|
142 { |
|
143 mKnownLoaders.AppendElement(aLoader); |
|
144 } |
|
145 |
|
146 void UnregisterLoader(imgLoader* aLoader) |
|
147 { |
|
148 mKnownLoaders.RemoveElement(aLoader); |
|
149 } |
|
150 |
|
151 private: |
|
152 nsTArray<imgLoader*> mKnownLoaders; |
|
153 |
|
154 struct AllSizes { |
|
155 size_t mUsedRaw; |
|
156 size_t mUsedUncompressedHeap; |
|
157 size_t mUsedUncompressedNonheap; |
|
158 size_t mUnusedRaw; |
|
159 size_t mUnusedUncompressedHeap; |
|
160 size_t mUnusedUncompressedNonheap; |
|
161 |
|
162 AllSizes() { |
|
163 memset(this, 0, sizeof(*this)); |
|
164 } |
|
165 }; |
|
166 |
|
167 static PLDHashOperator EntryAllSizes(const nsACString&, |
|
168 imgCacheEntry *entry, |
|
169 void *userArg) |
|
170 { |
|
171 nsRefPtr<imgRequest> req = entry->GetRequest(); |
|
172 Image *image = static_cast<Image*>(req->mImage.get()); |
|
173 if (image) { |
|
174 AllSizes *sizes = static_cast<AllSizes*>(userArg); |
|
175 if (entry->HasNoProxies()) { |
|
176 sizes->mUnusedRaw += |
|
177 image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf); |
|
178 sizes->mUnusedUncompressedHeap += |
|
179 image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf); |
|
180 sizes->mUnusedUncompressedNonheap += image->NonHeapSizeOfDecoded(); |
|
181 } else { |
|
182 sizes->mUsedRaw += |
|
183 image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf); |
|
184 sizes->mUsedUncompressedHeap += |
|
185 image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf); |
|
186 sizes->mUsedUncompressedNonheap += image->NonHeapSizeOfDecoded(); |
|
187 } |
|
188 } |
|
189 |
|
190 return PL_DHASH_NEXT; |
|
191 } |
|
192 |
|
193 static PLDHashOperator EntryUsedUncompressedSize(const nsACString&, |
|
194 imgCacheEntry *entry, |
|
195 void *userArg) |
|
196 { |
|
197 if (!entry->HasNoProxies()) { |
|
198 size_t *n = static_cast<size_t*>(userArg); |
|
199 nsRefPtr<imgRequest> req = entry->GetRequest(); |
|
200 Image *image = static_cast<Image*>(req->mImage.get()); |
|
201 if (image) { |
|
202 // Both this and EntryAllSizes measure images-content-used-uncompressed |
|
203 // memory. This function's measurement is secondary -- the result |
|
204 // doesn't go in the "explicit" tree -- so we use moz_malloc_size_of |
|
205 // instead of ImagesMallocSizeOf to prevent DMD from seeing it reported |
|
206 // twice. |
|
207 *n += image->HeapSizeOfDecodedWithComputedFallback(moz_malloc_size_of); |
|
208 *n += image->NonHeapSizeOfDecoded(); |
|
209 } |
|
210 } |
|
211 |
|
212 return PL_DHASH_NEXT; |
|
213 } |
|
214 }; |
|
215 |
|
216 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter) |
|
217 |
|
218 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, |
|
219 nsIProgressEventSink, |
|
220 nsIChannelEventSink, |
|
221 nsIInterfaceRequestor) |
|
222 |
|
223 NS_IMETHODIMP |
|
224 nsProgressNotificationProxy::OnProgress(nsIRequest* request, |
|
225 nsISupports* ctxt, |
|
226 uint64_t progress, |
|
227 uint64_t progressMax) |
|
228 { |
|
229 nsCOMPtr<nsILoadGroup> loadGroup; |
|
230 request->GetLoadGroup(getter_AddRefs(loadGroup)); |
|
231 |
|
232 nsCOMPtr<nsIProgressEventSink> target; |
|
233 NS_QueryNotificationCallbacks(mOriginalCallbacks, |
|
234 loadGroup, |
|
235 NS_GET_IID(nsIProgressEventSink), |
|
236 getter_AddRefs(target)); |
|
237 if (!target) |
|
238 return NS_OK; |
|
239 return target->OnProgress(mImageRequest, ctxt, progress, progressMax); |
|
240 } |
|
241 |
|
242 NS_IMETHODIMP |
|
243 nsProgressNotificationProxy::OnStatus(nsIRequest* request, |
|
244 nsISupports* ctxt, |
|
245 nsresult status, |
|
246 const char16_t* statusArg) |
|
247 { |
|
248 nsCOMPtr<nsILoadGroup> loadGroup; |
|
249 request->GetLoadGroup(getter_AddRefs(loadGroup)); |
|
250 |
|
251 nsCOMPtr<nsIProgressEventSink> target; |
|
252 NS_QueryNotificationCallbacks(mOriginalCallbacks, |
|
253 loadGroup, |
|
254 NS_GET_IID(nsIProgressEventSink), |
|
255 getter_AddRefs(target)); |
|
256 if (!target) |
|
257 return NS_OK; |
|
258 return target->OnStatus(mImageRequest, ctxt, status, statusArg); |
|
259 } |
|
260 |
|
261 NS_IMETHODIMP |
|
262 nsProgressNotificationProxy::AsyncOnChannelRedirect(nsIChannel *oldChannel, |
|
263 nsIChannel *newChannel, |
|
264 uint32_t flags, |
|
265 nsIAsyncVerifyRedirectCallback *cb) |
|
266 { |
|
267 // Tell the original original callbacks about it too |
|
268 nsCOMPtr<nsILoadGroup> loadGroup; |
|
269 newChannel->GetLoadGroup(getter_AddRefs(loadGroup)); |
|
270 nsCOMPtr<nsIChannelEventSink> target; |
|
271 NS_QueryNotificationCallbacks(mOriginalCallbacks, |
|
272 loadGroup, |
|
273 NS_GET_IID(nsIChannelEventSink), |
|
274 getter_AddRefs(target)); |
|
275 if (!target) { |
|
276 cb->OnRedirectVerifyCallback(NS_OK); |
|
277 return NS_OK; |
|
278 } |
|
279 |
|
280 // Delegate to |target| if set, reusing |cb| |
|
281 return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb); |
|
282 } |
|
283 |
|
284 NS_IMETHODIMP |
|
285 nsProgressNotificationProxy::GetInterface(const nsIID& iid, |
|
286 void** result) |
|
287 { |
|
288 if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) { |
|
289 *result = static_cast<nsIProgressEventSink*>(this); |
|
290 NS_ADDREF_THIS(); |
|
291 return NS_OK; |
|
292 } |
|
293 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { |
|
294 *result = static_cast<nsIChannelEventSink*>(this); |
|
295 NS_ADDREF_THIS(); |
|
296 return NS_OK; |
|
297 } |
|
298 if (mOriginalCallbacks) |
|
299 return mOriginalCallbacks->GetInterface(iid, result); |
|
300 return NS_NOINTERFACE; |
|
301 } |
|
302 |
|
303 static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, imgLoader* aLoader, |
|
304 imgRequest **aRequest, imgCacheEntry **aEntry) |
|
305 { |
|
306 nsRefPtr<imgRequest> request = new imgRequest(aLoader); |
|
307 nsRefPtr<imgCacheEntry> entry = new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry); |
|
308 request.forget(aRequest); |
|
309 entry.forget(aEntry); |
|
310 } |
|
311 |
|
312 static bool ShouldRevalidateEntry(imgCacheEntry *aEntry, |
|
313 nsLoadFlags aFlags, |
|
314 bool aHasExpired) |
|
315 { |
|
316 bool bValidateEntry = false; |
|
317 |
|
318 if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) |
|
319 return false; |
|
320 |
|
321 if (aFlags & nsIRequest::VALIDATE_ALWAYS) { |
|
322 bValidateEntry = true; |
|
323 } |
|
324 else if (aEntry->GetMustValidate()) { |
|
325 bValidateEntry = true; |
|
326 } |
|
327 // |
|
328 // The cache entry has expired... Determine whether the stale cache |
|
329 // entry can be used without validation... |
|
330 // |
|
331 else if (aHasExpired) { |
|
332 // |
|
333 // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache |
|
334 // entries to be used unless they have been explicitly marked to |
|
335 // indicate that revalidation is necessary. |
|
336 // |
|
337 if (aFlags & (nsIRequest::VALIDATE_NEVER | |
|
338 nsIRequest::VALIDATE_ONCE_PER_SESSION)) |
|
339 { |
|
340 bValidateEntry = false; |
|
341 } |
|
342 // |
|
343 // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise, |
|
344 // the entry must be revalidated. |
|
345 // |
|
346 else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) { |
|
347 bValidateEntry = true; |
|
348 } |
|
349 } |
|
350 |
|
351 return bValidateEntry; |
|
352 } |
|
353 |
|
354 // Returns true if this request is compatible with the given CORS mode on the |
|
355 // given loading principal, and false if the request may not be reused due |
|
356 // to CORS. |
|
357 static bool |
|
358 ValidateCORSAndPrincipal(imgRequest* request, bool forcePrincipalCheck, |
|
359 int32_t corsmode, nsIPrincipal* loadingPrincipal) |
|
360 { |
|
361 // If the entry's CORS mode doesn't match, or the CORS mode matches but the |
|
362 // document principal isn't the same, we can't use this request. |
|
363 if (request->GetCORSMode() != corsmode) { |
|
364 return false; |
|
365 } else if (request->GetCORSMode() != imgIRequest::CORS_NONE || |
|
366 forcePrincipalCheck) { |
|
367 nsCOMPtr<nsIPrincipal> otherprincipal = request->GetLoadingPrincipal(); |
|
368 |
|
369 // If we previously had a principal, but we don't now, we can't use this |
|
370 // request. |
|
371 if (otherprincipal && !loadingPrincipal) { |
|
372 return false; |
|
373 } |
|
374 |
|
375 if (otherprincipal && loadingPrincipal) { |
|
376 bool equals = false; |
|
377 otherprincipal->Equals(loadingPrincipal, &equals); |
|
378 return equals; |
|
379 } |
|
380 } |
|
381 |
|
382 return true; |
|
383 } |
|
384 |
|
385 static nsresult NewImageChannel(nsIChannel **aResult, |
|
386 // If aForcePrincipalCheckForCacheEntry is |
|
387 // true, then we will force a principal check |
|
388 // even when not using CORS before assuming we |
|
389 // have a cache hit on a cache entry that we |
|
390 // create for this channel. This is an out |
|
391 // param that should be set to true if this |
|
392 // channel ends up depending on |
|
393 // aLoadingPrincipal and false otherwise. |
|
394 bool *aForcePrincipalCheckForCacheEntry, |
|
395 nsIURI *aURI, |
|
396 nsIURI *aFirstPartyIsolationURI, |
|
397 nsIURI *aReferringURI, |
|
398 nsILoadGroup *aLoadGroup, |
|
399 const nsCString& aAcceptHeader, |
|
400 nsLoadFlags aLoadFlags, |
|
401 nsIChannelPolicy *aPolicy, |
|
402 nsIPrincipal *aLoadingPrincipal) |
|
403 { |
|
404 nsresult rv; |
|
405 nsCOMPtr<nsIHttpChannel> newHttpChannel; |
|
406 |
|
407 nsCOMPtr<nsIInterfaceRequestor> callbacks; |
|
408 |
|
409 if (aLoadGroup) { |
|
410 // Get the notification callbacks from the load group for the new channel. |
|
411 // |
|
412 // XXX: This is not exactly correct, because the network request could be |
|
413 // referenced by multiple windows... However, the new channel needs |
|
414 // something. So, using the 'first' notification callbacks is better |
|
415 // than nothing... |
|
416 // |
|
417 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); |
|
418 } |
|
419 |
|
420 // Pass in a nullptr loadgroup because this is the underlying network |
|
421 // request. This request may be referenced by several proxy image requests |
|
422 // (possibly in different documents). |
|
423 // If all of the proxy requests are canceled then this request should be |
|
424 // canceled too. |
|
425 // |
|
426 rv = NS_NewChannel(aResult, |
|
427 aURI, // URI |
|
428 nullptr, // Cached IOService |
|
429 nullptr, // LoadGroup |
|
430 callbacks, // Notification Callbacks |
|
431 aLoadFlags, |
|
432 aPolicy); |
|
433 if (NS_FAILED(rv)) |
|
434 return rv; |
|
435 |
|
436 *aForcePrincipalCheckForCacheEntry = false; |
|
437 |
|
438 // Initialize HTTP-specific attributes |
|
439 newHttpChannel = do_QueryInterface(*aResult); |
|
440 if (newHttpChannel) { |
|
441 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), |
|
442 aAcceptHeader, |
|
443 false); |
|
444 |
|
445 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(newHttpChannel); |
|
446 NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED); |
|
447 httpChannelInternal->SetDocumentURI(aFirstPartyIsolationURI); |
|
448 newHttpChannel->SetReferrer(aReferringURI); |
|
449 } |
|
450 |
|
451 // Image channels are loaded by default with reduced priority. |
|
452 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult); |
|
453 if (p) { |
|
454 uint32_t priority = nsISupportsPriority::PRIORITY_LOW; |
|
455 |
|
456 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) |
|
457 ++priority; // further reduce priority for background loads |
|
458 |
|
459 p->AdjustPriority(priority); |
|
460 } |
|
461 |
|
462 bool setOwner = nsContentUtils::SetUpChannelOwner(aLoadingPrincipal, |
|
463 *aResult, aURI, false); |
|
464 *aForcePrincipalCheckForCacheEntry = setOwner; |
|
465 |
|
466 // Create a new loadgroup for this new channel, using the old group as |
|
467 // the parent. The indirection keeps the channel insulated from cancels, |
|
468 // but does allow a way for this revalidation to be associated with at |
|
469 // least one base load group for scheduling/caching purposes. |
|
470 |
|
471 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); |
|
472 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup); |
|
473 if (childLoadGroup) { |
|
474 childLoadGroup->SetParentLoadGroup(aLoadGroup); |
|
475 } |
|
476 (*aResult)->SetLoadGroup(loadGroup); |
|
477 |
|
478 return NS_OK; |
|
479 } |
|
480 |
|
481 static uint32_t SecondsFromPRTime(PRTime prTime) |
|
482 { |
|
483 return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC)); |
|
484 } |
|
485 |
|
486 imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest *request, bool forcePrincipalCheck) |
|
487 : mLoader(loader), |
|
488 mRequest(request), |
|
489 mDataSize(0), |
|
490 mTouchedTime(SecondsFromPRTime(PR_Now())), |
|
491 mExpiryTime(0), |
|
492 mMustValidate(false), |
|
493 // We start off as evicted so we don't try to update the cache. PutIntoCache |
|
494 // will set this to false. |
|
495 mEvicted(true), |
|
496 mHasNoProxies(true), |
|
497 mForcePrincipalCheck(forcePrincipalCheck) |
|
498 {} |
|
499 |
|
500 imgCacheEntry::~imgCacheEntry() |
|
501 { |
|
502 LOG_FUNC(GetImgLog(), "imgCacheEntry::~imgCacheEntry()"); |
|
503 } |
|
504 |
|
505 void imgCacheEntry::Touch(bool updateTime /* = true */) |
|
506 { |
|
507 LOG_SCOPE(GetImgLog(), "imgCacheEntry::Touch"); |
|
508 |
|
509 if (updateTime) |
|
510 mTouchedTime = SecondsFromPRTime(PR_Now()); |
|
511 |
|
512 UpdateCache(); |
|
513 } |
|
514 |
|
515 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) |
|
516 { |
|
517 // Don't update the cache if we've been removed from it or it doesn't care |
|
518 // about our size or usage. |
|
519 if (!Evicted() && HasNoProxies()) { |
|
520 nsRefPtr<ImageURL> uri; |
|
521 mRequest->GetURI(getter_AddRefs(uri)); |
|
522 mLoader->CacheEntriesChanged(uri, diff); |
|
523 } |
|
524 } |
|
525 |
|
526 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) |
|
527 { |
|
528 #if defined(PR_LOGGING) |
|
529 nsRefPtr<ImageURL> uri; |
|
530 mRequest->GetURI(getter_AddRefs(uri)); |
|
531 nsAutoCString spec; |
|
532 if (uri) |
|
533 uri->GetSpec(spec); |
|
534 if (hasNoProxies) |
|
535 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies true", "uri", spec.get()); |
|
536 else |
|
537 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies false", "uri", spec.get()); |
|
538 #endif |
|
539 |
|
540 mHasNoProxies = hasNoProxies; |
|
541 } |
|
542 |
|
543 imgCacheQueue::imgCacheQueue() |
|
544 : mDirty(false), |
|
545 mSize(0) |
|
546 {} |
|
547 |
|
548 void imgCacheQueue::UpdateSize(int32_t diff) |
|
549 { |
|
550 mSize += diff; |
|
551 } |
|
552 |
|
553 uint32_t imgCacheQueue::GetSize() const |
|
554 { |
|
555 return mSize; |
|
556 } |
|
557 |
|
558 #include <algorithm> |
|
559 using namespace std; |
|
560 |
|
561 void imgCacheQueue::Remove(imgCacheEntry *entry) |
|
562 { |
|
563 queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry); |
|
564 if (it != mQueue.end()) { |
|
565 mSize -= (*it)->GetDataSize(); |
|
566 mQueue.erase(it); |
|
567 MarkDirty(); |
|
568 } |
|
569 } |
|
570 |
|
571 void imgCacheQueue::Push(imgCacheEntry *entry) |
|
572 { |
|
573 mSize += entry->GetDataSize(); |
|
574 |
|
575 nsRefPtr<imgCacheEntry> refptr(entry); |
|
576 mQueue.push_back(refptr); |
|
577 MarkDirty(); |
|
578 } |
|
579 |
|
580 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop() |
|
581 { |
|
582 if (mQueue.empty()) |
|
583 return nullptr; |
|
584 if (IsDirty()) |
|
585 Refresh(); |
|
586 |
|
587 nsRefPtr<imgCacheEntry> entry = mQueue[0]; |
|
588 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); |
|
589 mQueue.pop_back(); |
|
590 |
|
591 mSize -= entry->GetDataSize(); |
|
592 return entry.forget(); |
|
593 } |
|
594 |
|
595 void imgCacheQueue::Refresh() |
|
596 { |
|
597 std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); |
|
598 mDirty = false; |
|
599 } |
|
600 |
|
601 void imgCacheQueue::MarkDirty() |
|
602 { |
|
603 mDirty = true; |
|
604 } |
|
605 |
|
606 bool imgCacheQueue::IsDirty() |
|
607 { |
|
608 return mDirty; |
|
609 } |
|
610 |
|
611 uint32_t imgCacheQueue::GetNumElements() const |
|
612 { |
|
613 return mQueue.size(); |
|
614 } |
|
615 |
|
616 imgCacheQueue::iterator imgCacheQueue::begin() |
|
617 { |
|
618 return mQueue.begin(); |
|
619 } |
|
620 imgCacheQueue::const_iterator imgCacheQueue::begin() const |
|
621 { |
|
622 return mQueue.begin(); |
|
623 } |
|
624 |
|
625 imgCacheQueue::iterator imgCacheQueue::end() |
|
626 { |
|
627 return mQueue.end(); |
|
628 } |
|
629 imgCacheQueue::const_iterator imgCacheQueue::end() const |
|
630 { |
|
631 return mQueue.end(); |
|
632 } |
|
633 |
|
634 nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup, |
|
635 imgINotificationObserver *aObserver, |
|
636 nsLoadFlags aLoadFlags, imgRequestProxy **_retval) |
|
637 { |
|
638 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest); |
|
639 |
|
640 /* XXX If we move decoding onto separate threads, we should save off the |
|
641 calling thread here and pass it off to |proxyRequest| so that it call |
|
642 proxy calls to |aObserver|. |
|
643 */ |
|
644 |
|
645 imgRequestProxy *proxyRequest = new imgRequestProxy(); |
|
646 NS_ADDREF(proxyRequest); |
|
647 |
|
648 /* It is important to call |SetLoadFlags()| before calling |Init()| because |
|
649 |Init()| adds the request to the loadgroup. |
|
650 */ |
|
651 proxyRequest->SetLoadFlags(aLoadFlags); |
|
652 |
|
653 nsRefPtr<ImageURL> uri; |
|
654 aRequest->GetURI(getter_AddRefs(uri)); |
|
655 |
|
656 // init adds itself to imgRequest's list of observers |
|
657 nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, uri, aObserver); |
|
658 if (NS_FAILED(rv)) { |
|
659 NS_RELEASE(proxyRequest); |
|
660 return rv; |
|
661 } |
|
662 |
|
663 // transfer reference to caller |
|
664 *_retval = proxyRequest; |
|
665 |
|
666 return NS_OK; |
|
667 } |
|
668 |
|
669 class imgCacheObserver MOZ_FINAL : public nsIObserver |
|
670 { |
|
671 public: |
|
672 NS_DECL_ISUPPORTS |
|
673 NS_DECL_NSIOBSERVER |
|
674 }; |
|
675 |
|
676 NS_IMPL_ISUPPORTS(imgCacheObserver, nsIObserver) |
|
677 |
|
678 NS_IMETHODIMP |
|
679 imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aSomeData) |
|
680 { |
|
681 if (strcmp(aTopic, "memory-pressure") == 0) { |
|
682 DiscardTracker::DiscardAll(); |
|
683 } |
|
684 return NS_OK; |
|
685 } |
|
686 |
|
687 class imgCacheExpirationTracker MOZ_FINAL |
|
688 : public nsExpirationTracker<imgCacheEntry, 3> |
|
689 { |
|
690 enum { TIMEOUT_SECONDS = 10 }; |
|
691 public: |
|
692 imgCacheExpirationTracker(); |
|
693 |
|
694 protected: |
|
695 void NotifyExpired(imgCacheEntry *entry); |
|
696 }; |
|
697 |
|
698 imgCacheExpirationTracker::imgCacheExpirationTracker() |
|
699 : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000) |
|
700 {} |
|
701 |
|
702 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry *entry) |
|
703 { |
|
704 // Hold on to a reference to this entry, because the expiration tracker |
|
705 // mechanism doesn't. |
|
706 nsRefPtr<imgCacheEntry> kungFuDeathGrip(entry); |
|
707 |
|
708 #if defined(PR_LOGGING) |
|
709 nsRefPtr<imgRequest> req(entry->GetRequest()); |
|
710 if (req) { |
|
711 nsRefPtr<ImageURL> uri; |
|
712 req->GetURI(getter_AddRefs(uri)); |
|
713 nsAutoCString spec; |
|
714 uri->GetSpec(spec); |
|
715 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheExpirationTracker::NotifyExpired", "entry", spec.get()); |
|
716 } |
|
717 #endif |
|
718 |
|
719 // We can be called multiple times on the same entry. Don't do work multiple |
|
720 // times. |
|
721 if (!entry->Evicted()) |
|
722 entry->Loader()->RemoveFromCache(entry); |
|
723 |
|
724 entry->Loader()->VerifyCacheSizes(); |
|
725 } |
|
726 |
|
727 imgCacheObserver *gCacheObserver; |
|
728 |
|
729 double imgLoader::sCacheTimeWeight; |
|
730 uint32_t imgLoader::sCacheMaxSize; |
|
731 imgMemoryReporter* imgLoader::sMemReporter; |
|
732 |
|
733 nsCOMPtr<mozIThirdPartyUtil> imgLoader::sThirdPartyUtilSvc; |
|
734 |
|
735 NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference, nsIObserver) |
|
736 |
|
737 static imgLoader* gSingleton = nullptr; |
|
738 static imgLoader* gPBSingleton = nullptr; |
|
739 |
|
740 imgLoader* |
|
741 imgLoader::Singleton() |
|
742 { |
|
743 if (!gSingleton) |
|
744 gSingleton = imgLoader::Create(); |
|
745 return gSingleton; |
|
746 } |
|
747 |
|
748 imgLoader* |
|
749 imgLoader::PBSingleton() |
|
750 { |
|
751 if (!gPBSingleton) { |
|
752 gPBSingleton = imgLoader::Create(); |
|
753 gPBSingleton->RespectPrivacyNotifications(); |
|
754 } |
|
755 return gPBSingleton; |
|
756 } |
|
757 |
|
758 imgLoader::imgLoader() |
|
759 : mRespectPrivacy(false) |
|
760 { |
|
761 sMemReporter->AddRef(); |
|
762 sMemReporter->RegisterLoader(this); |
|
763 } |
|
764 |
|
765 already_AddRefed<imgLoader> |
|
766 imgLoader::GetInstance() |
|
767 { |
|
768 static StaticRefPtr<imgLoader> singleton; |
|
769 if (!singleton) { |
|
770 singleton = imgLoader::Create(); |
|
771 if (!singleton) |
|
772 return nullptr; |
|
773 ClearOnShutdown(&singleton); |
|
774 } |
|
775 nsRefPtr<imgLoader> loader = singleton.get(); |
|
776 return loader.forget(); |
|
777 } |
|
778 |
|
779 imgLoader::~imgLoader() |
|
780 { |
|
781 ClearChromeImageCache(); |
|
782 ClearImageCache(); |
|
783 sMemReporter->UnregisterLoader(this); |
|
784 sMemReporter->Release(); |
|
785 } |
|
786 |
|
787 void imgLoader::VerifyCacheSizes() |
|
788 { |
|
789 #ifdef DEBUG |
|
790 if (!mCacheTracker) |
|
791 return; |
|
792 |
|
793 uint32_t cachesize = mCache.Count() + mChromeCache.Count(); |
|
794 uint32_t queuesize = mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements(); |
|
795 uint32_t trackersize = 0; |
|
796 for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker); it.Next(); ) |
|
797 trackersize++; |
|
798 NS_ABORT_IF_FALSE(queuesize == trackersize, "Queue and tracker sizes out of sync!"); |
|
799 NS_ABORT_IF_FALSE(queuesize <= cachesize, "Queue has more elements than cache!"); |
|
800 #endif |
|
801 } |
|
802 |
|
803 imgLoader::imgCacheTable & imgLoader::GetCache(nsIURI *aURI) |
|
804 { |
|
805 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); |
|
806 bool chrome = false; |
|
807 aURI->SchemeIs("chrome", &chrome); |
|
808 return chrome ? mChromeCache : mCache; |
|
809 } |
|
810 |
|
811 imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI) |
|
812 { |
|
813 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); |
|
814 bool chrome = false; |
|
815 aURI->SchemeIs("chrome", &chrome); |
|
816 return chrome ? mChromeCacheQueue : mCacheQueue; |
|
817 |
|
818 } |
|
819 |
|
820 imgLoader::imgCacheTable & imgLoader::GetCache(ImageURL *aURI) |
|
821 { |
|
822 bool chrome = false; |
|
823 aURI->SchemeIs("chrome", &chrome); |
|
824 return chrome ? mChromeCache : mCache; |
|
825 } |
|
826 |
|
827 imgCacheQueue & imgLoader::GetCacheQueue(ImageURL *aURI) |
|
828 { |
|
829 bool chrome = false; |
|
830 aURI->SchemeIs("chrome", &chrome); |
|
831 return chrome ? mChromeCacheQueue : mCacheQueue; |
|
832 } |
|
833 |
|
834 void imgLoader::GlobalInit() |
|
835 { |
|
836 gCacheObserver = new imgCacheObserver(); |
|
837 NS_ADDREF(gCacheObserver); |
|
838 |
|
839 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
|
840 if (os) |
|
841 os->AddObserver(gCacheObserver, "memory-pressure", false); |
|
842 |
|
843 int32_t timeweight; |
|
844 nsresult rv = Preferences::GetInt("image.cache.timeweight", &timeweight); |
|
845 if (NS_SUCCEEDED(rv)) |
|
846 sCacheTimeWeight = timeweight / 1000.0; |
|
847 else |
|
848 sCacheTimeWeight = 0.5; |
|
849 |
|
850 int32_t cachesize; |
|
851 rv = Preferences::GetInt("image.cache.size", &cachesize); |
|
852 if (NS_SUCCEEDED(rv)) |
|
853 sCacheMaxSize = cachesize; |
|
854 else |
|
855 sCacheMaxSize = 5 * 1024 * 1024; |
|
856 |
|
857 sMemReporter = new imgMemoryReporter(); |
|
858 RegisterStrongMemoryReporter(sMemReporter); |
|
859 RegisterImagesContentUsedUncompressedDistinguishedAmount(imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount); |
|
860 |
|
861 sThirdPartyUtilSvc = do_GetService(THIRDPARTYUTIL_CONTRACTID); |
|
862 } |
|
863 |
|
864 nsresult imgLoader::InitCache() |
|
865 { |
|
866 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
|
867 if (!os) |
|
868 return NS_ERROR_FAILURE; |
|
869 |
|
870 os->AddObserver(this, "memory-pressure", false); |
|
871 os->AddObserver(this, "chrome-flush-skin-caches", false); |
|
872 os->AddObserver(this, "chrome-flush-caches", false); |
|
873 os->AddObserver(this, "last-pb-context-exited", false); |
|
874 os->AddObserver(this, "profile-before-change", false); |
|
875 os->AddObserver(this, "xpcom-shutdown", false); |
|
876 |
|
877 mCacheTracker = new imgCacheExpirationTracker(); |
|
878 |
|
879 return NS_OK; |
|
880 } |
|
881 |
|
882 nsresult imgLoader::Init() |
|
883 { |
|
884 InitCache(); |
|
885 |
|
886 ReadAcceptHeaderPref(); |
|
887 |
|
888 Preferences::AddWeakObserver(this, "image.http.accept"); |
|
889 |
|
890 return NS_OK; |
|
891 } |
|
892 |
|
893 NS_IMETHODIMP |
|
894 imgLoader::RespectPrivacyNotifications() |
|
895 { |
|
896 mRespectPrivacy = true; |
|
897 return NS_OK; |
|
898 } |
|
899 |
|
900 NS_IMETHODIMP |
|
901 imgLoader::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) |
|
902 { |
|
903 // We listen for pref change notifications... |
|
904 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { |
|
905 if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "image.http.accept")) { |
|
906 ReadAcceptHeaderPref(); |
|
907 } |
|
908 |
|
909 } else if (strcmp(aTopic, "memory-pressure") == 0) { |
|
910 MinimizeCaches(); |
|
911 } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 || |
|
912 strcmp(aTopic, "chrome-flush-caches") == 0) { |
|
913 MinimizeCaches(); |
|
914 ClearChromeImageCache(); |
|
915 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) { |
|
916 if (mRespectPrivacy) { |
|
917 ClearImageCache(); |
|
918 ClearChromeImageCache(); |
|
919 } |
|
920 } else if (strcmp(aTopic, "profile-before-change") == 0 || |
|
921 strcmp(aTopic, "xpcom-shutdown") == 0) { |
|
922 mCacheTracker = nullptr; |
|
923 } |
|
924 |
|
925 // (Nothing else should bring us here) |
|
926 else { |
|
927 NS_ABORT_IF_FALSE(0, "Invalid topic received"); |
|
928 } |
|
929 |
|
930 return NS_OK; |
|
931 } |
|
932 |
|
933 void imgLoader::ReadAcceptHeaderPref() |
|
934 { |
|
935 nsAdoptingCString accept = Preferences::GetCString("image.http.accept"); |
|
936 if (accept) |
|
937 mAcceptHeader = accept; |
|
938 else |
|
939 mAcceptHeader = IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5"; |
|
940 } |
|
941 |
|
942 /* void clearCache (in boolean chrome); */ |
|
943 NS_IMETHODIMP imgLoader::ClearCache(bool chrome) |
|
944 { |
|
945 if (chrome) |
|
946 return ClearChromeImageCache(); |
|
947 else |
|
948 return ClearImageCache(); |
|
949 } |
|
950 |
|
951 /* void removeEntry(in nsIURI uri); */ |
|
952 NS_IMETHODIMP imgLoader::RemoveEntry(nsIURI *uri) |
|
953 { |
|
954 if (RemoveMatchingUrlsFromCache(uri)) |
|
955 return NS_OK; |
|
956 |
|
957 return NS_ERROR_NOT_AVAILABLE; |
|
958 } |
|
959 |
|
960 static PLDHashOperator EnumAllEntries(const nsACString&, |
|
961 nsRefPtr<imgCacheEntry> &aData, |
|
962 void *data) |
|
963 { |
|
964 nsTArray<nsRefPtr<imgCacheEntry> > *entries = |
|
965 reinterpret_cast<nsTArray<nsRefPtr<imgCacheEntry> > *>(data); |
|
966 |
|
967 entries->AppendElement(aData); |
|
968 |
|
969 return PL_DHASH_NEXT; |
|
970 } |
|
971 |
|
972 /* imgIRequest findEntry(in nsIURI uri); */ |
|
973 NS_IMETHODIMP imgLoader::FindEntryProperties(nsIURI *uri, nsIProperties **_retval) |
|
974 { |
|
975 nsRefPtr<imgCacheEntry> entry; |
|
976 imgCacheTable &cache = GetCache(uri); |
|
977 *_retval = nullptr; |
|
978 |
|
979 // We must traverse the whole cache in O(N) looking for the first |
|
980 // matching URI. |
|
981 // |
|
982 // TODO: For now, it's ok to pick at random here. The images should be |
|
983 // identical unless there is a cache-tracking attack. And even if they |
|
984 // are not identical due to attack, this code is only used for save |
|
985 // dialogs at this point, so no differentiating info is leaked to |
|
986 // content. |
|
987 nsTArray<nsRefPtr<imgCacheEntry> > entries; |
|
988 cache.Enumerate(EnumAllEntries, &entries); |
|
989 |
|
990 for (uint32_t i = 0; i < entries.Length(); ++i) { |
|
991 bool isEqual = false; |
|
992 |
|
993 nsRefPtr<imgRequest> request = entries[i]->GetRequest(); |
|
994 if (request) { |
|
995 request->mURI->Equals(uri, &isEqual); |
|
996 if (isEqual) { |
|
997 if (mCacheTracker && entries[i]->HasNoProxies()) { |
|
998 mCacheTracker->MarkUsed(entries[i]); |
|
999 } |
|
1000 *_retval = request->Properties(); |
|
1001 NS_ADDREF(*_retval); |
|
1002 break; |
|
1003 } |
|
1004 } |
|
1005 } |
|
1006 if (*_retval) { |
|
1007 return NS_OK; |
|
1008 } |
|
1009 return NS_ERROR_NOT_AVAILABLE; |
|
1010 } |
|
1011 |
|
1012 void imgLoader::Shutdown() |
|
1013 { |
|
1014 NS_IF_RELEASE(gSingleton); |
|
1015 NS_IF_RELEASE(gPBSingleton); |
|
1016 NS_RELEASE(gCacheObserver); |
|
1017 sThirdPartyUtilSvc = nullptr; |
|
1018 } |
|
1019 |
|
1020 nsresult imgLoader::ClearChromeImageCache() |
|
1021 { |
|
1022 return EvictEntries(mChromeCache); |
|
1023 } |
|
1024 |
|
1025 nsresult imgLoader::ClearImageCache() |
|
1026 { |
|
1027 return EvictEntries(mCache); |
|
1028 } |
|
1029 |
|
1030 void imgLoader::MinimizeCaches() |
|
1031 { |
|
1032 EvictEntries(mCacheQueue); |
|
1033 EvictEntries(mChromeCacheQueue); |
|
1034 } |
|
1035 |
|
1036 bool imgLoader::PutIntoCache(nsAutoCString key, imgCacheEntry *entry) |
|
1037 { |
|
1038 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::PutIntoCache", "uri", key.get()); |
|
1039 imgCacheTable &cache = GetCache(entry->mRequest->mURI); |
|
1040 imgCacheQueue &queue = GetCacheQueue(entry->mRequest->mURI); |
|
1041 |
|
1042 // Check to see if this request already exists in the cache and is being |
|
1043 // loaded on a different thread. If so, don't allow this entry to be added to |
|
1044 // the cache. |
|
1045 nsRefPtr<imgCacheEntry> tmpCacheEntry; |
|
1046 if (cache.Get(key, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) { |
|
1047 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1048 ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", nullptr)); |
|
1049 nsRefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest(); |
|
1050 |
|
1051 // If it already exists, and we're putting the same key into the cache, we |
|
1052 // should remove the old version. |
|
1053 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1054 ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element", nullptr)); |
|
1055 |
|
1056 RemoveFromCache(key, cache, queue); |
|
1057 } else { |
|
1058 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1059 ("[this=%p] imgLoader::PutIntoCache -- Element NOT already in the cache", nullptr)); |
|
1060 } |
|
1061 |
|
1062 cache.Put(key, entry); |
|
1063 |
|
1064 // We can be called to resurrect an evicted entry. |
|
1065 if (entry->Evicted()) |
|
1066 entry->SetEvicted(false); |
|
1067 |
|
1068 // If we're resurrecting an entry with no proxies, put it back in the |
|
1069 // tracker and queue. |
|
1070 if (entry->HasNoProxies()) { |
|
1071 nsresult addrv = NS_OK; |
|
1072 |
|
1073 if (mCacheTracker) |
|
1074 addrv = mCacheTracker->AddObject(entry); |
|
1075 |
|
1076 if (NS_SUCCEEDED(addrv)) { |
|
1077 queue.Push(entry); |
|
1078 } |
|
1079 } |
|
1080 |
|
1081 nsRefPtr<imgRequest> request = entry->GetRequest(); |
|
1082 request->SetIsInCache(true); |
|
1083 |
|
1084 return true; |
|
1085 } |
|
1086 |
|
1087 bool imgLoader::SetHasNoProxies(ImageURL *imgURI, imgCacheEntry *entry) |
|
1088 { |
|
1089 #if defined(PR_LOGGING) |
|
1090 nsAutoCString spec; |
|
1091 imgURI->GetSpec(spec); |
|
1092 |
|
1093 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasNoProxies", "uri", spec.get()); |
|
1094 #endif |
|
1095 |
|
1096 if (entry->Evicted()) |
|
1097 return false; |
|
1098 |
|
1099 imgCacheQueue &queue = GetCacheQueue(imgURI); |
|
1100 |
|
1101 nsresult addrv = NS_OK; |
|
1102 |
|
1103 if (mCacheTracker) |
|
1104 addrv = mCacheTracker->AddObject(entry); |
|
1105 |
|
1106 if (NS_SUCCEEDED(addrv)) { |
|
1107 queue.Push(entry); |
|
1108 entry->SetHasNoProxies(true); |
|
1109 } |
|
1110 |
|
1111 imgCacheTable &cache = GetCache(imgURI); |
|
1112 CheckCacheLimits(cache, queue); |
|
1113 |
|
1114 return true; |
|
1115 } |
|
1116 |
|
1117 bool imgLoader::SetHasProxies(nsIURI *firstPartyIsolationURI, ImageURL *imgURI) |
|
1118 { |
|
1119 VerifyCacheSizes(); |
|
1120 |
|
1121 imgCacheTable &cache = GetCache(imgURI); |
|
1122 |
|
1123 nsAutoCString spec; |
|
1124 imgURI->GetSpec(spec); |
|
1125 |
|
1126 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasProxies", "uri", spec.get()); |
|
1127 |
|
1128 nsAutoCString key = GetCacheKey(firstPartyIsolationURI, imgURI, nullptr); |
|
1129 nsRefPtr<imgCacheEntry> entry; |
|
1130 if (cache.Get(key, getter_AddRefs(entry)) && entry && entry->HasNoProxies()) { |
|
1131 imgCacheQueue &queue = GetCacheQueue(imgURI); |
|
1132 queue.Remove(entry); |
|
1133 |
|
1134 if (mCacheTracker) |
|
1135 mCacheTracker->RemoveObject(entry); |
|
1136 |
|
1137 entry->SetHasNoProxies(false); |
|
1138 |
|
1139 return true; |
|
1140 } |
|
1141 |
|
1142 return false; |
|
1143 } |
|
1144 |
|
1145 void imgLoader::CacheEntriesChanged(ImageURL *uri, int32_t sizediff /* = 0 */) |
|
1146 { |
|
1147 imgCacheQueue &queue = GetCacheQueue(uri); |
|
1148 queue.MarkDirty(); |
|
1149 queue.UpdateSize(sizediff); |
|
1150 } |
|
1151 |
|
1152 void imgLoader::CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue) |
|
1153 { |
|
1154 if (queue.GetNumElements() == 0) |
|
1155 NS_ASSERTION(queue.GetSize() == 0, |
|
1156 "imgLoader::CheckCacheLimits -- incorrect cache size"); |
|
1157 |
|
1158 // Remove entries from the cache until we're back under our desired size. |
|
1159 while (queue.GetSize() >= sCacheMaxSize) { |
|
1160 // Remove the first entry in the queue. |
|
1161 nsRefPtr<imgCacheEntry> entry(queue.Pop()); |
|
1162 |
|
1163 NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer"); |
|
1164 |
|
1165 #if defined(PR_LOGGING) |
|
1166 nsRefPtr<imgRequest> req(entry->GetRequest()); |
|
1167 if (req) { |
|
1168 nsRefPtr<ImageURL> uri; |
|
1169 req->GetURI(getter_AddRefs(uri)); |
|
1170 nsAutoCString spec; |
|
1171 uri->GetSpec(spec); |
|
1172 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::CheckCacheLimits", "entry", spec.get()); |
|
1173 } |
|
1174 #endif |
|
1175 |
|
1176 if (entry) |
|
1177 RemoveFromCache(entry); |
|
1178 } |
|
1179 } |
|
1180 |
|
1181 bool imgLoader::ValidateRequestWithNewChannel(imgRequest *request, |
|
1182 nsIURI *aURI, |
|
1183 nsIURI *aFirstPartyIsolationURI, |
|
1184 nsIURI *aReferrerURI, |
|
1185 nsILoadGroup *aLoadGroup, |
|
1186 imgINotificationObserver *aObserver, |
|
1187 nsISupports *aCX, |
|
1188 nsLoadFlags aLoadFlags, |
|
1189 imgRequestProxy **aProxyRequest, |
|
1190 nsIChannelPolicy *aPolicy, |
|
1191 nsIPrincipal* aLoadingPrincipal, |
|
1192 int32_t aCORSMode) |
|
1193 { |
|
1194 // now we need to insert a new channel request object inbetween the real |
|
1195 // request and the proxy that basically delays loading the image until it |
|
1196 // gets a 304 or figures out that this needs to be a new request |
|
1197 |
|
1198 nsresult rv; |
|
1199 |
|
1200 // If we're currently in the middle of validating this request, just hand |
|
1201 // back a proxy to it; the required work will be done for us. |
|
1202 if (request->mValidator) { |
|
1203 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, |
|
1204 aLoadFlags, aProxyRequest); |
|
1205 if (NS_FAILED(rv)) { |
|
1206 return false; |
|
1207 } |
|
1208 |
|
1209 if (*aProxyRequest) { |
|
1210 imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest); |
|
1211 |
|
1212 // We will send notifications from imgCacheValidator::OnStartRequest(). |
|
1213 // In the mean time, we must defer notifications because we are added to |
|
1214 // the imgRequest's proxy list, and we can get extra notifications |
|
1215 // resulting from methods such as RequestDecode(). See bug 579122. |
|
1216 proxy->SetNotificationsDeferred(true); |
|
1217 |
|
1218 // Attach the proxy without notifying |
|
1219 request->mValidator->AddProxy(proxy); |
|
1220 } |
|
1221 |
|
1222 return NS_SUCCEEDED(rv); |
|
1223 |
|
1224 } else { |
|
1225 // We will rely on Necko to cache this request when it's possible, and to |
|
1226 // tell imgCacheValidator::OnStartRequest whether the request came from its |
|
1227 // cache. |
|
1228 nsCOMPtr<nsIChannel> newChannel; |
|
1229 bool forcePrincipalCheck; |
|
1230 rv = NewImageChannel(getter_AddRefs(newChannel), |
|
1231 &forcePrincipalCheck, |
|
1232 aURI, |
|
1233 aFirstPartyIsolationURI, |
|
1234 aReferrerURI, |
|
1235 aLoadGroup, |
|
1236 mAcceptHeader, |
|
1237 aLoadFlags, |
|
1238 aPolicy, |
|
1239 aLoadingPrincipal); |
|
1240 if (NS_FAILED(rv)) { |
|
1241 return false; |
|
1242 } |
|
1243 |
|
1244 nsRefPtr<imgRequestProxy> req; |
|
1245 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, |
|
1246 aLoadFlags, getter_AddRefs(req)); |
|
1247 if (NS_FAILED(rv)) { |
|
1248 return false; |
|
1249 } |
|
1250 |
|
1251 // Make sure that OnStatus/OnProgress calls have the right request set... |
|
1252 nsRefPtr<nsProgressNotificationProxy> progressproxy = |
|
1253 new nsProgressNotificationProxy(newChannel, req); |
|
1254 if (!progressproxy) |
|
1255 return false; |
|
1256 |
|
1257 nsRefPtr<imgCacheValidator> hvc = |
|
1258 new imgCacheValidator(progressproxy, this, request, aCX, forcePrincipalCheck); |
|
1259 |
|
1260 // Casting needed here to get past multiple inheritance. |
|
1261 nsCOMPtr<nsIStreamListener> listener = |
|
1262 do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc)); |
|
1263 NS_ENSURE_TRUE(listener, false); |
|
1264 |
|
1265 // We must set the notification callbacks before setting up the |
|
1266 // CORS listener, because that's also interested inthe |
|
1267 // notification callbacks. |
|
1268 newChannel->SetNotificationCallbacks(hvc); |
|
1269 |
|
1270 if (aCORSMode != imgIRequest::CORS_NONE) { |
|
1271 bool withCredentials = aCORSMode == imgIRequest::CORS_USE_CREDENTIALS; |
|
1272 nsRefPtr<nsCORSListenerProxy> corsproxy = |
|
1273 new nsCORSListenerProxy(listener, aLoadingPrincipal, withCredentials); |
|
1274 rv = corsproxy->Init(newChannel); |
|
1275 if (NS_FAILED(rv)) { |
|
1276 return false; |
|
1277 } |
|
1278 |
|
1279 listener = corsproxy; |
|
1280 } |
|
1281 |
|
1282 request->mValidator = hvc; |
|
1283 |
|
1284 imgRequestProxy* proxy = static_cast<imgRequestProxy*> |
|
1285 (static_cast<imgIRequest*>(req.get())); |
|
1286 |
|
1287 // We will send notifications from imgCacheValidator::OnStartRequest(). |
|
1288 // In the mean time, we must defer notifications because we are added to |
|
1289 // the imgRequest's proxy list, and we can get extra notifications |
|
1290 // resulting from methods such as RequestDecode(). See bug 579122. |
|
1291 proxy->SetNotificationsDeferred(true); |
|
1292 |
|
1293 // Add the proxy without notifying |
|
1294 hvc->AddProxy(proxy); |
|
1295 |
|
1296 mozilla::net::SeerLearn(aURI, aFirstPartyIsolationURI, |
|
1297 nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup); |
|
1298 |
|
1299 rv = newChannel->AsyncOpen(listener, nullptr); |
|
1300 if (NS_SUCCEEDED(rv)) |
|
1301 NS_ADDREF(*aProxyRequest = req.get()); |
|
1302 |
|
1303 return NS_SUCCEEDED(rv); |
|
1304 } |
|
1305 } |
|
1306 |
|
1307 bool imgLoader::ValidateEntry(imgCacheEntry *aEntry, |
|
1308 nsIURI *aURI, |
|
1309 nsIURI *aFirstPartyIsolationURI, |
|
1310 nsIURI *aReferrerURI, |
|
1311 nsILoadGroup *aLoadGroup, |
|
1312 imgINotificationObserver *aObserver, |
|
1313 nsISupports *aCX, |
|
1314 nsLoadFlags aLoadFlags, |
|
1315 bool aCanMakeNewChannel, |
|
1316 imgRequestProxy **aProxyRequest, |
|
1317 nsIChannelPolicy *aPolicy, |
|
1318 nsIPrincipal* aLoadingPrincipal, |
|
1319 int32_t aCORSMode) |
|
1320 { |
|
1321 LOG_SCOPE(GetImgLog(), "imgLoader::ValidateEntry"); |
|
1322 |
|
1323 bool hasExpired; |
|
1324 uint32_t expirationTime = aEntry->GetExpiryTime(); |
|
1325 if (expirationTime <= SecondsFromPRTime(PR_Now())) { |
|
1326 hasExpired = true; |
|
1327 } else { |
|
1328 hasExpired = false; |
|
1329 } |
|
1330 |
|
1331 nsresult rv; |
|
1332 |
|
1333 // Special treatment for file URLs - aEntry has expired if file has changed |
|
1334 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI)); |
|
1335 if (fileUrl) { |
|
1336 uint32_t lastModTime = aEntry->GetTouchedTime(); |
|
1337 |
|
1338 nsCOMPtr<nsIFile> theFile; |
|
1339 rv = fileUrl->GetFile(getter_AddRefs(theFile)); |
|
1340 if (NS_SUCCEEDED(rv)) { |
|
1341 PRTime fileLastMod; |
|
1342 rv = theFile->GetLastModifiedTime(&fileLastMod); |
|
1343 if (NS_SUCCEEDED(rv)) { |
|
1344 // nsIFile uses millisec, NSPR usec |
|
1345 fileLastMod *= 1000; |
|
1346 hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime; |
|
1347 } |
|
1348 } |
|
1349 } |
|
1350 |
|
1351 nsRefPtr<imgRequest> request(aEntry->GetRequest()); |
|
1352 |
|
1353 if (!request) |
|
1354 return false; |
|
1355 |
|
1356 if (!ValidateCORSAndPrincipal(request, aEntry->ForcePrincipalCheck(), |
|
1357 aCORSMode, aLoadingPrincipal)) |
|
1358 return false; |
|
1359 |
|
1360 // Never validate data URIs. |
|
1361 nsAutoCString scheme; |
|
1362 aURI->GetScheme(scheme); |
|
1363 if (scheme.EqualsLiteral("data")) |
|
1364 return true; |
|
1365 |
|
1366 bool validateRequest = false; |
|
1367 |
|
1368 // If the request's loadId is the same as the aCX, then it is ok to use |
|
1369 // this one because it has already been validated for this context. |
|
1370 // |
|
1371 // XXX: nullptr seems to be a 'special' key value that indicates that NO |
|
1372 // validation is required. |
|
1373 // |
|
1374 void *key = (void *)aCX; |
|
1375 if (request->mLoadId != key) { |
|
1376 // If we would need to revalidate this entry, but we're being told to |
|
1377 // bypass the cache, we don't allow this entry to be used. |
|
1378 if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) |
|
1379 return false; |
|
1380 |
|
1381 // Determine whether the cache aEntry must be revalidated... |
|
1382 validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired); |
|
1383 |
|
1384 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1385 ("imgLoader::ValidateEntry validating cache entry. " |
|
1386 "validateRequest = %d", validateRequest)); |
|
1387 } |
|
1388 #if defined(PR_LOGGING) |
|
1389 else if (!key) { |
|
1390 nsAutoCString spec; |
|
1391 aURI->GetSpec(spec); |
|
1392 |
|
1393 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1394 ("imgLoader::ValidateEntry BYPASSING cache validation for %s " |
|
1395 "because of NULL LoadID", spec.get())); |
|
1396 } |
|
1397 #endif |
|
1398 |
|
1399 // We can't use a cached request if it comes from a different |
|
1400 // application cache than this load is expecting. |
|
1401 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer; |
|
1402 nsCOMPtr<nsIApplicationCache> requestAppCache; |
|
1403 nsCOMPtr<nsIApplicationCache> groupAppCache; |
|
1404 if ((appCacheContainer = do_GetInterface(request->mRequest))) |
|
1405 appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache)); |
|
1406 if ((appCacheContainer = do_QueryInterface(aLoadGroup))) |
|
1407 appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache)); |
|
1408 |
|
1409 if (requestAppCache != groupAppCache) { |
|
1410 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1411 ("imgLoader::ValidateEntry - Unable to use cached imgRequest " |
|
1412 "[request=%p] because of mismatched application caches\n", |
|
1413 address_of(request))); |
|
1414 return false; |
|
1415 } |
|
1416 |
|
1417 if (validateRequest && aCanMakeNewChannel) { |
|
1418 LOG_SCOPE(GetImgLog(), "imgLoader::ValidateRequest |cache hit| must validate"); |
|
1419 |
|
1420 return ValidateRequestWithNewChannel(request, aURI, aFirstPartyIsolationURI, |
|
1421 aReferrerURI, aLoadGroup, aObserver, |
|
1422 aCX, aLoadFlags, aProxyRequest, aPolicy, |
|
1423 aLoadingPrincipal, aCORSMode); |
|
1424 } |
|
1425 |
|
1426 return !validateRequest; |
|
1427 } |
|
1428 |
|
1429 bool imgLoader::RemoveMatchingUrlsFromCache(nsIURI *aImgURI) |
|
1430 { |
|
1431 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); |
|
1432 |
|
1433 if (!aImgURI) return false; |
|
1434 |
|
1435 bool rv = true; |
|
1436 imgCacheTable &cache = GetCache(aImgURI); |
|
1437 |
|
1438 // We have to make a temporary, since RemoveFromCache removes the element |
|
1439 // from the queue, invalidating iterators. |
|
1440 nsTArray<nsRefPtr<imgCacheEntry> > entries; |
|
1441 cache.Enumerate(EnumAllEntries, &entries); |
|
1442 for (uint32_t i = 0; i < entries.Length(); ++i) { |
|
1443 bool isEqual = false; |
|
1444 |
|
1445 entries[i]->mRequest->mURI->Equals(aImgURI, &isEqual); |
|
1446 if (isEqual && !RemoveFromCache(entries[i])) |
|
1447 rv = false; |
|
1448 } |
|
1449 |
|
1450 return rv; |
|
1451 } |
|
1452 |
|
1453 bool imgLoader::RemoveFromCache(nsAutoCString key, |
|
1454 imgCacheTable &cache, |
|
1455 imgCacheQueue &queue) |
|
1456 { |
|
1457 if (key.IsEmpty()) return false; |
|
1458 |
|
1459 nsRefPtr<imgCacheEntry> entry; |
|
1460 if (cache.Get(key, getter_AddRefs(entry)) && entry) { |
|
1461 cache.Remove(key); |
|
1462 |
|
1463 NS_ABORT_IF_FALSE(!entry->Evicted(), "Evicting an already-evicted cache entry!"); |
|
1464 |
|
1465 // Entries with no proxies are in the tracker. |
|
1466 if (entry->HasNoProxies()) { |
|
1467 if (mCacheTracker) |
|
1468 mCacheTracker->RemoveObject(entry); |
|
1469 queue.Remove(entry); |
|
1470 } |
|
1471 |
|
1472 entry->SetEvicted(true); |
|
1473 |
|
1474 nsRefPtr<imgRequest> request = entry->GetRequest(); |
|
1475 request->SetIsInCache(false); |
|
1476 |
|
1477 return true; |
|
1478 } |
|
1479 else |
|
1480 return false; |
|
1481 } |
|
1482 |
|
1483 bool imgLoader::RemoveFromCache(imgCacheEntry *entry) |
|
1484 { |
|
1485 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache entry"); |
|
1486 |
|
1487 nsRefPtr<imgRequest> request = entry->GetRequest(); |
|
1488 if (request) { |
|
1489 nsRefPtr<ImageURL> imgURI; |
|
1490 if (NS_SUCCEEDED(request->GetURI(getter_AddRefs(imgURI))) && imgURI) { |
|
1491 nsCOMPtr<nsIURI> firstPartyIsolationURI = request->mFirstPartyIsolationURI; |
|
1492 imgCacheTable &cache = GetCache(imgURI); |
|
1493 imgCacheQueue &queue = GetCacheQueue(imgURI); |
|
1494 nsAutoCString spec = GetCacheKey(firstPartyIsolationURI, imgURI, nullptr); |
|
1495 |
|
1496 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::RemoveFromCache", "entry's uri", spec.get()); |
|
1497 |
|
1498 cache.Remove(spec); |
|
1499 |
|
1500 if (entry->HasNoProxies()) { |
|
1501 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache removing from tracker"); |
|
1502 if (mCacheTracker) |
|
1503 mCacheTracker->RemoveObject(entry); |
|
1504 queue.Remove(entry); |
|
1505 } |
|
1506 |
|
1507 entry->SetEvicted(true); |
|
1508 request->SetIsInCache(false); |
|
1509 |
|
1510 return true; |
|
1511 } |
|
1512 } |
|
1513 |
|
1514 return false; |
|
1515 } |
|
1516 |
|
1517 nsresult imgLoader::EvictEntries(imgCacheTable &aCacheToClear) |
|
1518 { |
|
1519 nsresult rv = NS_OK; |
|
1520 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries table"); |
|
1521 |
|
1522 // We have to make a temporary, since RemoveFromCache removes the element |
|
1523 // from the queue, invalidating iterators. |
|
1524 nsTArray<nsRefPtr<imgCacheEntry> > entries; |
|
1525 aCacheToClear.Enumerate(EnumAllEntries, &entries); |
|
1526 |
|
1527 for (uint32_t i = 0; i < entries.Length(); ++i) |
|
1528 if (!RemoveFromCache(entries[i])) |
|
1529 rv = NS_ERROR_FAILURE; |
|
1530 |
|
1531 return rv; |
|
1532 } |
|
1533 |
|
1534 nsresult imgLoader::EvictEntries(imgCacheQueue &aQueueToClear) |
|
1535 { |
|
1536 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries queue"); |
|
1537 |
|
1538 // We have to make a temporary, since RemoveFromCache removes the element |
|
1539 // from the queue, invalidating iterators. |
|
1540 nsTArray<nsRefPtr<imgCacheEntry> > entries(aQueueToClear.GetNumElements()); |
|
1541 for (imgCacheQueue::const_iterator i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i) |
|
1542 entries.AppendElement(*i); |
|
1543 |
|
1544 for (uint32_t i = 0; i < entries.Length(); ++i) |
|
1545 if (!RemoveFromCache(entries[i])) |
|
1546 return NS_ERROR_FAILURE; |
|
1547 |
|
1548 return NS_OK; |
|
1549 } |
|
1550 |
|
1551 #define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \ |
|
1552 nsIRequest::LOAD_FROM_CACHE) |
|
1553 |
|
1554 #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \ |
|
1555 nsIRequest::VALIDATE_NEVER | \ |
|
1556 nsIRequest::VALIDATE_ONCE_PER_SESSION) |
|
1557 |
|
1558 NS_IMETHODIMP imgLoader::LoadImageXPCOM(nsIURI *aURI, |
|
1559 nsIURI *aInitialDocumentURI, |
|
1560 nsIURI *aReferrerURI, |
|
1561 nsIPrincipal* aLoadingPrincipal, |
|
1562 nsILoadGroup *aLoadGroup, |
|
1563 imgINotificationObserver *aObserver, |
|
1564 nsISupports *aCX, |
|
1565 nsLoadFlags aLoadFlags, |
|
1566 nsISupports *aCacheKey, |
|
1567 nsIChannelPolicy *aPolicy, |
|
1568 imgIRequest **_retval) |
|
1569 { |
|
1570 imgRequestProxy *proxy; |
|
1571 nsresult result = LoadImage(aURI, |
|
1572 aInitialDocumentURI, |
|
1573 aReferrerURI, |
|
1574 aLoadingPrincipal, |
|
1575 aLoadGroup, |
|
1576 aObserver, |
|
1577 aCX, |
|
1578 aLoadFlags, |
|
1579 aCacheKey, |
|
1580 aPolicy, |
|
1581 EmptyString(), |
|
1582 &proxy); |
|
1583 *_retval = proxy; |
|
1584 return result; |
|
1585 } |
|
1586 |
|
1587 |
|
1588 /* imgIRequest loadImage (in nsIURI aURI, in nsIURI aUrlBarURI, in nsIPrincipal loadingPrincipal, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */ |
|
1589 |
|
1590 nsresult imgLoader::LoadImage(nsIURI *aURI, |
|
1591 nsIURI *aFirstPartyIsolationURI, |
|
1592 nsIURI *aReferrerURI, |
|
1593 nsIPrincipal* aLoadingPrincipal, |
|
1594 nsILoadGroup *aLoadGroup, |
|
1595 imgINotificationObserver *aObserver, |
|
1596 nsISupports *aCX, |
|
1597 nsLoadFlags aLoadFlags, |
|
1598 nsISupports *aCacheKey, |
|
1599 nsIChannelPolicy *aPolicy, |
|
1600 const nsAString& initiatorType, |
|
1601 imgRequestProxy **_retval) |
|
1602 { |
|
1603 VerifyCacheSizes(); |
|
1604 |
|
1605 NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer"); |
|
1606 |
|
1607 if (!aURI) |
|
1608 return NS_ERROR_NULL_POINTER; |
|
1609 |
|
1610 bool isIsolated = false; |
|
1611 nsAutoCString spec = GetCacheKey(aFirstPartyIsolationURI, aURI, &isIsolated); |
|
1612 |
|
1613 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage", "aURI", spec.get()); |
|
1614 |
|
1615 *_retval = nullptr; |
|
1616 |
|
1617 nsRefPtr<imgRequest> request; |
|
1618 |
|
1619 nsresult rv; |
|
1620 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; |
|
1621 |
|
1622 #ifdef DEBUG |
|
1623 bool isPrivate = false; |
|
1624 |
|
1625 if (aLoadGroup) { |
|
1626 nsCOMPtr<nsIInterfaceRequestor> callbacks; |
|
1627 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); |
|
1628 if (callbacks) { |
|
1629 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks); |
|
1630 isPrivate = loadContext && loadContext->UsePrivateBrowsing(); |
|
1631 } |
|
1632 } |
|
1633 MOZ_ASSERT(isPrivate == mRespectPrivacy); |
|
1634 #endif |
|
1635 |
|
1636 // Get the default load flags from the loadgroup (if possible)... |
|
1637 if (aLoadGroup) { |
|
1638 aLoadGroup->GetLoadFlags(&requestFlags); |
|
1639 } |
|
1640 // |
|
1641 // Merge the default load flags with those passed in via aLoadFlags. |
|
1642 // Currently, *only* the caching, validation and background load flags |
|
1643 // are merged... |
|
1644 // |
|
1645 // The flags in aLoadFlags take precedence over the default flags! |
|
1646 // |
|
1647 if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) { |
|
1648 // Override the default caching flags... |
|
1649 requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) | |
|
1650 (aLoadFlags & LOAD_FLAGS_CACHE_MASK); |
|
1651 } |
|
1652 if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) { |
|
1653 // Override the default validation flags... |
|
1654 requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) | |
|
1655 (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK); |
|
1656 } |
|
1657 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { |
|
1658 // Propagate background loading... |
|
1659 requestFlags |= nsIRequest::LOAD_BACKGROUND; |
|
1660 } |
|
1661 |
|
1662 int32_t corsmode = imgIRequest::CORS_NONE; |
|
1663 if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) { |
|
1664 corsmode = imgIRequest::CORS_ANONYMOUS; |
|
1665 } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) { |
|
1666 corsmode = imgIRequest::CORS_USE_CREDENTIALS; |
|
1667 } |
|
1668 |
|
1669 nsRefPtr<imgCacheEntry> entry; |
|
1670 |
|
1671 // Look in the cache for our URI, and then validate it. |
|
1672 // XXX For now ignore aCacheKey. We will need it in the future |
|
1673 // for correctly dealing with image load requests that are a result |
|
1674 // of post data. |
|
1675 imgCacheTable &cache = GetCache(aURI); |
|
1676 |
|
1677 if (cache.Get(spec, getter_AddRefs(entry)) && entry) { |
|
1678 if (ValidateEntry(entry, aURI, aFirstPartyIsolationURI, aReferrerURI, |
|
1679 aLoadGroup, aObserver, aCX, requestFlags, true, |
|
1680 _retval, aPolicy, aLoadingPrincipal, corsmode)) { |
|
1681 request = entry->GetRequest(); |
|
1682 |
|
1683 // If this entry has no proxies, its request has no reference to the entry. |
|
1684 if (entry->HasNoProxies()) { |
|
1685 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage() adding proxyless entry", "uri", spec.get()); |
|
1686 NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!"); |
|
1687 request->SetCacheEntry(entry); |
|
1688 |
|
1689 if (mCacheTracker) |
|
1690 mCacheTracker->MarkUsed(entry); |
|
1691 } |
|
1692 |
|
1693 entry->Touch(); |
|
1694 |
|
1695 #ifdef DEBUG_joe |
|
1696 printf("CACHEGET: %d %s %d\n", time(nullptr), spec.get(), entry->SizeOfData()); |
|
1697 #endif |
|
1698 } |
|
1699 else { |
|
1700 // We can't use this entry. We'll try to load it off the network, and if |
|
1701 // successful, overwrite the old entry in the cache with a new one. |
|
1702 entry = nullptr; |
|
1703 } |
|
1704 } |
|
1705 |
|
1706 // Keep the channel in this scope, so we can adjust its notificationCallbacks |
|
1707 // later when we create the proxy. |
|
1708 nsCOMPtr<nsIChannel> newChannel; |
|
1709 // If we didn't get a cache hit, we need to load from the network. |
|
1710 if (!request) { |
|
1711 LOG_SCOPE(GetImgLog(), "imgLoader::LoadImage |cache miss|"); |
|
1712 |
|
1713 bool forcePrincipalCheck; |
|
1714 rv = NewImageChannel(getter_AddRefs(newChannel), |
|
1715 &forcePrincipalCheck, |
|
1716 aURI, |
|
1717 aFirstPartyIsolationURI, |
|
1718 aReferrerURI, |
|
1719 aLoadGroup, |
|
1720 mAcceptHeader, |
|
1721 requestFlags, |
|
1722 aPolicy, |
|
1723 aLoadingPrincipal); |
|
1724 if (NS_FAILED(rv)) |
|
1725 return NS_ERROR_FAILURE; |
|
1726 |
|
1727 MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy); |
|
1728 |
|
1729 NewRequestAndEntry(forcePrincipalCheck, this, getter_AddRefs(request), getter_AddRefs(entry)); |
|
1730 |
|
1731 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1732 ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request.get())); |
|
1733 |
|
1734 nsCOMPtr<nsILoadGroup> channelLoadGroup; |
|
1735 newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup)); |
|
1736 request->Init(aURI, aURI, aFirstPartyIsolationURI, channelLoadGroup, newChannel, entry, aCX, |
|
1737 aLoadingPrincipal, corsmode); |
|
1738 |
|
1739 // Add the initiator type for this image load |
|
1740 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel); |
|
1741 if (timedChannel) { |
|
1742 timedChannel->SetInitiatorType(initiatorType); |
|
1743 } |
|
1744 |
|
1745 // Pass the inner window ID of the loading document, if possible. |
|
1746 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX); |
|
1747 if (doc) { |
|
1748 request->SetInnerWindowID(doc->InnerWindowID()); |
|
1749 } |
|
1750 |
|
1751 // create the proxy listener |
|
1752 nsCOMPtr<nsIStreamListener> pl = new ProxyListener(request.get()); |
|
1753 |
|
1754 // See if we need to insert a CORS proxy between the proxy listener and the |
|
1755 // request. |
|
1756 nsCOMPtr<nsIStreamListener> listener = pl; |
|
1757 if (corsmode != imgIRequest::CORS_NONE) { |
|
1758 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1759 ("[this=%p] imgLoader::LoadImage -- Setting up a CORS load", |
|
1760 this)); |
|
1761 bool withCredentials = corsmode == imgIRequest::CORS_USE_CREDENTIALS; |
|
1762 |
|
1763 nsRefPtr<nsCORSListenerProxy> corsproxy = |
|
1764 new nsCORSListenerProxy(pl, aLoadingPrincipal, withCredentials); |
|
1765 rv = corsproxy->Init(newChannel); |
|
1766 if (NS_FAILED(rv)) { |
|
1767 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1768 ("[this=%p] imgLoader::LoadImage -- nsCORSListenerProxy " |
|
1769 "creation failed: 0x%x\n", this, rv)); |
|
1770 request->CancelAndAbort(rv); |
|
1771 return NS_ERROR_FAILURE; |
|
1772 } |
|
1773 |
|
1774 listener = corsproxy; |
|
1775 } |
|
1776 |
|
1777 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1778 ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this)); |
|
1779 |
|
1780 mozilla::net::SeerLearn(aURI, aFirstPartyIsolationURI, |
|
1781 nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup); |
|
1782 |
|
1783 nsresult openRes = newChannel->AsyncOpen(listener, nullptr); |
|
1784 |
|
1785 if (NS_FAILED(openRes)) { |
|
1786 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
1787 ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n", |
|
1788 this, openRes)); |
|
1789 request->CancelAndAbort(openRes); |
|
1790 return openRes; |
|
1791 } |
|
1792 |
|
1793 if (isIsolated) // Try to add the new request into the cache. |
|
1794 PutIntoCache(spec, entry); |
|
1795 } else { |
|
1796 LOG_MSG_WITH_PARAM(GetImgLog(), |
|
1797 "imgLoader::LoadImage |cache hit|", "request", request); |
|
1798 } |
|
1799 |
|
1800 |
|
1801 // If we didn't get a proxy when validating the cache entry, we need to create one. |
|
1802 if (!*_retval) { |
|
1803 // ValidateEntry() has three return values: "Is valid," "might be valid -- |
|
1804 // validating over network", and "not valid." If we don't have a _retval, |
|
1805 // we know ValidateEntry is not validating over the network, so it's safe |
|
1806 // to SetLoadId here because we know this request is valid for this context. |
|
1807 // |
|
1808 // Note, however, that this doesn't guarantee the behaviour we want (one |
|
1809 // URL maps to the same image on a page) if we load the same image in a |
|
1810 // different tab (see bug 528003), because its load id will get re-set, and |
|
1811 // that'll cause us to validate over the network. |
|
1812 request->SetLoadId(aCX); |
|
1813 |
|
1814 LOG_MSG(GetImgLog(), "imgLoader::LoadImage", "creating proxy request."); |
|
1815 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, |
|
1816 requestFlags, _retval); |
|
1817 if (NS_FAILED(rv)) { |
|
1818 return rv; |
|
1819 } |
|
1820 |
|
1821 imgRequestProxy *proxy = *_retval; |
|
1822 |
|
1823 // Make sure that OnStatus/OnProgress calls have the right request set, if |
|
1824 // we did create a channel here. |
|
1825 if (newChannel) { |
|
1826 nsCOMPtr<nsIInterfaceRequestor> requestor( |
|
1827 new nsProgressNotificationProxy(newChannel, proxy)); |
|
1828 if (!requestor) |
|
1829 return NS_ERROR_OUT_OF_MEMORY; |
|
1830 newChannel->SetNotificationCallbacks(requestor); |
|
1831 } |
|
1832 |
|
1833 // Note that it's OK to add here even if the request is done. If it is, |
|
1834 // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and |
|
1835 // the proxy will be removed from the loadgroup. |
|
1836 proxy->AddToLoadGroup(); |
|
1837 |
|
1838 // If we're loading off the network, explicitly don't notify our proxy, |
|
1839 // because necko (or things called from necko, such as imgCacheValidator) |
|
1840 // are going to call our notifications asynchronously, and we can't make it |
|
1841 // further asynchronous because observers might rely on imagelib completing |
|
1842 // its work between the channel's OnStartRequest and OnStopRequest. |
|
1843 if (!newChannel) |
|
1844 proxy->NotifyListener(); |
|
1845 |
|
1846 return rv; |
|
1847 } |
|
1848 |
|
1849 NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value"); |
|
1850 |
|
1851 return NS_OK; |
|
1852 } |
|
1853 |
|
1854 nsAutoCString imgLoader::GetCacheKey(nsIURI *firstPartyIsolationURI, ImageURL *imgURI, |
|
1855 bool *isIsolated) |
|
1856 { |
|
1857 NS_ASSERTION(imgURI, "imgLoader::GetCacheKey -- NULL imgURI"); |
|
1858 if (isIsolated) |
|
1859 *isIsolated = false; |
|
1860 |
|
1861 nsAutoCString spec; |
|
1862 if (imgURI) |
|
1863 imgURI->GetSpec(spec); |
|
1864 |
|
1865 nsAutoCString hostKey; |
|
1866 if (firstPartyIsolationURI && sThirdPartyUtilSvc) |
|
1867 sThirdPartyUtilSvc->GetFirstPartyHostForIsolation(firstPartyIsolationURI, hostKey); |
|
1868 |
|
1869 if (hostKey.Length() > 0) { |
|
1870 if (isIsolated) |
|
1871 *isIsolated = true; |
|
1872 // Make a new key using host |
|
1873 // FIXME: This might involve a couple more copies than necessary.. |
|
1874 // But man, 18 string types? Who knows which one I need to use to do |
|
1875 // this cheaply.. |
|
1876 return hostKey + nsAutoCString("&") + spec; |
|
1877 } else { |
|
1878 // No hostKey found, so don't isolate image to a first party. |
|
1879 return spec; |
|
1880 } |
|
1881 } |
|
1882 |
|
1883 nsAutoCString imgLoader::GetCacheKey(nsIURI *firstPartyIsolationURI, nsIURI* uri, |
|
1884 bool *isIsolated) { |
|
1885 nsRefPtr<ImageURL> imageURI = new ImageURL(uri); |
|
1886 return GetCacheKey(firstPartyIsolationURI, imageURI, isIsolated); |
|
1887 } |
|
1888 |
|
1889 /* imgIRequest loadImageWithChannelXPCOM(in nsIChannel channel, in imgINotificationObserver aObserver, in nsISupports cx, out nsIStreamListener); */ |
|
1890 NS_IMETHODIMP imgLoader::LoadImageWithChannelXPCOM(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval) |
|
1891 { |
|
1892 nsresult result; |
|
1893 imgRequestProxy* proxy; |
|
1894 result = LoadImageWithChannel(channel, |
|
1895 aObserver, |
|
1896 aCX, |
|
1897 listener, |
|
1898 &proxy); |
|
1899 *_retval = proxy; |
|
1900 return result; |
|
1901 } |
|
1902 |
|
1903 nsresult imgLoader::LoadImageWithChannel(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgRequestProxy **_retval) |
|
1904 { |
|
1905 NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer"); |
|
1906 |
|
1907 MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy); |
|
1908 |
|
1909 if (!sThirdPartyUtilSvc) |
|
1910 return NS_ERROR_FAILURE; |
|
1911 |
|
1912 nsRefPtr<imgRequest> request; |
|
1913 |
|
1914 nsCOMPtr<nsIURI> uri; |
|
1915 channel->GetURI(getter_AddRefs(uri)); |
|
1916 |
|
1917 nsCOMPtr<nsIURI> firstPartyIsolationURI; |
|
1918 sThirdPartyUtilSvc->GetFirstPartyIsolationURI(channel, nullptr, |
|
1919 getter_AddRefs(firstPartyIsolationURI)); |
|
1920 |
|
1921 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; |
|
1922 channel->GetLoadFlags(&requestFlags); |
|
1923 |
|
1924 nsRefPtr<imgCacheEntry> entry; |
|
1925 imgCacheTable &cache = GetCache(uri); |
|
1926 nsAutoCString key = GetCacheKey(firstPartyIsolationURI, uri, nullptr); |
|
1927 |
|
1928 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { |
|
1929 imgCacheQueue &queue = GetCacheQueue(uri); |
|
1930 RemoveFromCache(key, cache, queue); |
|
1931 } else { |
|
1932 // Look in the cache for our URI, and then validate it. |
|
1933 // XXX For now ignore aCacheKey. We will need it in the future |
|
1934 // for correctly dealing with image load requests that are a result |
|
1935 // of post data. |
|
1936 |
|
1937 if (cache.Get(key, getter_AddRefs(entry)) && entry) { |
|
1938 // We don't want to kick off another network load. So we ask |
|
1939 // ValidateEntry to only do validation without creating a new proxy. If |
|
1940 // it says that the entry isn't valid any more, we'll only use the entry |
|
1941 // we're getting if the channel is loading from the cache anyways. |
|
1942 // |
|
1943 // XXX -- should this be changed? it's pretty much verbatim from the old |
|
1944 // code, but seems nonsensical. |
|
1945 if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver, aCX, |
|
1946 requestFlags, false, nullptr, nullptr, nullptr, |
|
1947 imgIRequest::CORS_NONE)) { |
|
1948 request = entry->GetRequest(); |
|
1949 } else { |
|
1950 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(channel)); |
|
1951 bool bUseCacheCopy; |
|
1952 |
|
1953 if (cacheChan) |
|
1954 cacheChan->IsFromCache(&bUseCacheCopy); |
|
1955 else |
|
1956 bUseCacheCopy = false; |
|
1957 |
|
1958 if (!bUseCacheCopy) { |
|
1959 entry = nullptr; |
|
1960 } else { |
|
1961 request = entry->GetRequest(); |
|
1962 } |
|
1963 } |
|
1964 |
|
1965 if (request && entry) { |
|
1966 // If this entry has no proxies, its request has no reference to the entry. |
|
1967 if (entry->HasNoProxies()) { |
|
1968 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri", key.get()); |
|
1969 NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!"); |
|
1970 request->SetCacheEntry(entry); |
|
1971 |
|
1972 if (mCacheTracker) |
|
1973 mCacheTracker->MarkUsed(entry); |
|
1974 } |
|
1975 } |
|
1976 } |
|
1977 } |
|
1978 |
|
1979 nsCOMPtr<nsILoadGroup> loadGroup; |
|
1980 channel->GetLoadGroup(getter_AddRefs(loadGroup)); |
|
1981 |
|
1982 // Filter out any load flags not from nsIRequest |
|
1983 requestFlags &= nsIRequest::LOAD_REQUESTMASK; |
|
1984 |
|
1985 nsresult rv = NS_OK; |
|
1986 if (request) { |
|
1987 // we have this in our cache already.. cancel the current (document) load |
|
1988 |
|
1989 channel->Cancel(NS_ERROR_PARSED_DATA_CACHED); // this should fire an OnStopRequest |
|
1990 |
|
1991 *listener = nullptr; // give them back a null nsIStreamListener |
|
1992 |
|
1993 rv = CreateNewProxyForRequest(request, loadGroup, aObserver, |
|
1994 requestFlags, _retval); |
|
1995 static_cast<imgRequestProxy*>(*_retval)->NotifyListener(); |
|
1996 } else { |
|
1997 // Default to doing a principal check because we don't know who |
|
1998 // started that load and whether their principal ended up being |
|
1999 // inherited on the channel. |
|
2000 NewRequestAndEntry(true, this, getter_AddRefs(request), getter_AddRefs(entry)); |
|
2001 |
|
2002 // We use originalURI here to fulfil the imgIRequest contract on GetURI. |
|
2003 nsCOMPtr<nsIURI> originalURI; |
|
2004 channel->GetOriginalURI(getter_AddRefs(originalURI)); |
|
2005 |
|
2006 // No principal specified here, because we're not passed one. |
|
2007 request->Init(originalURI, uri, firstPartyIsolationURI, channel, channel, entry, |
|
2008 aCX, nullptr, imgIRequest::CORS_NONE); |
|
2009 |
|
2010 ProxyListener *pl = new ProxyListener(static_cast<nsIStreamListener *>(request.get())); |
|
2011 NS_ADDREF(pl); |
|
2012 |
|
2013 *listener = static_cast<nsIStreamListener*>(pl); |
|
2014 NS_ADDREF(*listener); |
|
2015 |
|
2016 NS_RELEASE(pl); |
|
2017 |
|
2018 bool isIsolated = false; |
|
2019 nsAutoCString cacheKey = GetCacheKey(firstPartyIsolationURI, originalURI, &isIsolated); |
|
2020 if (isIsolated) // Try to add the new request into the cache. |
|
2021 PutIntoCache(cacheKey, entry); |
|
2022 |
|
2023 rv = CreateNewProxyForRequest(request, loadGroup, aObserver, |
|
2024 requestFlags, _retval); |
|
2025 |
|
2026 // Explicitly don't notify our proxy, because we're loading off the |
|
2027 // network, and necko (or things called from necko, such as |
|
2028 // imgCacheValidator) are going to call our notifications asynchronously, |
|
2029 // and we can't make it further asynchronous because observers might rely |
|
2030 // on imagelib completing its work between the channel's OnStartRequest and |
|
2031 // OnStopRequest. |
|
2032 } |
|
2033 |
|
2034 return rv; |
|
2035 } |
|
2036 |
|
2037 bool imgLoader::SupportImageWithMimeType(const char* aMimeType) |
|
2038 { |
|
2039 nsAutoCString mimeType(aMimeType); |
|
2040 ToLowerCase(mimeType); |
|
2041 return Image::GetDecoderType(mimeType.get()) != Image::eDecoderType_unknown; |
|
2042 } |
|
2043 |
|
2044 NS_IMETHODIMP imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest, |
|
2045 const uint8_t* aContents, |
|
2046 uint32_t aLength, |
|
2047 nsACString& aContentType) |
|
2048 { |
|
2049 return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType); |
|
2050 } |
|
2051 |
|
2052 /* static */ |
|
2053 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, uint32_t aLength, nsACString& aContentType) |
|
2054 { |
|
2055 /* Is it a GIF? */ |
|
2056 if (aLength >= 6 && (!nsCRT::strncmp(aContents, "GIF87a", 6) || |
|
2057 !nsCRT::strncmp(aContents, "GIF89a", 6))) |
|
2058 { |
|
2059 aContentType.AssignLiteral(IMAGE_GIF); |
|
2060 } |
|
2061 |
|
2062 /* or a PNG? */ |
|
2063 else if (aLength >= 8 && ((unsigned char)aContents[0]==0x89 && |
|
2064 (unsigned char)aContents[1]==0x50 && |
|
2065 (unsigned char)aContents[2]==0x4E && |
|
2066 (unsigned char)aContents[3]==0x47 && |
|
2067 (unsigned char)aContents[4]==0x0D && |
|
2068 (unsigned char)aContents[5]==0x0A && |
|
2069 (unsigned char)aContents[6]==0x1A && |
|
2070 (unsigned char)aContents[7]==0x0A)) |
|
2071 { |
|
2072 aContentType.AssignLiteral(IMAGE_PNG); |
|
2073 } |
|
2074 |
|
2075 /* maybe a JPEG (JFIF)? */ |
|
2076 /* JFIF files start with SOI APP0 but older files can start with SOI DQT |
|
2077 * so we test for SOI followed by any marker, i.e. FF D8 FF |
|
2078 * this will also work for SPIFF JPEG files if they appear in the future. |
|
2079 * |
|
2080 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00) |
|
2081 */ |
|
2082 else if (aLength >= 3 && |
|
2083 ((unsigned char)aContents[0])==0xFF && |
|
2084 ((unsigned char)aContents[1])==0xD8 && |
|
2085 ((unsigned char)aContents[2])==0xFF) |
|
2086 { |
|
2087 aContentType.AssignLiteral(IMAGE_JPEG); |
|
2088 } |
|
2089 |
|
2090 /* or how about ART? */ |
|
2091 /* ART begins with JG (4A 47). Major version offset 2. |
|
2092 * Minor version offset 3. Offset 4 must be nullptr. |
|
2093 */ |
|
2094 else if (aLength >= 5 && |
|
2095 ((unsigned char) aContents[0])==0x4a && |
|
2096 ((unsigned char) aContents[1])==0x47 && |
|
2097 ((unsigned char) aContents[4])==0x00 ) |
|
2098 { |
|
2099 aContentType.AssignLiteral(IMAGE_ART); |
|
2100 } |
|
2101 |
|
2102 else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) { |
|
2103 aContentType.AssignLiteral(IMAGE_BMP); |
|
2104 } |
|
2105 |
|
2106 // ICOs always begin with a 2-byte 0 followed by a 2-byte 1. |
|
2107 // CURs begin with 2-byte 0 followed by 2-byte 2. |
|
2108 else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) || |
|
2109 !memcmp(aContents, "\000\000\002\000", 4))) { |
|
2110 aContentType.AssignLiteral(IMAGE_ICO); |
|
2111 } |
|
2112 |
|
2113 else { |
|
2114 /* none of the above? I give up */ |
|
2115 return NS_ERROR_NOT_AVAILABLE; |
|
2116 } |
|
2117 |
|
2118 return NS_OK; |
|
2119 } |
|
2120 |
|
2121 /** |
|
2122 * proxy stream listener class used to handle multipart/x-mixed-replace |
|
2123 */ |
|
2124 |
|
2125 #include "nsIRequest.h" |
|
2126 #include "nsIStreamConverterService.h" |
|
2127 |
|
2128 NS_IMPL_ISUPPORTS(ProxyListener, |
|
2129 nsIStreamListener, |
|
2130 nsIThreadRetargetableStreamListener, |
|
2131 nsIRequestObserver) |
|
2132 |
|
2133 ProxyListener::ProxyListener(nsIStreamListener *dest) : |
|
2134 mDestListener(dest) |
|
2135 { |
|
2136 /* member initializers and constructor code */ |
|
2137 } |
|
2138 |
|
2139 ProxyListener::~ProxyListener() |
|
2140 { |
|
2141 /* destructor code */ |
|
2142 } |
|
2143 |
|
2144 |
|
2145 /** nsIRequestObserver methods **/ |
|
2146 |
|
2147 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ |
|
2148 NS_IMETHODIMP ProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) |
|
2149 { |
|
2150 if (!mDestListener) |
|
2151 return NS_ERROR_FAILURE; |
|
2152 |
|
2153 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); |
|
2154 if (channel) { |
|
2155 nsAutoCString contentType; |
|
2156 nsresult rv = channel->GetContentType(contentType); |
|
2157 |
|
2158 if (!contentType.IsEmpty()) { |
|
2159 /* If multipart/x-mixed-replace content, we'll insert a MIME decoder |
|
2160 in the pipeline to handle the content and pass it along to our |
|
2161 original listener. |
|
2162 */ |
|
2163 if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) { |
|
2164 |
|
2165 nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv)); |
|
2166 if (NS_SUCCEEDED(rv)) { |
|
2167 nsCOMPtr<nsIStreamListener> toListener(mDestListener); |
|
2168 nsCOMPtr<nsIStreamListener> fromListener; |
|
2169 |
|
2170 rv = convServ->AsyncConvertData("multipart/x-mixed-replace", |
|
2171 "*/*", |
|
2172 toListener, |
|
2173 nullptr, |
|
2174 getter_AddRefs(fromListener)); |
|
2175 if (NS_SUCCEEDED(rv)) |
|
2176 mDestListener = fromListener; |
|
2177 } |
|
2178 } |
|
2179 } |
|
2180 } |
|
2181 |
|
2182 return mDestListener->OnStartRequest(aRequest, ctxt); |
|
2183 } |
|
2184 |
|
2185 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ |
|
2186 NS_IMETHODIMP ProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) |
|
2187 { |
|
2188 if (!mDestListener) |
|
2189 return NS_ERROR_FAILURE; |
|
2190 |
|
2191 return mDestListener->OnStopRequest(aRequest, ctxt, status); |
|
2192 } |
|
2193 |
|
2194 /** nsIStreamListener methods **/ |
|
2195 |
|
2196 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ |
|
2197 NS_IMETHODIMP |
|
2198 ProxyListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, |
|
2199 nsIInputStream *inStr, uint64_t sourceOffset, |
|
2200 uint32_t count) |
|
2201 { |
|
2202 if (!mDestListener) |
|
2203 return NS_ERROR_FAILURE; |
|
2204 |
|
2205 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); |
|
2206 } |
|
2207 |
|
2208 /** nsThreadRetargetableStreamListener methods **/ |
|
2209 NS_IMETHODIMP |
|
2210 ProxyListener::CheckListenerChain() |
|
2211 { |
|
2212 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); |
|
2213 nsresult rv = NS_OK; |
|
2214 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = |
|
2215 do_QueryInterface(mDestListener, &rv); |
|
2216 if (retargetableListener) { |
|
2217 rv = retargetableListener->CheckListenerChain(); |
|
2218 } |
|
2219 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
2220 ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%x]", |
|
2221 (NS_SUCCEEDED(rv) ? "success" : "failure"), |
|
2222 this, (nsIStreamListener*)mDestListener, rv)); |
|
2223 return rv; |
|
2224 } |
|
2225 |
|
2226 /** |
|
2227 * http validate class. check a channel for a 304 |
|
2228 */ |
|
2229 |
|
2230 NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver, |
|
2231 nsIThreadRetargetableStreamListener, |
|
2232 nsIChannelEventSink, nsIInterfaceRequestor, |
|
2233 nsIAsyncVerifyRedirectCallback) |
|
2234 |
|
2235 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress, |
|
2236 imgLoader* loader, imgRequest *request, |
|
2237 void *aContext, bool forcePrincipalCheckForCacheEntry) |
|
2238 : mProgressProxy(progress), |
|
2239 mRequest(request), |
|
2240 mContext(aContext), |
|
2241 mImgLoader(loader) |
|
2242 { |
|
2243 NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, getter_AddRefs(mNewRequest), |
|
2244 getter_AddRefs(mNewEntry)); |
|
2245 } |
|
2246 |
|
2247 imgCacheValidator::~imgCacheValidator() |
|
2248 { |
|
2249 if (mRequest) { |
|
2250 mRequest->mValidator = nullptr; |
|
2251 } |
|
2252 } |
|
2253 |
|
2254 void imgCacheValidator::AddProxy(imgRequestProxy *aProxy) |
|
2255 { |
|
2256 // aProxy needs to be in the loadgroup since we're validating from |
|
2257 // the network. |
|
2258 aProxy->AddToLoadGroup(); |
|
2259 |
|
2260 mProxies.AppendObject(aProxy); |
|
2261 } |
|
2262 |
|
2263 /** nsIRequestObserver methods **/ |
|
2264 |
|
2265 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ |
|
2266 NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) |
|
2267 { |
|
2268 // If this request is coming from cache and has the same URI as our |
|
2269 // imgRequest, the request all our proxies are pointing at is valid, and all |
|
2270 // we have to do is tell them to notify their listeners. |
|
2271 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest)); |
|
2272 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); |
|
2273 if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) { |
|
2274 bool isFromCache = false; |
|
2275 cacheChan->IsFromCache(&isFromCache); |
|
2276 |
|
2277 nsCOMPtr<nsIURI> channelURI; |
|
2278 bool sameURI = false; |
|
2279 channel->GetURI(getter_AddRefs(channelURI)); |
|
2280 if (channelURI) |
|
2281 channelURI->Equals(mRequest->mCurrentURI, &sameURI); |
|
2282 |
|
2283 if (isFromCache && sameURI) { |
|
2284 uint32_t count = mProxies.Count(); |
|
2285 for (int32_t i = count-1; i>=0; i--) { |
|
2286 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]); |
|
2287 |
|
2288 // Proxies waiting on cache validation should be deferring notifications. |
|
2289 // Undefer them. |
|
2290 NS_ABORT_IF_FALSE(proxy->NotificationsDeferred(), |
|
2291 "Proxies waiting on cache validation should be " |
|
2292 "deferring notifications!"); |
|
2293 proxy->SetNotificationsDeferred(false); |
|
2294 |
|
2295 // Notify synchronously, because we're already in OnStartRequest, an |
|
2296 // asynchronously-called function. |
|
2297 proxy->SyncNotifyListener(); |
|
2298 } |
|
2299 |
|
2300 // We don't need to load this any more. |
|
2301 aRequest->Cancel(NS_BINDING_ABORTED); |
|
2302 |
|
2303 mRequest->SetLoadId(mContext); |
|
2304 mRequest->mValidator = nullptr; |
|
2305 |
|
2306 mRequest = nullptr; |
|
2307 |
|
2308 mNewRequest = nullptr; |
|
2309 mNewEntry = nullptr; |
|
2310 |
|
2311 return NS_OK; |
|
2312 } |
|
2313 } |
|
2314 |
|
2315 // We can't load out of cache. We have to create a whole new request for the |
|
2316 // data that's coming in off the channel. |
|
2317 nsCOMPtr<nsIURI> uri; |
|
2318 { |
|
2319 nsRefPtr<ImageURL> imageURL; |
|
2320 mRequest->GetURI(getter_AddRefs(imageURL)); |
|
2321 uri = imageURL->ToIURI(); |
|
2322 } |
|
2323 |
|
2324 #if defined(PR_LOGGING) |
|
2325 nsAutoCString spec; |
|
2326 uri->GetSpec(spec); |
|
2327 LOG_MSG_WITH_PARAM(GetImgLog(), "imgCacheValidator::OnStartRequest creating new request", "uri", spec.get()); |
|
2328 #endif |
|
2329 |
|
2330 int32_t corsmode = mRequest->GetCORSMode(); |
|
2331 nsCOMPtr<nsIPrincipal> loadingPrincipal = mRequest->GetLoadingPrincipal(); |
|
2332 nsCOMPtr<nsIURI> firstPartyIsolationURI = mRequest->mFirstPartyIsolationURI; |
|
2333 |
|
2334 // Doom the old request's cache entry |
|
2335 mRequest->RemoveFromCache(); |
|
2336 |
|
2337 mRequest->mValidator = nullptr; |
|
2338 mRequest = nullptr; |
|
2339 |
|
2340 // We use originalURI here to fulfil the imgIRequest contract on GetURI. |
|
2341 nsCOMPtr<nsIURI> originalURI; |
|
2342 channel->GetOriginalURI(getter_AddRefs(originalURI)); |
|
2343 mNewRequest->Init(originalURI, uri, firstPartyIsolationURI, aRequest, channel, |
|
2344 mNewEntry, mContext, loadingPrincipal, corsmode); |
|
2345 |
|
2346 mDestListener = new ProxyListener(mNewRequest); |
|
2347 |
|
2348 // Try to add the new request into the cache. Note that the entry must be in |
|
2349 // the cache before the proxies' ownership changes, because adding a proxy |
|
2350 // changes the caching behaviour for imgRequests. |
|
2351 bool isIsolated = false; |
|
2352 nsAutoCString key = mImgLoader->GetCacheKey(firstPartyIsolationURI, originalURI, |
|
2353 &isIsolated); |
|
2354 if (isIsolated) |
|
2355 mImgLoader->PutIntoCache(key, mNewEntry); |
|
2356 |
|
2357 uint32_t count = mProxies.Count(); |
|
2358 for (int32_t i = count-1; i>=0; i--) { |
|
2359 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]); |
|
2360 proxy->ChangeOwner(mNewRequest); |
|
2361 |
|
2362 // Notify synchronously, because we're already in OnStartRequest, an |
|
2363 // asynchronously-called function. |
|
2364 proxy->SetNotificationsDeferred(false); |
|
2365 proxy->SyncNotifyListener(); |
|
2366 } |
|
2367 |
|
2368 mNewRequest = nullptr; |
|
2369 mNewEntry = nullptr; |
|
2370 |
|
2371 return mDestListener->OnStartRequest(aRequest, ctxt); |
|
2372 } |
|
2373 |
|
2374 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ |
|
2375 NS_IMETHODIMP imgCacheValidator::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) |
|
2376 { |
|
2377 if (!mDestListener) |
|
2378 return NS_OK; |
|
2379 |
|
2380 return mDestListener->OnStopRequest(aRequest, ctxt, status); |
|
2381 } |
|
2382 |
|
2383 /** nsIStreamListener methods **/ |
|
2384 |
|
2385 |
|
2386 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ |
|
2387 NS_IMETHODIMP |
|
2388 imgCacheValidator::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, |
|
2389 nsIInputStream *inStr, |
|
2390 uint64_t sourceOffset, uint32_t count) |
|
2391 { |
|
2392 if (!mDestListener) { |
|
2393 // XXX see bug 113959 |
|
2394 uint32_t _retval; |
|
2395 inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval); |
|
2396 return NS_OK; |
|
2397 } |
|
2398 |
|
2399 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); |
|
2400 } |
|
2401 |
|
2402 /** nsIThreadRetargetableStreamListener methods **/ |
|
2403 |
|
2404 NS_IMETHODIMP |
|
2405 imgCacheValidator::CheckListenerChain() |
|
2406 { |
|
2407 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); |
|
2408 nsresult rv = NS_OK; |
|
2409 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = |
|
2410 do_QueryInterface(mDestListener, &rv); |
|
2411 if (retargetableListener) { |
|
2412 rv = retargetableListener->CheckListenerChain(); |
|
2413 } |
|
2414 PR_LOG(GetImgLog(), PR_LOG_DEBUG, |
|
2415 ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %d=%s", |
|
2416 this, NS_SUCCEEDED(rv) ? "succeeded" : "failed", rv)); |
|
2417 return rv; |
|
2418 } |
|
2419 |
|
2420 /** nsIInterfaceRequestor methods **/ |
|
2421 |
|
2422 NS_IMETHODIMP imgCacheValidator::GetInterface(const nsIID & aIID, void **aResult) |
|
2423 { |
|
2424 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) |
|
2425 return QueryInterface(aIID, aResult); |
|
2426 |
|
2427 return mProgressProxy->GetInterface(aIID, aResult); |
|
2428 } |
|
2429 |
|
2430 // These functions are materially the same as the same functions in imgRequest. |
|
2431 // We duplicate them because we're verifying whether cache loads are necessary, |
|
2432 // not unconditionally loading. |
|
2433 |
|
2434 /** nsIChannelEventSink methods **/ |
|
2435 NS_IMETHODIMP imgCacheValidator::AsyncOnChannelRedirect(nsIChannel *oldChannel, |
|
2436 nsIChannel *newChannel, uint32_t flags, |
|
2437 nsIAsyncVerifyRedirectCallback *callback) |
|
2438 { |
|
2439 // Note all cache information we get from the old channel. |
|
2440 mNewRequest->SetCacheValidation(mNewEntry, oldChannel); |
|
2441 |
|
2442 // Prepare for callback |
|
2443 mRedirectCallback = callback; |
|
2444 mRedirectChannel = newChannel; |
|
2445 |
|
2446 return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); |
|
2447 } |
|
2448 |
|
2449 NS_IMETHODIMP imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult) |
|
2450 { |
|
2451 // If we've already been told to abort, just do so. |
|
2452 if (NS_FAILED(aResult)) { |
|
2453 mRedirectCallback->OnRedirectVerifyCallback(aResult); |
|
2454 mRedirectCallback = nullptr; |
|
2455 mRedirectChannel = nullptr; |
|
2456 return NS_OK; |
|
2457 } |
|
2458 |
|
2459 // make sure we have a protocol that returns data rather than opens |
|
2460 // an external application, e.g. mailto: |
|
2461 nsCOMPtr<nsIURI> uri; |
|
2462 mRedirectChannel->GetURI(getter_AddRefs(uri)); |
|
2463 bool doesNotReturnData = false; |
|
2464 NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, |
|
2465 &doesNotReturnData); |
|
2466 |
|
2467 nsresult result = NS_OK; |
|
2468 |
|
2469 if (doesNotReturnData) { |
|
2470 result = NS_ERROR_ABORT; |
|
2471 } |
|
2472 |
|
2473 mRedirectCallback->OnRedirectVerifyCallback(result); |
|
2474 mRedirectCallback = nullptr; |
|
2475 mRedirectChannel = nullptr; |
|
2476 return NS_OK; |
|
2477 } |