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 thewindowWatchlist
. - 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 "plete"
).
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 promise, 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 es 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 petence 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.
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 thewindowWatchlist
. - 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 "plete"
).
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 promise, 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 es 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 petence 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.
-
In
windows.onCreated
, you get the list of tabs in the new window; don't they have anopenerTabId
set? – Xan Commented Sep 25, 2017 at 14:06 -
If it's a site, I usually insert a script element from a content script that runs on
document_start
(i.e. before the site scripts) and spoofwindow.open
with a dummy function. – woxxom Commented Sep 25, 2017 at 14:07 -
@Xan forgot to add it to the question, no
openerTabId
is set on the tabs of the new window, sadly. – Marco Bonelli Commented Sep 25, 2017 at 14:12 - @wOxxOm that's a clever solution... I thought about it but I actually didn't try it. Replacing the open method with a closure that checks what's going on could be a valid option. I was trying not to inject anything in the site, but I'll probably have to if I don't find any other solution. – Marco Bonelli Commented Sep 25, 2017 at 14:16
-
UPDATE: Sadly it looks like is not possible to use the approach suggested by @wOxxOm, since that iframes that are loaded without a
src="..."
attribute and with asrcdoc="..."
attribute (containing the DOM) are not affected bychrome.tabs.executeScript
, even with the optionallFrames: true
. Sites that use this kind of frames can do whatever they want inside the DOM of such frames, and an extension is not able to touch the code or make anything before it gets executed, so if some Malicious Site™ puts awindow.open
inside an<iframe srcdoc="...">
there's no way to block it. – Marco Bonelli Commented Sep 25, 2017 at 22:45
2 Answers
Reset to default 3I 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.