I am writing a chrome extension that when clicked, will close the current tab after a given amount of time.
I am sending a message with the time, from popup.js to background.js. But the tab won't close.
The alert works when I uncomment it, so it seems to be just the remove line. I assume it's something about tab.id.
chrome.extension.onMessage.addListener(
function message(request, sender, callback) {
var ctr = 0;
ctr = parseInt(request.text, 10);
setTimeout(function() {
chrome.tabs.getCurrent(function(tab) {
//window.alert("Working?");
chrome.tabs.remove(tab.id, function(){});
});
}, ctr);
}
);
1.
chrome.extension has no onMessage event. I assume you mean the correct chrome.runtime.onMessage
2.
You have probably misunderstood(*) the purpose of chrome.tabs.getCurrent:
Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).
Since, you are calling it from a non-tab context (namely the background page), tab will be undefined.
(*): "misunderstood" as in "not bother to read the manual"...
3.
It is not clear if you want to close the active tab at the moment the timer is set or at the moment it is triggered. (In your code, you are attempting to do the latter, although the former would make more sense to me.)
The correct way to do it:
chrome.runtime.onMessage.addListener(function message(msg) {
var ctr = 0;
ctr = parseInt(msg.text, 10);
setTimeout(function() {
chrome.tabs.query({ active: true }, function(tabs) {
chrome.tabs.remove(tabs[0].id);
});
}, ctr);
});
Also, note that using functions like setTimeout and setInteval will only work reliably in persistent background pages (but not in event pages). If possible, you are advised to migrate to event pages (which are more "resource-friendly"), in which case you will also have to switch to the alarms API.
Related
While building my Chrome extension, it's often very useful to open a new browser tab and paste this into it:
chrome-extension://xyzfegpcoexyzlibqrpmoeoodfiocgcn/popup.html
When I do that I'm able to work on my popup UI without it ever closing, and without having to click the extension icon at the top right and have the popup sometimes close on me.
Here's the problem: I need my js (referenced by popup.html) to know whether i'm in this debug tab, or whether it's running in "regular mode" (clicking the extension icon and running it normally). I first tried this:
var isDebugExtensionTab = (location.href.indexOf("chrome-extension:") == 0);
That doesn't work because it always evaluates to true -- that is the location.href in all cases, debug tab or regular mode.
How can I detect the difference?
Use chrome.tabs.getCurrent:
Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).
var isDebugExtensionTab = false;
chrome.tabs.getCurrent(function(tab) { isDebugExtensionTab = !!tab; });
It's asynchronous as all chrome.* API methods that may accept a callback so the result won't be available until the current context exits. If you need to use the value immediately, do it in the callback:
var isDebugExtensionTab = false;
chrome.tabs.getCurrent(function(tab) {
isDebugExtensionTab = !!tab;
runSomeDebugFunction();
});
In a Firefox Add-on SDK add-on, how do I tell whether a window is in the background, ie. visible but not focused?
For example, if I bring a different application to the foreground, the Firefox window becomes unfocused but is still visible.
The reason why I want to do this is because I have a CPU-intensive content script running in the active window, and I'd like to pause it to avoid unnecessary overhead whenever the user isn't actively engaged with the window - meaning it's in the background or minimized.
require("sdk/windows").activeWindow keeps returning the last clicked window even if it's in the background or minimized. There doesn't seem to be any property for the window's focus state.
I can also get use the following code to get an nsIDocShell:
var mostRecentWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
var docShell = require("sdk/window/utils").getWindowDocShell(mostRecentWindow);
Now when I query the docShell.isActive property, it returns true even if the window is in the background.
The one advantage of docShell.isActive is that it returns false when the window is minimized, while activeWindow returns true even in this case. But it's still missing information about whether the window is in the background or not.
Based on the suggestion by #willlma, this code seems to do the trick:
const windows = require('sdk/windows').browserWindows;
const tabs = require("sdk/tabs");
var anyWindowActive = true;
var refreshTimeoutId;
windows.on('deactivate', function(window) {
if (window == windows.activeWindow) {
anyWindowActive = false;
}
clearTimeout(refreshTimeoutId);
refreshTimeoutId = setTimeout(refreshTabStates, 50);
});
windows.on('activate', function(window) {
anyWindowActive = true;
clearTimeout(refreshTimeoutId);
refreshTimeoutId = setTimeout(refreshTabStates, 50);
});
tabs.on('activate', function(tab) {
clearTimeout(refreshTimeoutId);
refreshTimeoutId = setTimeout(refreshTabStates, 50);
});
function refreshTabStates() {
refreshTimeoutId = null;
for (let win of windows) {
for (let tab of win.tabs) {
var shouldBeActive = anyWindowActive
&& tab == tabs.activeTab
&& win == windows.activeWindow;
notifyTab(tab, shouldBeActive);
}
}
}
where notifyTab() is a function that posts a message to that tab's content script (if any) about whether it should be running or not.
setTimeout is used to avoid multiple calls to refreshTabStates in quick succession. For example, if you click on an inactive tab in a window that's not the current one, that one click results in window.deactivate, window.activate and tab.activate events.
Also, the initial state is a problem. What if the user launches Firefox and puts it in the background before any script has managed to run?
I want to add a tab whenever a new Firefox window is loaded for my bootstrap extension. I use this code listing:
var WindowListener = {
setupBrowserUI: function(window) {
window.gBrowser.selectedTab=window.gBrowser.addTab("http://google.com");
},
tearDownBrowserUI: function(window) {
},
// nsIWindowMediatorListener functions
onOpenWindow: function(xulWindow) {
var domWindow = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
// Wait for it to finish loading
domWindow.addEventListener("load", function listener() {
domWindow.removeEventListener("load", listener, false);
// If this is a browser window then setup its UI
if (domWindow.document.documentElement.getAttribute("windowtype")=="navigator:browser") domWindow.gBrowser.selectedTab=domWindow.gBrowser.addTab("http://google.com");
}, false);
},
onCloseWindow: function(xulWindow) {
},
onWindowTitleChange: function(xulWindow, newTitle) {
}
};
let wm = Components.classes["#mozilla.org/appshell/window-mediator;1"].
getService(Components.interfaces.nsIWindowMediator);
// Wait for any new browser windows to open
wm.addListener(WindowListener);
You can try it in Scratchpad.
onOpenWindow method have the code to open tab in new window but it executes before the window is loaded completely so adding tab in this state does not seem to work although MDN code says "Wait for it to finish loading".
Setting a timeout by setTimeout function does the job but it looks ugly.
domWindow.setTimeout(function(){domWindow.gBrowser.selectedTab=domWindow.gBrowser.addTab("http://google.com");},1000);
Is it possible to add tab for new Firefox windows after window completely is loaded without setTimeouts?
I'd go with a setTimeout(..., 0) hack. That ought to be the most reliable option and is used throughout the Firefox code itself :p
if (domWindow.gBrowser) {
setTimeout(function() {
domWindow.gBrowser.selectedTab =
domWindow.gBrowser.addTab("http://google.com");
}, 0);
}
It's really weird. I can't explain it. But from the line:
if (domWindow.document.documentElement.getAttribute("windowtype")=="navigator:browser") domWindow.gBrowser.selectedTab=domWindow.gBrowser.addTab("http://google.com");
remove the domWindow.gBrowser.selectedTab = so change it to:
if (domWindow.document.documentElement.getAttribute("windowtype")=="navigator:browser") {
domWindow.gBrowser.addTab("http://google.com");
}
this succesfully loads the url BUT it doesnt select the tab SO I tried and absolutely new idea why this stuff FAILED:
if (domWindow.document.documentElement.getAttribute("windowtype")=="navigator:browser") {
var tab = domWindow.gBrowser.addTab("http://google.com");
}
as soon as i put that var tab = in front it fails. If it didn't fail i was planning to put on next line: domWindow.gBrowser.selectedTab = tab
THEN this also fails:
loadOneTab has inBackground parameter, if set it to false it will focus the tab:
if (domWindow.document.documentElement.getAttribute("windowtype")=="navigator:browser") {
domWindow.gBrowser.loadOneTab("http://google.com", {inBackground:false});
}
Absolutely no idea but this fails to load url but it focuses the tab. If you set inBackground to true it loads the url and of course it wont focus the tab. Absolutely weird...
Posted so others can maybe find out where the problem is, maybe we need to report something on bugzilla.
I'm rather new to Javascript and Crossrider. I believe what I'm trying to do is a rather simple thing - maybe I missed something here?
I am writing an extension that automatically logs you into Dropbox and at a later time will log you out. I can log the user into Dropbox automatically, but now my client wants me to automatically log those people out of dropbox by FINDING the open Dropbox windows and logging each one of them out.
He says he's seen it and it's possible.
Basically what I want is some code that allows me to get the active tabs, and set the location.href of those tabs. Or even close them. So far this is what I got:
//background.js:
appAPI.ready(function($) {
// Initiate background timer
backgroundTimer();
// Function to run backround task every minute
function backgroundTimer() {
if (appAPI.db.get('logout') == true)
{
// retrieves the array of tabs
appAPI.tabs.getAllTabs(function(allTabInfo)
{
// loop through tabs
for (var i=0; i<allTabInfo.length; i++)
{
//is this dropbox?
if (allTabInfo[i].tabUrl.indexOf('www.dropbox.com')!=-1)
{
appAPI.tabs.setActive(allTabInfo[i].tabId);
//gives me something like chrome-extension://...
window.alert(window.location.href);
//code below doesn't work
//window.location.href = 'https://www.dropbox.com/logout';
}
}
appAPI.db.set('logout',false);
});
window.alert('logged out.');
}
setTimeout(function() {
backgroundTimer();
}, 10 * 1000);
}
});
When I do appAPI.tabs.setActive(allTabInfo[i].tabId); and then window.alert(window.location.href); I get as address "chrome-extension://xxx" - which I believe is the address of my extension, which is totally not what I need, but rather the URL of the active window! More than that, I need to navigate the current window to the log out page... or at least refresh it. Can anybody help, please?
-Rowan R. J.
P.S.
Earlier I tried saving the window reference of the dropbox URL I opened, but I couldn't save the window reference into the appAPI.db, so I changed technique. Help!
In general, your use of the Crossrider APIs looks good.
The issue here is that you are trying to use window.location.href to get the address of the active tab. However, in the background scope, the window object relates to the background page/tab and and not the active tab; hence you receive the URL of the background page. [Note: Scopes can't directly interactive with each others objects]
Since your objective is to change/close the URL of the active dropbox tab, you can achieve this using messaging between scopes. So, in your example you can send a message from the background scope to the extension page scope with the request to logout. For example (and I've taken the liberty to simplify the code):
background.js:
appAPI.ready(function($) {
appAPI.setInterval(function() {
if (appAPI.db.get('logout')) {
appAPI.tabs.getAllTabs(function(allTabInfo) {
for (var i=0; i<allTabInfo.length; i++) {
if (allTabInfo[i].tabUrl.indexOf('www.dropbox.com')!=-1) {
// Send a message to all tabs using tabId as an identifier
appAPI.message.toAllTabs({
action: 'logout',
tabId: allTabInfo[i].tabId
});
}
}
appAPI.db.set('logout',false);
});
}
}, 10 * 1000);
});
extension.js:
appAPI.ready(function($) {
// Listen for messsages
appAPI.message.addListener(function(msg) {
// Logout if the tab ids match
if (msg.action === 'logout' && msg.tabId === appAPI.getTabId()) {
// change URL or close code
}
});
});
Disclaimer: I am a Crossrider employee
I am opening a popup window and attaching an onbeforeunload event to it like this:
win = window.open("http://www.google.com", "", "width=300px,height=300px");
win.onbeforeunload = function() {
//do your stuff here
alert("Closed");
};
If I leave the URL empty, the new popup opens with "about:blank" as the address but when I close it, I see the alert.
If I open in as you see it (with an external URL), once it's closed, I cannot see the alert anymore. Any idea why this is happening?
As mentioned, same origin policy prevents Javascript from detecting such events. But there's a quite simple solution which allows you to detect closure of such windows.
Here's the JS code:
var openDialog = function(uri, name, options, closeCallback) {
var win = window.open(uri, name, options);
var interval = window.setInterval(function() {
try {
if (win == null || win.closed) {
window.clearInterval(interval);
closeCallback(win);
}
}
catch (e) {
}
}, 1000);
return win;
};
What it does: it creates new window with provided parameters and then sets the checker function with 1s interval. The function then checks if the window object is present and has its closed property set to false. If either ot these is not true, this means, that the window is (probably) closed and we should fire the 'closeCallback function' callback.
This function should work with all modern browsers. Some time ago Opera caused errors when checking properties from windows on other domains - thus the try..catch block. But I've tested it now and it seems it works quite ok.
We used this technique to create 'facebook-style' login popups for sites which doesn't support them via SDK (ehem... Twitter... ehem). This required a little bit of extra work - we couldn't get any message from Twitter itself, but the Oauth redireced us back to our domain, and then we were able to put some data in popup window object which were accessible from the opener. Then in the close callback function we parsed those data and presented the actual results.
One drawback of this method is that the callback is invoked AFTER the window has been closed. Well, this is the best I was able to achieve with cross domain policies in place.
You could listen to the 'focus' event of the opener window which fires when the user closes the popup.
Unfortunately, you're trying to communicate across domains which is prohibited by JavaScript's same origin policy. You'd have to use a server-side proxy or some other ugly hack to get around it.
You could try creating a page on your site that loads the external website in an iframe. You could then pop open that page and listen for it to unload.
I combined #ThomasZ's answer with this one to set an interval limit (didn't want to use setTimeout).
Example (in Typescript, declared anonymously so as not lose reference to "this"):
private _callMethodWithInterval = (url: string, callback: function, delay: number, repetitions: number) => {
const newWindow = window.open(url, "WIndowName", null, true);
let x = 0;
let intervalID = window.setInterval(() => {
//stops interval if newWindow closed or doesn't exist
try {
if (newWindow == null || newWindow.closed) {
console.info("window closed - interval cleared")
callback();
window.clearInterval(intervalID);
}
}
catch (e) {
console.error(`newWindow never closed or null - ${e}`)
}
//stops interval after number of intervals
if (++x === repetitions) {
console.info("max intervals reached - interval cleared")
window.clearInterval(intervalID);
}
}, delay)
}//end _callMethodWithInterval