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