I am trying to write a Chrome extension which can be disabled with a simple click on the browser action icon. I want to give the users this option because the extension raises a javascript alert when the alarm is triggered. The problem is that even after clearing the alarms, I am seeing alerts being raised. Also the icon click, which I want to work as a switch, isn't working as intended.
I have declared background.js as my background javascript file in manifest.json
"background": {
"scripts": ["alert.js"],
"persistent": true
},
"browser_action": {
"default_icon": "images/green.png",
"default_action": "popup.html",
"default_title": "Toggle Productiwitty"
}
background.js
var ExtensionOn = true;
function SwitchOn(e)
{
chrome.alarms.create("Alarm", {delayInMinutes: 0.1, periodInMinutes: 1} );
}
function SwitchOff(e)
{
chrome.alarms.clear("Alarm");
}
function showpopup()
{
alert("Inside function showpopup");
console.log("alert shown");
}
function click(e)
{
if(ExtensionOn)
{
SwitchOff();
console.log("switched off");
chrome.browserAction.setBadgeText({text: "Off"});
}
else if(!ExtensionOn)
{
SwitchOn();
console.log("switched on");
chrome.browserAction.setBadgeText({text: "ON"});
// Replace 15.0 with user selected time in minutes
}
//Toggle ExtensionOn
ExtensionOn = ~ ExtensionOn;
}
if(ExtensionOn)
{
chrome.alarms.onAlarm.addListener(showpopup);
}
chrome.browserAction.onClicked.addListener(click);
My "default_action": "popup.html" calls popup.js which creates the alarm
chrome.alarms.create("Alarm", {delayInMinutes: 0.1, periodInMinutes: 1} );
The idea is that once the extension is loaded, it should show a popup every 1 minute and if you click the icon, the extension gets disabled temporarily. On clicking the icon again, the same alert will be raised periodically.
ExtensionOn = true
> true
ExtensionOn = ~ ExtensionOn
> -2
Boolean(ExtensionOn)
> true
If you want to toggle a boolean, use !, not ~.
if(ExtensionOn)
{
chrome.alarms.onAlarm.addListener(showpopup);
}
This is only called once, when a page first loads. If the extension isn’t on at that time, the listener won’t be added, and the function will never be called. I’d recommend moving the if test into showpopup.
From https://developer.chrome.com/extensions/management#method-setEnabled
chrome.management.setEnabled(ExtId, true/false, callback)
But i'm not sure disabling the extension is what you actually want to do,
once you disable it you have to go to chrome://extension and manually re-enable it.
Related
Hi i wanted to create extension that will trigger alert every 30mins to remind me check my posture. But i got stuck. I dont know how to make it so that alert triggers only in tab that im currently in. Now it triggers in every tab i have opened. Can someone help me please? Thanks.
As im thinking right now this way it will start new cycle every time i open new tab right? So im gonna see it in 30min only if i stay in that current tab.
setInterval(function() {
alert("Posture!");
}, 5000);
{
"name": "Posture Checker",
"version": "1.0",
"manifest_version": 2,
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["posturecheck.js"]
}
]
}
You can check if a tab is in focus by checking if document.hidden.
if (document.hidden) {
// Document/tab/window is hidden
} else {
// Document/tab/window is visible
}
Alternatively, you can also check document.visibiliyState, but it does not return a boolean but a string value that you need to check against:
if (document.visibilityState === 'hidden') {
// Document/tab/window is hidden
} else if (document.visibilityState === 'visible') {
// Document/tab/window is visible
}
I'm writing a simple chrome extension which on click inserts some text in Swedish in the DOM. Works fine. So whenever a user clicks the extension, the text in inserted.
Now I want to extend this a little bit. What I want to achieve:
1) When the user clicks the extension the first time, a text in Swedish is inserted
2) When the user clicks a second time, a text in English is inserted instead
So basically I want to "toggle" the language inserted between Swedish and English.
My manifest.json:
{
"name": "TextInserter",
"version": "1",
"manifest_version" : 2,
"description": "Inserts text",
"background" : {
"scripts" : ["background.js"],
"persistent": false
},
"permissions": ["activeTab"],
"browser_action": {}
}
The code in background.js:
chrome.tabs.executeScript(tab.id, {
code: 'var toggle = true;' // How can I toggle this value?
}, function() {
chrome.tabs.executeScript(tab.id, {file: 'content.js'});
});
The code in content.js:
var textarea = document.getElementById('description');
if (textarea && toggle) { // Is always true
textarea.value="Hej";
} else if (textarea && !toggle) {
textarea.value="Hello";
}
toggle = !toggle; // Change the value of toggle so next time text is inserted in other language
This of course doesn't work since I'm setting
var toggle = true;
in background.js.
How can I achieve this toggle effect?
It seems Chrome doesn't have an API to open the popup, but has a dedicated system for doing it with a hotkey: _execute_browser_action key in commands.
The special functionality of _execute_browser_action in commands is not supported(1) by Firefox.
The type of popup I care about is browserAction, not pageAction.
How can I have the browserAction popup open when a keyboard shortcut/hotkey combination is pressed?
Available natively in Firefox versions >= 52
This functionality will be natively available in Firefox 52, which is currently Firefox Developer Edition (i.e. Firefox 52.0a2).
As you know, for WebExtensions, you create a global hotkey using the _execute_browser_action key within the object supplied for the commands key. For example:
"commands":{
"_execute_browser_action": {
"suggested_key": {
"default": "Alt+Shift+J"
}
}
}
Open a pseudo-popup (polyfill this functionality in the older versions of Firefox)
While the explicit functionality will not be available until Firefox 52, you can polyfill this functionality in the current version of Firefox, by defining a custom command that is named "_execute_browser_action". It's going to look a bit different than your normal popup, but it will be functional. It will be in a panel, which you may have to account for with some associated styling which is applied only when it is in a panel instead of a popup. There may also be some differences in what the active tab is when your panel is open. However, the code below at least accounts for that when performing queries with chrome.tabs.query(), or browser.tabs.query(), by making the response be what would be expected if it was open in a real popup instead of a panel.
The same code will continue to work on Firefox 52+. On Firefox 52+, the "_execute_browser_action" directly activates the browser action click, or popup.
For when you aren't using a popup, the primary thing is that you do not use an anonymous function for the browserAction.onClicked listener. This allows the functionality to also be called by the commands.onCommand listener. The commands.onCommand was introduced in Firefox 48, so this should work on any version which is 48+.
You may have some issues with needing permissions other than activeTab when using this polyfill. Exactly what is needed, if anything, will depend on your code.
The following is an extension which causes the functionality invoked with a browser action button to be executed when you hit the keyboard shortcut Alt-Shift-J. It will either activate the doActionButton() function, or, if a popup is defined, it will open your popup as a panel which will behave similarly to how a popup normally behaves, but it is not perfect. It gets the name of the popup file from the one that is currently defined for the current active tab, as would be the case for clicking the browserAction button.
manifest.json:
{
"description": "Polyfill browserAction keyboard shortcut, including popups.",
"manifest_version": 2,
"name": "Polyfill browserAction keyboard shortcut",
"version": "0.1",
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_icon": {
"32": "myIcon.png"
},
"default_title": "Open popup",
"default_popup": "popup.html"
},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Alt+Shift+J"
}
}
}
}
background.js:
chrome.browserAction.onClicked.addListener(doActionButton);
function doActionButton(tab){
console.log('Action Button clicked. Tab:',tab);
}
chrome.commands.onCommand.addListener(function(command) {
//Polyfill the Browser Action button
if(command === '_execute_browser_action') {
chrome.tabs.query({active:true,currentWindow:true},function(tabs){
//Get the popup for the current tab
chrome.browserAction.getPopup({tabId:tabs[0].id},function(popupFile){
if(popupFile){
openPopup(tabs[0],popupFile);
} else {
//There is no popup defined, so we do what is supposed to be done for
// the browserAction button.
doActionButton(tabs[0]);
}
});
});
return;
} //else
});
//popupWindowId can be true, false, or the popup's window Id.
var popupWindowId = false;
var lastFocusedWin;
var lastActiveTab;
function openPopup(tab,popupFile){
chrome.windows.getLastFocused(function(win){
lastFocusedWin=win;
if(popupWindowId === false){
//This prevents user from pressing the button quickly multiple times in a row.
popupWindowId = true;
lastActiveTab = tab;
chrome.windows.create({
url: popupFile,
type: 'popup',
},function(win){
popupWindowId = win.id;
//Poll for the view of the window ID. Poll every 50ms for a
// maximum of 20 times (1 second). Then do a second set of polling to
// accommodate slower machines.
// Testing on a single moderately fast machine indicated the view
// was available after, at most, the second 50ms delay.
waitForWindowId(popupWindowId,50,20,actOnPopupViewFound,do2ndWaitForWinId);
});
return;
}else if(typeof popupWindowId === 'number'){
//The window is open, and the user pressed the hotkey combo.
// Close the window (as happens for a browserAction popup).
closePopup();
}
});
}
function closePopup(){
if(typeof popupWindowId === 'number'){
chrome.windows.remove(popupWindowId,function(){
popupWindowId = false;
});
}
}
chrome.windows.onRemoved.addListener(function(winId){
if(popupWindowId === winId){
popupWindowId = false;
}
});
chrome.windows.onFocusChanged.addListener(function(winId){
//If the focus is no longer the popup, then close the popup.
if(typeof popupWindowId === 'number'){
if(popupWindowId !== winId){
closePopup();
}
} else if(popupWindowId){
}
});
function actOnPopupViewFound(view){
//Make tabs.query act as if the panel is a popup.
if(typeof view.chrome === 'object'){
view.chrome.tabs.query = fakeTabsQuery;
}
if(typeof view.browser === 'object'){
view.browser.tabs.query = fakeTabsQuery;
}
view.document.addEventListener('DOMContentLoaded',function(ev){
let boundRec = view.document.body.getBoundingClientRect();
updatePopupWindow({
width:boundRec.width + 20,
height:boundRec.height + 40
});
});
updatePopupWindow({});
}
function updatePopupWindow(opt){
let width,height;
if(opt){
width =typeof opt.width === 'number'?opt.width :400;
height=typeof opt.height === 'number'?opt.height:300;
}
//By the time we get here it is too late to find the window for which we
// are trying to open the popup.
let left = lastFocusedWin.left + lastFocusedWin.width - (width +40);
let top = lastFocusedWin.top + 85; //Just a value that works in the default case.
let updateInfo = {
width:width,
height:height,
top:top,
left:left
};
chrome.windows.update(popupWindowId,updateInfo);
}
function waitForWindowId(id,delay,maxTries,foundCallback,notFoundCallback) {
if(maxTries--<=0){
if(typeof notFoundCallback === 'function'){
notFoundCallback(id,foundCallback);
}
return;
}
let views = chrome.extension.getViews({windowId:id});
if(views.length > 0){
if(typeof foundCallback === 'function'){
foundCallback(views[0]);
}
} else {
setTimeout(waitForWindowId,delay,id,delay,maxTries,foundCallback,notFoundCallback);
}
}
function do2ndWaitForWinId(winId,foundCallback){
//Poll for the view of the window ID. Poll every 500ms for a
// maximum of 40 times (20 seconds).
waitForWindowId(winId,500,40,foundCallback,windowViewNotFound);
}
function windowViewNotFound(winId,foundCallback){
//Did not find the view for the window. Do what you want here.
// Currently fail quietly.
}
function fakeTabsQuery(options,callback){
//This fakes the response of chrome.tabs.query and browser.tabs.query, which in
// a browser action popup returns the tab that is active in the window which
// was the current window when the popup was opened. We need to emulate this
// in the popup as panel.
//The popup is also stripped from responses if the response contains multiple
// tabs.
let origCallback = callback;
function stripPopupWinFromResponse(tabs){
return tabs.filter(tab=>{
return tab.windowId !== popupWindowId;
});
}
function stripPopupWinFromResponseIfMultiple(tabs){
if(tabs.length>1){
return stripPopupWinFromResponse(tabs);
}else{
return tabs;
}
}
function callbackWithStrippedTabs(tabs){
origCallback(stripPopupWinFromResponseIfMultiple(tabs));
}
if(options.currentWindow || options.lastFocusedWindow){
//Make the query use the window which was active prior to the panel being
// opened.
delete options.currentWindow;
delete options.lastFocusedWindow;
options.windowId = lastActiveTab.windowId;
}
if(typeof callback === 'function') {
callback = callbackWithStrippedTabs;
chrome.tabs.query.apply(this,arguments);
return;
}else{
return browser.tabs.query.apply(this,arguments)
.then(stripPopupWinFromResponseIfMultiple);
}
}
WebExtensions is still in development:
The WebExtensions API is very much still in development. What is working improves with each version of Firefox. For now, you are probably best off developing and testing your WebExtension add-on with Firefox Developer Edition, or Firefox Nightly (for _execute_browser_action). You should also make careful note of what version of Firefox is required for the functionality you desire to use. This information is contained in the "Browser compatibility" section of the MDN documentation pages.
Some portions of the code in this question have been copied/modified from various other answers of mine.
Support for _exectue_browser_action is on its way: https://bugzilla.mozilla.org/show_bug.cgi?id=1246034
Meanwhile I'm quite sure it's not possible.
_exectue_browser_action, _execute_page_action, _execute_sidebar_action implemented: Special shortcuts.
Background
I have a Chrome extension with a browser action to launch index.html in a new tab.
I'd like to update the extension to open index.html in a popup first, and then include a button users can click to optionally open the app in a new tab.
I don't want this button to show when it's not a popup (since it wouldn't make sense), which means the content script needs to know whether it is a popup in order to show the button.
Questions
This is a two part question:
How does a Chrome extension popup know it's a popup?
How do I pass that information to a content script before the popup is rendered?
What I've tried
I've tried to use chrome.extension.getViews in background.js to firstly determine if a popup is open. Then, I send a message to the content script which then shows the button. However I haven't gotten it to work - views is always an empty array, and the message doesn't seem to ever be received by the content script.
Here are the relevant parts of my manifest.json file:
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": {
"19": "img/icon19.png",
"38": "img/icon38.png"
},
"default_title": "Super Simple Tasks",
"default_popup": "index.html"
}
And here's what I've been trying in my background.js:
// Get all popups
var views = chrome.extension.getViews({ type: "popup" });
// Send a message if there is a popup
if (views.length > 0){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id, {action: "popup_open"}, function(response) {});
});
};
And then in my content script, I listen for the message and then add a class to the body:
// Listen for the message
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action === 'popup_open') {
// My code here to show the button
}
});
After talking with a friend I discovered an elegant solution that doesn't involve messaging or even a background.js script at all.
I can specify ?popup=true in manifest.json and check for that parameter in my extension's content script. Here's the code:
manifest.json now looks like this:
"browser_action": {
"default_icon": {
"19": "img/icon19.png",
"38": "img/icon38.png"
},
"default_title": "Super Simple Tasks",
"default_popup": "index.html?popup=true"
}
The following code in my content script (taken from this answer) checks for ?popup=true. Worth noting that this function can handle multiple URL parameters split by the & character.
function getUrlParameter(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
}
var isPopup;
isPopup = getUrlParameter('popup') === 'true';
Finally, add a class to the body if it's a popup:
$('body').toggleClass('popup', isPopup)
In the manifest file add a hash to the url:
"browser_action": {
"default_popup": "index.html#popup"
}
In JavaScript:
if (location.hash === '#popup')
// do something awesome!
I needed something similar as i wanted to create some cross-compatible code for all script types.
I found that this worked quite well.
const SCRIPT_TYPE = (() => {
if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() === window) {
return 'BACKGROUND';
} else if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() !== window) {
return 'POPUP';
} else if (!chrome || !chrome.runtime || !chrome.runtime.onMessage) {
return 'WEB';
} else {
return 'CONTENT';
}
})();
chrome.tabs.getCurrent(function(tab) {
if(tab == undefined)
document.getElementById('mButton').style.display = 'inline-block';
});
I initially set the button's display: none; if the returned tab is undefined, means it's not a tab (so it is popup) and then I display button. You can reverse it of course.
======
Well the sending parameter also works, which in that case you won't need to add the query string in the manifest, just adding it in button's click listener would suffice.
btn.addEventListener('click', function() {
chrome.tabs.create({url: "index.html?popup=false"});
});
And then the same process (reading the query string and comparing, etc).
======
Alternatively you can make a copy of index.html say index2.html, remove the button from index.html, use index2.html in the manifest and index.html for button click. :)
My project is a Chrome extension that will do the following.
Push the extension icon.
Popup will appear (from popup.html)
5 buttons will be in the popup.
When you click one of the four buttons, one javascript code will be executed.
close popup window.
So depending on the answer of this post over here
Detect a button click in the browser_action form of a Google Chrome Extension
(big ups to Michael for his enormous help)
This example is only for one button. Created it with only one of my javascript code and works perfect.
But when it comes to put all of the 5 buttons i 've tried to make this kind of coding but it didnt work at all (im new at javascript code so dont hate)
Here are the codes
MANIFEST.JSON
{
"background": {
"scripts": [ "background.js" ]
},
"browser_action": {
"default_icon": "img/icon.png",
"default_title": "TITLE",
"default_popup": "popup.html"
},
"icons": {
"128": "img/icon_128.png",
"19": "img/icon19.png",
"38": "img/icon38.png",
"48": "img/icon_48_2.png"
},
"manifest_version": 2,
"name": " NAME",
"description": " DESCR ",
"permissions": [ "activeTab" ],
"version": "2.0"
}
POPUP.HTML
<html>
<head>
<script src="popup.js"></script>
<style type="text/css" media="screen">
body { min-width:250px; text-align: center; }
#click-me-l { font-size: 20px; }
#click-me-f { font-size: 20px; }
</style>
</head>
<body>
<button id='click-me-l'>Click1</button>
<button id='click-me-f'>Click2</button>
</body>
</html>
POPUP.JS
function clickHandler(e) {
chrome.extension.sendMessage({directive: "popup-click-l"}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('click-me-l').addEventListener('click', clickHandler);
})
function clickHandler(e) {
chrome.extension.sendMessage({directive: "popup-click-f"}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('click-me-f').addEventListener('click', clickHandler);
})
BACKGROUND.JS
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch (request.directive) {
case 1 "popup-click-l":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
file: "script1.js", // script to inject into page and run in sandbox
allFrames: true // This injects script into iframes in the page and doesn't work before 4.0.266.0.
});
case 2 "popup-click-f":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
file: "script2.js", // script to inject into page and run in sandbox
allFrames: true // This injects script into iframes in the page and doesn't work before 4.0.266.0.
});
sendResponse({}); // sending back empty response to sender
break;
default:
// helps debug when request directive doesn't match
alert("Unmatched request of '" + request + "' from script to background.js from " + sender);
}
}
);
So the codes in the link are working PERFECT for only 1 button.
in this example i am trying to make it work for 2 buttons but i cant find what im doing wrong. If anyone has any idea i would appreciate it.
Thanks a lot for your time!!!
(UPDATE 2. Updated codes for 2 buttons but not working.)
You’re defining clickHandler twice, so only the second one counts. One fix would be:
function clickHandler(e) {
chrome.extension.sendMessage({"directive": e.target.id}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
In general, you’re repeating yourself too much. You could combine your DOMContentLoaded events into one:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('click-me-l').addEventListener('click', clickHandler);
document.getElementById('click-me-f').addEventListener('click', clickHandler);
})
but even better would be to put all the buttons into an array, so that popup.js is now:
function clickHandler(e) {
chrome.extension.sendMessage({"directive": e.target.id}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
document.addEventListener('DOMContentLoaded', function () {
var buttons = document.getElementsByTagName("button");
for ( var i = 0 ; i < buttons.length ; i++ ) {
buttons[i].addEventListener('click',clickHandler);
}
})
(And I’d recommend button { font-size: 20px; } in your style instead of five separate ids.)
Finally, your switch statement is buggy. Once you start a case, you’ll keep going until you get to a break, so that case "popup-click-l" hits both cases. You could have a separate executeScript for each case, but even better would be to assign to fileName based on the case, and have a single injection at the end. Or best of all would be to have a javascript object define which files go with which ids, so that background.js is now:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
var injected = {
"click-me-l": "script1.js",
"click-me-f": "script2.js"
};
chrome.tabs.executeScript(null, {
"file": injected[request.directive],
"allFrames": true
});
sendResponse({});
}
);
Fundamentally, this comes back to a point I made in a comment: browser extensions are a bad way to learn javascript, because you’re learning two separate things at the same time. Your difficulties with switch, {}, and generally following the code is a javascript problem. Not seeing when the console tells you about syntax errors is more of a browser extension problem. And your biggest problem is that you’re not seeing which error is which.