Skip to content

Instantly share code, notes, and snippets.

@oshybystyi
Last active May 30, 2017 01:12
Show Gist options
  • Save oshybystyi/8cf882bc8b0c9a95a116 to your computer and use it in GitHub Desktop.
Save oshybystyi/8cf882bc8b0c9a95a116 to your computer and use it in GitHub Desktop.
Load default preferences for firefox bootstrap (restartless) addons
/**
* Default addon bootstrap file
* full example can be found here https://github.com/oshybystyi/FireX-Pixel-Perfect/blob/issue-5-make-addon-restartless/bootstrap.js
*/
const defaultPreferencesLoaderLink = 'chrome://<addon-alias/<path-to>/defaultPreferencesLoader.jsm';
function startup(data) {
/** some code here ... **/
loadDefaultPreferences(data.installPath);
/** some code here ... **/
}
function shutdown(data, reason) {
if (reason == APP_SHUTDOWN)
return;
/** Remove default preferences on extension being uninstalled **/
unloadDefaultPreferences();
/** some code here ... **/
}
function loadDefaultPreferences(installPath) {
Cu.import(defaultPreferencesLoaderLink);
this.defaultPreferencesLoader = new DefaultPreferencesLoader(installPath);
this.defaultPreferencesLoader.parseDirectory();
}
function unloadDefaultPreferences() {
this.defaultPreferencesLoader.clearDefaultPrefs();
Cu.unload(defaultPreferencesLoaderLink);
}
/**
* Working example can be found here
* https://github.com/oshybystyi/FireX-Pixel-Perfect/blob/issue-5-make-addon-restartless/content/lib/defaultPreferencesLoader.jsm
*
* Important this module was tested only with <em:unpack>true</em:unpack>, most
* likely it won't work with false value
*
* A lot of stuff was borrowed from https://github.com/firebug/firebug/blob/master/extension/modules/prefLoader.js
*/
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
Cu.import('resource://gre/modules/Services.jsm');
var EXPORTED_SYMBOLS = ['DefaultPreferencesLoader'];
/**
* Read defaults/preferences/* and set Services.pref default branch
*/
function DefaultPreferencesLoader(installPath) {
let readFrom = installPath.clone(); // don't modify the original object
['defaults', 'preferences'].forEach(function(dir) {
readFrom.append(dir);
});
this.baseURI = Services.io.newFileURI(readFrom);
if (readFrom.exists() !== true) {
throw new DefaultsDirectoryMissingError(readFrom);
}
this.readFrom = readFrom;
this.defaultBranch = Services.prefs.getDefaultBranch("");
}
DefaultPreferencesLoader.prototype = {
/**
* Iterate over files in the default/preferences/*
*
* @param {function} prefFunc the function that should be used instead of
* pref
*/
parseDirectory: function(prefFunc) {
prefFunc = prefFunc || this.pref.bind(this);
let entries = this.readFrom.directoryEntries;
while (entries.hasMoreElements()) {
let fileURI = Services.io.newFileURI(entries.getNext());
Services.scriptloader.loadSubScript(fileURI.spec, { pref: prefFunc });
}
},
/**
* Emulates firefox pref function to load default preferences
*/
pref: function(key, value) {
switch (typeof value) {
case 'boolean':
this.defaultBranch.setBoolPref(key, value);
break;
case 'number':
this.defaultBranch.setIntPref(key, value);
break;
case 'string':
/**
* Using setComplexValue instead of setCharPref because of
* unicode support
*/
let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
str.value = value;
this.defaultBranch.setComplexValue(key, Ci.nsISupportsString, str);
break;
default:
throw new NotSupportedValueTypeError(key);
break;
}
},
/**
* Clears default preferences according to AMO reviewers reccommendation
* This should be invoked on bootstrap::shutdown
* @see https://github.com/firebug/firebug/blob/master/extension/modules/prefLoader.js
*/
clearDefaultPrefs: function() {
this.parseDirectory(this.prefUnload.bind(this));
},
prefUnload: function(key) {
let branch = this.defaultBranch;
if (branch.prefHasUserValue(key) !== true) {
branch.deleteBranch(key);
}
}
};
/**
* Exception type on missing defaults/preferences folder
*/
function DefaultsDirectoryMissingError(installPath) {
this.name = 'DefaultsDirectoryMissingError';
this.message = '\'' + installPath.path + '\' does no exist';
}
/** Inherit from Error for error stack and pretty output in terminal **/
DefaultsDirectoryMissingError.prototype = new Error();
/**
* Not supported value type to store by pref
*/
function NotSupportedValueTypeError(key) {
this.name = 'NotSupportedValueType';
this.message = 'Value type for key \'' + key + '\' is not supported';
}
NotSupportedValueTypeError.prototype = new Error();
@oshybystyi
Copy link
Author

This module allows automatic loading files from default/preferences/* with pref('...', ...) instructions for restartless firefox addons (a hack against firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=564675)

Examples of usage can be found on https://github.com/oshybystyi/FireX-Pixel-Perfect/tree/issue-5-make-addon-restartless (here I ported FireX-Pixel-Perfect from overlay based into bootstrap one)

@oshybystyi
Copy link
Author

After review on bugzilla I found similar firebug extension.

So I borrowed and modified some code in prefLoader (d2dd3ea6e6a93b98bacee5b5276a64fd7895c6ca)

Among changes:

  • instead of parsing file data with regex - use loadSubScript with pref function.
  • replaced setCharPref with setComplexValue to support unicode.

Why should you use this component instead of firebug prefLoader?

  • it is easier to deploy into bootstrap.js (because I have example in this gist)

Other benefits from this gist?

@jikamens
Copy link

jikamens commented May 28, 2017

It appears that str.value on line 76 should be str.data?

@jikamens
Copy link

jikamens commented May 30, 2017

This doesn't appear to work for a packed extension. The code in function DefaultPreferencesLoader is trying to append the path "defaults/preferences" to the path of the XPI file, which doesn't work. I think you need to implement alternative code using nsIZipReader or something.

Something like this seems to work (the rest of the code other than the section shown here remains unchanged):

/**
 * Read defaults/preferences/* and set Services.pref default branch
 */
function DefaultPreferencesLoader(installPath) {
    var readFrom = [];

    // Maybe instead just test if it's a file rather than a directory?
    // Not sure.
    if (/\.xpi$/i.test(installPath.path)) {
        let baseURI = Services.io.newFileURI(installPath);
        // Packed extension, need to read ZIP to get list of preference files
        // and then use "jar:" URIs to access them.
        let zr = Cc['@mozilla.org/libjar/zip-reader;1'].createInstance(
            Ci.nsIZipReader);
        zr.open(installPath);
        let entries = zr.findEntries('defaults/preferences/?*');
        while (entries.hasMore()) {
            let entry = entries.getNext();
            readFrom.push('jar:' + baseURI.spec + "!/" + entry);
        }
    }
    else {
        let dirPath = installPath.clone(); // don't modify the original object

        ['defaults', 'preferences'].forEach(function(dir) {
            dirPath.append(dir);
        });

        if (dirPath.exists() !== true) {
            throw new DefaultsDirectoryMissingError(dirPath);
        }

        let entries = dirPath.directoryEntries;

        while (entries.hasMoreElements()) {
            let fileURI = Services.io.newFileURI(entries.getNext());
            readFrom.push(fileURI.spec);
        }
    }

    this.readFrom = readFrom;

    this.defaultBranch = Services.prefs.getDefaultBranch("");
} 

DefaultPreferencesLoader.prototype = {
    /**
     * Iterate over files in the default/preferences/*
     *
     * @param {function} prefFunc the function that should be used instead of
     * pref
     */
    parseDirectory: function(prefFunc) {
        prefFunc = prefFunc || this.pref.bind(this);

        this.readFrom.forEach(function(uri) {
	    Services.scriptloader.loadSubScript(uri, { pref: prefFunc });
	});
    },

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