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