How I can make a browser action button that looks and acts like a toggle
The target is get a WebExtension for Firefox that can be activated/deactivated by a user in the toolbar, like an on/off switch.
I'm using a background.js with this code:
browser.browserAction.onClicked.addListener(function (tab) {
switch (button) {
case 'turn-on':
enable();
break;
case 'turn-off':
disable();
break;
}
});
function enable() {
browser.browserAction.setIcon({ path: '/ui/is-on.png', });
browser.browserAction.setPopup({ popup: '/ui/turn-off.js', });
browser.webNavigation.onCommitted.addListener(onTabLoad);
}
function disable() {
browser.browserAction.setIcon({ path: '/ui/is-off.png', });
browser.browserAction.setPopup({ popup: '/ui/turn-on.js', });
browser.webNavigation.onCommitted.removeListener(onTabLoad);
}
function onTabLoad(details) {
browser.tabs.executeScript(details.tabId, {
file: '/gc.js',
allFrames true,
});
}
enable(); // enable or disable by default
Obviously I'm doing something wrong. I'm kind newbie in coding. This is a personal project I'm trying to finish.
Your code
While you added a switch
statement to switch on button
, you never defined button
, nor changed its state. You also did not have a default case, just in case the button
variable was not one of the values for which you were testing in your case
statements.
You should not be using browserAction.setPopup()
to set a popup. Setting a popup will result in the popup being opened instead of your background page receiving a click
event. In addition, the popup needs to be an HTML page, not JavaScript.
See the section below for the Firefox bug which you need to work around in onTabLoad()
.
Listening to webNavigation.onCommitted
is not sufficient to cover all cases of when your script will need to be injected. In other words, webNavigation.onCommitted
does not fire every time a page is loaded. To fully cover every situation where your script will need to be injected is something that you will need to ask in another question.
var nextButtonState;
browser.browserAction.onClicked.addListener(function (tab) {
switch (nextButtonState) {
case 'turn-on':
enable();
break;
case 'turn-off':
default:
disable();
break;
}
});
function enable() {
browser.browserAction.setIcon({ path: '/ui/is-on.png', });
//browser.browserAction.setPopup({ popup: '/ui/turn-off.js', });
browser.webNavigation.onCommitted.addListener(onTabLoad);
nextButtonState = 'turn-off';
}
function disable() {
browser.browserAction.setIcon({ path: '/ui/is-off.png', });
//browser.browserAction.setPopup({ popup: '/ui/turn-on.js', });
browser.webNavigation.onCommitted.removeListener(onTabLoad);
nextButtonState = 'turn-on';
}
function onTabLoad(details) {
//Add a setTimout to avoid a Firefox bug that Firefox is not quite ready to
// have tabs.executeScript() inject a script when the onCommitted event fires.
setTimeout(function(){
chrome.tabs.executeScript(details.tabId, {
file: '/gc.js',
allFrames true,
});
},0);
}
enable(); // enable or disable by default
Workaround for a Firefox webNavigation.onCommitted
bug
There is a change needed to your onTabLoad()
code for using a webNavigation.onCommitted
listener to inject scripts using tabs.executeScript()
in Firefox (this is not needed in Chrome). This is due to a bug in Firefox which causes tabs.executeScript()
to fail if executed immediately from a webNavigation.onCommitted
listener. The workaround I use is to inject the script after a setTimeout(function,0)
delay. This allows Firefox to execute the code needed to set up the environment necessary for executeScript()
to be functional.
function onTabLoad(details) {
//Add a setTimout to avoid a Firefox bug that Firefox is not quite ready to
// have tabs.executeScript() inject a script when the onCommitted event fires.
setTimeout(function(){
chrome.tabs.executeScript(details.tabId, {
file: '/gc.js',
allFrames true,
});
},0);
}
Generalized solution for multi-state buttons (e.g. a toggle button)
The code I use to make a Browser Action button behave like a toggle is below. I have modified the browserButtonStates
Object, which describes both what the buttons do and what they look like, to add and remove your webNavigation.onCommitted
listener, onTabLoad()
. See above for the issues with onTabLoad()
.
The code below is more complex than what you need. I wrote it intending to be able to move it from project to project with only needing to change the contents of the browserButtonStates
object. Then, just by changing that object the icon, text, badge text, badge color, and action that is performed in each state (e.g. on/off) can be changed.
background.js
//The browserButtonStates Object describes the states the button can be in and the
// 'action' function to be called when the button is clicked when in that state.
// In this case, we have two states 'on' and 'off'.
// You could expand this to as many states as you desire.
//icon is a string, or details Object for browserAction.setIcon()
//title must be unique for each state. It is used to track the state.
// It indicates to the user what will happen when the button is clicked.
// In other words, it reflects what the _next_ state is, from the user's
// perspective.
//action is the function to call when the button is clicked in this state.
var browserButtonStates = {
defaultState: 'off',
on: {
icon : '/ui/is-on.png'
//badgeText : 'On',
//badgeColor : 'green',
title : 'Turn Off',
action : function(tab) {
chrome.webNavigation.onCommitted.removeListener(onTabLoad);
},
nextState : 'off'
},
off: {
icon : '/ui/is-off.png'
//badgeText : 'Off',
//badgeColor : 'red',
title : 'Turn On',
action : function(tab) {
chrome.webNavigation.onCommitted.addListener(onTabLoad);
},
nextState : 'on'
}
}
//This moves the Browser Action button between states and executes the action
// when the button is clicked. With two states, this toggles between them.
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.browserAction.getTitle({tabId:tab.id},function(title){
//After checking for errors, the title is used to determine
// if this is going to turn On, or Off.
if(chrome.runtime.lastError){
console.log('browserAction:getTitle: Encountered an error: '
+ chrome.runtime.lastError);
return;
}
//Check to see if the current button title matches a button state
let newState = browserButtonStates.defaultState;
Object.keys(browserButtonStates).some(key=> {
if(key === 'defaultState') {
return false;
}
let state = browserButtonStates[key];
if(title === state.title) {
newState = state.nextState;
setBrowserActionButton(browserButtonStates[newState]);
if(typeof state.action === 'function') {
//Do the action of the matching state
state.action(tab);
}
//Stop looking
return true;
}
});
setBrowserActionButton(browserButtonStates[newState]);
});
});
function setBrowserActionButton(tabId,details){
if(typeof tabId === 'object' && tabId !== null){
//If the tabId parameter is an object, then no tabId was passed.
details = tabId;
tabId = null;
}
let icon = details.icon;
let title = details.title;
let text = details.badgeText;
let color = details.badgeColor;
//Supplying a tabId is optional. If not provided, changes are to all tabs.
let tabIdObject = {};
if(tabId !== null && typeof tabId !== 'undefined'){
tabIdObject.tabId = tabId;
}
if(typeof icon === 'string'){
//Assume a string is the path to a file
// If not a string, then it needs to be a full Object as is to be passed to
// setIcon().
icon = {path:icon};
}
if(icon) {
Object.assign(icon,tabIdObject);
chrome.browserAction.setIcon(icon);
}
if(title) {
let detailsObject = {title};
Object.assign(detailsObject,tabIdObject);
chrome.browserAction.setTitle(detailsObject);
}
if(text) {
let detailsObject = {text};
Object.assign(detailsObject,tabIdObject);
chrome.browserAction.setBadgeText(detailsObject);
}
if(color) {
let detailsObject = {color};
Object.assign(detailsObject,tabIdObject);
chrome.browserAction.setBadgeBackgroundColor(detailsObject);
}
}
//Set the starting button state to the default state
setBrowserActionButton(browserButtonStates[browserButtonStates.defaultState]);
manifest.json:
{
"description": "Demo Button toggle",
"manifest_version": 2,
"name": "Demo Button toggle",
"version": "0.1",
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_icon": {
"32": "myIcon.png"
},
"default_title": "Turn On",
"browser_style": true
}
}