forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			671 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			671 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* - This Source Code Form is subject to the terms of the Mozilla Public
 | |
|    - License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|    - You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| // We attach Preferences to the window object so other contexts (tests, JSMs)
 | |
| // have access to it.
 | |
| const Preferences = (window.Preferences = (function () {
 | |
|   const { EventEmitter } = ChromeUtils.importESModule(
 | |
|     "resource://gre/modules/EventEmitter.sys.mjs"
 | |
|   );
 | |
| 
 | |
|   const lazy = {};
 | |
|   ChromeUtils.defineESModuleGetters(lazy, {
 | |
|     DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
 | |
|   });
 | |
| 
 | |
|   function getElementsByAttribute(name, value) {
 | |
|     // If we needed to defend against arbitrary values, we would escape
 | |
|     // double quotes (") and escape characters (\) in them, i.e.:
 | |
|     //   ${value.replace(/["\\]/g, '\\$&')}
 | |
|     return value
 | |
|       ? document.querySelectorAll(`[${name}="${value}"]`)
 | |
|       : document.querySelectorAll(`[${name}]`);
 | |
|   }
 | |
| 
 | |
|   const domContentLoadedPromise = new Promise(resolve => {
 | |
|     window.addEventListener("DOMContentLoaded", resolve, {
 | |
|       capture: true,
 | |
|       once: true,
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   const Preferences = {
 | |
|     _all: {},
 | |
| 
 | |
|     _add(prefInfo) {
 | |
|       if (this._all[prefInfo.id]) {
 | |
|         throw new Error(`preference with id '${prefInfo.id}' already added`);
 | |
|       }
 | |
|       const pref = new Preference(prefInfo);
 | |
|       this._all[pref.id] = pref;
 | |
|       domContentLoadedPromise.then(() => {
 | |
|         if (!this.updateQueued) {
 | |
|           pref.updateElements();
 | |
|         }
 | |
|       });
 | |
|       return pref;
 | |
|     },
 | |
| 
 | |
|     add(prefInfo) {
 | |
|       const pref = this._add(prefInfo);
 | |
|       return pref;
 | |
|     },
 | |
| 
 | |
|     addAll(prefInfos) {
 | |
|       prefInfos.map(prefInfo => this._add(prefInfo));
 | |
|     },
 | |
| 
 | |
|     get(id) {
 | |
|       return this._all[id] || null;
 | |
|     },
 | |
| 
 | |
|     getAll() {
 | |
|       return Object.values(this._all);
 | |
|     },
 | |
| 
 | |
|     defaultBranch: Services.prefs.getDefaultBranch(""),
 | |
| 
 | |
|     get type() {
 | |
|       return document.documentElement.getAttribute("type") || "";
 | |
|     },
 | |
| 
 | |
|     get instantApply() {
 | |
|       // The about:preferences page forces instantApply.
 | |
|       // TODO: Remove forceEnableInstantApply in favor of always applying in a
 | |
|       // parent and never applying in a child (bug 1775386).
 | |
|       if (this._instantApplyForceEnabled) {
 | |
|         return true;
 | |
|       }
 | |
| 
 | |
|       // Dialogs of type="child" are never instantApply.
 | |
|       return this.type !== "child";
 | |
|     },
 | |
| 
 | |
|     _instantApplyForceEnabled: false,
 | |
| 
 | |
|     // Override the computed value of instantApply for this window.
 | |
|     forceEnableInstantApply() {
 | |
|       this._instantApplyForceEnabled = true;
 | |
|     },
 | |
| 
 | |
|     observe(subject, topic, data) {
 | |
|       const pref = this._all[data];
 | |
|       if (pref) {
 | |
|         pref.value = pref.valueFromPreferences;
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     updateQueued: false,
 | |
| 
 | |
|     queueUpdateOfAllElements() {
 | |
|       if (this.updateQueued) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       this.updateQueued = true;
 | |
| 
 | |
|       Services.tm.dispatchToMainThread(() => {
 | |
|         let startTime = performance.now();
 | |
| 
 | |
|         const elements = getElementsByAttribute("preference");
 | |
|         for (const element of elements) {
 | |
|           const id = element.getAttribute("preference");
 | |
|           let preference = this.get(id);
 | |
|           if (!preference) {
 | |
|             console.error(`Missing preference for ID ${id}`);
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           preference.setElementValue(element);
 | |
|         }
 | |
| 
 | |
|         ChromeUtils.addProfilerMarker(
 | |
|           "Preferences",
 | |
|           { startTime },
 | |
|           `updateAllElements: ${elements.length} preferences updated`
 | |
|         );
 | |
| 
 | |
|         this.updateQueued = false;
 | |
|       });
 | |
|     },
 | |
| 
 | |
|     onUnload() {
 | |
|       Services.prefs.removeObserver("", this);
 | |
|     },
 | |
| 
 | |
|     QueryInterface: ChromeUtils.generateQI(["nsITimerCallback", "nsIObserver"]),
 | |
| 
 | |
|     _deferredValueUpdateElements: new Set(),
 | |
| 
 | |
|     writePreferences(aFlushToDisk) {
 | |
|       // Write all values to preferences.
 | |
|       if (this._deferredValueUpdateElements.size) {
 | |
|         this._finalizeDeferredElements();
 | |
|       }
 | |
| 
 | |
|       const preferences = Preferences.getAll();
 | |
|       for (const preference of preferences) {
 | |
|         preference.batching = true;
 | |
|         preference.valueFromPreferences = preference.value;
 | |
|         preference.batching = false;
 | |
|       }
 | |
|       if (aFlushToDisk) {
 | |
|         Services.prefs.savePrefFile(null);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     getPreferenceElement(aStartElement) {
 | |
|       let temp = aStartElement;
 | |
|       while (
 | |
|         temp &&
 | |
|         temp.nodeType == Node.ELEMENT_NODE &&
 | |
|         !temp.hasAttribute("preference")
 | |
|       ) {
 | |
|         temp = temp.parentNode;
 | |
|       }
 | |
|       return temp && temp.nodeType == Node.ELEMENT_NODE ? temp : aStartElement;
 | |
|     },
 | |
| 
 | |
|     _deferredValueUpdate(aElement) {
 | |
|       delete aElement._deferredValueUpdateTask;
 | |
|       const prefID = aElement.getAttribute("preference");
 | |
|       const preference = Preferences.get(prefID);
 | |
|       const prefVal = preference.getElementValue(aElement);
 | |
|       preference.value = prefVal;
 | |
|       this._deferredValueUpdateElements.delete(aElement);
 | |
|     },
 | |
| 
 | |
|     _finalizeDeferredElements() {
 | |
|       for (const el of this._deferredValueUpdateElements) {
 | |
|         if (el._deferredValueUpdateTask) {
 | |
|           el._deferredValueUpdateTask.finalize();
 | |
|         }
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     userChangedValue(aElement) {
 | |
|       const element = this.getPreferenceElement(aElement);
 | |
|       if (element.hasAttribute("preference")) {
 | |
|         if (element.getAttribute("delayprefsave") != "true") {
 | |
|           const preference = Preferences.get(
 | |
|             element.getAttribute("preference")
 | |
|           );
 | |
|           const prefVal = preference.getElementValue(element);
 | |
|           preference.value = prefVal;
 | |
|         } else {
 | |
|           if (!element._deferredValueUpdateTask) {
 | |
|             element._deferredValueUpdateTask = new lazy.DeferredTask(
 | |
|               this._deferredValueUpdate.bind(this, element),
 | |
|               1000
 | |
|             );
 | |
|             this._deferredValueUpdateElements.add(element);
 | |
|           } else {
 | |
|             // Each time the preference is changed, restart the delay.
 | |
|             element._deferredValueUpdateTask.disarm();
 | |
|           }
 | |
|           element._deferredValueUpdateTask.arm();
 | |
|         }
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     onCommand(event) {
 | |
|       // This "command" event handler tracks changes made to preferences by
 | |
|       // the user in this window.
 | |
|       if (event.sourceEvent) {
 | |
|         event = event.sourceEvent;
 | |
|       }
 | |
|       this.userChangedValue(event.target);
 | |
|     },
 | |
| 
 | |
|     onChange(event) {
 | |
|       // This "change" event handler tracks changes made to preferences by
 | |
|       // the user in this window.
 | |
|       this.userChangedValue(event.target);
 | |
|     },
 | |
| 
 | |
|     onInput(event) {
 | |
|       // This "input" event handler tracks changes made to preferences by
 | |
|       // the user in this window.
 | |
|       this.userChangedValue(event.target);
 | |
|     },
 | |
| 
 | |
|     _fireEvent(aEventName, aTarget) {
 | |
|       try {
 | |
|         const event = new CustomEvent(aEventName, {
 | |
|           bubbles: true,
 | |
|           cancelable: true,
 | |
|         });
 | |
|         return aTarget.dispatchEvent(event);
 | |
|       } catch (e) {
 | |
|         console.error(e);
 | |
|       }
 | |
|       return false;
 | |
|     },
 | |
| 
 | |
|     onDialogAccept(event) {
 | |
|       let dialog = document.querySelector("dialog");
 | |
|       if (!this._fireEvent("beforeaccept", dialog)) {
 | |
|         event.preventDefault();
 | |
|         return false;
 | |
|       }
 | |
|       this.writePreferences(true);
 | |
|       return true;
 | |
|     },
 | |
| 
 | |
|     close(event) {
 | |
|       if (Preferences.instantApply) {
 | |
|         window.close();
 | |
|       }
 | |
|       event.stopPropagation();
 | |
|       event.preventDefault();
 | |
|     },
 | |
| 
 | |
|     handleEvent(event) {
 | |
|       switch (event.type) {
 | |
|         case "toggle":
 | |
|         case "change":
 | |
|           return this.onChange(event);
 | |
|         case "command":
 | |
|           return this.onCommand(event);
 | |
|         case "dialogaccept":
 | |
|           return this.onDialogAccept(event);
 | |
|         case "input":
 | |
|           return this.onInput(event);
 | |
|         case "unload":
 | |
|           return this.onUnload(event);
 | |
|         default:
 | |
|           return undefined;
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     _syncFromPrefListeners: new WeakMap(),
 | |
|     _syncToPrefListeners: new WeakMap(),
 | |
| 
 | |
|     addSyncFromPrefListener(aElement, callback) {
 | |
|       this._syncFromPrefListeners.set(aElement, callback);
 | |
|       if (this.updateQueued) {
 | |
|         return;
 | |
|       }
 | |
|       // Make sure elements are updated correctly with the listener attached.
 | |
|       let elementPref = aElement.getAttribute("preference");
 | |
|       if (elementPref) {
 | |
|         let pref = this.get(elementPref);
 | |
|         if (pref) {
 | |
|           pref.updateElements();
 | |
|         }
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     addSyncToPrefListener(aElement, callback) {
 | |
|       this._syncToPrefListeners.set(aElement, callback);
 | |
|       if (this.updateQueued) {
 | |
|         return;
 | |
|       }
 | |
|       // Make sure elements are updated correctly with the listener attached.
 | |
|       let elementPref = aElement.getAttribute("preference");
 | |
|       if (elementPref) {
 | |
|         let pref = this.get(elementPref);
 | |
|         if (pref) {
 | |
|           pref.updateElements();
 | |
|         }
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     removeSyncFromPrefListener(aElement) {
 | |
|       this._syncFromPrefListeners.delete(aElement);
 | |
|     },
 | |
| 
 | |
|     removeSyncToPrefListener(aElement) {
 | |
|       this._syncToPrefListeners.delete(aElement);
 | |
|     },
 | |
|   };
 | |
| 
 | |
|   Services.prefs.addObserver("", Preferences);
 | |
|   window.addEventListener("toggle", Preferences);
 | |
|   window.addEventListener("change", Preferences);
 | |
|   window.addEventListener("command", Preferences);
 | |
|   window.addEventListener("dialogaccept", Preferences);
 | |
|   window.addEventListener("input", Preferences);
 | |
|   window.addEventListener("select", Preferences);
 | |
|   window.addEventListener("unload", Preferences, { once: true });
 | |
| 
 | |
|   class Preference extends EventEmitter {
 | |
|     constructor({ id, type, inverted }) {
 | |
|       super();
 | |
|       this.on("change", this.onChange.bind(this));
 | |
| 
 | |
|       this._value = null;
 | |
|       this.readonly = false;
 | |
|       this._useDefault = false;
 | |
|       this.batching = false;
 | |
| 
 | |
|       this.id = id;
 | |
|       this.type = type;
 | |
|       this.inverted = !!inverted;
 | |
| 
 | |
|       // In non-instant apply mode, we must try and use the last saved state
 | |
|       // from any previous opens of a child dialog instead of the value from
 | |
|       // preferences, to pick up any edits a user may have made.
 | |
| 
 | |
|       if (
 | |
|         Preferences.type == "child" &&
 | |
|         window.opener &&
 | |
|         window.opener.Preferences &&
 | |
|         window.opener.document.nodePrincipal.isSystemPrincipal
 | |
|       ) {
 | |
|         // Try to find the preference in the parent window.
 | |
|         const preference = window.opener.Preferences.get(this.id);
 | |
| 
 | |
|         // Don't use the value setter here, we don't want updateElements to be
 | |
|         // prematurely fired.
 | |
|         this._value = preference ? preference.value : this.valueFromPreferences;
 | |
|       } else {
 | |
|         this._value = this.valueFromPreferences;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     reset() {
 | |
|       // defer reset until preference update
 | |
|       this.value = undefined;
 | |
|     }
 | |
| 
 | |
|     _reportUnknownType() {
 | |
|       const msg = `Preference with id=${this.id} has unknown type ${this.type}.`;
 | |
|       Services.console.logStringMessage(msg);
 | |
|     }
 | |
| 
 | |
|     setElementValue(aElement) {
 | |
|       if (this.locked) {
 | |
|         aElement.disabled = true;
 | |
|       }
 | |
| 
 | |
|       if (!this.isElementEditable(aElement)) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       let rv = undefined;
 | |
| 
 | |
|       if (Preferences._syncFromPrefListeners.has(aElement)) {
 | |
|         rv = Preferences._syncFromPrefListeners.get(aElement)(aElement);
 | |
|       }
 | |
|       let val = rv;
 | |
|       if (val === undefined) {
 | |
|         val = Preferences.instantApply ? this.valueFromPreferences : this.value;
 | |
|       }
 | |
|       // if the preference is marked for reset, show default value in UI
 | |
|       if (val === undefined) {
 | |
|         val = this.defaultValue;
 | |
|       }
 | |
| 
 | |
|       /**
 | |
|        * Initialize a UI element property with a value. Handles the case
 | |
|        * where an element has not yet had a XBL binding attached for it and
 | |
|        * the property setter does not yet exist by setting the same attribute
 | |
|        * on the XUL element using DOM apis and assuming the element's
 | |
|        * constructor or property getters appropriately handle this state.
 | |
|        */
 | |
|       function setValue(element, attribute, value) {
 | |
|         if (attribute in element) {
 | |
|           element[attribute] = value;
 | |
|         } else if (attribute === "checked" || attribute === "pressed") {
 | |
|           // The "checked" attribute can't simply be set to the specified value;
 | |
|           // it has to be set if the value is true and removed if the value
 | |
|           // is false in order to be interpreted correctly by the element.
 | |
|           if (value) {
 | |
|             // In theory we can set it to anything; however xbl implementation
 | |
|             // of `checkbox` only works with "true".
 | |
|             element.setAttribute(attribute, "true");
 | |
|           } else {
 | |
|             element.removeAttribute(attribute);
 | |
|           }
 | |
|         } else {
 | |
|           element.setAttribute(attribute, value);
 | |
|         }
 | |
|       }
 | |
|       if (
 | |
|         aElement.localName == "checkbox" ||
 | |
|         (aElement.localName == "input" && aElement.type == "checkbox")
 | |
|       ) {
 | |
|         setValue(aElement, "checked", val);
 | |
|       } else if (aElement.localName == "moz-toggle") {
 | |
|         setValue(aElement, "pressed", val);
 | |
|       } else {
 | |
|         setValue(aElement, "value", val);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     getElementValue(aElement) {
 | |
|       if (Preferences._syncToPrefListeners.has(aElement)) {
 | |
|         try {
 | |
|           const rv = Preferences._syncToPrefListeners.get(aElement)(aElement);
 | |
|           if (rv !== undefined) {
 | |
|             return rv;
 | |
|           }
 | |
|         } catch (e) {
 | |
|           console.error(e);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       /**
 | |
|        * Read the value of an attribute from an element, assuming the
 | |
|        * attribute is a property on the element's node API. If the property
 | |
|        * is not present in the API, then assume its value is contained in
 | |
|        * an attribute, as is the case before a binding has been attached.
 | |
|        */
 | |
|       function getValue(element, attribute) {
 | |
|         if (attribute in element) {
 | |
|           return element[attribute];
 | |
|         }
 | |
|         return element.getAttribute(attribute);
 | |
|       }
 | |
|       let value;
 | |
|       if (
 | |
|         aElement.localName == "checkbox" ||
 | |
|         (aElement.localName == "input" && aElement.type == "checkbox")
 | |
|       ) {
 | |
|         value = getValue(aElement, "checked");
 | |
|       } else if (aElement.localName == "moz-toggle") {
 | |
|         value = getValue(aElement, "pressed");
 | |
|       } else {
 | |
|         value = getValue(aElement, "value");
 | |
|       }
 | |
| 
 | |
|       switch (this.type) {
 | |
|         case "int":
 | |
|           return parseInt(value, 10) || 0;
 | |
|         case "bool":
 | |
|           return typeof value == "boolean" ? value : value == "true";
 | |
|       }
 | |
|       return value;
 | |
|     }
 | |
| 
 | |
|     isElementEditable(aElement) {
 | |
|       switch (aElement.localName) {
 | |
|         case "checkbox":
 | |
|         case "input":
 | |
|         case "radiogroup":
 | |
|         case "textarea":
 | |
|         case "menulist":
 | |
|         case "moz-toggle":
 | |
|           return true;
 | |
|       }
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     updateElements() {
 | |
|       let startTime = performance.now();
 | |
| 
 | |
|       if (!this.id) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       const elements = getElementsByAttribute("preference", this.id);
 | |
|       for (const element of elements) {
 | |
|         this.setElementValue(element);
 | |
|       }
 | |
| 
 | |
|       ChromeUtils.addProfilerMarker(
 | |
|         "Preferences",
 | |
|         { startTime, captureStack: true },
 | |
|         `updateElements for ${this.id}`
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     onChange() {
 | |
|       this.updateElements();
 | |
|     }
 | |
| 
 | |
|     get value() {
 | |
|       return this._value;
 | |
|     }
 | |
| 
 | |
|     set value(val) {
 | |
|       if (this.value !== val) {
 | |
|         this._value = val;
 | |
|         if (Preferences.instantApply) {
 | |
|           this.valueFromPreferences = val;
 | |
|         }
 | |
|         this.emit("change");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     get locked() {
 | |
|       return Services.prefs.prefIsLocked(this.id);
 | |
|     }
 | |
| 
 | |
|     updateControlDisabledState(val) {
 | |
|       if (!this.id) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       val = val || this.locked;
 | |
| 
 | |
|       const elements = getElementsByAttribute("preference", this.id);
 | |
|       for (const element of elements) {
 | |
|         element.disabled = val;
 | |
| 
 | |
|         const labels = getElementsByAttribute("control", element.id);
 | |
|         for (const label of labels) {
 | |
|           label.disabled = val;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     get hasUserValue() {
 | |
|       return (
 | |
|         Services.prefs.prefHasUserValue(this.id) && this.value !== undefined
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     get defaultValue() {
 | |
|       this._useDefault = true;
 | |
|       const val = this.valueFromPreferences;
 | |
|       this._useDefault = false;
 | |
|       return val;
 | |
|     }
 | |
| 
 | |
|     get _branch() {
 | |
|       return this._useDefault ? Preferences.defaultBranch : Services.prefs;
 | |
|     }
 | |
| 
 | |
|     get valueFromPreferences() {
 | |
|       try {
 | |
|         // Force a resync of value with preferences.
 | |
|         switch (this.type) {
 | |
|           case "int":
 | |
|             return this._branch.getIntPref(this.id);
 | |
|           case "bool": {
 | |
|             const val = this._branch.getBoolPref(this.id);
 | |
|             return this.inverted ? !val : val;
 | |
|           }
 | |
|           case "wstring":
 | |
|             return this._branch.getComplexValue(
 | |
|               this.id,
 | |
|               Ci.nsIPrefLocalizedString
 | |
|             ).data;
 | |
|           case "string":
 | |
|           case "unichar":
 | |
|             return this._branch.getStringPref(this.id);
 | |
|           case "fontname": {
 | |
|             const family = this._branch.getStringPref(this.id);
 | |
|             const fontEnumerator = Cc[
 | |
|               "@mozilla.org/gfx/fontenumerator;1"
 | |
|             ].createInstance(Ci.nsIFontEnumerator);
 | |
|             return fontEnumerator.getStandardFamilyName(family);
 | |
|           }
 | |
|           case "file": {
 | |
|             const f = this._branch.getComplexValue(this.id, Ci.nsIFile);
 | |
|             return f;
 | |
|           }
 | |
|           default:
 | |
|             this._reportUnknownType();
 | |
|         }
 | |
|       } catch (e) {}
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     set valueFromPreferences(val) {
 | |
|       // Exit early if nothing to do.
 | |
|       if (this.readonly || this.valueFromPreferences == val) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // The special value undefined means 'reset preference to default'.
 | |
|       if (val === undefined) {
 | |
|         Services.prefs.clearUserPref(this.id);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // Force a resync of preferences with value.
 | |
|       switch (this.type) {
 | |
|         case "int":
 | |
|           Services.prefs.setIntPref(this.id, val);
 | |
|           break;
 | |
|         case "bool":
 | |
|           Services.prefs.setBoolPref(this.id, this.inverted ? !val : val);
 | |
|           break;
 | |
|         case "wstring": {
 | |
|           const pls = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(
 | |
|             Ci.nsIPrefLocalizedString
 | |
|           );
 | |
|           pls.data = val;
 | |
|           Services.prefs.setComplexValue(
 | |
|             this.id,
 | |
|             Ci.nsIPrefLocalizedString,
 | |
|             pls
 | |
|           );
 | |
|           break;
 | |
|         }
 | |
|         case "string":
 | |
|         case "unichar":
 | |
|         case "fontname":
 | |
|           Services.prefs.setStringPref(this.id, val);
 | |
|           break;
 | |
|         case "file": {
 | |
|           let lf;
 | |
|           if (typeof val == "string") {
 | |
|             lf = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
 | |
|             lf.persistentDescriptor = val;
 | |
|             if (!lf.exists()) {
 | |
|               lf.initWithPath(val);
 | |
|             }
 | |
|           } else {
 | |
|             lf = val.QueryInterface(Ci.nsIFile);
 | |
|           }
 | |
|           Services.prefs.setComplexValue(this.id, Ci.nsIFile, lf);
 | |
|           break;
 | |
|         }
 | |
|         default:
 | |
|           this._reportUnknownType();
 | |
|       }
 | |
|       if (!this.batching) {
 | |
|         Services.prefs.savePrefFile(null);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return Preferences;
 | |
| })());
 | 
