Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/protocol/about/nsAboutCache.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/. */
5
6
#include "nsAboutCache.h"
7
#include "nsIInputStream.h"
8
#include "nsIStorageStream.h"
9
#include "nsIURI.h"
10
#include "nsCOMPtr.h"
11
#include "nsNetUtil.h"
12
#include "nsIPipe.h"
13
#include "nsContentUtils.h"
14
#include "nsEscape.h"
15
#include "nsAboutProtocolUtils.h"
16
#include "nsPrintfCString.h"
17
18
#include "nsICacheStorageService.h"
19
#include "nsICacheStorage.h"
20
#include "CacheFileUtils.h"
21
#include "CacheObserver.h"
22
23
#include "nsThreadUtils.h"
24
25
using namespace mozilla::net;
26
27
NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule)
28
NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest, nsICacheStorageVisitor)
29
30
NS_IMETHODIMP
31
nsAboutCache::NewChannel(nsIURI* aURI,
32
                         nsILoadInfo* aLoadInfo,
33
                         nsIChannel** result)
34
0
{
35
0
    nsresult rv;
36
0
37
0
    NS_ENSURE_ARG_POINTER(aURI);
38
0
39
0
    RefPtr<Channel> channel = new Channel();
40
0
    rv = channel->Init(aURI, aLoadInfo);
41
0
    if (NS_FAILED(rv)) return rv;
42
0
43
0
    channel.forget(result);
44
0
45
0
    return NS_OK;
46
0
}
47
48
nsresult
49
nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo)
50
0
{
51
0
    nsresult rv;
52
0
53
0
    mCancel = false;
54
0
55
0
    nsCOMPtr<nsIInputStream> inputStream;
56
0
    rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream),
57
0
                    16384, (uint32_t)-1,
58
0
                    true, // non-blocking input
59
0
                    false // blocking output
60
0
    );
61
0
    if (NS_FAILED(rv)) return rv;
62
0
63
0
    nsAutoCString storageName;
64
0
    rv = ParseURI(aURI, storageName);
65
0
    if (NS_FAILED(rv)) return rv;
66
0
67
0
    mOverview = storageName.IsEmpty();
68
0
    if (mOverview) {
69
0
        // ...and visit all we can
70
0
        mStorageList.AppendElement(NS_LITERAL_CSTRING("memory"));
71
0
        mStorageList.AppendElement(NS_LITERAL_CSTRING("disk"));
72
0
        mStorageList.AppendElement(NS_LITERAL_CSTRING("appcache"));
73
0
    } else {
74
0
        // ...and visit just the specified storage, entries will output too
75
0
        mStorageList.AppendElement(storageName);
76
0
    }
77
0
78
0
    // The entries header is added on encounter of the first entry
79
0
    mEntriesHeaderAdded = false;
80
0
81
0
    rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel),
82
0
                                          aURI,
83
0
                                          inputStream.forget(),
84
0
                                          NS_LITERAL_CSTRING("text/html"),
85
0
                                          NS_LITERAL_CSTRING("utf-8"),
86
0
                                          aLoadInfo);
87
0
    if (NS_FAILED(rv)) return rv;
88
0
89
0
    mBuffer.AssignLiteral(
90
0
        "<!DOCTYPE html>\n"
91
0
        "<html>\n"
92
0
        "<head>\n"
93
0
        "  <title>Network Cache Storage Information</title>\n"
94
0
        "  <meta charset=\"utf-8\">\n"
95
0
        "  <meta http-equiv=\"Content-Security-Policy\" content=\"default-src chrome:\"/>\n"
96
0
        "  <link rel=\"stylesheet\" href=\"chrome://global/skin/about.css\"/>\n"
97
0
        "  <link rel=\"stylesheet\" href=\"chrome://global/skin/aboutCache.css\"/>\n"
98
0
        "</head>\n"
99
0
        "<body class=\"aboutPageWideContainer\">\n"
100
0
        "<h1>Information about the Network Cache Storage Service</h1>\n");
101
0
102
0
    // Add the context switch controls
103
0
    mBuffer.AppendLiteral(
104
0
        "<label><input id='priv' type='checkbox'/> Private</label>\n"
105
0
        "<label><input id='anon' type='checkbox'/> Anonymous</label>\n"
106
0
    );
107
0
108
0
    // Visit scoping by browser and appid is not implemented for
109
0
    // the old cache, simply don't add these controls.
110
0
    // The appid/inbrowser entries are already mixed in the default
111
0
    // view anyway.
112
0
    mBuffer.AppendLiteral(
113
0
        "<label><input id='appid' type='text' size='6'/> AppID</label>\n"
114
0
        "<label><input id='inbrowser' type='checkbox'/> In Browser Element</label>\n"
115
0
    );
116
0
117
0
    mBuffer.AppendLiteral(
118
0
        "<label><input id='submit' type='button' value='Update'/></label>\n"
119
0
    );
120
0
121
0
    if (!mOverview) {
122
0
        mBuffer.AppendLiteral("<a href=\"about:cache?storage=&amp;context=");
123
0
        nsAppendEscapedHTML(mContextString, mBuffer);
124
0
        mBuffer.AppendLiteral("\">Back to overview</a>");
125
0
    }
126
0
127
0
    rv = FlushBuffer();
128
0
    if (NS_FAILED(rv)) {
129
0
        NS_WARNING("Failed to flush buffer");
130
0
    }
131
0
132
0
    return NS_OK;
133
0
}
134
135
NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
136
0
{
137
0
    nsresult rv;
138
0
139
0
    if (!mChannel) {
140
0
        return NS_ERROR_UNEXPECTED;
141
0
    }
142
0
143
0
    // Kick the walk loop.
144
0
    rv = VisitNextStorage();
145
0
    if (NS_FAILED(rv)) return rv;
146
0
147
0
    MOZ_ASSERT(!aContext, "asyncOpen2() does not take a context argument");
148
0
    rv = NS_MaybeOpenChannelUsingAsyncOpen2(mChannel, aListener);
149
0
    if (NS_FAILED(rv)) return rv;
150
0
151
0
    return NS_OK;
152
0
}
153
154
NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen2(nsIStreamListener *aListener)
155
0
{
156
0
    return AsyncOpen(aListener, nullptr);
157
0
}
158
159
NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream * *_retval)
160
0
{
161
0
    return NS_ERROR_NOT_IMPLEMENTED;
162
0
}
163
164
NS_IMETHODIMP nsAboutCache::Channel::Open2(nsIInputStream * *_retval)
165
0
{
166
0
    return NS_ERROR_NOT_IMPLEMENTED;
167
0
}
168
169
nsresult
170
nsAboutCache::Channel::ParseURI(nsIURI * uri, nsACString & storage)
171
0
{
172
0
    //
173
0
    // about:cache[?storage=<storage-name>[&context=<context-key>]]
174
0
    //
175
0
    nsresult rv;
176
0
177
0
    nsAutoCString path;
178
0
    rv = uri->GetPathQueryRef(path);
179
0
    if (NS_FAILED(rv)) return rv;
180
0
181
0
    mContextString.Truncate();
182
0
    mLoadInfo = CacheFileUtils::ParseKey(NS_LITERAL_CSTRING(""));
183
0
    storage.Truncate();
184
0
185
0
    nsACString::const_iterator start, valueStart, end;
186
0
    path.BeginReading(start);
187
0
    path.EndReading(end);
188
0
189
0
    valueStart = end;
190
0
    if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), start, valueStart)) {
191
0
        return NS_OK;
192
0
    }
193
0
194
0
    nsACString::const_iterator storageNameBegin = valueStart;
195
0
196
0
    start = valueStart;
197
0
    valueStart = end;
198
0
    if (!FindInReadable(NS_LITERAL_CSTRING("&context="), start, valueStart))
199
0
        start = end;
200
0
201
0
    nsACString::const_iterator storageNameEnd = start;
202
0
203
0
    mContextString = Substring(valueStart, end);
204
0
    mLoadInfo = CacheFileUtils::ParseKey(mContextString);
205
0
    storage.Assign(Substring(storageNameBegin, storageNameEnd));
206
0
207
0
    return NS_OK;
208
0
}
209
210
nsresult
211
nsAboutCache::Channel::VisitNextStorage()
212
0
{
213
0
    if (!mStorageList.Length())
214
0
        return NS_ERROR_NOT_AVAILABLE;
215
0
216
0
    mStorageName = mStorageList[0];
217
0
    mStorageList.RemoveElementAt(0);
218
0
219
0
    // Must re-dispatch since we cannot start another visit cycle
220
0
    // from visitor callback.  The cache v1 service doesn't like it.
221
0
    // TODO - mayhemer, bug 913828, remove this dispatch and call
222
0
    // directly.
223
0
    return NS_DispatchToMainThread(
224
0
      mozilla::NewRunnableMethod("nsAboutCache::Channel::FireVisitStorage",
225
0
                                 this,
226
0
                                 &nsAboutCache::Channel::FireVisitStorage));
227
0
}
228
229
void
230
nsAboutCache::Channel::FireVisitStorage()
231
0
{
232
0
    nsresult rv;
233
0
234
0
    rv = VisitStorage(mStorageName);
235
0
    if (NS_FAILED(rv)) {
236
0
        if (mLoadInfo) {
237
0
            nsAutoCString escaped;
238
0
            nsAppendEscapedHTML(mStorageName, escaped);
239
0
            mBuffer.Append(
240
0
                nsPrintfCString("<p>Unrecognized storage name '%s' in about:cache URL</p>",
241
0
                                escaped.get()));
242
0
        } else {
243
0
            nsAutoCString escaped;
244
0
            nsAppendEscapedHTML(mContextString, escaped);
245
0
            mBuffer.Append(
246
0
                nsPrintfCString("<p>Unrecognized context key '%s' in about:cache URL</p>",
247
0
                                escaped.get()));
248
0
        }
249
0
250
0
        rv = FlushBuffer();
251
0
        if (NS_FAILED(rv)) {
252
0
            NS_WARNING("Failed to flush buffer");
253
0
        }
254
0
255
0
        // Simulate finish of a visit cycle, this tries the next storage
256
0
        // or closes the output stream (i.e. the UI loader will stop spinning)
257
0
        OnCacheEntryVisitCompleted();
258
0
    }
259
0
}
260
261
nsresult
262
nsAboutCache::Channel::VisitStorage(nsACString const & storageName)
263
0
{
264
0
    nsresult rv;
265
0
266
0
    rv = GetStorage(storageName, mLoadInfo, getter_AddRefs(mStorage));
267
0
    if (NS_FAILED(rv)) return rv;
268
0
269
0
    rv = mStorage->AsyncVisitStorage(this, !mOverview);
270
0
    if (NS_FAILED(rv)) return rv;
271
0
272
0
    return NS_OK;
273
0
}
274
275
//static
276
nsresult
277
nsAboutCache::GetStorage(nsACString const & storageName,
278
                         nsILoadContextInfo* loadInfo,
279
                         nsICacheStorage **storage)
280
0
{
281
0
    nsresult rv;
282
0
283
0
    nsCOMPtr<nsICacheStorageService> cacheService =
284
0
             do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
285
0
    if (NS_FAILED(rv)) return rv;
286
0
287
0
    nsCOMPtr<nsICacheStorage> cacheStorage;
288
0
    if (storageName == "disk") {
289
0
        rv = cacheService->DiskCacheStorage(
290
0
            loadInfo, false, getter_AddRefs(cacheStorage));
291
0
    } else if (storageName == "memory") {
292
0
        rv = cacheService->MemoryCacheStorage(
293
0
            loadInfo, getter_AddRefs(cacheStorage));
294
0
    } else if (storageName == "appcache") {
295
0
        rv = cacheService->AppCacheStorage(
296
0
            loadInfo, nullptr, getter_AddRefs(cacheStorage));
297
0
    } else {
298
0
        rv = NS_ERROR_UNEXPECTED;
299
0
    }
300
0
    if (NS_FAILED(rv)) return rv;
301
0
302
0
    cacheStorage.forget(storage);
303
0
    return NS_OK;
304
0
}
305
306
NS_IMETHODIMP
307
nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount, uint64_t aConsumption,
308
                                          uint64_t aCapacity, nsIFile * aDirectory)
309
0
{
310
0
    // We need mStream for this
311
0
    if (!mStream) {
312
0
        return NS_ERROR_FAILURE;
313
0
    }
314
0
315
0
    mBuffer.AssignLiteral("<h2>");
316
0
    nsAppendEscapedHTML(mStorageName, mBuffer);
317
0
    mBuffer.AppendLiteral("</h2>\n"
318
0
                          "<table id=\"");
319
0
    mBuffer.AppendLiteral("\">\n");
320
0
321
0
    // Write out cache info
322
0
    // Number of entries
323
0
    mBuffer.AppendLiteral("  <tr>\n"
324
0
                          "    <th>Number of entries:</th>\n"
325
0
                          "    <td>");
326
0
    mBuffer.AppendInt(aEntryCount);
327
0
    mBuffer.AppendLiteral("</td>\n"
328
0
                          "  </tr>\n");
329
0
330
0
    // Maximum storage size
331
0
    mBuffer.AppendLiteral("  <tr>\n"
332
0
                          "    <th>Maximum storage size:</th>\n"
333
0
                          "    <td>");
334
0
    mBuffer.AppendInt(aCapacity / 1024);
335
0
    mBuffer.AppendLiteral(" KiB</td>\n"
336
0
                          "  </tr>\n");
337
0
338
0
    // Storage in use
339
0
    mBuffer.AppendLiteral("  <tr>\n"
340
0
                          "    <th>Storage in use:</th>\n"
341
0
                          "    <td>");
342
0
    mBuffer.AppendInt(aConsumption / 1024);
343
0
    mBuffer.AppendLiteral(" KiB</td>\n"
344
0
                          "  </tr>\n");
345
0
346
0
    // Storage disk location
347
0
    mBuffer.AppendLiteral("  <tr>\n"
348
0
                          "    <th>Storage disk location:</th>\n"
349
0
                          "    <td>");
350
0
    if (aDirectory) {
351
0
        nsAutoString path;
352
0
        aDirectory->GetPath(path);
353
0
        mBuffer.Append(NS_ConvertUTF16toUTF8(path));
354
0
    } else {
355
0
        mBuffer.AppendLiteral("none, only stored in memory");
356
0
    }
357
0
    mBuffer.AppendLiteral("    </td>\n"
358
0
                          "  </tr>\n");
359
0
360
0
    if (mOverview) { // The about:cache case
361
0
        if (aEntryCount != 0) { // Add the "List Cache Entries" link
362
0
            mBuffer.AppendLiteral("  <tr>\n"
363
0
                                  "    <th><a href=\"about:cache?storage=");
364
0
            nsAppendEscapedHTML(mStorageName, mBuffer);
365
0
            mBuffer.AppendLiteral("&amp;context=");
366
0
            nsAppendEscapedHTML(mContextString, mBuffer);
367
0
            mBuffer.AppendLiteral("\">List Cache Entries</a></th>\n"
368
0
                                  "  </tr>\n");
369
0
        }
370
0
    }
371
0
372
0
    mBuffer.AppendLiteral("</table>\n");
373
0
374
0
    // The entries header is added on encounter of the first entry
375
0
    mEntriesHeaderAdded = false;
376
0
377
0
    nsresult rv = FlushBuffer();
378
0
    if (NS_FAILED(rv)) {
379
0
        NS_WARNING("Failed to flush buffer");
380
0
    }
381
0
382
0
    if (mOverview) {
383
0
        // OnCacheEntryVisitCompleted() is not called when we do not iterate
384
0
        // cache entries.  Since this moves forward to the next storage in
385
0
        // the list we want to visit, artificially call it here.
386
0
        OnCacheEntryVisitCompleted();
387
0
    }
388
0
389
0
    return NS_OK;
390
0
}
391
392
NS_IMETHODIMP
393
nsAboutCache::Channel::OnCacheEntryInfo(nsIURI *aURI, const nsACString & aIdEnhance,
394
                                        int64_t aDataSize, int32_t aFetchCount,
395
                                        uint32_t aLastModified, uint32_t aExpirationTime,
396
                                        bool aPinned, nsILoadContextInfo* aInfo)
397
0
{
398
0
    // We need mStream for this
399
0
    if (!mStream || mCancel) {
400
0
        // Returning a failure from this callback stops the iteration
401
0
        return NS_ERROR_FAILURE;
402
0
    }
403
0
404
0
    if (!mEntriesHeaderAdded) {
405
0
        mBuffer.AppendLiteral("<hr/>\n"
406
0
                              "<table id=\"entries\">\n"
407
0
                              "  <colgroup>\n"
408
0
                              "   <col id=\"col-key\">\n"
409
0
                              "   <col id=\"col-dataSize\">\n"
410
0
                              "   <col id=\"col-fetchCount\">\n"
411
0
                              "   <col id=\"col-lastModified\">\n"
412
0
                              "   <col id=\"col-expires\">\n"
413
0
                              "   <col id=\"col-pinned\">\n"
414
0
                              "  </colgroup>\n"
415
0
                              "  <thead>\n"
416
0
                              "    <tr>\n"
417
0
                              "      <th>Key</th>\n"
418
0
                              "      <th>Data size</th>\n"
419
0
                              "      <th>Fetch count</th>\n"
420
0
                              "      <th>Last Modifed</th>\n"
421
0
                              "      <th>Expires</th>\n"
422
0
                              "      <th>Pinning</th>\n"
423
0
                              "    </tr>\n"
424
0
                              "  </thead>\n");
425
0
        mEntriesHeaderAdded = true;
426
0
    }
427
0
428
0
    // Generate a about:cache-entry URL for this entry...
429
0
430
0
    nsAutoCString url;
431
0
    url.AssignLiteral("about:cache-entry?storage=");
432
0
    nsAppendEscapedHTML(mStorageName, url);
433
0
434
0
    url.AppendLiteral("&amp;context=");
435
0
    nsAppendEscapedHTML(mContextString, url);
436
0
437
0
    url.AppendLiteral("&amp;eid=");
438
0
    nsAppendEscapedHTML(aIdEnhance, url);
439
0
440
0
    nsAutoCString cacheUriSpec;
441
0
    aURI->GetAsciiSpec(cacheUriSpec);
442
0
    nsAutoCString escapedCacheURI;
443
0
    nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI);
444
0
    url.AppendLiteral("&amp;uri=");
445
0
    url += escapedCacheURI;
446
0
447
0
    // Entry start...
448
0
    mBuffer.AppendLiteral("  <tr>\n");
449
0
450
0
    // URI
451
0
    mBuffer.AppendLiteral("    <td><a href=\"");
452
0
    mBuffer.Append(url);
453
0
    mBuffer.AppendLiteral("\">");
454
0
    if (!aIdEnhance.IsEmpty()) {
455
0
        nsAppendEscapedHTML(aIdEnhance, mBuffer);
456
0
        mBuffer.Append(':');
457
0
    }
458
0
    mBuffer.Append(escapedCacheURI);
459
0
    mBuffer.AppendLiteral("</a></td>\n");
460
0
461
0
    // Content length
462
0
    mBuffer.AppendLiteral("    <td>");
463
0
    mBuffer.AppendInt(aDataSize);
464
0
    mBuffer.AppendLiteral(" bytes</td>\n");
465
0
466
0
    // Number of accesses
467
0
    mBuffer.AppendLiteral("    <td>");
468
0
    mBuffer.AppendInt(aFetchCount);
469
0
    mBuffer.AppendLiteral("</td>\n");
470
0
471
0
    // vars for reporting time
472
0
    char buf[255];
473
0
474
0
    // Last modified time
475
0
    mBuffer.AppendLiteral("    <td>");
476
0
    if (aLastModified) {
477
0
        PrintTimeString(buf, sizeof(buf), aLastModified);
478
0
        mBuffer.Append(buf);
479
0
    } else {
480
0
        mBuffer.AppendLiteral("No last modified time");
481
0
    }
482
0
    mBuffer.AppendLiteral("</td>\n");
483
0
484
0
    // Expires time
485
0
    mBuffer.AppendLiteral("    <td>");
486
0
487
0
    // Bug - 633747.
488
0
    // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
489
0
    // So we check if time is 0, then we show a message, "Expired Immediately"
490
0
    if (aExpirationTime == 0) {
491
0
        mBuffer.AppendLiteral("Expired Immediately");
492
0
    } else if (aExpirationTime < 0xFFFFFFFF) {
493
0
        PrintTimeString(buf, sizeof(buf), aExpirationTime);
494
0
        mBuffer.Append(buf);
495
0
    } else {
496
0
        mBuffer.AppendLiteral("No expiration time");
497
0
    }
498
0
    mBuffer.AppendLiteral("</td>\n");
499
0
500
0
    // Pinning
501
0
    mBuffer.AppendLiteral("    <td>");
502
0
    if (aPinned) {
503
0
      mBuffer.AppendLiteral("Pinned");
504
0
    } else {
505
0
      mBuffer.AppendLiteral("&nbsp;");
506
0
    }
507
0
    mBuffer.AppendLiteral("</td>\n");
508
0
509
0
    // Entry is done...
510
0
    mBuffer.AppendLiteral("  </tr>\n");
511
0
512
0
    return FlushBuffer();
513
0
}
514
515
NS_IMETHODIMP
516
nsAboutCache::Channel::OnCacheEntryVisitCompleted()
517
0
{
518
0
    if (!mStream) {
519
0
        return NS_ERROR_FAILURE;
520
0
    }
521
0
522
0
    if (mEntriesHeaderAdded) {
523
0
        mBuffer.AppendLiteral("</table>\n");
524
0
    }
525
0
526
0
    // Kick another storage visiting (from a storage that allows us.)
527
0
    while (mStorageList.Length()) {
528
0
        nsresult rv = VisitNextStorage();
529
0
        if (NS_SUCCEEDED(rv)) {
530
0
            // Expecting new round of OnCache* calls.
531
0
            return NS_OK;
532
0
        }
533
0
    }
534
0
535
0
    // We are done!
536
0
    mBuffer.AppendLiteral("</body>\n"
537
0
                          "<script src=\"chrome://global/content/aboutCache.js\">"
538
0
                          "</script>\n"
539
0
                          "</html>\n");
540
0
    nsresult rv = FlushBuffer();
541
0
    if (NS_FAILED(rv)) {
542
0
        NS_WARNING("Failed to flush buffer");
543
0
    }
544
0
    mStream->Close();
545
0
546
0
    return NS_OK;
547
0
}
548
549
nsresult
550
nsAboutCache::Channel::FlushBuffer()
551
0
{
552
0
    nsresult rv;
553
0
554
0
    uint32_t bytesWritten;
555
0
    rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
556
0
    mBuffer.Truncate();
557
0
558
0
    if (NS_FAILED(rv)) {
559
0
        mCancel = true;
560
0
    }
561
0
562
0
    return rv;
563
0
}
564
565
NS_IMETHODIMP
566
nsAboutCache::GetURIFlags(nsIURI *aURI, uint32_t *result)
567
0
{
568
0
    *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
569
0
    return NS_OK;
570
0
}
571
572
// static
573
nsresult
574
nsAboutCache::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
575
0
{
576
0
    nsAboutCache* about = new nsAboutCache();
577
0
    if (about == nullptr)
578
0
        return NS_ERROR_OUT_OF_MEMORY;
579
0
    NS_ADDREF(about);
580
0
    nsresult rv = about->QueryInterface(aIID, aResult);
581
0
    NS_RELEASE(about);
582
0
    return rv;
583
0
}
584
585
////////////////////////////////////////////////////////////////////////////////