I've created a simple Firefox add-on where each time the user selects some text on a page, a small popup with the user's search engines appears, which you can then click to search the web. It mimics plugins such as this one or FastestFox's popup panel.
The add-on uses all of the user's search engines by default, but I want to let the user hide certain engines from my panel, without affecting the browser's search engines manager. Using the SDK's simple-prefs
, I can create settings in packages.json
: int values, buttons (controls), checkboxes, dropdown menus, etc. However, search engines are different for each user and dynamic (they can be added or removed from the browser).
I added a button to my add-on's preferences menu:
This button should open some window that shows all search engines and a checkbox behind each of them (to enable/disable them in my popup). I think I could save these extra settings using simple-storage
. But now I don't know how to proceed from here.
This is what Adblock does:
I've also seen at least one add-on place all its settings in an HTML page.
Actual questions: Should I create a window for this? (What is the API I should use for that?) Or is it better to generate a webpage and handle everything through HTML and JavaScript? (Seems much harder!)
This is the first add-on I try developing for Firefox and I'm having a hard time finding info on the web. Everything in my add-on comes from the High-Level APIs of the Add-on SDK (No overlay extensions. No XUL.) Actually, having all these options confuses me.
As I didn't want to mix the Add-on SDK (high level modules) with XUL extension development, I ended up creating a function that is triggered when the "Select from available search engines" button is clicked. This function creates an HTML panel (sdk/panel) that is then populated with the available search engines, placing a checkbox behind each one. Some events are also set so that, when a checkbox is clicked, the engine's status (enabled/disabled) is saved using the sdk/simple-storage module.
This is the code for the button in the add-on options (in package.json):
...
"preferences": [
{
"name": "selectEnginesButton",
"title": "Select from available search engines",
"type": "control",
"description": "Enable/disable the search engines that show on the popup panel.",
"label": "Select"
},
...
Then, in main.js:
var simplePrefs = require('sdk/simple-prefs');
var simpleStorage = require("sdk/simple-storage");
// ...some code for other things here...
if (!simpleStorage.storage.searchEngines) {
simpleStorage.storage.searchEngines = {};
}
simplePrefs.on("selectEnginesButton", function() {
createSettingsPanel();
});
function createSettingsPanel()
{
var visibleEngines = searchService.getVisibleEngines({});
var engines = visibleEngines.filter(function(engine) { return !engine.hidden; });
// Create simple objects with engine information to pass to the panel's content script
// This is because only main.js can work with the search engine objects themselves
// i.e. the ones in "engines"
var engineObjects = engines.map(function(engine) {
var engineObject = {};
engineObject.name = engine.name;
engineObject.iconSpec = (engine.iconURI != null ? engine.iconURI.spec : null);
engineObject.active = isEngineActive(engine);
return engineObject;
});
var pan = panel.Panel({
contentURL: data.url("engine-settings.html"),
contentScriptFile: data.url("engine-settings.js"),
width: 400,
height: 225
});
pan.port.emit('setupPanel', engineObjects);
pan.port.on("onSearchEngineToggle", onSearchEngineToggle);
pan.show();
}
function onSearchEngineToggle(engine)
{
simpleStorage.storage.searchEngines[engine.name] = engine.active;
}
function isEngineActive(engine)
{
return simpleStorage.storage.searchEngines[engine.name];
}
Finally, the panel's HTML (engine-settings.html):
<html>
<head>
<!-- some css and other stuff -->
</head>
<body>
<div id="title">Select what search engines are shown on the popup panel:</div>
<div id="engines"></div>
</body>
</html>
...and the panel's JavaScript (engine-settings.js):
self.port.on('setupPanel', setupPanel);
self.port.on('logInnerHTML', logInnerHTML);
function setupPanel(engines)
{
engines.forEach(addEngineToLayout);
}
function addEngineToLayout(engine)
{
// WARNING: this might not be the best way to populate HTML from js. I'm certainly no expert here :)
var element = document.createElement("div");
var description = document.createTextNode(engine.name);
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.value = engine.name;
checkbox.checked = engine.active;
element.appendChild(checkbox);
var icon = document.createElement("img");
// default.png is a png of mine, in case an engine has no icon
icon.setAttribute("src", (engine.iconSpec != null ? engine.iconSpec : "default.png"));
element.appendChild(document.createElement("span"));
element.appendChild(icon);
element.appendChild(document.createElement("span"));
element.appendChild(description);
document.getElementById('engines').appendChild(element);
checkbox.addEventListener("mouseup", function(e) {
engine.active = !engine.active;
self.port.emit('onSearchEngineToggle', engine);
});
}
Of course, this is not a perfect solution, but you get a simple panel that shows on top of the other settings when you click the button in the add-on preferences. It disappears when you press outside of it.
Note that this code has engines disabled by default, as they will not be present in simpleStorage.storage.searchEngines
. You can change this by doing this in isEngineActive
:
function isEngineActive(engine)
{
if (!simpleStorage.storage.searchEngines.hasOwnProperty(engine.name)) {
simpleStorage.storage.searchEngines[engine.name] = true;
}
return simpleStorage.storage.searchEngines[engine.name];
}
When engines are removed from the browser, they'll still be forever present in simpleStorage.storage.searchEngines
. You can easily fix this by going through everything in there when your main.js
runs and removing engines that don't exist anymore.
I hope this helps someone! :)