I am building a simple chrome extension that will only work on a specific domain, and I'm now facing the problem of detecting a tab's close in background page. As I have read around, there is no specific way to achieve it, but window.unonload and window.onbeforeunload can help detecting page variatons (though they also get fired if the page is reloaded). However, since the extension is programmed to work in the same tab but it has to reload the page many times, it would be a bad solution for my case. So this is what I thought:
1-at the very beginning of his execution, the content script sends a message to the backpage, which will now be able to detect the sender tab id and store it to a global variable.
2-then, the backpage should programmaticaly check if a tab with that id is currently open, for example in a while loop or something like it.
3-as the control returns false, the backpage will know that the page has been closed and the extension isn't running.
This one seems to be a simple and short way to check if a page is open, but I can't find a way for the backpage to determine if a tab with a certain id is open. I've also read chrome documentation (https://developer.chrome.com/extensions/tabs) but it sounds like there isn't a specific function to do this. So what I'm wondering about is: what might be a work-around to solve this problem? Might message passing be helpful?
P.S. I'm not using jquery.
Thanks for help.
*EDIT:**
I forgot to say that I have already tried this code but it's not changing the running variable
background.js
var running;
while (0!=1){
chrome.runtime.sendMessage(idtab, {quest: "running"}, function(response) {
if (response.ans!="yep"){
running=false;
}
});
}
content_script
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.quest=="running"){
sendResponse({ans:"yep"});
}
});
-the idtab variable in background is defined before and it already contains the id of the tab in which the script is workin
Related
So, I've got a situation where I want a background and content script to be run everytime the browser extension icon is clicked. The ideal behaviour is that extension icon is clicked, the script runs, and the popup will open, displaying the data that was grabbed by the script (but this should happen quickly, the script runs and gets the data very fast). Since chrome.pageAction.onClicked will not work if there is a default_popup defined in manifest.json, I think this leaves me with two options:
Use default_popup and figure out that the extension icon has been clicked some other way. I found this solution in another stack overflow post, but the workaround is to use default_popup: "popup.html", and then the popup.js that is defined in popup.html will send a message saying that the icon has been clicked, then when background.js receives this message, it executes the script. I implemented this idea and it worked... kinda. The trouble is, the popup will always come up before the script is fully executed, so you can't actually display the data grabbed by the script in the popup until the next click. I'm not sure there's any way to get the behaviour I desire using this method, so on to the next:
The other solution I can possible think of is to use onClicked, and then make the popup come up some other way, besides using default_popup in manifest.json. I'm also not sure if this is possible, I have looked on stackoverflow and haven't found anything similar.
Is the second method possible? Can the first method work somehow?
Your option #1 is correct, I think all that is needed is a loading screen when the user first sees the popup, and add some code that updates the popup as soon as it hears from the backend. Might need to see some code to better help there.
Option #2 will not really work, unless you opened the popup in a new tab (or just made it a whole new HTML page). I say this because there is a note here from the Chrome Dev team they will not support opening a popup unless it is from a user gesture -- https://stackoverflow.com/a/10484764/4875295.
If you wanted to go that route it would probably look something like:
Delete from your manifest.json browser_action.default_popup
In your background script add something like:
chrome.browserAction.onClicked.addListener(() => {
const data = dataMaker();
chrome.tabs.create({
url: `${chrome.runtime.getURL("app.html")}?data=${data}`
});
});
Then in your html file have some JS that reads the query string and updates the page accordingly.
Though, that's a different approach than you asked for and I think your original route may still be the best bet (with some added JS around loading).
The problem
I've had this issue for months now, but the concept is pretty straightforward: I want to block some Malicious Site™ from programmatically opening tabs or popup windows.
With the chrome.tabs API, I can listen with onCreated when a new tab is created, and I can easily check who (i.e. which tab) opened that particular tab accessing the openerTabId property of the Tab object passed to the callback function.
Now, I would like to do the exact same thing when a new window is created: I would like to know which tab opened the window (if any, because it could have been opened by the user too), check its URL to see if it is the Malicious Site™, and act accordingly (i.e. block the popup). I tried doing it in the exact same way: request the array of tabs in the new window and check their openerTabId property, but unfortunately such property is not defined! I searched the documentation and Googled for hours, but sadly it looks like there's no simple way to check who opened a window.
A very clumsy solution
Stated the above, the only way I was able to do something even remotely close to what I really want, is the following:
Every time a new window is created, its ID is added to an array called windowWatchlist.
Every time a tab is updated (NB: updated, not created), a script is injected inside it to check its document.referrer, which should contain the URL of the site which opened the tab: if the referrer URL contains the address of the Malicious Site™ I want to block popups from, the window is then closed and removed from the windowWatchlist.
Every time a window is closed, if its ID is in the windowWatchlist, it gets removed from it.
Here's the code (which runs in my background.js script):
// Called on chrome.windows.onCreated
function watchPopupWindow(window) {
windowWatchlist.push(window.id);
console.log('Added window #' + window.id + ' to watchlist.');
}
// Called on chrome.windows.onRemoved
function unwatchPopupWindow(windowID) {
var index = windowWatchlist.indexOf(windowID);
// If the windowID is in the watchlist:
if (index != -1) {
// Remove it:
windowWatchlist.splice(index, 1);
console.log('Removed window #' + windowID + ' from watchlist.');
}
}
// Called on chrome.tabs.onUpdated
function blockPopupWindow(tabID, info, tab) {
// If this tab is in a window which is in the watchlist:
if (windowWatchlist.indexOf(tab.windowId) != -1 && info.url && info.url != 'about:blank') {
// Check the referrer of this tab:
chrome.tabs.executeScript(tabID, {code: 'document.referrer;'}, function(ref) {
// If the referrer is the malicious site to block:
if (ref && ref[0] && ref[0].indexOf("http://MALICIOUS-SITE.XXX") != -1) {
// Close the popup window:
chrome.windows.remove(tab.windowId, function() {
console.log('Blocked popup window #' + tab.windowId + '.');
if (chrome.runtime.lastError)
console.error(chrome.runtime.lastError.message);
});;
}
});
}
}
var windowWatchlist = [];
chrome.windows.onCreated.addListener(watchPopupWindow, {windowTypes: ['popup']});
chrome.windows.onRemoved.addListener(unwatchPopupWindow, {windowTypes: ['popup']});
chrome.tabs.onUpdated.addListener(blockPopupWindow);
Now, you may be wondering: why do you need all this mess only to check a referrer? Couldn't you just check the tabs contained in the window when the window is opened and check their referrer directly inside the callback of chrome.window.onCreated? That's a clever question, and the answer is simple: the problem is that I cannot check the referrer of the tabs right when they are created, because they almost always need some time to load, and the referrer isn't loaded until the page starts loading inside the tab. Therefore, I need to check when a tab is updated, see if its window is in my watchlist, and then check its referrer. This is why chrome.tabs.onUpdated is needed, since it fires its listeners whenever a tab changes state (e.g. tab.status changes from "loading" to "complete").
Why this solution doesn't work
The reason why I call this solution "clumsy" and the reason why it doesn't really work should be already clear to anyone with some experience of JavaScript and web developing: document.referrer isn't reliable at all, and is very often undefined or (in case of multiple redirects) not the right one. This makes my script fail about 90% of the times, because it is unable to determine whether the popup window was opened by the Malicious Site™ or not.
Moreover, the Malicious Site™ often opens popups with URL about:blank or no URL at all, and only when they are loaded, injects data into them, making them basically impossible to detect, even with chrome.tabs.onUpdated which doesn't fire any listener in this situation.
I could decide to block any popup with URL about:blank or undefined, and this is what I'm doing right now indeed, but is a pretty bad compromise, since that I end up closing popups opened by any site which uses this method, and not only the Malicious Site™ I want to block.
In conclusion
My question is simple, but I don't know about its solution: does anyone know any other more reliable method which could be used to detect which tab opened a new window? Nothing comes to my mind, maybe something could be possible using the chrome.webRequest API? I don't really know. For months I've been accepting the fact that a simple solution just wasn't possible, and helplessly waited for an update or something, but I never actually thought about asking here, because the problem looked above the competence of an average Chrome Extension programmer, but hopefully I was wrong.
UPDATE: The solution to inject a script inside the site and replace the window.open function with something else isn't viable: if an <iframe> is loaded without a src attribute, but with an already written DOM inside the srcdoc attribute, Chrome will not execute a content script inside it, even if the call to chrome.tabs.executeScript is made with allFrames: true, and even if the content script is declared inside the extension's manifest.
I came across the same problem and found the webNavigation.onCreatedNavigationTarget event that yields the source tab/frame id when a new window is opened.
Solution found from this post: Is it possible to determine a tab's opener within a Google Chrome extension?
Since you are already doing code injection this is what I would do.
Inject code to override window.open and have it window.postMessage to child window telling them who opened them. Also will need to inject code to listen to the effect of window.addEventListener('message', messageHandler) which will decided if they should window.close().
On second though I think I would just override window.open and not even open the child windows if you don't want to allow a give site to open windows.
I need to spawn a tab using window.open('...', '_blank');
Then, I need that tab to CLOSE ITSELF, when the user click a button (button is in the new tab).
I have control over the codebase and server of both applications.
I tried the following:
in app#1:
window.tab = window.open('http://localhost:5007', '_blank');
in app#2:
function clickedButton() {
window.opener.tab.close();
}
Unfortunately I get security exception:
Error: Blocked a frame with origin "http://localhost:5007" from accessing a cross-origin frame.
How can I get around this error? Is there anyway I can use this library to overcome this? https://github.com/ternarylabs/porthole
I am simply going to quote documentation here, just for anyone who needs a reference click W3C and MDN.
function openWin() {
myWindow = window.open("", "myWindow", "width=200, height=100"); // Opens a new window
}
function closeWin() {
myWindow.close(); // Closes the new window
}
To break it down, the open and close functions use parameters than can be very useful, such as the URL, when desiring to open or close the current window, or in your case the opened window.
A practical example would be this stack overflow question. N
I hope it helps!
EDIT
To answer the OP's edit to the question: If it is a matter of triggering an event on a window which was opened, you can on the new window have an event handler which will trigger window.close() like so:
$('#anElementId').click(function() { window.opener.$('body').trigger('theCloseEvent', anyPassedData); })
However, if you truly do have control over the new tab, because it leads to a URL whose code base you are in control of, then it is only a matter of triggering an event there that you can trigger either once the window loads, or once you click a button... like so:
HTML
<button id="close-window">Close me</button>
Javascript/jQuery:
$(document).ready(function(){
$("#close-window").click(function(){
alert("ok");
window.close();
});
});
EDIT #2
To further extend the OP's edit, I want to include here an issue that can easily be encountered when attempting to trigger the opened window to close itself.
Quoted from How can I close a browser window without receiving the “Do you want to close this window” prompt?:
Scripts are not allowed to close a window that a user opened. This is considered a security risk. Though it isn't in any standard, all browser vendors follow this (Mozilla docs). If this happens in some browsers, it's a security bug that (ideally) gets patched very quickly.
None of the hacks in the answers on this question work any longer, and if someone would come up with another dirty hack, eventually it will stop working as well.
I suggest you don't waste energy fighting this and embrace the method that the browser so helpfully gives you — ask the user before you seemingly crash their page.
In other words, unless your webpage's script has control of the window which was opened, you should/cannot close said window. That is because the script running the window.close is not in control of the opened window.
EDIT #3
So many edits I know! But I am answering this in between my daily routine so bear with me. To answer the porthole.js question, it should be much more possible to do something with it, however you need to realize that you are working with iframes.
There is a significant difference when working with websites vs working with iframes, in which iframes are Widgets and websites (including mini sites) are given a URL. There are lots of considerations as well in terms of security and sandboxing, as can be seen on the portholejs demo here. This difference is also what does not allow you to work with different websites the way you want to initially.
My advise would be to evaluate your options with respect to your implementation: website to website vs website to widget.
Good luck!
Ok, I was able to accomplish this in the way I theorized by using porthole.js.
I believe this is the only cross-browser way to accomplish this without using hacks.
The solution consists of 2 apps (you must add code to both app for this to work).
app #1: http://localhost:4000
app #2: http://localhost:5000
In my case, I needed app#1 to spawn a need tab containing app#2. Then I needed app#2 to be able to CLOSE ITSELF upon clicking a button inside app#2.
If these apps were on the same domain (including same port), this would be relatively easy by saving a reference to the tab in app#1:
window.tab = window.open('...', '_blank');
And then accessing that reference from within app#2 via window.opener.tab.close()
However, for my case the apps needed to be on diff domains and doing this method resulted in a browser security exception. So instead, what I needed to do was host app#2 within an iframe inside app#1 (on some specific route, say /iframe), this way they ARE on the same domain as far as the browser windows are concerned, and now the second tab should be able to close itself using window.opener.tab.close().
However, a problem still remained because I needed the trigger to be a button INSIDE app#2 (aka a button inside the iframe), and since the hosting app and the iframe app are again not on the same domain, it seems like I would be back to square one... or maybe not.
In this case, porthole.js saves the day. You must load porthole.js into both apps (this is why you need access to both codebases). Here's the code:
in app#1 (http://localhost:4000/iframe)
// create a proxy window to send to and receive messages from the iFrame
var windowProxy;
window.onload = function() {
windowProxy = new Porthole.WindowProxy(
'http://localhost:5000', 'embedded-iframe');
windowProxy.addEventListener(function(event) {
//handle click event from iframe and close the tab
if(event == 'event:close-window') {
window.opener && window.opener.tab && window.opener.tab.close();
}
});
}
in app#2: (http://localhost:5000)
var windowProxy;
window.onload = function() {
windowProxy = new Porthole.WindowProxy(
'http://localhost:4000/#/iframe');
$('button').on('click', function() {
windowProxy.post('event:close-window');
});
}
And wa-lah, a self closing tab.
I am building a Firefox add-on using the addon-sdk.
The add-on has one button that displays a panel on click. The panel has a content script running it. Now, I need the panel to look different depending on the user's current tab and occasionally show an external url.
So, the main.js script tracks the current tab of the user and sends messages to the content script using panel.port.emit() and the content script changes the HTML of the panel to match whatever is needed.
However, as I mentioned, some times the panel is just showing an external url, so I need to reset it back to its original url. This is my code:
function panelListMode(currentTab, data){
panel.resize(350,300);
//This is the line causing the trouble
panel.contentURL = self.data.url("panel.html");
console.log("sending message");
panel.port.emit("generateList",data);
}
If I emit the message after changing the contentURL the content script does not seem to receive a thing. I now that specific line is the one causing the problem because if I comment it the content script receives the message.
Something tells me this is because the panel needs to load the DOM again and I need to wait until it is ready before the content script is able to do anything. But... aren't content scripts something apart from just included scripts?
As far as I am able to tell the panel has no "ready" event of sorts that I can listen to for emitting the message.
What am I missing?
Edit: After some experimentation I have been able to find some more. If I copy panel.html to the same directory as panel2.html and change my function so it goes:
function panelListMode(currentTab, data){
panel.resize(350,300);
//This is the line causing the trouble
panel.contentURL = self.data.url("panel2.html");
console.log("sending message");
panel.port.emit("generateList",data);
}
The problem is gone. I have experimented a bit and it seems that when I try to change the contentURL to the html that is already set in the panel the content script stops working all together.
This is some really odd behavior... have I encountered a bug?
As far as I am able to tell the panel has no "ready" event of sorts that I can listen to for emitting the message.
I think it does, at least internally it seems to but I'm not sure whether they're exposed.
But even if it doesn't you can simply fire your own port message from the content script once it has been attached and registered its own listeners. Upon receiving that message in the addon you can then resume using the port on that side.
I'm trying to make a simple bot that based on some conditions redirects me to a new URL on the same tab and clicks a button automatically. I have everything working dandy but when I get redirected to this new tab my code disappears and everything is reset. The redirect is all on the same domain. By the way I'm putting this code into the console in Chrome if that helps anything.
var a = URL
window.location.replace(a) //Redirects to a given URL and clears the console
$("input[type='submit']").click() //Doesn't get executed due to console being cleared
If there is no "clear cut" way to do this is there anyway I can work around this? If you need anymore information I'll be happy to provide it.
Edit: I tried using iframe to solve this but just threw an error which based off some google searches can't be solved.
Refused to display 'www.tacos.com' in a frame because it set 'X-Frame-Options' to 'DENY'.
Thank you in advance.
You'll need to execute this code in separate statements (execute your last line after the second page loads). Also, it may help to right-click in the console and choose "Preserve log upon navigation".
If you take this code out of the console, it won't work. You may be able to load the new page in an <iframe> and run the javascript against the document in the frame.
When you change the url, your page is redirected so your code stops executing. The solution would depend on whether the page is on your domain, as a solution would be an iframe, but if the url is off-domain, you will run into security issues if you try to fire the button from outside of the frame.
The only way you could get that is by:
Using $("input[type='submit']").click() on the page that will be presented.
Use ajax and replace the current page with the url's content. Be aware of using jquery's live()
You could add a querystring to the url that is parsed on a document load event and does your click action.