Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firefox WebExtension settings page

I have a settings page on my WebExtension, but I dont know how to acces the values of the settings with javascript.

Current .xul-File:

<?xml version="1.0"?>
<!DOCTYPE mydialog SYSTEM "chrome://myaddon/locale/mydialog.dtd">

<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <setting type="string" pref="extensions.check.email" title="email" desc="please insert your email here" />
</vbox>

How do I acces the "email" value? Can I just write something like "getPreferences('email')"?

like image 694
Patrick Mlr Avatar asked Aug 30 '16 07:08

Patrick Mlr


1 Answers

Don't use XUL in a WebExtension add-on:
If you are using XUL from within a WebExtension, then something is probably wrong. If you are writing a WebExtension, and you start to do XUL: take a step back. Make sure that is really what you are supposed to be doing. One of the points of WebExtensions is that it insulates the add-on from the internals of Firefox (i.e. from XUL).

Options panels and pages:
In general, options should be stored in storage.local (or storage.sync, when supported). If you are attempting to communicate from an options page, or a panel back to your main background script there are, at least, 4 somewhat different ways to do so:

  1. Options are stored to storage.local in the options.js code. The background code listens for events from storage.onChanged. No need to pass messages back and forth. No need for your options/panel code to specifically notify the background page that changes have occurred.
  2. Options are stored to storage.local in the options.js code. Then, the options.js directly invokes the a function in your background script to have the background script re-read the options. In the code below, the getOptions() function in the background.js file is directly invoked by the options.js code.
  3. Options are stored to storage.local in the options.js code. Use chrome.runtime.sendMessage() to send a message to your background page that the options have changed. In the code below: After storing the options in storage.local, the options.js sends an optionsUpdated message to the background script that the options have been updated. The background script then re-reads the options. [The message can be whatever you want that indicates this. optionsUpdated is merely what I chose as the message in the code below.]
  4. Use chrome.runtime.sendMessage() to send a message with all options data to the background page. In the code below: An optionsData message is sent from the options.js code to the background page when the options are change which contains a data payload with all of the options. The options are then stored to storage.local in the background script. Once the options are stored, the background script sends a optionsStored message back to the options.js code. The options.js code then indicates to the user that the options have been saved. [The message can be whatever you want that indicates this. optionsData and optionsStored is merely what I chose as the message in the code below.]

Below is a WebExtension that demonstrates those four different methods of getting the changed option information back to the background script.

Note: The code below uses a browser_action button to bring up a panel with the exact same options as are used for options_ui. This is done for the purpose of demonstration. If you are going to have a button that opens your options, it may be better to directly open your options_ui page with runtime.openOptionsPage(). Which you do depends on the user interface you want to present to the user.

background.js:

var useDirect=0; //Holds the state of how we communicate with options.js
var emailAddress=''; //The email address from options.
const useDirectTypes=[ 'Listen to chrome.storage changes'
                      ,'Directly invoke functions in the background script from'
                          + ' the options/panel code'
                      ,'Send a message that data in storage.local was updated'
                      ,'Send a message with all options data' ];


//Register the message listener 
chrome.runtime.onMessage.addListener(receiveMessage);

function receiveMessage(message,sender,sendResponse){
    //Receives a message that must be an object with a property 'type'.
    //  This format is imposed because in a larger extension we may
    //  be using messages for multiple purposes. Having the 'type'
    //  provides a defined way for other parts of the extension to
    //  both indicate the purpose of the message and send arbitrary
    //  data (other properties in the object).
    console.log('Received message: ',message);
    if(typeof message !== 'object' || !message.hasOwnProperty('type')){
        //Message does not have the format we have imposed for our use.
        //Message is not one we understand.
        return;
    }
    if(message.type === "optionsUpdated"){
        //The options have been updated and stored by options.js.
        //Re-read all options.
        getOptions();
    }
    if(message.type === "optionsData"){
        saveOptionsSentAsData(message.data,function(){
            //Callback function executed once data is stored in storage.local
            console.log('Sending response back to options page/panel');
            //Send a message back to options.js that the data has been stored.
            sendResponse({type:'optionsStored'});
            //Re-read all options.
            getOptions();
        });
        //Return true to leave the message channel open so we can 
        //  asynchronously send a message back to options.js that the
        //  data has actually been stored.
        return true;
    }
}

function detectStorageChange(change){
    //Ignore the change information. Just re-get all options
    console.log('Background.js: Detected storage change');
    getOptions();
}

function listenToStorageChanges(){
    chrome.storage.onChanged.addListener(detectStorageChange);
}

function stopListeningToStorageChanges(){
    chrome.storage.onChanged.removeListener(detectStorageChange);
}

function getOptions(){
    //Options are normally in storage.sync (sync'ed across the profile).
    //This example is using storage.local.
    //Firefox does not currently support storage.sync.
    chrome.storage.local.get({
        useDirect: 0,
        emailAddress: ''
    }, function(items) {
        if(typeof items.useDirect !== 'number' || items.useDirect <0
            || items.useDirect >= useDirectTypes.length) {
            items.useDirect=0;
        }
        useDirect = items.useDirect;
        emailAddress = items.emailAddress;
        console.log('useDirect=' + useDirectTypes[useDirect]);
        console.log('email address=' + emailAddress);
    });
}

function saveOptionsSentAsData(data,callback) {
    //Options data received as a message from options.js is 
    //  stored in storeage.local.
    chrome.storage.local.set(data, function() {
        //Invoke a callback function if we were passed one.
        if(typeof callback === 'function'){
            callback();
        }
    });
}

//Read the options stored from prior runs of the extension.
getOptions();

//On Firefox, open the Browser Console:
//To determine if this is Chrome, multiple methods which are not implemented
//  in Firefox are checked. Multiple ones are used as Firefox will eventually 
//  support more APIs.
var isChrome = !! window.InstallTrigger
               || (!!chrome.extension.setUpdateUrlData
               && !!chrome.runtime.reload
               && !!chrome.runtime.restart);
if(!isChrome) {
    //In Firefox cause the Browser Console to open by using alert()
    window.alert('Open the console. isChrome=' + isChrome);
}

options.js:

// Saves options to chrome.storage.local.
// It is recommended by Google that options be saved to chrome.storage.sync.
// Firefox does not yet support storage.sync.
function saveOptions(data, callback) {
    chrome.storage.local.set(data, function() {
        if(typeof callback === 'function'){
            callback();
        }
        // Update status to let user know options were saved.
        notifyOptionsSaved();
    });
}

function optionsChanged() {
    //Get the selected option values from the DOM
    let useDirectValue = document.getElementById('useDirect').value;
    let email = document.getElementById('email').value;
    useDirectValue = +useDirectValue; //Force to number, not string
    //Put all the option data in a single object
    let optionData = {
        useDirect: useDirectValue,
        emailAddress: email
    }
    setBackgroundPageNotListeningToStorageChanges();
    if(useDirectValue == 0 ) {
        //Use listening to storage changes
        //console.log('Going to listen for storage changes');
        setBackgroundPageListeningToStorageChanges();
        saveOptions(optionData);
    } else if (useDirectValue == 1) {
        //We save the options in the options page, or popup
        saveOptions(optionData, function(){
            //After the save is complete:
            //The getOptions() functon already exists to retrieve options from
            //  storage.local upon startup of the extension. It is easiest to use that.
            //  We could remove and add the listener here, but that code already
            //  exists in background.js. There is no reason to duplicate the code here.
            let backgroundPage = chrome.extension.getBackgroundPage();
            backgroundPage.getOptions();
        });
    } else if (useDirectValue == 2) {
        //We save the options in the options page, or popup
        saveOptions(optionData, function(){
            //Send a message to background.js that options in storage.local were updated.
            chrome.runtime.sendMessage({type:'optionsUpdated'});
        });
    } else {
        //Send all the options data to background.js and let it be dealt with there.
        chrome.runtime.sendMessage({
            type:'optionsData',
            data: optionData
        }, function(message){
            //Get a message back that may indicate we have stored the data.
            if(typeof message === 'object' && message.hasOwnProperty('type')){
                if(message.type === 'optionsStored') {
                    //The message received back indicated the option data has
                    //  been stored by background.js.
                    //Notify the user that the options have been saved.
                    notifyOptionsSaved();
                }
            }
        });
    }
}

function setBackgroundPageListeningToStorageChanges(){
    //Normally the listener would be set up once, and only once, within the
    //  background page script.  We are doing it here to demonstrate switing
    //  between the different methods of notification.
    let backgroundPage = chrome.extension.getBackgroundPage();
    //either add the listener directly:
    chrome.storage.onChanged.addListener(backgroundPage.detectStorageChange);
    //or let the background page add it:
    //backgroundPage.listenToStorageChanges();
}

function setBackgroundPageNotListeningToStorageChanges(){
    //Normally the listener would be set up once, and only once, within the
    //  background page script.  We are doing it here to demonstrate switing
    //  between the different methods of notification.
    let backgroundPage = chrome.extension.getBackgroundPage();
    //either remove the listener directly:
    chrome.storage.onChanged.removeListener(backgroundPage.detectStorageChange);
    //or let the background page add it:
    //backgroundPage.stopListeningToStorageChanges();
}

// Restores select box and input using the preferences
// stored in chrome.storage.
function useStoredOptionsForDisplayInDOM() {
    chrome.storage.local.get({
        useDirect: 0,
        emailAddress: ''
    }, function(items) {
        //Store retrieved options as the selected values in the DOM
        document.getElementById('useDirect').value = items.useDirect;
        document.getElementById('email').value = items.emailAddress;
    });
    //notifyStatusChange('Option read');
}

function notifyOptionsSaved(callback){
    //Notify the user that the options have been saved
    notifyStatusChange('Options saved.',callback);
}

function notifyStatusChange(newStatus,callback){
    let status = document.getElementById('status');
    status.textContent = newStatus;
    //Clear the notification after a second
    setTimeout(function() {
        status.textContent = '';
        if(typeof callback === 'function'){
            callback();
        }
    }, 1000);
}

document.addEventListener('DOMContentLoaded', useStoredOptionsForDisplayInDOM);
document.getElementById('optionsArea').addEventListener('change',optionsChanged);
//In order to track the change if this is open in _both_ the browser_action panel
//  and the add-on options page, we need to listen for changes to storage.
//  There is already a function which reads all of the options, so don't
//  use the information as to what changed, just that a change occurred.
chrome.storage.onChanged.addListener(useStoredOptionsForDisplayInDOM);

options.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>WebRequest Logging Options</title>
    <style>
        body: { padding: 10px; }
    </style>
</head>

<body>
    <div id="optionsArea">
        Communication with background page:
        <select id="useDirect">
            <option value="0">Listen for storage changes</option>
            <option value="1">Directly call background page functions</option>
            <option value="2">Send a Message Updated storage.local</option>
            <option value="3">Send a Message with all Data</option>
        </select>
        <div>Email:
            <input id="email"></input>
        </div>
    </div>
    <div id="status" style="top:0px;display:inline-block;"></div>

    <script src="options.js"></script>
</body>
</html>

manifest.json:

{
    "description": "Demonstrate an email field in options with various ways of communicating with the background script.",
    "manifest_version": 2,
    "name": "email-in-options-demo",
    "version": "0.1",

    "applications": {
        "gecko": {
            //Firefox: must define id to use option_ui:
            "id": "[email protected]",
            "strict_min_version": "48.0"
        }
    },

    "permissions": [
        "storage"
    ],

    "background": {
        "scripts": [
            "background.js"
        ]
    },

    "browser_action": {
        "default_icon": {
            "48": "myIcon.png"
        },
        "default_title": "Show panel",
        "browser_style": true,
        "default_popup": "options.html"
    },

    "options_ui": {
      "page": "options.html",
      "chrome_style": true
    }
}

The code above is based on code in my answer to Update WebExtension webRequest.onBeforeRequest listener URL settings from separate script.

like image 53
Makyen Avatar answered Nov 11 '22 15:11

Makyen