Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "mozilla/dom/cache/QuotaClient.h"
8
9
#include "DBAction.h"
10
#include "FileUtils.h"
11
#include "mozilla/dom/cache/Manager.h"
12
#include "mozilla/dom/quota/QuotaCommon.h"
13
#include "mozilla/dom/quota/QuotaManager.h"
14
#include "mozilla/dom/quota/UsageInfo.h"
15
#include "mozilla/ipc/BackgroundParent.h"
16
#include "mozilla/Telemetry.h"
17
#include "mozilla/Unused.h"
18
#include "nsIFile.h"
19
#include "nsISimpleEnumerator.h"
20
#include "nsThreadUtils.h"
21
22
namespace {
23
24
using mozilla::Atomic;
25
using mozilla::MutexAutoLock;
26
using mozilla::Some;
27
using mozilla::Unused;
28
using mozilla::dom::ContentParentId;
29
using mozilla::dom::cache::DirPaddingFile;
30
using mozilla::dom::cache::Manager;
31
using mozilla::dom::cache::QuotaInfo;
32
using mozilla::dom::quota::AssertIsOnIOThread;
33
using mozilla::dom::quota::Client;
34
using mozilla::dom::quota::PersistenceType;
35
using mozilla::dom::quota::QuotaManager;
36
using mozilla::dom::quota::UsageInfo;
37
using mozilla::ipc::AssertIsOnBackgroundThread;
38
39
static nsresult GetBodyUsage(nsIFile* aMorgueDir, const Atomic<bool>& aCanceled,
40
UsageInfo* aUsageInfo, const bool aInitializing) {
41
AssertIsOnIOThread();
42
43
nsCOMPtr<nsIDirectoryEnumerator> entries;
44
nsresult rv = aMorgueDir->GetDirectoryEntries(getter_AddRefs(entries));
45
if (NS_WARN_IF(NS_FAILED(rv))) {
46
return rv;
47
}
48
49
nsCOMPtr<nsIFile> bodyDir;
50
while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(bodyDir))) &&
51
bodyDir && !aCanceled) {
52
if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
53
return NS_ERROR_ABORT;
54
}
55
bool isDir;
56
rv = bodyDir->IsDirectory(&isDir);
57
if (NS_WARN_IF(NS_FAILED(rv))) {
58
return rv;
59
}
60
61
if (!isDir) {
62
QuotaInfo dummy;
63
mozilla::DebugOnly<nsresult> result =
64
RemoveNsIFile(dummy, bodyDir, /* aTrackQuota */ false);
65
// Try to remove the unexpected files, and keep moving on even if it fails
66
// because it might be created by virus or the operation system
67
MOZ_ASSERT(NS_SUCCEEDED(result));
68
continue;
69
}
70
71
const QuotaInfo dummy;
72
const auto getUsage = [&aUsageInfo](nsIFile* bodyFile,
73
const nsACString& leafName,
74
bool& fileDeleted) {
75
MOZ_DIAGNOSTIC_ASSERT(bodyFile);
76
Unused << leafName;
77
78
int64_t fileSize;
79
nsresult rv = bodyFile->GetFileSize(&fileSize);
80
if (NS_WARN_IF(NS_FAILED(rv))) {
81
return rv;
82
}
83
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
84
aUsageInfo->AppendToFileUsage(Some(fileSize));
85
86
fileDeleted = false;
87
88
return NS_OK;
89
};
90
rv = mozilla::dom::cache::BodyTraverseFiles(dummy, bodyDir, getUsage,
91
/* aCanRemoveFiles */
92
aInitializing,
93
/* aTrackQuota */ false);
94
if (NS_WARN_IF(NS_FAILED(rv))) {
95
return rv;
96
}
97
}
98
if (NS_WARN_IF(NS_FAILED(rv))) {
99
return rv;
100
}
101
102
return NS_OK;
103
}
104
105
static nsresult LockedGetPaddingSizeFromDB(nsIFile* aDir,
106
const nsACString& aGroup,
107
const nsACString& aOrigin,
108
int64_t* aPaddingSizeOut) {
109
MOZ_DIAGNOSTIC_ASSERT(aDir);
110
MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut);
111
112
*aPaddingSizeOut = 0;
113
114
nsCOMPtr<mozIStorageConnection> conn;
115
QuotaInfo quotaInfo;
116
quotaInfo.mGroup = aGroup;
117
quotaInfo.mOrigin = aOrigin;
118
nsresult rv = mozilla::dom::cache::OpenDBConnection(quotaInfo, aDir,
119
getter_AddRefs(conn));
120
if (rv == NS_ERROR_FILE_NOT_FOUND ||
121
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
122
// Return NS_OK with size = 0 if both the db and padding file don't exist.
123
// There is no other way to get the overall padding size of an origin.
124
return NS_OK;
125
}
126
if (NS_WARN_IF(NS_FAILED(rv))) {
127
return rv;
128
}
129
130
// Make sure that the database has the latest schema before we try to read
131
// from it. We have to do this because LockedGetPaddingSizeFromDB is called
132
// by QuotaClient::GetUsageForOrigin which may run at any time (there's no
133
// guarantee that SetupAction::RunSyncWithDBOnTarget already checked the
134
// schema for the given origin).
135
rv = mozilla::dom::cache::db::CreateOrMigrateSchema(conn);
136
if (NS_WARN_IF(NS_FAILED(rv))) {
137
return rv;
138
}
139
140
int64_t paddingSize = 0;
141
rv = mozilla::dom::cache::LockedDirectoryPaddingRestore(
142
aDir, conn, /* aMustRestore */ false, &paddingSize);
143
if (NS_WARN_IF(NS_FAILED(rv))) {
144
return rv;
145
}
146
147
*aPaddingSizeOut = paddingSize;
148
149
return rv;
150
}
151
152
class CacheQuotaClient final : public Client {
153
static CacheQuotaClient* sInstance;
154
155
public:
156
CacheQuotaClient()
157
: mDirPaddingFileMutex("DOMCacheQuotaClient.mDirPaddingFileMutex") {
158
AssertIsOnBackgroundThread();
159
MOZ_DIAGNOSTIC_ASSERT(!sInstance);
160
sInstance = this;
161
}
162
163
static CacheQuotaClient* Get() {
164
MOZ_DIAGNOSTIC_ASSERT(sInstance);
165
return sInstance;
166
}
167
168
virtual Type GetType() override { return DOMCACHE; }
169
170
virtual nsresult InitOrigin(PersistenceType aPersistenceType,
171
const nsACString& aGroup,
172
const nsACString& aOrigin,
173
const AtomicBool& aCanceled,
174
UsageInfo* aUsageInfo,
175
bool aForGetUsage) override {
176
AssertIsOnIOThread();
177
178
// The QuotaManager passes a nullptr UsageInfo if there is no quota being
179
// enforced against the origin.
180
if (!aUsageInfo) {
181
return NS_OK;
182
}
183
184
return GetUsageForOriginInternal(aPersistenceType, aGroup, aOrigin,
185
aCanceled, aUsageInfo,
186
/* aInitializing*/ true);
187
}
188
189
virtual nsresult GetUsageForOrigin(PersistenceType aPersistenceType,
190
const nsACString& aGroup,
191
const nsACString& aOrigin,
192
const AtomicBool& aCanceled,
193
UsageInfo* aUsageInfo) override {
194
return GetUsageForOriginInternal(aPersistenceType, aGroup, aOrigin,
195
aCanceled, aUsageInfo,
196
/* aInitializing*/ false);
197
}
198
199
virtual void OnOriginClearCompleted(PersistenceType aPersistenceType,
200
const nsACString& aOrigin) override {
201
// Nothing to do here.
202
}
203
204
virtual void ReleaseIOThreadObjects() override {
205
// Nothing to do here as the Context handles cleaning everything up
206
// automatically.
207
}
208
209
virtual void AbortOperations(const nsACString& aOrigin) override {
210
AssertIsOnBackgroundThread();
211
212
Manager::Abort(aOrigin);
213
}
214
215
virtual void AbortOperationsForProcess(
216
ContentParentId aContentParentId) override {
217
// The Cache and Context can be shared by multiple client processes. They
218
// are not exclusively owned by a single process.
219
//
220
// As far as I can tell this is used by QuotaManager to abort operations
221
// when a particular process goes away. We definitely don't want this
222
// since we are shared. Also, the Cache actor code already properly
223
// handles asynchronous actor destruction when the child process dies.
224
//
225
// Therefore, do nothing here.
226
}
227
228
virtual void StartIdleMaintenance() override {}
229
230
virtual void StopIdleMaintenance() override {}
231
232
virtual void ShutdownWorkThreads() override {
233
AssertIsOnBackgroundThread();
234
235
// spins the event loop and synchronously shuts down all Managers
236
Manager::ShutdownAll();
237
}
238
239
nsresult UpgradeStorageFrom2_0To2_1(nsIFile* aDirectory) override {
240
AssertIsOnIOThread();
241
MOZ_DIAGNOSTIC_ASSERT(aDirectory);
242
243
MutexAutoLock lock(mDirPaddingFileMutex);
244
245
nsresult rv = mozilla::dom::cache::LockedDirectoryPaddingInit(aDirectory);
246
if (NS_WARN_IF(NS_FAILED(rv))) {
247
return rv;
248
}
249
250
return rv;
251
}
252
253
// static
254
template <typename Callable>
255
nsresult MaybeUpdatePaddingFileInternal(nsIFile* aBaseDir,
256
mozIStorageConnection* aConn,
257
const int64_t aIncreaseSize,
258
const int64_t aDecreaseSize,
259
Callable aCommitHook) {
260
MOZ_ASSERT(!NS_IsMainThread());
261
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
262
MOZ_DIAGNOSTIC_ASSERT(aConn);
263
MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
264
MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
265
266
nsresult rv;
267
268
// Temporary should be removed at the end of each action. If not, it means
269
// the failure happened.
270
bool temporaryPaddingFileExist =
271
mozilla::dom::cache::DirectoryPaddingFileExists(
272
aBaseDir, DirPaddingFile::TMP_FILE);
273
274
if (aIncreaseSize == aDecreaseSize && !temporaryPaddingFileExist) {
275
// Early return here, since most cache actions won't modify padding size.
276
rv = aCommitHook();
277
Unused << NS_WARN_IF(NS_FAILED(rv));
278
return rv;
279
}
280
281
{
282
MutexAutoLock lock(mDirPaddingFileMutex);
283
rv = mozilla::dom::cache::LockedUpdateDirectoryPaddingFile(
284
aBaseDir, aConn, aIncreaseSize, aDecreaseSize,
285
temporaryPaddingFileExist);
286
if (NS_WARN_IF(NS_FAILED(rv))) {
287
// Don't delete the temporary padding file here to force the next action
288
// recalculate the padding size.
289
return rv;
290
}
291
292
rv = aCommitHook();
293
if (NS_WARN_IF(NS_FAILED(rv))) {
294
// Don't delete the temporary padding file here to force the next action
295
// recalculate the padding size.
296
return rv;
297
}
298
299
rv = mozilla::dom::cache::LockedDirectoryPaddingFinalizeWrite(aBaseDir);
300
if (NS_WARN_IF(NS_FAILED(rv))) {
301
// Force restore file next time.
302
Unused << mozilla::dom::cache::LockedDirectoryPaddingDeleteFile(
303
aBaseDir, DirPaddingFile::FILE);
304
305
// Ensure that we are able to force the padding file to be restored.
306
MOZ_ASSERT(mozilla::dom::cache::DirectoryPaddingFileExists(
307
aBaseDir, DirPaddingFile::TMP_FILE));
308
309
// Since both the body file and header have been stored in the
310
// file-system, just make the action be resolve and let the padding file
311
// be restored in the next action.
312
rv = NS_OK;
313
}
314
}
315
316
return rv;
317
}
318
319
// static
320
nsresult RestorePaddingFileInternal(nsIFile* aBaseDir,
321
mozIStorageConnection* aConn) {
322
MOZ_ASSERT(!NS_IsMainThread());
323
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
324
MOZ_DIAGNOSTIC_ASSERT(aConn);
325
326
int64_t dummyPaddingSize;
327
328
MutexAutoLock lock(mDirPaddingFileMutex);
329
330
nsresult rv = mozilla::dom::cache::LockedDirectoryPaddingRestore(
331
aBaseDir, aConn, /* aMustRestore */ true, &dummyPaddingSize);
332
Unused << NS_WARN_IF(NS_FAILED(rv));
333
334
return rv;
335
}
336
337
// static
338
nsresult WipePaddingFileInternal(const QuotaInfo& aQuotaInfo,
339
nsIFile* aBaseDir) {
340
MOZ_ASSERT(!NS_IsMainThread());
341
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
342
343
MutexAutoLock lock(mDirPaddingFileMutex);
344
345
MOZ_ASSERT(mozilla::dom::cache::DirectoryPaddingFileExists(
346
aBaseDir, DirPaddingFile::FILE));
347
348
int64_t paddingSize = 0;
349
bool temporaryPaddingFileExist =
350
mozilla::dom::cache::DirectoryPaddingFileExists(
351
aBaseDir, DirPaddingFile::TMP_FILE);
352
353
if (temporaryPaddingFileExist ||
354
NS_WARN_IF(NS_FAILED(mozilla::dom::cache::LockedDirectoryPaddingGet(
355
aBaseDir, &paddingSize)))) {
356
// XXXtt: Maybe have a method in the QuotaManager to clean the usage under
357
// the quota client and the origin.
358
// There is nothing we can do to recover the file.
359
NS_WARNING("Cannnot read padding size from file!");
360
paddingSize = 0;
361
}
362
363
if (paddingSize > 0) {
364
mozilla::dom::cache::DecreaseUsageForQuotaInfo(aQuotaInfo, paddingSize);
365
}
366
367
nsresult rv = mozilla::dom::cache::LockedDirectoryPaddingDeleteFile(
368
aBaseDir, DirPaddingFile::FILE);
369
if (NS_WARN_IF(NS_FAILED(rv))) {
370
return rv;
371
}
372
373
// Remove temporary file if we have one.
374
rv = mozilla::dom::cache::LockedDirectoryPaddingDeleteFile(
375
aBaseDir, DirPaddingFile::TMP_FILE);
376
if (NS_WARN_IF(NS_FAILED(rv))) {
377
return rv;
378
}
379
380
rv = mozilla::dom::cache::LockedDirectoryPaddingInit(aBaseDir);
381
Unused << NS_WARN_IF(NS_FAILED(rv));
382
383
return rv;
384
}
385
386
private:
387
~CacheQuotaClient() {
388
AssertIsOnBackgroundThread();
389
MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
390
391
sInstance = nullptr;
392
}
393
394
nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType,
395
const nsACString& aGroup,
396
const nsACString& aOrigin,
397
const AtomicBool& aCanceled,
398
UsageInfo* aUsageInfo,
399
const bool aInitializing) {
400
AssertIsOnIOThread();
401
MOZ_DIAGNOSTIC_ASSERT(aUsageInfo);
402
#ifndef NIGHTLY_BUILD
403
Unused << aInitializing;
404
#endif
405
406
QuotaManager* qm = QuotaManager::Get();
407
MOZ_DIAGNOSTIC_ASSERT(qm);
408
409
nsCOMPtr<nsIFile> dir;
410
nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
411
getter_AddRefs(dir));
412
if (NS_WARN_IF(NS_FAILED(rv))) {
413
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaExternalError,
414
Cache_GetDirForOri);
415
return rv;
416
}
417
418
rv = dir->Append(NS_LITERAL_STRING(DOMCACHE_DIRECTORY_NAME));
419
if (NS_WARN_IF(NS_FAILED(rv))) {
420
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaExternalError,
421
Cache_Append);
422
return rv;
423
}
424
425
int64_t paddingSize = 0;
426
{
427
// If the tempoary file still exists after locking, it means the previous
428
// action fails, so restore the padding file.
429
MutexAutoLock lock(mDirPaddingFileMutex);
430
431
if (mozilla::dom::cache::DirectoryPaddingFileExists(
432
dir, DirPaddingFile::TMP_FILE) ||
433
NS_WARN_IF(NS_FAILED(mozilla::dom::cache::LockedDirectoryPaddingGet(
434
dir, &paddingSize)))) {
435
rv = LockedGetPaddingSizeFromDB(dir, aGroup, aOrigin, &paddingSize);
436
if (NS_WARN_IF(NS_FAILED(rv))) {
437
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaInternalError,
438
Cache_GetPaddingSize);
439
return rv;
440
}
441
}
442
}
443
444
aUsageInfo->AppendToFileUsage(Some(paddingSize));
445
446
nsCOMPtr<nsIDirectoryEnumerator> entries;
447
rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
448
if (NS_WARN_IF(NS_FAILED(rv))) {
449
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaExternalError,
450
Cache_GetDirEntries);
451
return rv;
452
}
453
454
nsCOMPtr<nsIFile> file;
455
while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(file))) &&
456
file && !aCanceled) {
457
if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
458
return NS_ERROR_ABORT;
459
}
460
461
nsAutoString leafName;
462
rv = file->GetLeafName(leafName);
463
if (NS_WARN_IF(NS_FAILED(rv))) {
464
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaExternalError,
465
Cache_GetLeafName);
466
return rv;
467
}
468
469
bool isDir;
470
rv = file->IsDirectory(&isDir);
471
if (NS_WARN_IF(NS_FAILED(rv))) {
472
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaExternalError,
473
Cache_IsDirectory);
474
return rv;
475
}
476
477
if (isDir) {
478
if (leafName.EqualsLiteral("morgue")) {
479
rv = GetBodyUsage(file, aCanceled, aUsageInfo, aInitializing);
480
if (NS_WARN_IF(NS_FAILED(rv))) {
481
if (rv != NS_ERROR_ABORT) {
482
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaExternalError,
483
Cache_GetBodyUsage);
484
}
485
return rv;
486
}
487
} else {
488
NS_WARNING("Unknown Cache directory found!");
489
}
490
491
continue;
492
}
493
494
// Ignore transient sqlite files and marker files
495
if (leafName.EqualsLiteral("caches.sqlite-journal") ||
496
leafName.EqualsLiteral("caches.sqlite-shm") ||
497
leafName.Find(NS_LITERAL_CSTRING("caches.sqlite-mj"), false, 0, 0) ==
498
0 ||
499
leafName.EqualsLiteral("context_open.marker")) {
500
continue;
501
}
502
503
if (leafName.EqualsLiteral("caches.sqlite") ||
504
leafName.EqualsLiteral("caches.sqlite-wal")) {
505
int64_t fileSize;
506
rv = file->GetFileSize(&fileSize);
507
if (NS_WARN_IF(NS_FAILED(rv))) {
508
REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kQuotaExternalError,
509
Cache_GetFileSize);
510
return rv;
511
}
512
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
513
514
aUsageInfo->AppendToDatabaseUsage(Some(fileSize));
515
continue;
516
}
517
518
// Ignore directory padding file
519
if (leafName.EqualsLiteral(PADDING_FILE_NAME) ||
520
leafName.EqualsLiteral(PADDING_TMP_FILE_NAME)) {
521
continue;
522
}
523
524
NS_WARNING("Unknown Cache file found!");
525
}
526
if (NS_WARN_IF(NS_FAILED(rv))) {
527
return rv;
528
}
529
530
return NS_OK;
531
}
532
533
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheQuotaClient, override)
534
535
// Mutex lock to protect directroy padding files. It should only be acquired
536
// in DOM Cache IO threads and Quota IO thread.
537
mozilla::Mutex mDirPaddingFileMutex;
538
};
539
540
// static
541
CacheQuotaClient* CacheQuotaClient::sInstance = nullptr;
542
543
} // namespace
544
545
namespace mozilla {
546
namespace dom {
547
namespace cache {
548
549
// static
550
already_AddRefed<quota::Client> CreateQuotaClient() {
551
AssertIsOnBackgroundThread();
552
553
RefPtr<CacheQuotaClient> ref = new CacheQuotaClient();
554
return ref.forget();
555
}
556
557
// static
558
template <typename Callable>
559
nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn,
560
const int64_t aIncreaseSize,
561
const int64_t aDecreaseSize,
562
Callable aCommitHook) {
563
MOZ_ASSERT(!NS_IsMainThread());
564
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
565
MOZ_DIAGNOSTIC_ASSERT(aConn);
566
MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
567
MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
568
569
RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
570
MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
571
572
nsresult rv = cacheQuotaClient->MaybeUpdatePaddingFileInternal(
573
aBaseDir, aConn, aIncreaseSize, aDecreaseSize, aCommitHook);
574
Unused << NS_WARN_IF(NS_FAILED(rv));
575
576
return rv;
577
}
578
579
// static
580
nsresult RestorePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn) {
581
MOZ_ASSERT(!NS_IsMainThread());
582
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
583
MOZ_DIAGNOSTIC_ASSERT(aConn);
584
585
RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
586
MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
587
588
nsresult rv = cacheQuotaClient->RestorePaddingFileInternal(aBaseDir, aConn);
589
Unused << NS_WARN_IF(NS_FAILED(rv));
590
591
return rv;
592
}
593
594
// static
595
nsresult WipePaddingFile(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir) {
596
MOZ_ASSERT(!NS_IsMainThread());
597
MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
598
599
RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
600
MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
601
602
nsresult rv = cacheQuotaClient->WipePaddingFileInternal(aQuotaInfo, aBaseDir);
603
Unused << NS_WARN_IF(NS_FAILED(rv));
604
605
return rv;
606
}
607
} // namespace cache
608
} // namespace dom
609
} // namespace mozilla