Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Noitidart/e0d3c21ab38822fbfd17 to your computer and use it in GitHub Desktop.
Save Noitidart/e0d3c21ab38822fbfd17 to your computer and use it in GitHub Desktop.
_ff-addon-template-bootstrapPrefsSkeleton - Shows the bare minimum I use for my addons with preferences. Utilizes inline options interface.
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/devtools/Console.jsm');
//start pref stuff
const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
const self = {
name: 'Bootstrap Preferences Skeleton',
id: 'Bootstrap-Preferences-Skeleton@jetpack', //because pref listener inits before startup and in startup is where aData.self.id becomes available
path: {},
aData: 0,
};
//start pref stuff
const myPrefBranch = 'extensions.' + self.name + '@jetpack.';
var myPrefListener;
//needs ES5, i dont know what min browser version of FF starts support for ES5
/**
* if want to change value of preference dont do prefs.holdTime.value = blah, instead must do `prefs.holdTime.setval(500)`
* because this will then properly set the pref on the branch then it will do the onChange properly with oldVal being correct
* NOTE: this fucntion prefSetval is not to be used directly, its only here as a contructor
*/
PrefListener.prototype.prefSetval = function(pass_pref_name, pass_branch_name) {
//console.log('this outside', this);
var passBranchObj = this.watchBranches[pass_branch_name];
var passPrefObj = passBranchObj.prefNames[pass_pref_name];
var func = function(updateTo, iHave__on_PrefOnObj_Change__butOnNextChangeSkipExecute) {
var pref_name = pass_pref_name;
var branch_name = pass_branch_name;
var branchObj = passBranchObj; //this.watchBranches[branch_name];
var prefObj = passPrefObj; //branchObj.prefNames[pref_name];
//console.info('in prefSetval', 'this:', this, 'branchObj', branchObj, 'prefObj', prefObj, 'pref_name', pass_pref_name);
if (iHave__on_PrefOnObj_Change__butOnNextChangeSkipExecute) {
var curValOnTree = branchObj._branchLive['get' + typeStr_from_typeLong(prefObj.type) + 'Pref'](pref_name);
if (curValOnTree == updateTo) {
console.warn('setval called said to mark it for skipOnChange, however updateTo and curValOnTree are same so on_PrefOnTree_Change will not call after running this updateTo so will not mark for skip');
} else {
prefObj.iHave__on_PrefOnObj_Change__butOnNextChangeSkipExecute = new Date().getTime();
}
}
branchObj._branchLive['set' + typeStr_from_typeLong(prefObj.type) + 'Pref'](pref_name, updateTo);
console.log('set doooone');
};
return func;
}
function typeStr_from_typeLong(typeLong) {
switch (typeLong) {
case Ci.nsIPrefBranch.PREF_STRING:
return 'Char';
case Ci.nsIPrefBranch.PREF_INT:
return 'Int';
case Ci.nsIPrefBranch.PREF_BOOL:
return 'Bool';
case Ci.nsIPrefBranch.PREF_INVALID:
//probably pref does not exist
throw new Error('typeLong is PREF_INVALID so probably pref DOES NOT EXIST');
default:
throw new Error('unrecognized typeLong:', typeLong);
}
}
///pref listener generic stuff NO NEED TO EDIT
/**
* @constructor
*
* @param {string} branch_name
* @param {Function} callback must have the following arguments:
* branch, pref_leaf_name
*/
//note: a weakness with this api i made for prefs, is that, if upgrading/downgrading and in installing rev a pref is no longer in use, the old pref will stay in the about:config system. prefs are only deleted when addon is uninstalled note: as of 080314 though i think i have a solution for this, watch the info/warn dump and if it holds true than edit it in
//note: good thing about this overhaul of the pref skeleton is that i can have this skeleton pasted in, and if no prefs being watched it doesnt do anything funky
function PrefListener() {
//is an array
// Keeping a reference to the observed preference branch or it will get garbage collected.
Object.keys(this.watchBranches).forEach(function(branch_name) {
this.watchBranches[branch_name]._branchLive = Services.prefs.getBranch(branch_name);
this.watchBranches[branch_name]._branchDefault = Services.prefs.getDefaultBranch(branch_name);
//this.watchBranches[branch_name]._branchLive.QueryInterface(Ci.nsIPrefBranch2); //do not need this anymore as i dont support FF3.x
}.bind(this));
}
//start - edit in here your prefs to watch
PrefListener.prototype.watchBranches = {
/*
// start - demo
'branch.name': { //for own branch we handle this outside of this object as we key to `myPrefBranch` for others branch like set to `'gecko.handlerService.schemes.mailto'`
ownType: 0, //0-full, 1-none, 2-partial //defines whether all prefs on this branch are owned (full,0), you own none of them so just watching whole, or partial which is mix
prefNames: { //this is an object of the prefs that i add into this branch, meaning i set the defaults on them. my prefs meaning that they belong to this addon and should be removed when this addon is uninstalled
//each key here must match the exact name the pref is saved in the about:config database (without the prefix)
//note: if i include a default key on the pref then it is a pref that i make on this branch
someNameOfPref: { //this pref gets created if not found in this branch in about:config, a defaultBranch value is set for it too, this pref is also deleted on uninstall of the addon. createdPrefs are denoted by supplying a `default` and `type` key
owned: true, //set this to true as we create this, if set to false it expects that the pref was made and we just want to watch
default: 300, //if owned is true must have default, else if its false, then cannot have default
value: undefined, //start value at undefined
type: Ci.nsIPrefBranch.PREF_STRING, //should call thi skey typeLong but whatever //Ci.nsIPrefBranch.PREF_BOOL or Ci.nsIPrefBranch.PREF_STRING or Ci.nsIPrefBranch.PREF_INT
//json: null, //if want to use json type must be string //NOT SUPPORTED IN V2.0 //12/8/14
on_PrefOnObj_Change: function(oldVal, newVal, refObj) { } //on change means on change of the object prefs.blah.value within. NOT on change of the pref in about:config. likewise onPreChange means before chanigng the perfs.blah.value, this is because if users changes pref from about:config, newVal is always obtained by doing a getIntVal etc //refObj holds
}
},
unknownNameOnChange: function(oldVal, newVal, refObj) { //really this just is unspecifiedNameOnChange
//this onChange function is called for prefs not found in the the prefNames object. if the pref_name change exists in the prefNames object and it doesnt have an onChange, then no onChange is called for that. So again this unknownNameOnChange is only called for if pref_name does not exist in prefNames obj
}
},
'gecko.handlerService.schemes.mailto': {
ownType: 1, //1 means none, but if i create any prefs on this (by setting nams in prefNames to owned:true) then i should set this to partial
prefNames: {
nameOfPreSpecifiedPref: {
owned: false, //set to false we're just watching, set to true, it gets created
}
},
// unknownNameOnChange: function(oldVal, newVal, refObj) {} //this is not required
}
// end - demo
*/
}
PrefListener.prototype.watchBranches[myPrefBranch] = { //have to do it this way because in the watchBranches obj i can't do { myPrefBranch: {...} }
ownType: 0, //0-full, 1-none, 2-partial
prefNames: {
'notifications': { //name of your pref
owned: true,
default: true,
value: undefined,
type: Ci.nsIPrefBranch.PREF_BOOL,
on_PrefOnObj_Change: writePrefToIni
}
},
on_UnknownPrefNameOnObj_Change: function(oldVal, newVal, refObj) {
console.warn('on_UnknownPrefNameOnObj_Change', 'oldVal:', oldVal, 'newVal:', newVal, 'refObj:', refObj);
}
};
//end - edit in here your prefs to watch
PrefListener.prototype.observe = function(subject, topic, data) {
//console.log('incoming PrefListener observe :: ', 'topic:', topic, 'data:', data, 'subject:', subject);
//console.info('compare subject to this._branchLive[extensions.MailtoWebmails@jetpack.]', this.watchBranches[subject.root]._branchLive);
if (topic == 'nsPref:changed') {
var branch_name = subject.root;
var pref_name = data;
this.on_PrefOnTree_Change(branch_name, pref_name);
} else {
console.warn('topic is something totally unexpected it is:', topic);
}
};
/**
* @param {boolean=} trigger if true triggers the registered function
* on registration, that is, when this method is called.
*/
PrefListener.prototype.register = function(aReason, exec__on_PrefOnObj_Change__onRegister) {
var branchesOnObj = Object.keys(this.watchBranches);
for (var i=0; i<branchesOnObj.length; i++) {
var branch_name = branchesOnObj[i];
var branchObj = this.watchBranches[branch_name];
if (branchObj.ownType == 0) {
var unusedPrefNamesOnTree = branchObj._branchLive.getChildList('', {});
}
var prefNamesOnObj = Object.keys(this.watchBranches[branch_name].prefNames);
for (var j=0; j<prefNamesOnObj.length; j++) {
var pref_name_on_obj = prefNamesOnObj[j];
var prefObj = branchObj.prefNames[pref_name_on_obj];
if (prefObj.owned) {
prefObj.setval = this.prefSetval(pref_name_on_obj, branch_name);
if (aReason == ADDON_INSTALL) {
prefObj.value = prefObj.default;
} else {
console.log('not install so fetching value of owned pref, as it should exist, may need to catch error here and on error set to default');
console.info('aReason == ', aReason);
try {
prefObj.value = branchObj._branchLive['get' + typeStr_from_typeLong(prefObj.type) + 'Pref'](pref_name_on_obj);
} catch(ex) {
//console.warn('excpetion occured when trying to fetch value, startup is not install so it should exist, however it probably doesnt so weird, so setting it to default, CAN GET HERE IF say have v1.2 installed and prefs were introduced in v1.3, so on update it can get here. ex:', ex); //this may happen if prefs were deleted somehow even though not uninstalled
console.warn('pref is missing, aReason == ', aReason); //expected if startup and pref value was default value on shutdown. or if upgrade/downgrade to new version which has prefs that were not there in previous version.
prefObj.value = prefObj.default;
var prefMissing = true;
}
}
if (prefMissing || [ADDON_INSTALL, ADDON_UPGRADE, ADDON_DOWNGRADE].indexOf(aReason) > -1) {
if (prefMissing) {
console.error('setting on default branch because prefMissing is true, aReason btw is ', aReason);
} else {
console.error('setting on default branch because aReason == ', aReason);
}
branchObj._branchDefault['set' + typeStr_from_typeLong(prefObj.type) + 'Pref'](pref_name_on_obj, prefObj.default);
} else {
console.error('NOT setting on default branch because aReason == ', aReason);
}
if (branchObj.ownType == 0) {
var indexOfPrefName_ON_unusedPrefNamesOnTree = unusedPrefNamesOnTree.indexOf(pref_name_on_obj);
if (indexOfPrefName_ON_unusedPrefNamesOnTree > -1) {
unusedPrefNamesOnTree.splice(indexOfPrefName_ON_unusedPrefNamesOnTree, 1);
}
}
} else {
prefObj.type = branchObj._branchLive.getPrefType(pref_name_on_obj); //use _branchLive in case it doesnt have default value //and its got to have _branchLive value as it is NOT owned UNLESS dev messed ownership up
prefObj.default = branchObj._branchDefault['get' + typeStr_from_typeLong(prefObj.type) + 'Pref'](pref_name_on_obj);
prefObj.value = branchObj._branchLive['get' + typeStr_from_typeLong(prefObj.type) + 'Pref'](pref_name_on_obj);
prefObj.setval = this.prefSetval(pref_name_on_obj, branch_name);
}
}
branchObj._branchLive.addObserver('', this, false);
for (var j=0; j<unusedPrefNamesOnTree.length; j++) {
var pref_name_in_arr = unusedPrefNamesOnTree[j];
/*
if (!this._branchDefault) {
this._branchDefault = Services.prefs.getDefaultBranch(null);
}
this._branchDefault.deleteBranch(branch_name + pref_name); //delete default value
branchObj._branchLive.clearUserPref(pref_name_in_arr); //delete live value
*/
Services.prefs.deleteBranch(branch_name + pref_name_in_arr); //deletes the default and live value so pref_name is gone from tree
}
}
if (exec__on_PrefOnObj_Change__onRegister) { //for robustness this must not be a per branch or a per pref property but on the whole watchBranches
for (var i=0; i<branchesOnObj.length; i++) {
var branch_name = branchesOnObj[i];
var branchObj = this.watchBranches[branch_name];
var prefNamesOnObj = Object.keys(this.watchBranches[branch_name].prefNames);
for (var j=0; j<prefNamesOnObj.length; j++) {
var pref_name_on_obj = prefNamesOnObj[j];
var prefObj = branchObj.prefNames[pref_name_on_obj];
if (prefObj.on_PrefOnObj_Change) {
var oldVal = undefined; //because this is what value on obj was before i set it to something
var newVal = prefObj.value;
var refObj = {
branch_name: branch_name,
pref_name: pref_name_on_obj,
prefObj: prefObj,
branchObj: branchObj
};
prefObj.on_PrefOnObj_Change(oldVal, newVal, refObj);
}
}
}
}
};
PrefListener.prototype.unregister = function() {
var branchesOnObj = Object.keys(this.watchBranches);
for (var i=0; i<branchesOnObj.length; i++) {
var branch_name = branchesOnObj[i];
var branchObj = this.watchBranches[branch_name];
branchObj._branchLive.removeObserver('', this);
console.log('removed observer from branch_name', branch_name);
}
};
PrefListener.prototype.uninstall = function(aReason) {
console.log('in PrefListener.uninstall proc');
if (aReason == ADDON_UNINSTALL) {
var branchesOnObj = Object.keys(this.watchBranches);
for (var i=0; i<branchesOnObj.length; i++) {
var branch_name = branchesOnObj[i];
var branchObj = this.watchBranches[branch_name];
if (branchObj.ownType == 0) {
Services.prefs.deleteBranch(branch_name);
} else {
var prefNamesOnObj = Object.keys(this.watchBranches[branch_name].prefNames);
for (var j=0; j<prefNamesOnObj.length; j++) {
var pref_name_on_obj = prefNamesOnObj[j];
var prefObj = branchObj.prefNames[pref_name_on_obj];
if (prefObj.owned) {
Services.prefs.deleteBranch(branch_name + pref_name_on_obj);
}
}
}
}
} else {
console.log('not real uninstall so quitting preflistener.uninstall proc');
}
};
PrefListener.prototype.on_PrefOnTree_Change = function (branch_name, pref_name_on_tree) {
console.log('on_PrefOnTree_Change', 'pref_name_on_tree:', pref_name_on_tree, 'branch_name:', branch_name);
var branchObj = this.watchBranches[branch_name];
var refObj = {
branch_name: branch_name,
pref_name: pref_name_on_tree,
branchObj: branchObj
};
if (pref_name_on_tree in branchObj.prefNames) {
var prefObj = branchObj.prefNames[pref_name_on_tree];
var oldVal = prefObj.value;
try {
var newVal = branchObj._branchLive['get' + typeStr_from_typeLong(prefObj.type) + 'Pref'](pref_name_on_tree);
} catch (ex) {
console.info('probably deleted', 'newVal exception:', ex);
}
refObj.prefObj = prefObj;
if (prefObj.iHave__on_PrefOnObj_Change__butOnNextChangeSkipExecute) {
var msAgo_markedForSkip = new Date().getTime() - prefObj.iHave__on_PrefOnObj_Change__butOnNextChangeSkipExecute;
console.log('skipping this onChange as 2nd arg told to skip it, it was marked for skip this many ms ago:', msAgo_markedForSkip);
delete prefObj.iHave__on_PrefOnObj_Change__butOnNextChangeSkipExecute
} else {
if (prefObj.on_PrefOnObj_Change) {
prefObj.on_PrefOnObj_Change(oldVal, newVal, refObj);
} else {
//do nothing
}
}
prefObj.value = newVal;
console.log('prefObj value updated, prefObj:', prefObj);
} else {
if (branchObj.on_UnknownPrefNameOnObj_Change) {
var oldVal = null; //i actually dont know if it existed before
refObj.type = branchObj._branchLive.getPrefType(pref_name_on_tree);
console.info('refObj.type:', refObj.type);
if (refObj.type == 0) {
console.info('unknownNameOnObj pref probably deleted');
newVal = null;
}
var newVal = branchObj._branchLive['get' + typeStr_from_typeLong(refObj.type) + 'Pref'](pref_name_on_tree);
refObj.setval = function(updateTo) {
branchObj._branchLive['set' + typeStr_from_typeLong(refObj.type) + 'Pref'](pref_name_on_tree, updateTo);
}
branchObj.on_UnknownPrefNameOnObj_Change(oldVal, newVal, refObj);
} else {
//do nothing
}
}
console.log('DONE on_PrefOnTree_Change');
};
////end pref listener stuff
//end pref stuff
function startup(aData, aReason) {
console.log('startup reason = ', aReason);
self.aData = aData; //must go first, because functions in loadIntoWindow use self.aData
//start pref stuff more
myPrefListener = new PrefListener(); //init
console.info('myPrefListener', myPrefListener);
myPrefListener.register(aReason, false);
//end pref stuff more
//windowListener.register();
}
function shutdown(aData, aReason) {
console.log('shutdown reason = ', aReason);
if (aReason == APP_SHUTDOWN) return;
//windowListener.unregister();
//start pref stuff more
myPrefListener.unregister();
//end pref stuff more
}
function install(aData, aReason) {
//must have arguments of aData and aReason otherwise the uninstall function doesn't trigger
}
function uninstall(aData, aReason) {
console.info('UNINSTALLING reason = ', aReason);
//start pref stuff more
if (!myPrefListener) {
//lets not register observer/listener lets just "install" it which populates branches
console.log('in uninstall had to init (soft install) myPrefListener')
myPrefListener = new PrefListener(); //this pouplates this.watchBranches[branch_name] so we can access .branchLive and .branchDefault IT WILL NOT register the perf observer/listener so no overhead there
}
myPrefListener.uninstall(aReason); //deletes owned branches AND owned prefs on UNowned branches, this is optional, you can choose to leave your preferences on the users computer
//end pref stuff more
}
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>Bootstrap-Preferences-Skeleton@jetpack</em:id>
<em:version>initial</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:unpack>false</em:unpack>
<!-- Firefox -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>7.0</em:minVersion>
<em:maxVersion>27.0</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Bootstrap Preferences Skeleton</em:name>
<em:description>Shows the bare minimum I use for my addons with preferences. Utilizes inline options interface.</em:description>
<em:creator>Noitidart</em:creator>
<em:contributor>jmiller29 for Idea</em:contributor>
<em:contributor>Isaac Grant for Icon</em:contributor>
<em:contributor>therube for SeaMonkey Compatability</em:contributor>
</Description>
</RDF>
<?xml version="1.0" ?>
<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<setting pref="extensions.Bootstrap-Preferences-Skeleton@jetpack.dummy" title="Dummy" type="integer">
This is a dummy pref of integer type.
</setting>
<setting pref="extensions.Bootstrap-Preferences-Skeleton@jetpack.otherPref" title="Other Preference Example" type="string">
This is an example of string type.
</setting>
</vbox>
@Noitidart
Copy link
Author

README

Note This template and readme is in progress, I plan to write in detail how to use this. Basically though, all you need to edit is prefPrefix and the prefs object.

A lot of people found it very helpful and useful so I thought I would share it. They liked the onChange function capability that supplies it with the old and new value of the preference. They also liked how you can just do prefs.prefName.setval('blah') because it properly triggers the onChange and especially because they don't need to call so many different functions such as setCharPref or setIntPref or setBoolPref.

Note The onChange function is triggered even by the register function, but onChange the oldVal will be null so this is how to detect if it's a startup thing.

Rev1

  • Untested but got the basic stuff done
  • On uninstall prefs not deleted

Rev2

  • Figured out how to do uninstall properly

Rev3

  • Tested the uninstall function and uninstall actually triggers on downgrade and upgrade too so I had to test if it's really an uninstall

Rev4

  • Accidentally left the delete pref branch in the shutdown function, so removed that now

Rev5

  • Accidently left in the windowListener register and unregister functions, commented out
  • Research with @nmaier shows that uninstall triggers even if function install() {} is not coded, so no need to even pass arguments and leave body blank, but i didnt edit this out yet of gist above

Rev6

  • Updated to v2.0
  • Supports watching prefs on other branches
  • Handles the on Firefox shutdown if a pref is at default it is deleted, so on startup it restores it if its at default
  • Supports adding in prefs into pre-existing branches

Rev7

  • Just added a dummy file so can properly name/index gist

@Noitidart
Copy link
Author

I'm thining about an argument aReason for prefCallback. For reasons being registering (but this is easy to detect as if its registering prefs[n].value === null), another reason being pref changed, another reason being forced.

This is what I was working on but I decided against adding it, at least for now:

the reason i decided against it, is because there is oldVal and newVal in the onChange so we can test there what reason is. If there is no change (meaning oldVal === newVal), obviously it was forced. If oldVal === null then it is obvioulsy registering/initializing. Outstanding q: HOW TO IDENT ON UNINSTALL ITS DELETING PREF?

for example:

        onChange: function(oldVal, newVal, refObj) {
            var msga = '?';
            if (oldVal === null) {
                msga = 'register/init';
            } else if (oldVal == newVal) {
                msga = 'probably a programmatic force';
            } else if (oldVal != newVal) {
                msga = 'really chaning';
            }
            Services.prompt.alert(null, 'prefChange - ' + refObj.name, msga);

BUT anyways this is the aReason thing I was working on, i was thinking of passing it as an argument. It was work in progress when i decided to quit as its likely not needed. (was working in throbber restored rev ~21)

//start pref stuff
const prefPrefix = 'extensions.ThrobberRestored.'; //cannot put this in startup and cannot use self.aData.id
var prefs = { //each key here must match the exact name the pref is saved in the about:config database (without the prefix)
    customImgIdle: {
        default: '',
        value: null,
        type: 'Char',
        onChange: function(oldVal, newVal, refObj) {
            Services.prompt.alert(null, "pref change", "pref change function triggering on - " + refObj.name);
            if (oldVal && oldVal != '') {
                myServices.sss.unregisterSheet(cssUri_CustomImgIdle, myServices.sss.USER_SHEET);
            }
            newVal = newVal.trim();
            if (newVal == '') {
                cssUri_CustomImgIdle = '';
            } else {
                var normalized = OS.Path.normalize(newVal);
                var file = new FileUtils.File(normalized);
                var fileuri = Services.io.newFileURI(file).spec;
                console.log('fileuri', fileuri);
                //var newuri = Services.io.newURI(newVal, null, null);
                //var newValRep = 'file:///' + newuri.spec.replace(/\\/g, '/');

                var css = '#navigator-throbber:not([loading]) { list-style-image: url("' + fileuri + '") !important; }';
                var newURIParam = {
                    aURL: 'data:text/css,' + encodeURIComponent(css),
                    aOriginCharset: null,
                    aBaseURI: null
                };
                cssUri_CustomImgIdle = Services.io.newURI(newURIParam.aURL, newURIParam.aOriginCharset, newURIParam.aBaseURI);
                myServices.sss.loadAndRegisterSheet(cssUri_CustomImgIdle, myServices.sss.USER_SHEET); //running this last as i think its syncronus
            }
        }
    },
    customImgLoading: {
        default: '',
        value: null,
        type: 'Char',
        onChange: function(oldVal, newVal, refObj) {
            Services.prompt.alert(null, "pref change", "pref change function triggering on - " + refObj.name);
            if (oldVal && oldVal != '') {
                myServices.sss.unregisterSheet(cssUri_CustomImgLoading, myServices.sss.USER_SHEET);
            }
            newVal = newVal.trim();
            if (newVal == '') {
                cssUri_CustomImgLoading = '';
            } else {
                var normalized = OS.Path.normalize(newVal);
                var file = new FileUtils.File(normalized);
                var fileuri = Services.io.newFileURI(file).spec;
                console.log('fileuri', fileuri);

                //var newuri = Services.io.newURI(newVal, null, null);
                //var newValRep = 'file:///' + newuri.spec.replace(/\\/g, '/');

                var css = '#navigator-throbber[loading] { list-style-image: url("' + fileuri + '") !important; }';
                var newURIParam = {
                    aURL: 'data:text/css,' + encodeURIComponent(css),
                    aOriginCharset: null,
                    aBaseURI: null
                };
                cssUri_CustomImgLoading = Services.io.newURI(newURIParam.aURL, newURIParam.aOriginCharset, newURIParam.aBaseURI);
                myServices.sss.loadAndRegisterSheet(cssUri_CustomImgLoading, myServices.sss.USER_SHEET); //running this last as i think its syncronus
            }
        }
    }
}
/**
 * if want to change value of preference dont do prefs.holdTime.value = blah, instead must do `prefs.holdTime.setval(500)`
 * because this will then properly set the pref on the branch then it will do the onChange properly with oldVal being correct
 * NOTE: this fucntion prefSetval is not to be used directly, its only here as a contructor
 */
function prefSetval(name) {
    return function(updateTo) {
        console.log('in prefSetval');
        console.info('this = ', this);
        if ('json' in this) {
            //updateTo must be an object
            if (Object.prototype.toString.call(updateTo) != '[object Object]') {
                console.warn('EXCEPTION: prefs[name] is json but updateTo supplied is not an object');
                return;
            }

            var stringify = JSON.stringify(updateTo); //uneval(updateTo);
            myPrefListener._branch['set' + this.type + 'Pref'](name, stringify);
            //prefs[name].value = {};
            //for (var p in updateTo) {
            //  prefs[name].value[p] = updateTo[p];
            //}
        } else {
            //prefs[name].value = updateTo;
            myPrefListener._branch['set' + this.type + 'Pref'](name, updateTo);
        }
    };
}
///pref listener generic stuff NO NEED TO EDIT
/**
 * @constructor
 *
 * @param {string} branch_name
 * @param {Function} callback must have the following arguments:
 *   branch, pref_leaf_name
 */
function PrefListener(branch_name, callback) {
  // Keeping a reference to the observed preference branch or it will get garbage collected.
    this._branch = Services.prefs.getBranch(branch_name);
    this._defaultBranch = Services.prefs.getDefaultBranch(branch_name);
    this._branch.QueryInterface(Ci.nsIPrefBranch2);
    this._callback = callback;
}

PrefListener.prototype.observe = function(subject, topic, data) {
    console.log('incomcing PrefListener observe', 'topic=', topic, 'data=', data, 'subject=', subject);
    if (topic == 'nsPref:changed')
        this._callback(this._branch, data);
};

/**
 * @param {boolean=} trigger if true triggers the registered function
 *   on registration, that is, when this method is called.
 */
PrefListener.prototype.register = function(setDefaults, trigger) {
    //adds the observer to all prefs and gives it the seval function

    for (var p in prefs) {
        prefs[p].setval = new prefSetval(p);
    }

    console.log('added setval');
    if (setDefaults) {
        this.setDefaults();
        console.log('finished set defaults');
    }

    //should add observer after setting defaults otherwise it triggers the callbacks
    this._branch.addObserver('', this, false);
    console.log('added observer');

    if (trigger) {
        console.log('trigger callbacks');
        this.forceCallbacks({aReason:});
        console.log('finished all callbacks');
    }
};

PrefListener.prototype.forceCallbacks = function(argObj) {
    console.log('forcing pref callbacks');
    let that = this;
    this._branch.getChildList('', {}).
      forEach(function (pref_leaf_name)
        { that._callback(that._branch, pref_leaf_name); });
};

PrefListener.prototype.setDefaults = function() {
    //sets defaults on the prefs in prefs obj
    console.log('doing setDefaults');
    for (var p in prefs) {
        console.log('will now set default on ', p);
        console.log('will set it to "' + prefs[p].default + '"');
        this._defaultBranch['set' + prefs[p].type + 'Pref'](p, prefs[p].default);
        console.log('fined setting default on ', p);
    }
    console.log('set defaults done');
};

PrefListener.prototype.unregister = function() {
  if (this._branch)
    this._branch.removeObserver('', this);
};

var myPrefListener = new PrefListener(prefPrefix, function (branch, name, aReason) {
    /* aReason - reason for calling this callback
    *  0 - registering
    *  1 - pref changed
    *  2 - i as the programmer programatticaly called forceCallbacks, i should set this in the argObj
    */
    //extensions.myextension[name] was changed
    console.log('callback start for pref: ', name);
    if (!(name in prefs)) {
        console.warn('name is not in prefs so return name = ', name);
        //added this because apparently some pref named prefPreix + '.sdk.console.logLevel' gets created when testing with builder
        //ALSO gets here if say upgraded, and in this version this pref is not used (same with downgraded)
        return;
    }

    var refObj = {name: name, aReason: aReason}; //passed to onPreChange and onChange
    var oldVal = 'json' in prefs[name] ? prefs[name].json : prefs[name].value;
    try {
        var newVal = myPrefListener._branch['get' + prefs[name].type + 'Pref'](name);
    } catch (ex) {
        console.warn('exception when getting newVal (likely the pref was removed): ' + ex);
        var newVal = null; //note: if ex thrown then pref was removed (likely probably)
    }
    console.log('oldVal == ', oldVal);
    console.log('newVal == ', newVal);
    prefs[name].value = newVal === null ? prefs[name].default : newVal;

    if ('json' in prefs[name]) {
        refObj.oldValStr = oldVal;
        oldVal = JSON.parse(oldVal); //function(){ return eval('(' + oldVal + ')') }();

        refObj.newValStr = prefs[name].value;
        prefs[name].json = prefs[name].value;
        prefs[name].value =  JSON.parse(prefs[name].value); //function(){ return eval('(' + prefs[name].value + ')') }();
    }

    if (prefs[name].onChange) {
        prefs[name].onChange(oldVal, prefs[name].value, refObj);
    }
    console.log('myPrefCallback done');
});
////end pref listener stuff
//end pref stuff

@Noitidart
Copy link
Author

see initial.rev5 of MailtoWebservices repo here i use it to monitor some default prefs, which means i have not set a default value. so my solution was just check if default key exists, if it doesnt than dont set default on it.

also had to change this part:

prefs[name].value = newVal === null ? prefs[name].default : newVal;

to:

    if ('default' in prefs[name]) {
        prefs[name].value = newVal === null ? prefs[name].default : newVal;
    } else {
        prefs[name].value = null;
    }

@Noitidart
Copy link
Author

PrefSkel 2.0

Needed Features

  • Create and monitor multiple branches
    • On uninstall option to delete branches
    • If new pref gets created after register it should get added in and should use unknownOnChange
  • Watch unowned branches
    • Add single pref to prefNames to get ability to add onChange or add the single pref and omit the onChange to make it ignore unknownOnChange
    • During run time, after registered this listener, if new pref gets created on the branch, it should get added and should use unknownOnChange
    • Any single pref that is not in prefNames should use unknownOnChange
  • Watch single prefs on unowned branches (gets ability to set the value to)
    • On uninstall option to clearUserPref on these watched values
  • DO NOT ALLOW
    • Creating single prefs on unowned branches (this is problematic for an add-on like DevPrefs)

@velrest
Copy link

velrest commented Mar 9, 2017

Is option.uxl intended? i thought that xul would be the right file ending.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment