Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / netwerk / protocol / about / nsAboutCache.cpp
blob1873277a7a683bb135de7468bab18f1294991ea5
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsAboutCache.h"
7 #include "nsIInputStream.h"
8 #include "nsIURI.h"
9 #include "nsCOMPtr.h"
10 #include "nsNetUtil.h"
11 #include "nsIPipe.h"
12 #include "nsContentUtils.h"
13 #include "nsEscape.h"
14 #include "nsAboutProtocolUtils.h"
15 #include "nsPrintfCString.h"
17 #include "nsICacheStorageService.h"
18 #include "nsICacheStorage.h"
19 #include "CacheFileUtils.h"
20 #include "CacheObserver.h"
22 #include "nsThreadUtils.h"
24 using namespace mozilla::net;
26 NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule)
27 NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest,
28 nsICacheStorageVisitor)
30 NS_IMETHODIMP
31 nsAboutCache::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
32 nsIChannel** result) {
33 nsresult rv;
35 NS_ENSURE_ARG_POINTER(aURI);
37 RefPtr<Channel> channel = new Channel();
38 rv = channel->Init(aURI, aLoadInfo);
39 if (NS_FAILED(rv)) return rv;
41 channel.forget(result);
43 return NS_OK;
46 nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
47 nsresult rv;
49 mCancel = false;
51 nsCOMPtr<nsIInputStream> inputStream;
52 NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream), 16384,
53 (uint32_t)-1,
54 true, // non-blocking input
55 false // blocking output
58 nsAutoCString storageName;
59 rv = ParseURI(aURI, storageName);
60 if (NS_FAILED(rv)) return rv;
62 mOverview = storageName.IsEmpty();
63 if (mOverview) {
64 // ...and visit all we can
65 mStorageList.AppendElement("memory"_ns);
66 mStorageList.AppendElement("disk"_ns);
67 } else {
68 // ...and visit just the specified storage, entries will output too
69 mStorageList.AppendElement(storageName);
72 // The entries header is added on encounter of the first entry
73 mEntriesHeaderAdded = false;
75 rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), aURI,
76 inputStream.forget(), "text/html"_ns,
77 "utf-8"_ns, aLoadInfo);
78 if (NS_FAILED(rv)) return rv;
80 mBuffer.AssignLiteral(
81 "<!DOCTYPE html>\n"
82 "<html>\n"
83 "<head>\n"
84 " <title>Network Cache Storage Information</title>\n"
85 " <meta charset=\"utf-8\">\n"
86 " <meta name=\"color-scheme\" content=\"light dark\">\n"
87 " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
88 "chrome:; object-src 'none'\"/>\n"
89 " <link rel=\"stylesheet\" "
90 "href=\"chrome://global/skin/in-content/info-pages.css\"/>\n"
91 " <link rel=\"stylesheet\" "
92 "href=\"chrome://global/skin/aboutCache.css\"/>\n"
93 "</head>\n"
94 "<body class=\"aboutPageWideContainer\">\n"
95 "<h1>Information about the Network Cache Storage Service</h1>\n");
97 if (!mOverview) {
98 mBuffer.AppendLiteral(
99 "<a href=\"about:cache?storage=\">Back to overview</a>");
102 rv = FlushBuffer();
103 if (NS_FAILED(rv)) {
104 NS_WARNING("Failed to flush buffer");
107 return NS_OK;
110 NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener* aListener) {
111 nsresult rv;
113 if (!mChannel) {
114 return NS_ERROR_UNEXPECTED;
117 // Kick the walk loop.
118 rv = VisitNextStorage();
119 if (NS_FAILED(rv)) return rv;
121 rv = mChannel->AsyncOpen(aListener);
122 if (NS_FAILED(rv)) return rv;
124 return NS_OK;
127 NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream** _retval) {
128 return NS_ERROR_NOT_IMPLEMENTED;
131 nsresult nsAboutCache::Channel::ParseURI(nsIURI* uri, nsACString& storage) {
133 // about:cache[?storage=<storage-name>[&context=<context-key>]]
135 nsresult rv;
137 nsAutoCString path;
138 rv = uri->GetPathQueryRef(path);
139 if (NS_FAILED(rv)) return rv;
141 storage.Truncate();
143 nsACString::const_iterator start, valueStart, end;
144 path.BeginReading(start);
145 path.EndReading(end);
147 valueStart = end;
148 if (!FindInReadable("?storage="_ns, start, valueStart)) {
149 return NS_OK;
152 storage.Assign(Substring(valueStart, end));
154 return NS_OK;
157 nsresult nsAboutCache::Channel::VisitNextStorage() {
158 if (!mStorageList.Length()) return NS_ERROR_NOT_AVAILABLE;
160 mStorageName = mStorageList[0];
161 mStorageList.RemoveElementAt(0);
163 // Must re-dispatch since we cannot start another visit cycle
164 // from visitor callback. The cache v1 service doesn't like it.
165 // TODO - mayhemer, bug 913828, remove this dispatch and call
166 // directly.
167 return NS_DispatchToMainThread(mozilla::NewRunnableMethod(
168 "nsAboutCache::Channel::FireVisitStorage", this,
169 &nsAboutCache::Channel::FireVisitStorage));
172 void nsAboutCache::Channel::FireVisitStorage() {
173 nsresult rv;
175 rv = VisitStorage(mStorageName);
176 if (NS_FAILED(rv)) {
177 nsAutoCString escaped;
178 nsAppendEscapedHTML(mStorageName, escaped);
179 mBuffer.Append(nsPrintfCString(
180 "<p>Unrecognized storage name '%s' in about:cache URL</p>",
181 escaped.get()));
183 rv = FlushBuffer();
184 if (NS_FAILED(rv)) {
185 NS_WARNING("Failed to flush buffer");
188 // Simulate finish of a visit cycle, this tries the next storage
189 // or closes the output stream (i.e. the UI loader will stop spinning)
190 OnCacheEntryVisitCompleted();
194 nsresult nsAboutCache::Channel::VisitStorage(nsACString const& storageName) {
195 nsresult rv;
197 rv = GetStorage(storageName, nullptr, getter_AddRefs(mStorage));
198 if (NS_FAILED(rv)) return rv;
200 rv = mStorage->AsyncVisitStorage(this, !mOverview);
201 if (NS_FAILED(rv)) return rv;
203 return NS_OK;
206 // static
207 nsresult nsAboutCache::GetStorage(nsACString const& storageName,
208 nsILoadContextInfo* loadInfo,
209 nsICacheStorage** storage) {
210 nsresult rv;
212 nsCOMPtr<nsICacheStorageService> cacheService =
213 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
214 if (NS_FAILED(rv)) return rv;
216 nsCOMPtr<nsICacheStorage> cacheStorage;
217 if (storageName == "disk") {
218 rv = cacheService->DiskCacheStorage(loadInfo, getter_AddRefs(cacheStorage));
219 } else if (storageName == "memory") {
220 rv = cacheService->MemoryCacheStorage(loadInfo,
221 getter_AddRefs(cacheStorage));
222 } else {
223 rv = NS_ERROR_UNEXPECTED;
225 if (NS_FAILED(rv)) return rv;
227 cacheStorage.forget(storage);
228 return NS_OK;
231 NS_IMETHODIMP
232 nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount,
233 uint64_t aConsumption,
234 uint64_t aCapacity,
235 nsIFile* aDirectory) {
236 // We need mStream for this
237 if (!mStream) {
238 return NS_ERROR_FAILURE;
241 mBuffer.AssignLiteral("<h2>");
242 nsAppendEscapedHTML(mStorageName, mBuffer);
243 mBuffer.AppendLiteral(
244 "</h2>\n"
245 "<table id=\"");
246 mBuffer.AppendLiteral("\">\n");
248 // Write out cache info
249 // Number of entries
250 mBuffer.AppendLiteral(
251 " <tr>\n"
252 " <th>Number of entries:</th>\n"
253 " <td>");
254 mBuffer.AppendInt(aEntryCount);
255 mBuffer.AppendLiteral(
256 "</td>\n"
257 " </tr>\n");
259 // Maximum storage size
260 mBuffer.AppendLiteral(
261 " <tr>\n"
262 " <th>Maximum storage size:</th>\n"
263 " <td>");
264 mBuffer.AppendInt(aCapacity / 1024);
265 mBuffer.AppendLiteral(
266 " KiB</td>\n"
267 " </tr>\n");
269 // Storage in use
270 mBuffer.AppendLiteral(
271 " <tr>\n"
272 " <th>Storage in use:</th>\n"
273 " <td>");
274 mBuffer.AppendInt(aConsumption / 1024);
275 mBuffer.AppendLiteral(
276 " KiB</td>\n"
277 " </tr>\n");
279 // Storage disk location
280 mBuffer.AppendLiteral(
281 " <tr>\n"
282 " <th>Storage disk location:</th>\n"
283 " <td>");
284 if (aDirectory) {
285 nsAutoString path;
286 aDirectory->GetPath(path);
287 mBuffer.Append(NS_ConvertUTF16toUTF8(path));
288 } else {
289 mBuffer.AppendLiteral("none, only stored in memory");
291 mBuffer.AppendLiteral(
292 " </td>\n"
293 " </tr>\n");
295 if (mOverview) { // The about:cache case
296 if (aEntryCount != 0) { // Add the "List Cache Entries" link
297 mBuffer.AppendLiteral(
298 " <tr>\n"
299 " <td colspan=\"2\"><a href=\"about:cache?storage=");
300 nsAppendEscapedHTML(mStorageName, mBuffer);
301 mBuffer.AppendLiteral(
302 "\">List Cache Entries</a></td>\n"
303 " </tr>\n");
307 mBuffer.AppendLiteral("</table>\n");
309 // The entries header is added on encounter of the first entry
310 mEntriesHeaderAdded = false;
312 nsresult rv = FlushBuffer();
313 if (NS_FAILED(rv)) {
314 NS_WARNING("Failed to flush buffer");
317 if (mOverview) {
318 // OnCacheEntryVisitCompleted() is not called when we do not iterate
319 // cache entries. Since this moves forward to the next storage in
320 // the list we want to visit, artificially call it here.
321 OnCacheEntryVisitCompleted();
324 return NS_OK;
327 NS_IMETHODIMP
328 nsAboutCache::Channel::OnCacheEntryInfo(
329 nsIURI* aURI, const nsACString& aIdEnhance, int64_t aDataSize,
330 int64_t aAltDataSize, uint32_t aFetchCount, uint32_t aLastModified,
331 uint32_t aExpirationTime, bool aPinned, nsILoadContextInfo* aInfo) {
332 // We need mStream for this
333 if (!mStream || mCancel) {
334 // Returning a failure from this callback stops the iteration
335 return NS_ERROR_FAILURE;
338 if (!mEntriesHeaderAdded) {
339 mBuffer.AppendLiteral(
340 "<hr/>\n"
341 "<table id=\"entries\">\n"
342 " <colgroup>\n"
343 " <col id=\"col-key\">\n"
344 " <col id=\"col-dataSize\">\n"
345 " <col id=\"col-altDataSize\">\n"
346 " <col id=\"col-fetchCount\">\n"
347 " <col id=\"col-lastModified\">\n"
348 " <col id=\"col-expires\">\n"
349 " <col id=\"col-pinned\">\n"
350 " </colgroup>\n"
351 " <thead>\n"
352 " <tr>\n"
353 " <th>Key</th>\n"
354 " <th>Data size</th>\n"
355 " <th>Alternative Data size</th>\n"
356 " <th>Fetch count</th>\n"
357 " <th>Last Modifed</th>\n"
358 " <th>Expires</th>\n"
359 " <th>Pinning</th>\n"
360 " </tr>\n"
361 " </thead>\n");
362 mEntriesHeaderAdded = true;
365 // Generate a about:cache-entry URL for this entry...
367 nsAutoCString url;
368 url.AssignLiteral("about:cache-entry?storage=");
369 nsAppendEscapedHTML(mStorageName, url);
371 nsAutoCString context;
372 CacheFileUtils::AppendKeyPrefix(aInfo, context);
373 url.AppendLiteral("&amp;context=");
374 nsAppendEscapedHTML(context, url);
376 url.AppendLiteral("&amp;eid=");
377 nsAppendEscapedHTML(aIdEnhance, url);
379 nsAutoCString cacheUriSpec;
380 aURI->GetAsciiSpec(cacheUriSpec);
381 nsAutoCString escapedCacheURI;
382 nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI);
383 url.AppendLiteral("&amp;uri=");
384 url += escapedCacheURI;
386 // Entry start...
387 mBuffer.AppendLiteral(" <tr>\n");
389 // URI
390 mBuffer.AppendLiteral(" <td><a href=\"");
391 mBuffer.Append(url);
392 mBuffer.AppendLiteral("\">");
393 if (!aIdEnhance.IsEmpty()) {
394 nsAppendEscapedHTML(aIdEnhance, mBuffer);
395 mBuffer.Append(':');
397 mBuffer.Append(escapedCacheURI);
398 mBuffer.AppendLiteral("</a>");
400 if (!context.IsEmpty()) {
401 mBuffer.AppendLiteral("<br><span title=\"Context separation key\">");
402 nsAutoCString escapedContext;
403 nsAppendEscapedHTML(context, escapedContext);
404 mBuffer.Append(escapedContext);
405 mBuffer.AppendLiteral("</span>");
408 mBuffer.AppendLiteral("</td>\n");
410 // Content length
411 mBuffer.AppendLiteral(" <td>");
412 mBuffer.AppendInt(aDataSize);
413 mBuffer.AppendLiteral(" bytes</td>\n");
415 // Length of alternative content
416 mBuffer.AppendLiteral(" <td>");
417 mBuffer.AppendInt(aAltDataSize);
418 mBuffer.AppendLiteral(" bytes</td>\n");
420 // Number of accesses
421 mBuffer.AppendLiteral(" <td>");
422 mBuffer.AppendInt(aFetchCount);
423 mBuffer.AppendLiteral("</td>\n");
425 // vars for reporting time
426 char buf[255];
428 // Last modified time
429 mBuffer.AppendLiteral(" <td>");
430 if (aLastModified) {
431 PrintTimeString(buf, sizeof(buf), aLastModified);
432 mBuffer.Append(buf);
433 } else {
434 mBuffer.AppendLiteral("No last modified time");
436 mBuffer.AppendLiteral("</td>\n");
438 // Expires time
439 mBuffer.AppendLiteral(" <td>");
441 // Bug - 633747.
442 // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
443 // So we check if time is 0, then we show a message, "Expired Immediately"
444 if (aExpirationTime == 0) {
445 mBuffer.AppendLiteral("Expired Immediately");
446 } else if (aExpirationTime < 0xFFFFFFFF) {
447 PrintTimeString(buf, sizeof(buf), aExpirationTime);
448 mBuffer.Append(buf);
449 } else {
450 mBuffer.AppendLiteral("No expiration time");
452 mBuffer.AppendLiteral("</td>\n");
454 // Pinning
455 mBuffer.AppendLiteral(" <td>");
456 if (aPinned) {
457 mBuffer.AppendLiteral("Pinned");
458 } else {
459 mBuffer.AppendLiteral("&nbsp;");
461 mBuffer.AppendLiteral("</td>\n");
463 // Entry is done...
464 mBuffer.AppendLiteral(" </tr>\n");
466 return FlushBuffer();
469 NS_IMETHODIMP
470 nsAboutCache::Channel::OnCacheEntryVisitCompleted() {
471 if (!mStream) {
472 return NS_ERROR_FAILURE;
475 if (mEntriesHeaderAdded) {
476 mBuffer.AppendLiteral("</table>\n");
479 // Kick another storage visiting (from a storage that allows us.)
480 while (mStorageList.Length()) {
481 nsresult rv = VisitNextStorage();
482 if (NS_SUCCEEDED(rv)) {
483 // Expecting new round of OnCache* calls.
484 return NS_OK;
488 // We are done!
489 mBuffer.AppendLiteral(
490 "</body>\n"
491 "</html>\n");
492 nsresult rv = FlushBuffer();
493 if (NS_FAILED(rv)) {
494 NS_WARNING("Failed to flush buffer");
496 mStream->Close();
498 return NS_OK;
501 nsresult nsAboutCache::Channel::FlushBuffer() {
502 nsresult rv;
504 uint32_t bytesWritten;
505 rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
506 mBuffer.Truncate();
508 if (NS_FAILED(rv)) {
509 mCancel = true;
512 return rv;
515 NS_IMETHODIMP
516 nsAboutCache::GetURIFlags(nsIURI* aURI, uint32_t* result) {
517 *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
518 nsIAboutModule::IS_SECURE_CHROME_UI;
519 return NS_OK;
522 // static
523 nsresult nsAboutCache::Create(REFNSIID aIID, void** aResult) {
524 RefPtr<nsAboutCache> about = new nsAboutCache();
525 return about->QueryInterface(aIID, aResult);
528 NS_IMETHODIMP
529 nsAboutCache::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
530 return NS_ERROR_ILLEGAL_VALUE;
533 ////////////////////////////////////////////////////////////////////////////////