Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
/* globals LoadContextInfo, FormHistory, Accounts */
6
7
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8
const { XPCOMUtils } = ChromeUtils.import(
10
);
11
const { Integration } = ChromeUtils.import(
13
);
14
15
XPCOMUtils.defineLazyModuleGetters(this, {
18
EventDispatcher: "resource://gre/modules/Messaging.jsm",
20
OfflineAppCacheHelper: "resource://gre/modules/offlineAppCache.jsm",
23
});
24
25
XPCOMUtils.defineLazyServiceGetters(this, {
26
quotaManagerService: [
27
"@mozilla.org/dom/quota-manager-service;1",
28
"nsIQuotaManagerService",
29
],
30
});
31
32
/* global DownloadIntegration */
33
Integration.downloads.defineModuleGetter(
34
this,
35
"DownloadIntegration",
37
);
38
39
var EXPORTED_SYMBOLS = ["Sanitizer"];
40
41
function Sanitizer() {}
42
Sanitizer.prototype = {
43
clearItem: function(aItemName, startTime, clearUnfinishedDownloads) {
44
// Only a subset of items support deletion with startTime.
45
// Those who do not will be rejected with error message.
46
if (typeof startTime != "undefined") {
47
switch (aItemName) {
48
// Normal call to DownloadFiles remove actual data from storage, but our web-extension consumer
49
// deletes only download history. So, for this reason we are passing a flag 'deleteFiles'.
50
case "downloadHistory":
51
return this._clear("downloadFiles", {
52
startTime,
53
deleteFiles: false,
54
});
55
case "formdata":
56
return this._clear(aItemName, { startTime });
57
default:
58
return Promise.reject({
59
message: `Invalid argument: ${aItemName} does not support startTime argument.`,
60
});
61
}
62
} else if (
63
aItemName === "downloadFiles" &&
64
typeof clearUnfinishedDownloads != "undefined"
65
) {
66
return this._clear(aItemName, { clearUnfinishedDownloads });
67
} else {
68
return this._clear(aItemName);
69
}
70
},
71
72
_clear: function(aItemName, options) {
73
let item = this.items[aItemName];
74
let canClear = item.canClear;
75
if (typeof canClear == "function") {
76
let maybeDoClear = async () => {
77
let canClearResult = await new Promise(resolve => {
78
canClear(resolve);
79
});
80
81
if (canClearResult) {
82
return item.clear(options);
83
}
84
};
85
return maybeDoClear();
86
} else if (canClear) {
87
return item.clear(options);
88
}
89
},
90
91
// This code is mostly based on the Sanitizer code for desktop Firefox
92
// (browser/modules/Sanitzer.jsm), however over the course of time some
93
// general differences have evolved:
94
// - async shutdown (and seenException handling) isn't implemented in Fennec
95
// - currently there is only limited support for range-based clearing of data
96
97
// Any further specific differences caused by architectural differences between
98
// Fennec and desktop Firefox are documented below for each item.
99
items: {
100
// The difference is specifically the Sanitize:Cache message,
101
// so that the Android front-end can clear its caches as well,
102
// while everything else is unchanged.
103
cache: {
104
clear: function() {
105
let refObj = {};
106
TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
107
108
try {
109
Services.cache2.clear();
110
} catch (er) {}
111
112
let imageCache = Cc["@mozilla.org/image/tools;1"]
113
.getService(Ci.imgITools)
114
.getImgCacheForDocument(null);
115
try {
116
imageCache.clearCache(false); // true=chrome, false=content
117
} catch (er) {}
118
119
return EventDispatcher.instance
120
.sendRequestForResult({ type: "Sanitize:Cache" })
121
.catch(err => {
122
Cu.reportError(
123
`Java-side cache clearing failed with error: ${err}`
124
);
125
})
126
.then(() => {
127
TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
128
});
129
},
130
131
get canClear() {
132
return true;
133
},
134
},
135
136
// Compared to desktop, we don't clear plugin data, as plugins
137
// aren't supported on Android.
138
cookies: {
139
clear: function() {
140
return new Promise(function(resolve, reject) {
141
let refObj = {};
142
TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
143
144
Services.cookies.removeAll();
145
146
TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
147
148
// Clear deviceIds. Done asynchronously (returns before complete).
149
try {
150
let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"].getService(
151
Ci.nsIMediaManagerService
152
);
153
mediaMgr.sanitizeDeviceIds(0);
154
} catch (er) {}
155
156
resolve();
157
});
158
},
159
160
get canClear() {
161
return true;
162
},
163
},
164
165
// Same as desktop Firefox.
166
siteSettings: {
167
async clear() {
168
let refObj = {};
169
TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
170
171
// Clear site-specific permissions like "Allow this site to open popups"
172
Services.perms.removeAll();
173
174
// Clear site-specific settings like page-zoom level
175
Cc["@mozilla.org/content-pref/service;1"]
176
.getService(Ci.nsIContentPrefService2)
177
.removeAllDomains(null);
178
179
// Clear site security settings
180
var sss = Cc["@mozilla.org/ssservice;1"].getService(
181
Ci.nsISiteSecurityService
182
);
183
sss.clearAll();
184
185
// Clear push subscriptions
186
await new Promise((resolve, reject) => {
187
let push = Cc["@mozilla.org/push/Service;1"].getService(
188
Ci.nsIPushService
189
);
190
push.clearForDomain("*", status => {
191
if (Components.isSuccessCode(status)) {
192
resolve();
193
} else {
194
reject(new Error("Error clearing push subscriptions: " + status));
195
}
196
});
197
});
198
TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
199
},
200
201
get canClear() {
202
return true;
203
},
204
},
205
206
// Same as desktop Firefox.
207
offlineApps: {
208
async clear() {
209
// AppCache
210
// This doesn't wait for the cleanup to be complete.
211
OfflineAppCacheHelper.clear();
212
213
// LocalStorage
214
Services.obs.notifyObservers(null, "extension:purge-localStorage");
215
216
// ServiceWorkers
217
await ServiceWorkerCleanUp.removeAll();
218
219
// QuotaManager
220
let promises = [];
221
await new Promise(resolve => {
222
quotaManagerService.getUsage(request => {
223
if (request.resultCode != Cr.NS_OK) {
224
// We are probably shutting down. We don't want to propagate the
225
// error, rejecting the promise.
226
resolve();
227
return;
228
}
229
230
for (let item of request.result) {
231
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
232
item.origin
233
);
234
let uri = principal.URI;
235
if (
236
uri.scheme == "http" ||
237
uri.scheme == "https" ||
238
uri.scheme == "file"
239
) {
240
promises.push(
241
new Promise(r => {
242
let req = quotaManagerService.clearStoragesForPrincipal(
243
principal
244
);
245
req.callback = () => {
246
r();
247
};
248
})
249
);
250
}
251
}
252
resolve();
253
});
254
});
255
256
return Promise.all(promises);
257
},
258
259
get canClear() {
260
return true;
261
},
262
},
263
264
// History on Android is implemented by the Java frontend and requires
265
// different handling. Everything else is the same as for desktop Firefox.
266
history: {
267
clear: function() {
268
let refObj = {};
269
TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
270
271
return EventDispatcher.instance
272
.sendRequestForResult({ type: "Sanitize:ClearHistory" })
273
.catch(e => Cu.reportError("Java-side history clearing failed: " + e))
274
.then(function() {
275
TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
276
try {
277
Services.obs.notifyObservers(
278
null,
279
"browser:purge-session-history"
280
);
281
} catch (e) {}
282
283
try {
284
var predictor = Cc["@mozilla.org/network/predictor;1"].getService(
285
Ci.nsINetworkPredictor
286
);
287
predictor.reset();
288
} catch (e) {}
289
});
290
},
291
292
get canClear() {
293
// bug 347231: Always allow clearing history due to dependencies on
294
// the browser:purge-session-history notification. (like error console)
295
return true;
296
},
297
},
298
299
// Equivalent to openWindows on desktop, but specific to Fennec's implementation
300
// of tabbed browsing and the session store.
301
openTabs: {
302
clear: function() {
303
let refObj = {};
304
TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS", refObj);
305
306
return EventDispatcher.instance
307
.sendRequestForResult({ type: "Sanitize:OpenTabs" })
308
.catch(e => Cu.reportError("Java-side tab clearing failed: " + e))
309
.then(function() {
310
try {
311
// clear "Recently Closed" tabs in Android App
312
Services.obs.notifyObservers(null, "browser:purge-session-tabs");
313
} catch (e) {}
314
TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
315
});
316
},
317
318
get canClear() {
319
return true;
320
},
321
},
322
323
// Specific to Fennec.
324
searchHistory: {
325
clear: function() {
326
return EventDispatcher.instance
327
.sendRequestForResult({
328
type: "Sanitize:ClearHistory",
329
clearSearchHistory: true,
330
})
331
.catch(e =>
332
Cu.reportError("Java-side search history clearing failed: " + e)
333
);
334
},
335
336
get canClear() {
337
return true;
338
},
339
},
340
341
// Browser search is handled by searchHistory above and the find bar doesn't
342
// require extra handling. FormHistory itself is cleared like on desktop.
343
formdata: {
344
clear: function({ startTime = 0 } = {}) {
345
return new Promise(function(resolve, reject) {
346
let refObj = {};
347
TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
348
349
// Conver time to microseconds
350
let time = startTime * 1000;
351
FormHistory.update(
352
{
353
op: "remove",
354
firstUsedStart: time,
355
},
356
{
357
handleCompletion() {
358
TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
359
resolve();
360
},
361
}
362
);
363
});
364
},
365
366
canClear: function(aCallback) {
367
let count = 0;
368
let countDone = {
369
handleResult: function(aResult) {
370
count = aResult;
371
},
372
handleError: function(aError) {
373
Cu.reportError(aError);
374
},
375
handleCompletion: function(aReason) {
376
aCallback(aReason == 0 && count > 0);
377
},
378
};
379
FormHistory.count({}, countDone);
380
},
381
},
382
383
// Adapted from desktop, but heavily modified - see comments below.
384
downloadFiles: {
385
async clear({
386
startTime = 0,
387
deleteFiles = true,
388
clearUnfinishedDownloads = false,
389
} = {}) {
390
let refObj = {};
391
TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
392
393
let list = await Downloads.getList(Downloads.ALL);
394
let downloads = await list.getAll();
395
var finalizePromises = [];
396
397
// Logic copied from DownloadList.removeFinished. Ideally, we would
398
// just use that method directly, but we want to be able to remove the
399
// downloaded files as well.
400
for (let download of downloads) {
401
let downloadFinished =
402
download.stopped && (!download.hasPartialData || download.error);
403
if (
404
(downloadFinished || clearUnfinishedDownloads) &&
405
download.startTime.getTime() >= startTime
406
) {
407
// Remove the download first, so that the views don't get the change
408
// notifications that may occur during finalization.
409
await list.remove(download);
410
// Ensure that the download is stopped and no partial data is kept.
411
// This works even if the download state has changed meanwhile. We
412
// don't need to wait for the procedure to be complete before
413
// processing the other downloads in the list.
414
finalizePromises.push(
415
download.finalize(true).then(() => null, Cu.reportError)
416
);
417
418
if (deleteFiles) {
419
// Delete the downloaded files themselves.
420
OS.File.remove(download.target.path).then(
421
() => null,
422
ex => {
423
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
424
Cu.reportError(ex);
425
}
426
}
427
);
428
}
429
}
430
}
431
432
await Promise.all(finalizePromises);
433
await DownloadIntegration.forceSave();
434
TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
435
},
436
437
get canClear() {
438
return true;
439
},
440
},
441
442
// Specific to Fennec.
443
passwords: {
444
clear: function() {
445
return new Promise(function(resolve, reject) {
446
Services.logins.removeAllLogins();
447
resolve();
448
});
449
},
450
451
get canClear() {
452
let count = Services.logins.countLogins("", "", ""); // count all logins
453
return count > 0;
454
},
455
},
456
457
// Same as desktop Firefox.
458
sessions: {
459
clear: function() {
460
return new Promise(function(resolve, reject) {
461
let refObj = {};
462
TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
463
464
// clear all auth tokens
465
var sdr = Cc["@mozilla.org/security/sdr;1"].getService(
466
Ci.nsISecretDecoderRing
467
);
468
sdr.logoutAndTeardown();
469
470
// clear FTP and plain HTTP auth sessions
471
Services.obs.notifyObservers(null, "net:clear-active-logins");
472
473
TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS", refObj);
474
resolve();
475
});
476
},
477
478
get canClear() {
479
return true;
480
},
481
},
482
483
// Specific to Fennec.
484
syncedTabs: {
485
clear: function() {
486
return EventDispatcher.instance
487
.sendRequestForResult({ type: "Sanitize:ClearSyncedTabs" })
488
.catch(e =>
489
Cu.reportError("Java-side synced tabs clearing failed: " + e)
490
);
491
},
492
493
canClear: function(aCallback) {
494
Accounts.anySyncAccountsExist()
495
.then(aCallback)
496
.catch(function(err) {
497
Cu.reportError("Java-side synced tabs clearing failed: " + err);
498
aCallback(false);
499
});
500
},
501
},
502
},
503
};
504
505
var Sanitizer = new Sanitizer();