]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/sanitizer.jsm
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / modules / sanitizer.jsm
1 // Copyright (c) 2009 by Doug Kearns <dougkearns@gmail.com>
2 // Copyright (c) 2009-2011 by Kris Maglione <maglione.k at Gmail>
3 //
4 // This work is licensed for reuse under an MIT license. Details are
5 // given in the LICENSE.txt file included with this file.
6 /* use strict */
7
8 // TODO:
9 //   - fix Sanitize autocommand
10 //   - add warning for TIMESPAN_EVERYTHING?
11
12 // FIXME:
13 //   - finish 1.9.0 support if we're going to support sanitizing in Melodactyl
14
15 Components.utils.import("resource://dactyl/bootstrap.jsm");
16 defineModule("sanitizer", {
17     exports: ["Range", "Sanitizer", "sanitizer"],
18     require: ["config", "prefs", "services", "util"]
19 }, this);
20
21 this.lazyRequire("messages", ["_"]);
22 this.lazyRequire("storage", ["storage"]);
23 this.lazyRequire("template", ["teplate"]);
24
25 let tmp = {};
26 JSMLoader.loadSubScript("chrome://browser/content/sanitize.js", tmp);
27 tmp.Sanitizer.prototype.__proto__ = Class.prototype;
28
29 var Range = Struct("min", "max");
30 update(Range.prototype, {
31     contains: function (date) date == null ||
32         (this.min == null || date >= this.min) && (this.max == null || date <= this.max),
33
34     get isEternity() this.max == null && this.min == null,
35     get isSession() this.max == null && this.min == sanitizer.sessionStart,
36
37     get native() this.isEternity ? null : [this.min || 0, this.max == null ? Number.MAX_VALUE : this.max]
38 });
39
40 var Item = Class("SanitizeItem", {
41     init: function (name, params) {
42         this.name = name;
43         this.description = params.description;
44     },
45
46     // Hack for completion:
47     "0": Class.Property({ get: function () this.name }),
48     "1": Class.Property({ get: function () this.description }),
49
50     description: Messages.Localized(""),
51
52     get cpdPref() (this.builtin ? "" : Item.PREFIX) + Item.BRANCH + Sanitizer.argToPref(this.name),
53     get shutdownPref() (this.builtin ? "" : Item.PREFIX) + Item.SHUTDOWN_BRANCH + Sanitizer.argToPref(this.name),
54     get cpd() prefs.get(this.cpdPref),
55     get shutdown() prefs.get(this.shutdownPref),
56
57     shouldSanitize: function (shutdown) (!shutdown || this.builtin || this.persistent) &&
58         prefs.get(shutdown ? this.shutdownPref : this.pref)
59 }, {
60     PREFIX: config.prefs.branch.root,
61     BRANCH: "privacy.cpd.",
62     SHUTDOWN_BRANCH: "privacy.clearOnShutdown."
63 });
64
65 var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference], tmp.Sanitizer), {
66     sessionStart: Date.now() * 1000,
67
68     init: function () {
69         const self = this;
70
71         util.addObserver(this);
72
73         services.add("contentPrefs", "@mozilla.org/content-pref/service;1", Ci.nsIContentPrefService);
74         services.add("cookies",      "@mozilla.org/cookiemanager;1",        [Ci.nsICookieManager, Ci.nsICookieManager2,
75                                                                              Ci.nsICookieService]);
76         services.add("loginManager", "@mozilla.org/login-manager;1",        Ci.nsILoginManager);
77         services.add("permissions",  "@mozilla.org/permissionmanager;1",    Ci.nsIPermissionManager);
78
79         this.itemMap = {};
80
81         this.addItem("all", { description: "Sanitize all items", shouldSanitize: function () false });
82         // Builtin items
83         this.addItem("cache",       { builtin: true, description: "Cache" });
84         this.addItem("downloads",   { builtin: true, description: "Download history" });
85         this.addItem("formdata",    { builtin: true, description: "Saved form and search history" });
86         this.addItem("offlineapps", { builtin: true, description: "Offline website data" });
87         this.addItem("passwords",   { builtin: true, description: "Saved passwords" });
88         this.addItem("sessions",    { builtin: true, description: "Authenticated sessions" });
89
90         // These builtin methods don't support hosts or otherwise have
91         // insufficient granularity
92         this.addItem("cookies", {
93             builtin: true,
94             description: "Cookies",
95             persistent: true,
96             action: function (range, host) {
97                 for (let c in Sanitizer.iterCookies(host))
98                     if (range.contains(c.creationTime) || timespan.isSession && c.isSession)
99                         services.cookies.remove(c.host, c.name, c.path, false);
100             },
101             override: true
102         });
103         this.addItem("history", {
104             builtin: true,
105             description: "Browsing history",
106             persistent: true,
107             sessionHistory: true,
108             action: function (range, host) {
109                 if (host)
110                     services.history.removePagesFromHost(host, true);
111                 else {
112                     if (range.isEternity)
113                         services.history.removeAllPages();
114                     else
115                         services.history.removeVisitsByTimeframe(range.native[0], Math.min(Date.now() * 1000, range.native[1])); // XXX
116                     services.observer.notifyObservers(null, "browser:purge-session-history", "");
117                 }
118
119                 if (!host || util.isDomainURL(prefs.get("general.open_location.last_url"), host))
120                     prefs.reset("general.open_location.last_url");
121             },
122             override: true
123         });
124         if (services.has("privateBrowsing"))
125             this.addItem("host", {
126                 description: "All data from the given host",
127                 action: function (range, host) {
128                     if (host)
129                         services.privateBrowsing.removeDataFromDomain(host);
130                 }
131             });
132         this.addItem("sitesettings", {
133             builtin: true,
134             description: "Site preferences",
135             persistent: true,
136             action: function (range, host) {
137                 if (range.isSession)
138                     return;
139                 if (host) {
140                     for (let p in Sanitizer.iterPermissions(host)) {
141                         services.permissions.remove(util.createURI(p.host), p.type);
142                         services.permissions.add(util.createURI(p.host), p.type, 0);
143                     }
144                     for (let p in iter(services.contentPrefs.getPrefs(util.createURI(host))))
145                         services.contentPrefs.removePref(util.createURI(host), p.QueryInterface(Ci.nsIProperty).name);
146                 }
147                 else {
148                     // "Allow this site to open popups" ...
149                     services.permissions.removeAll();
150                     // Zoom level, ...
151                     services.contentPrefs.removeGroupedPrefs();
152                 }
153
154                 // "Never remember passwords" ...
155                 for each (let domain in services.loginManager.getAllDisabledHosts())
156                     if (!host || util.isSubdomain(domain, host))
157                         services.loginManager.setLoginSavingEnabled(host, true);
158             },
159             override: true
160         });
161
162         function ourItems(persistent) [
163             item for (item in values(self.itemMap))
164             if (!item.builtin && (!persistent || item.persistent) && item.name !== "all")
165         ];
166
167         function prefOverlay(branch, persistent, local) update(Object.create(local), {
168             before: array.toObject([
169                 [branch.substr(Item.PREFIX.length) + "history",
170                     <preferences xmlns={XUL}>{
171                       template.map(ourItems(persistent), function (item)
172                         <preference type="bool" id={branch + item.name} name={branch + item.name}/>)
173                     }</preferences>.*::*]
174             ]),
175             init: function init(win) {
176                 let pane = win.document.getElementById("SanitizeDialogPane");
177                 for (let [, pref] in iter(pane.preferences))
178                     pref.updateElements();
179                 init.superapply(this, arguments);
180             }
181         });
182
183         let (branch = Item.PREFIX + Item.SHUTDOWN_BRANCH) {
184             util.overlayWindow("chrome://browser/content/preferences/sanitize.xul",
185                                function (win) prefOverlay(branch, true, {
186                 append: {
187                     SanitizeDialogPane:
188                         <groupbox orient="horizontal" xmlns={XUL}>
189                           <caption label={config.appName + /*L*/" (see :help privacy)"}/>
190                           <grid flex="1">
191                             <columns><column flex="1"/><column flex="1"/></columns>
192                             <rows>{
193                               let (items = ourItems(true))
194                                  template.map(util.range(0, Math.ceil(items.length / 2)), function (i)
195                                    <row xmlns={XUL}>{
196                                      template.map(items.slice(i * 2, i * 2 + 2), function (item)
197                                        <checkbox xmlns={XUL} label={item.description} preference={branch + item.name}/>)
198                                    }</row>)
199                             }</rows>
200                           </grid>
201                         </groupbox>
202                 }
203             }));
204         }
205         let (branch = Item.PREFIX + Item.BRANCH) {
206             util.overlayWindow("chrome://browser/content/sanitize.xul",
207                                function (win) prefOverlay(branch, false, {
208                 append: {
209                     itemList: <>
210                         <listitem xmlns={XUL} label={/*L*/"See :help privacy for the following:"} disabled="true" style="font-style: italic; font-weight: bold;"/>
211                         {
212                           template.map(ourItems(), function ([item, desc])
213                             <listitem xmlns={XUL} type="checkbox"
214                                       label={config.appName + " " + desc}
215                                       preference={branch + item}
216                                       onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>)
217                         }
218                     </>
219                 },
220                 ready: function ready(win) {
221                     let elem =  win.document.getElementById("itemList");
222                     elem.setAttribute("rows", elem.itemCount);
223                     win.Sanitizer = Class("Sanitizer", win.Sanitizer, {
224                         sanitize: function sanitize() {
225                             self.withSavedValues(["sanitizing"], function () {
226                                 self.sanitizing = true;
227                                 sanitize.superapply(this, arguments);
228                                 sanitizer.sanitizeItems([item.name for (item in values(self.itemMap))
229                                                          if (item.shouldSanitize(false))],
230                                                         Range.fromArray(this.range || []));
231                             }, this);
232                         }
233                     });
234                 }
235             }));
236         }
237     },
238
239     firstRun: 0,
240
241     addItem: function addItem(name, params) {
242         let item = this.itemMap[name] || Item(name, params);
243         this.itemMap[name] = item;
244
245         for (let [k, prop] in iterOwnProperties(params))
246             if (!("value" in prop) || !callable(prop.value) && !(k in item))
247                 Object.defineProperty(item, k, prop);
248
249         let names = Set([name].concat(params.contains || []).map(function (e) "clear-" + e));
250         if (params.action)
251             storage.addObserver("sanitizer",
252                 function (key, event, arg) {
253                     if (event in names)
254                         params.action.apply(params, arg);
255                 },
256                 Class.objectGlobal(params.action));
257
258         if (params.privateEnter || params.privateLeave)
259             storage.addObserver("private-mode",
260                 function (key, event, arg) {
261                     let meth = params[arg ? "privateEnter" : "privateLeave"];
262                     if (meth)
263                         meth.call(params);
264                 },
265                 Class.objectGlobal(params.action));
266     },
267
268     observers: {
269         "browser:purge-domain-data": function (subject, host) {
270             storage.fireEvent("sanitize", "domain", host);
271             // If we're sanitizing, our own sanitization functions will already
272             // be called, and with much greater granularity. Only process this
273             // event if it's triggered externally.
274             if (!this.sanitizing)
275                 this.sanitizeItems(null, Range(), data);
276         },
277         "browser:purge-session-history": function (subject, data) {
278             // See above.
279             if (!this.sanitizing)
280                 this.sanitizeItems(null, Range(this.sessionStart), null, "sessionHistory");
281         },
282         "quit-application-granted": function (subject, data) {
283             if (this.runAtShutdown && !this.sanitizeItems(null, Range(), null, "shutdown"))
284                 this.ranAtShutdown = true;
285         },
286         "private-browsing": function (subject, data) {
287             if (data == "enter")
288                 storage.privateMode = true;
289             else if (data == "exit")
290                 storage.privateMode = false;
291             storage.fireEvent("private-mode", "change", storage.privateMode);
292         }
293     },
294
295     get ranAtShutdown()    config.prefs.get("didSanitizeOnShutdown"),
296     set ranAtShutdown(val) config.prefs.set("didSanitizeOnShutdown", Boolean(val)),
297     get runAtShutdown()    prefs.get("privacy.sanitize.sanitizeOnShutdown"),
298     set runAtShutdown(val) prefs.set("privacy.sanitize.sanitizeOnShutdown", Boolean(val)),
299
300     sanitize: function (items, range)
301         this.withSavedValues(["sanitizing"], function () {
302             this.sanitizing = true;
303             let errors = this.sanitizeItems(items, range, null);
304
305             for (let itemName in values(items)) {
306                 try {
307                     let item = this.items[Sanitizer.argToPref(itemName)];
308                     if (item && !this.itemMap[itemName].override) {
309                         item.range = range.native;
310                         if ("clear" in item && item.canClear)
311                             item.clear();
312                     }
313                 }
314                 catch (e) {
315                     errors = errors || {};
316                     errors[itemName] = e;
317                     util.dump("Error sanitizing " + itemName);
318                     util.reportError(e);
319                 }
320             }
321             return errors;
322         }),
323
324     sanitizeItems: function (items, range, host, key)
325         this.withSavedValues(["sanitizing"], function () {
326             this.sanitizing = true;
327             if (items == null)
328                 items = Object.keys(this.itemMap);
329
330             let errors;
331             for (let itemName in values(items))
332                 try {
333                     if (!key || this.itemMap[itemName][key])
334                         storage.fireEvent("sanitizer", "clear-" + itemName, [range, host]);
335                 }
336                 catch (e) {
337                     errors = errors || {};
338                     errors[itemName] = e;
339                     util.dump("Error sanitizing " + itemName);
340                     util.reportError(e);
341                 }
342             return errors;
343         })
344 }, {
345     PERMS: {
346         unset:   0,
347         allow:   1,
348         deny:    2,
349         session: 8
350     },
351
352     UNPERMS: Class.Memoize(function () iter(this.PERMS).map(Array.reverse).toObject()),
353
354     COMMANDS: {
355         unset:   /*L*/"Unset",
356         allow:   /*L*/"Allowed",
357         deny:    /*L*/"Denied",
358         session: /*L*/"Allowed for the current session",
359         list:    /*L*/"List all cookies for domain",
360         clear:   /*L*/"Clear all cookies for domain",
361         "clear-persistent": /*L*/"Clear all persistent cookies for domain",
362         "clear-session":    /*L*/"Clear all session cookies for domain"
363     },
364
365     argPrefMap: {
366         offlineapps:  "offlineApps",
367         sitesettings: "siteSettings"
368     },
369     argToPref: function (arg) Sanitizer.argPrefMap[arg] || arg,
370     prefToArg: function (pref) pref.replace(/.*\./, "").toLowerCase(),
371
372     iterCookies: function iterCookies(host) {
373         for (let c in iter(services.cookies, Ci.nsICookie2))
374             if (!host || util.isSubdomain(c.rawHost, host) ||
375                     c.host[0] == "." && c.host.length < host.length
376                         && host.indexOf(c.host) == host.length - c.host.length)
377                 yield c;
378
379     },
380     iterPermissions: function iterPermissions(host) {
381         for (let p in iter(services.permissions, Ci.nsIPermission))
382             if (!host || util.isSubdomain(p.host, host))
383                 yield p;
384     }
385 }, {
386     load: function (dactyl, modules, window) {
387         if (!sanitizer.firstRun++ && sanitizer.runAtShutdown && !sanitizer.ranAtShutdown)
388             sanitizer.sanitizeItems(null, Range(), null, "shutdown");
389         sanitizer.ranAtShutdown = false;
390     },
391     autocommands: function (dactyl, modules, window) {
392         const { autocommands } = modules;
393
394         storage.addObserver("private-mode",
395             function (key, event, value) {
396                 autocommands.trigger("PrivateMode", { state: value });
397             }, window);
398         storage.addObserver("sanitizer",
399             function (key, event, value) {
400                 if (event == "domain")
401                     autocommands.trigger("SanitizeDomain", { domain: value });
402                 else if (!value[1])
403                     autocommands.trigger("Sanitize", { name: event.substr("clear-".length), domain: value[1] });
404             }, window);
405     },
406     commands: function (dactyl, modules, window) {
407         const { commands } = modules;
408         commands.add(["sa[nitize]"],
409             "Clear private data",
410             function (args) {
411                 dactyl.assert(!modules.options['private'], _("command.sanitize.privateMode"));
412
413                 if (args["-host"] && !args.length && !args.bang)
414                     args[0] = "all";
415
416                 let timespan = args["-timespan"] || modules.options["sanitizetimespan"];
417
418                 let range = Range();
419                 let [match, num, unit] = /^(\d+)([mhdw])$/.exec(timespan) || [];
420                 range[args["-older"] ? "max" : "min"] =
421                     match ? 1000 * (Date.now() - 1000 * parseInt(num, 10) * { m: 60, h: 3600, d: 3600 * 24, w: 3600 * 24 * 7 }[unit])
422                           : (timespan[0] == "s" ? sanitizer.sessionStart : null);
423
424                 let opt = modules.options.get("sanitizeitems");
425                 if (args.bang)
426                     dactyl.assert(args.length == 0, _("error.trailingCharacters"));
427                 else {
428                     dactyl.assert(opt.validator(args), _("error.invalidArgument"));
429                     opt = { __proto__: opt, value: args.slice() };
430                 }
431
432                 let items = Object.keys(sanitizer.itemMap).slice(1).filter(opt.has, opt);
433
434                 function sanitize(items) {
435                     sanitizer.range = range.native;
436                     sanitizer.ignoreTimespan = range.min == null;
437                     sanitizer.sanitizing = true;
438                     if (args["-host"]) {
439                         args["-host"].forEach(function (host) {
440                             sanitizer.sanitizing = true;
441                             sanitizer.sanitizeItems(items, range, host);
442                         });
443                     }
444                     else
445                         sanitizer.sanitize(items, range);
446                 }
447
448                 if (array.nth(opt.value, function (i) i == "all" || /^!/.test(i), 0) == "all" && !args["-host"])
449                     modules.commandline.input(_("sanitize.prompt.deleteAll") + " ",
450                         function (resp) {
451                             if (resp.match(/^y(es)?$/i)) {
452                                 sanitize(items);
453                                 dactyl.echomsg(_("command.sanitize.allDeleted"));
454                             }
455                             else
456                                 dactyl.echo(_("command.sanitize.noneDeleted"));
457                         });
458                 else
459                     sanitize(items);
460
461             },
462             {
463                 argCount: "*", // FIXME: should be + and 0
464                 bang: true,
465                 completer: function (context) {
466                     context.title = ["Privacy Item", "Description"];
467                     context.completions = modules.options.get("sanitizeitems").values;
468                 },
469                 domains: function (args) args["-host"] || [],
470                 options: [
471                     {
472                         names: ["-host", "-h"],
473                         description: "Only sanitize items referring to listed host or hosts",
474                         completer: function (context, args) {
475                             context.filters.push(function (item)
476                                 !args["-host"].some(function (host) util.isSubdomain(item.text, host)));
477                             modules.completion.domain(context);
478                         },
479                         type: modules.CommandOption.LIST
480                     }, {
481                         names: ["-older", "-o"],
482                         description: "Sanitize items older than timespan",
483                         type: modules.CommandOption.NOARG
484                     }, {
485                         names: ["-timespan", "-t"],
486                         description: "Timespan for which to sanitize items",
487                         completer: function (context) modules.options.get("sanitizetimespan").completer(context),
488                         type: modules.CommandOption.STRING,
489                         validator: function (arg) modules.options.get("sanitizetimespan").validator(arg),
490                     }
491                 ],
492                 privateData: true
493             });
494
495             function getPerms(host) {
496                 let uri = util.createURI(host);
497                 if (uri)
498                     return Sanitizer.UNPERMS[services.permissions.testPermission(uri, "cookie")];
499                 return "unset";
500             }
501             function setPerms(host, perm) {
502                 let uri = util.createURI(host);
503                 services.permissions.remove(uri, "cookie");
504                 services.permissions.add(uri, "cookie", Sanitizer.PERMS[perm]);
505             }
506             commands.add(["cookies", "ck"],
507                 "Change cookie permissions for sites",
508                 function (args) {
509                     let host = args.shift();
510                     let session = true;
511                     if (!args.length)
512                         args = modules.options["cookies"];
513
514                     for (let [, cmd] in Iterator(args))
515                         switch (cmd) {
516                         case "clear":
517                             for (let c in Sanitizer.iterCookies(host))
518                                 services.cookies.remove(c.host, c.name, c.path, false);
519                             break;
520                         case "clear-persistent":
521                             session = false;
522                         case "clear-session":
523                             for (let c in Sanitizer.iterCookies(host))
524                                 if (c.isSession == session)
525                                     services.cookies.remove(c.host, c.name, c.path, false);
526                             return;
527
528                         case "list":
529                             modules.commandline.commandOutput(template.tabular(
530                                 ["Host", "Expiry (UTC)", "Path", "Name", "Value"],
531                                 ["padding-right: 1em", "padding-right: 1em", "padding-right: 1em", "max-width: 12em; overflow: hidden;", "padding-left: 1ex;"],
532                                 ([c.host,
533                                   c.isSession ? <span highlight="Enabled">session</span>
534                                               : (new Date(c.expiry * 1000).toJSON() || "Never").replace(/:\d\d\.000Z/, "").replace("T", " ").replace(/-/g, "/"),
535                                   c.path,
536                                   c.name,
537                                   c.value]
538                                   for (c in Sanitizer.iterCookies(host)))));
539                             return;
540                         default:
541                             util.assert(cmd in Sanitizer.PERMS, _("error.invalidArgument"));
542                             setPerms(host, cmd);
543                         }
544                 }, {
545                     argCount: "+",
546                     completer: function (context, args) {
547                         switch (args.completeArg) {
548                         case 0:
549                             modules.completion.visibleHosts(context);
550                             context.title[1] = "Current Permissions";
551                             context.keys.description = function desc(host) {
552                                 let count = [0, 0];
553                                 for (let c in Sanitizer.iterCookies(host))
554                                     count[c.isSession + 0]++;
555                                 return <>{Sanitizer.COMMANDS[getPerms(host)]} (session: {count[1]} persistent: {count[0]})</>;
556                             };
557                             break;
558                         case 1:
559                             context.completions = Sanitizer.COMMANDS;
560                             break;
561                         }
562                     },
563                 });
564     },
565     completion: function (dactyl, modules, window) {
566         modules.completion.visibleHosts = function completeHosts(context) {
567             let res = util.visibleHosts(window.content);
568             if (context.filter && !res.some(function (host) host.indexOf(context.filter) >= 0))
569                 res.push(context.filter);
570
571             context.title = ["Domain"];
572             context.anchored = false;
573             context.compare = modules.CompletionContext.Sort.unsorted;
574             context.keys = { text: util.identity, description: util.identity };
575             context.completions = res;
576         };
577     },
578     options: function (dactyl, modules) {
579         const options = modules.options;
580         if (services.has("privateBrowsing"))
581             options.add(["private", "pornmode"],
582                 "Set the 'private browsing' option",
583                 "boolean", false,
584                 {
585                     initialValue: true,
586                     getter: function () services.privateBrowsing.privateBrowsingEnabled,
587                     setter: function (value) {
588                         if (services.privateBrowsing.privateBrowsingEnabled != value)
589                             services.privateBrowsing.privateBrowsingEnabled = value;
590                     },
591                     persist: false
592                 });
593
594         options.add(["sanitizeitems", "si"],
595             "The default list of private items to sanitize",
596             "stringlist", "all",
597             {
598                 get values() values(sanitizer.itemMap).toArray(),
599
600                 completer: function completer(context, extra) {
601                     if (context.filter[0] == "!")
602                         context.advance(1);
603                     return completer.superapply(this, arguments);
604                 },
605
606                 has: function has(val)
607                     let (res = array.nth(this.value, function (v) v == "all" || v.replace(/^!/, "") == val, 0))
608                         res && !/^!/.test(res),
609
610                 validator: function (values) values.length &&
611                     values.every(function (val) val === "all" || Set.has(sanitizer.itemMap, val.replace(/^!/, "")))
612             });
613
614         options.add(["sanitizeshutdown", "ss"],
615             "The items to sanitize automatically at shutdown",
616             "stringlist", "",
617             {
618                 initialValue: true,
619                 get values() [i for (i in values(sanitizer.itemMap)) if (i.persistent || i.builtin)],
620                 getter: function () !sanitizer.runAtShutdown ? [] : [
621                     item.name for (item in values(sanitizer.itemMap))
622                     if (item.shouldSanitize(true))
623                 ],
624                 setter: function (value) {
625                     if (value.length === 0)
626                         sanitizer.runAtShutdown = false;
627                     else {
628                         sanitizer.runAtShutdown = true;
629                         let have = Set(value);
630                         for (let item in values(sanitizer.itemMap))
631                             prefs.set(item.shutdownPref,
632                                       Boolean(Set.has(have, item.name) ^ Set.has(have, "all")));
633                     }
634                     return value;
635                 }
636             });
637
638         options.add(["sanitizetimespan", "sts"],
639             "The default sanitizer time span",
640             "string", "all",
641             {
642                 completer: function (context) {
643                     context.compare = context.constructor.Sort.Unsorted;
644                     context.completions = this.values;
645                 },
646                 values: {
647                     "all":     "Everything",
648                     "session": "The current session",
649                     "10m":     "Last ten minutes",
650                     "1h":      "Past hour",
651                     "1d":      "Past day",
652                     "1w":      "Past week"
653                 },
654                 validator: bind("test", /^(a(ll)?|s(ession)|\d+[mhdw])$/)
655             });
656
657         options.add(["cookies", "ck"],
658             "The default mode for newly added cookie permissions",
659             "stringlist", "session",
660             { get values() Sanitizer.COMMANDS });
661
662         options.add(["cookieaccept", "ca"],
663             "When to accept cookies",
664             "string", "all",
665             {
666                 PREF: "network.cookie.cookieBehavior",
667                 values: [
668                     ["all", "Accept all cookies"],
669                     ["samesite", "Accept all non-third-party cookies"],
670                     ["none", "Accept no cookies"]
671                 ],
672                 getter: function () (this.values[prefs.get(this.PREF)] || ["all"])[0],
673                 setter: function (val) {
674                     prefs.set(this.PREF, this.values.map(function (i) i[0]).indexOf(val));
675                     return val;
676                 },
677                 initialValue: true,
678                 persist: false
679             });
680
681         options.add(["cookielifetime", "cl"],
682             "The lifetime for which to accept cookies",
683             "string", "default", {
684                 PREF: "network.cookie.lifetimePolicy",
685                 PREF_DAYS: "network.cookie.lifetime.days",
686                 values: [
687                     ["default", "The lifetime requested by the setter"],
688                     ["prompt",  "Always prompt for a lifetime"],
689                     ["session", "The current session"]
690                 ],
691                 getter: function () (this.values[prefs.get(this.PREF)] || [prefs.get(this.PREF_DAYS)])[0],
692                 setter: function (value) {
693                     let val = this.values.map(function (i) i[0]).indexOf(value);
694                     if (val > -1)
695                         prefs.set(this.PREF, val);
696                     else {
697                         prefs.set(this.PREF, 3);
698                         prefs.set(this.PREF_DAYS, parseInt(value));
699                     }
700                 },
701                 initialValue: true,
702                 persist: false,
703                 validator: function validator(val) parseInt(val) == val || validator.superapply(this, arguments)
704             });
705     }
706 });
707
708 endModule();
709
710 // catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
711
712 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: