I create a new window in my Chrome extension like this:
chrome.windows.create({
url: 'https://example.com',
focused: false,
state: "minimized"
}, function(hiddenWindow) {
var code = "console.log('Some JS code goes here');"
chrome.tabs.onUpdated.addListener(function(tabId, info) {
if (info.status === 'complete') {
chrome.tabs.executeScript(hiddenWindow.tabs[0].id, {
code: code
},
function(results) {
console.log(results);
});
}
});
});
It is possible somehow to do this one:
First time we create a window with one tab inside (like in my code above)
Then each time we check if this window is not closed by user
If the window still exists, then open a new tab in this window and close the previous tab.
If this window no longer exists, then do it all over again starting from #1
Will be grateful for any help and ideas!
Store the id of the window in a variable (global, for example) and use chrome.windows.get - when the window is closed the API will return an error in chrome.runtime.lastError. Also, instead of closing the previous tab it seems simpler to navigate the existing tab to a new URL.
Now, the above scheme would require us to use an elaborate cascade of callbacks at worst or Promises at best, but since it's 2019 let's use the modern async/await syntax instead with the help of Mozilla WebExtension polyfill.
let wndId;
const wndOptions = {
focused: false,
state: 'minimized',
};
const code = `(${() => {
console.log('Some JS code goes here');
}})()`;
async function openMinimized(url) {
const w =
wndId &&
await browser.windows.get(wndId, {populate: true}).catch(() => {}) ||
await browser.windows.create({url, ...wndOptions});
wndId = w.id;
const [wTab] = w.tabs;
if (wTab.url !== url) browser.tabs.update(wTab.id, {url});
return new Promise(resolve => {
browser.tabs.onUpdated.addListener(async function onUpdated(tabId, info) {
if (tabId === wTab.id && info.status === 'complete') {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve(browser.tabs.executeScript(tabId, {code}));
}
});
});
}
Usage:
openMinimized('https://example.com').then(results => {
console.log(results);
});
Notes:
onUpdated listener checks tabId to process only this tab
onUpdated listener unregisters itself
code can be written as normal JS with syntax highlight inside an IIFE in a template string
How to use the polyfill: download browser-polyfill.min.js from its official repo on unpkg, save in your extension directory, and load it just like any other script in your extension, for example, as a background script in manifest.json:
"background": {
"scripts": ["browser-polyfill.min.js", "background.js"]
}
I have service worker which handles push notification click event:
self.addEventListener('notificationclick', function (e) {
e.notification.close();
e.waitUntil(
clients.openWindow(e.notification.data.url)
);
});
When notification comes it takes url from data and displays it in new window.
The code works, however, I want different behavior. When User clicks on the link, then it should check if there is any opened window within service worker scope. If yes, then it should focus on the window and navigate to the given url.
I have checked this answer but it is not exactly what I want.
Any idea how it can be done?
P.S. I wrote this code but it still doesn't work. The first two messages are however shown in the log.
self.addEventListener('notificationclick', function (e) {
e.notification.close();
var redirectUrl = e.notification.data.redirect_url.toString();
var scopeUrl = e.notification.data.scope_url.toString();
console.log(redirectUrl);
console.log(scopeUrl);
e.waitUntil(
clients.matchAll({type: 'window'}).then(function(clients) {
for (i = 0; i < clients.length; i++) {
console.log(clients[i].url);
if (clients[i].url.toString().indexOf(scopeUrl) !== -1) {
// Scope url is the part of main url
clients[i].navigate(givenUrl);
clients[i].focus();
break;
}
}
})
);
});
Ok, here is the piece of code which works as expected. Notice that I am passing scope_url together with redirect_url into the web notification. After that I am checking if scope_url is part of sw location. Only after that I navigate to redirect_url.
self.addEventListener('notificationclick', function (e) {
e.notification.close();
var redirectUrl = e.notification.data.redirect_url;
var scopeUrl = e.notification.data.scope_url;
e.waitUntil(
clients.matchAll({includeUncontrolled: true, type: 'window'}).then(function(clients) {
for (i = 0; i < clients.length; i++) {
if (clients[i].url.indexOf(scopeUrl) !== -1) {
// Scope url is the part of main url
clients[i].navigate(redirectUrl);
clients[i].focus();
break;
}
}
})
);
});
If I understand you correctly, most of the code you linked to works here.
First retrieve all the clients
If there are more than one, choose one of them
Navigate that to somewhere and focus
Else open a new window
Right?
event.waitUntil(
clients.matchAll({type: 'window'})
.then(clients => {
// clients is an array with all the clients
if (clients.length > 0) {
// if you have multiple clients, decide
// choose one of the clients here
const someClient = clients[..someindex..]
return someClient.navigate(navigationUrl)
.then(client => client.focus());
} else {
// if you don't have any clients
return clients.openWindow(navigationUrl);
}
})
);
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
}
}
I am trying to display a 'mask' on my client while a file is dynamically generated server side. Seems like the recommend work around for this (since its not ajax) is to use an iframe and listen from the onload or done event to determine when the file has actually shipped to the client from the server.
here is my angular code:
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
e.load(function() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
});
angular.element('body').append(e);
This works great in Firefox but no luck in Chrome. I have also tried to use the onload function:
e.onload = function() { //unmask here }
But I did not have any luck there either.
Ideas?
Unfortunately it is not possible to use an iframe's onload event in Chrome if the content is an attachment. This answer may provide you with an idea of how you can work around it.
I hate this, but I couldn't find any other way than checking whether it is still loading or not except by checking at intervals.
var timer = setInterval(function () {
iframe = document.getElementById('iframedownload');
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// Check if loading is complete
if (iframeDoc.readyState == 'complete' || iframeDoc.readyState == 'interactive') {
loadingOff();
clearInterval(timer);
return;
}
}, 4000);
You can do it in another way:
In the main document:
function iframeLoaded() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
}
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element('body').append(e);
In the iframe document (this is, inside the html of the page referenced by url)
window.onload = function() {
parent.iframeLoaded();
}
This will work if the main page, and the page inside the iframe are in the same domain.
Actually, you can access the parent through:
window.parent
parent
//and, if the parent is the top-level document, and not inside another frame
top
window.top
It's safer to use window.parent since the variables parent and top could be overwritten (usually not intended).
you have to consider 2 points:
1- first of all, if your url has different domain name, it is not possible to do this except when you have access to the other domain to add the Access-Control-Allow-Origin: * header, to fix this go to this link.
2- but if it has the same domain or you have added Access-Control-Allow-Origin: * to the headers of your domain, you can do what you want like this:
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element(document.body).append(e);
e[0].contentWindow.onload = function() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
};
I have done this in all kinds of browsers.
I had problems with the iframe taking too long to load. The iframe registered as loaded while the request wasn't handled. I came up with the following solution:
JS
Function:
function iframeReloaded(iframe, callback) {
let state = iframe.contentDocument.readyState;
let checkLoad = setInterval(() => {
if (state !== iframe.contentDocument.readyState) {
if (iframe.contentDocument.readyState === 'complete') {
clearInterval(checkLoad);
callback();
}
state = iframe.contentDocument.readyState;
}
}, 200)
}
Usage:
iframeReloaded(iframe[0], function () {
console.log('Reloaded');
})
JQuery
Function:
$.fn.iframeReloaded = function (callback) {
if (!this.is('iframe')) {
throw new Error('The element is not an iFrame, please provide the correct element');
}
let iframe = this[0];
let state = iframe.contentDocument.readyState;
let checkLoad = setInterval(() => {
if (state !== iframe.contentDocument.readyState) {
if (iframe.contentDocument.readyState === 'complete') {
clearInterval(checkLoad);
callback();
}
state = iframe.contentDocument.readyState;
}
}, 200)
}
Usage:
iframe.iframeReloaded(function () {
console.log('Reloaded');
})
I've just noticed that Chrome is not always firing the load event for the main page so this could have an effect on iframes too as they are basically treated the same way.
Use Dev Tools or the Performance api to check if the load event is being fired at all.
I just checked http://ee.co.uk/ and if you open the console and enter window.performance.timing you'll find the entries for domComplete, loadEventStart and loadEventEnd are 0 - at least at this current time:)
Looks like there is a problem with Chrome here - I've checked it on 2 PCs using the latest version 31.0.1650.63.
Update: checked ee again and load event fired but not on subsequent reloads so this is intermittent and may possibly be related to loading errors on their site. But the load event should fire whatever.
This problem has occurred on 5 or 6 sites for me now in the last day since I noticed my own site monitoring occasionally failed. Only just pinpointed the cause to this. I need some beauty sleep then I'll investigate further when I'm more awake.
I am aware of javascript techniques to detect whether a popup is blocked in other browsers (as described in the answer to this question). Here's the basic test:
var newWin = window.open(url);
if(!newWin || newWin.closed || typeof newWin.closed=='undefined')
{
//POPUP BLOCKED
}
But this does not work in Chrome. The "POPUP BLOCKED" section is never reached when the popup is blocked.
Of course, the test is working to an extent since Chrome doesn't actually block the popup, but opens it in a tiny minimized window at the lower right corner which lists "blocked" popups.
What I would like to do is be able to tell if the popup was blocked by Chrome's popup blocker. I try to avoid browser sniffing in favor of feature detection. Is there a way to do this without browser sniffing?
Edit: I have now tried making use of newWin.outerHeight, newWin.left, and other similar properties to accomplish this. Google Chrome returns all position and height values as 0 when the popup is blocked.
Unfortunately, it also returns the same values even if the popup is actually opened for an unknown amount of time. After some magical period (a couple of seconds in my testing), the location and size information is returned as the correct values. In other words, I'm still no closer to figuring this out. Any help would be appreciated.
Well the "magical time" you speak of is probably when the popup's DOM has been loaded. Or else it might be when everything (images, outboard CSS, etc.) has been loaded. You could test this easily by adding a very large graphic to the popup (clear your cache first!). If you were using a Javascript Framework like jQuery (or something similar), you could use the ready() event (or something similar) to wait for the DOM to load before checking the window offset. The danger in this is that Safari detection works in a conflicting way: the popup's DOM will never be ready() in Safari because it'll give you a valid handle for the window you're trying to open -- whether it actually opens or not. (in fact, i believe your popup test code above won't work for safari.)
I think the best thing you can do is wrap your test in a setTimeout() and give the popup 3-5 seconds to complete loading before running the test. It's not perfect, but it should work at least 95% of the time.
Here's the code I use for cross-browser detection, without the Chrome part.
function _hasPopupBlocker(poppedWindow) {
var result = false;
try {
if (typeof poppedWindow == 'undefined') {
// Safari with popup blocker... leaves the popup window handle undefined
result = true;
}
else if (poppedWindow && poppedWindow.closed) {
// This happens if the user opens and closes the client window...
// Confusing because the handle is still available, but it's in a "closed" state.
// We're not saying that the window is not being blocked, we're just saying
// that the window has been closed before the test could be run.
result = false;
}
else if (poppedWindow && poppedWindow.test) {
// This is the actual test. The client window should be fine.
result = false;
}
else {
// Else we'll assume the window is not OK
result = true;
}
} catch (err) {
//if (console) {
// console.warn("Could not access popup window", err);
//}
}
return result;
}
What I do is run this test from the parent and wrap it in a setTimeout(), giving the child window 3-5 seconds to load. In the child window, you need to add a test function:
function test() {}
The popup blocker detector tests to see whether the "test" function exists as a member of the child window.
ADDED JUNE 15 2015:
I think the modern way to handle this would be to use window.postMessage() to have the child notify the parent that the window has been loaded. The approach is similar (child tells parent it's loaded), but the means of communication has improved. I was able to do this cross-domain from the child:
$(window).load(function() {
this.opener.postMessage({'loaded': true}, "*");
this.close();
});
The parent listens for this message using:
$(window).on('message', function(event) {
alert(event.originalEvent.data.loaded)
});
Hope this helps.
Just one improvement to InvisibleBacon's snipet (tested in IE9, Safari 5, Chrome 9 and FF 3.6):
var myPopup = window.open("popupcheck.htm", "", "directories=no,height=150,width=150,menubar=no,resizable=no,scrollbars=no,status=no,titlebar=no,top=0,location=no");
if (!myPopup)
alert("failed for most browsers");
else {
myPopup.onload = function() {
setTimeout(function() {
if (myPopup.screenX === 0) {
alert("failed for chrome");
} else {
// close the test window if popups are allowed.
myPopup.close();
}
}, 0);
};
}
The following is a jQuery solution to popup blocker checking. It has been tested in FF (v11), Safari (v6), Chrome (v23.0.127.95) & IE (v7 & v9). Update the _displayError function to handle the error message as you see fit.
var popupBlockerChecker = {
check: function(popup_window){
var _scope = this;
if (popup_window) {
if(/chrome/.test(navigator.userAgent.toLowerCase())){
setTimeout(function () {
_scope._is_popup_blocked(_scope, popup_window);
},200);
}else{
popup_window.onload = function () {
_scope._is_popup_blocked(_scope, popup_window);
};
}
}else{
_scope._displayError();
}
},
_is_popup_blocked: function(scope, popup_window){
if ((popup_window.innerHeight > 0)==false){ scope._displayError(); }
},
_displayError: function(){
alert("Popup Blocker is enabled! Please add this site to your exception list.");
}
};
Usage:
var popup = window.open("http://www.google.ca", '_blank');
popupBlockerChecker.check(popup);
Hope this helps! :)
Rich's answer isn't going to work anymore for Chrome. Looks like Chrome actually executes any Javascript in the popup window now. I ended up checking for a screenX value of 0 to check for blocked popups. I also think I found a way to guarantee that this property is final before checking. This only works for popups on your domain, but you can add an onload handler like this:
var myPopup = window.open("site-on-my-domain", "screenX=100");
if (!myPopup)
alert("failed for most browsers");
else {
myPopup.onload = function() {
setTimeout(function() {
if (myPopup.screenX === 0)
alert("failed for chrome");
}, 0);
};
}
As many have reported, the "screenX" property sometimes reports non-zero for failed popups, even after onload. I experienced this behavior as well, but if you add the check after a zero ms timeout, the screenX property always seems to output a consistent value.
Let me know if there are ways to make this script more robust. Seems to work for my purposes though.
This worked for me:
cope.PopupTest.params = 'height=1,width=1,left=-100,top=-100,location=no,toolbar=no,menubar=no,scrollbars=no,resizable=no,directories=no,status=no';
cope.PopupTest.testWindow = window.open("popupTest.htm", "popupTest", cope.PopupTest.params);
if( !cope.PopupTest.testWindow
|| cope.PopupTest.testWindow.closed
|| (typeof cope.PopupTest.testWindow.closed=='undefined')
|| cope.PopupTest.testWindow.outerHeight == 0
|| cope.PopupTest.testWindow.outerWidth == 0
) {
// pop-ups ARE blocked
document.location.href = 'popupsBlocked.htm';
}
else {
// pop-ups are NOT blocked
cope.PopupTest.testWindow.close();
}
The outerHeight and outerWidth are for chrome because the 'about:blank' trick from above doesn't work in chrome anymore.
I'm going to just copy/paste the answer provided here: https://stackoverflow.com/a/27725432/892099 by DanielB . works on chrome 40 and it's very clean. no dirty hacks or waiting involves.
function popup(urlToOpen) {
var popup_window=window.open(urlToOpen,"myWindow","toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, copyhistory=yes, width=400, height=400");
try {
popup_window.focus();
}
catch (e) {
alert("Pop-up Blocker is enabled! Please add this site to your exception list.");
}
}
How about a Promise approach ?
const openPopUp = (...args) => new Promise(s => {
const win = window.open(...args)
if (!win || win.closed) return s()
setTimeout(() => (win.innerHeight > 0 && !win.closed) ? s(win) : s(), 200)
})
And you can use it like the classic window.open
const win = await openPopUp('popuptest.htm', 'popuptest')
if (!win) {
// popup closed or blocked, handle alternative case
}
You could change the code so that it fail the promise instead of returning undefined, I just thought that if was an easier control flow than try / catch for this case.
Check the position of the window relative to the parent. Chrome makes the window appear almost off-screen.
I had a similar problem with popups not opening in Chrome. I was frustrated because I wasn't trying to do something sneaky, like an onload popup, just opening a window when the user clicked. I was DOUBLY frustrated because running my function which included the window.open() from the firebug command line worked, while actually clicking on my link didn't! Here was my solution:
Wrong way: running window.open() from an event listener (in my case, dojo.connect to the onclick event method of a DOM node).
dojo.connect(myNode, "onclick", function() {
window.open();
}
Right way: assigning a function to the onclick property of the node that called window.open().
myNode.onclick = function() {
window.open();
}
And, of course, I can still do event listeners for that same onclick event if I need to. With this change, I could open my windows even though Chrome was set to "Do not allow any site to show pop-ups". Joy.
If anyone wise in the ways of Chrome can tell the rest of us why it makes a difference, I'd love to hear it, although I suspect it's just an attempt to shut the door on malicious programmatic popups.
Here's a version that is currently working in Chrome. Just a small alteration away from Rich's solution, though I added in a wrapper that handles the timing too.
function checkPopupBlocked(poppedWindow) {
setTimeout(function(){doCheckPopupBlocked(poppedWindow);}, 5000);
}
function doCheckPopupBlocked(poppedWindow) {
var result = false;
try {
if (typeof poppedWindow == 'undefined') {
// Safari with popup blocker... leaves the popup window handle undefined
result = true;
}
else if (poppedWindow && poppedWindow.closed) {
// This happens if the user opens and closes the client window...
// Confusing because the handle is still available, but it's in a "closed" state.
// We're not saying that the window is not being blocked, we're just saying
// that the window has been closed before the test could be run.
result = false;
}
else if (poppedWindow && poppedWindow.outerWidth == 0) {
// This is usually Chrome's doing. The outerWidth (and most other size/location info)
// will be left at 0, EVEN THOUGH the contents of the popup will exist (including the
// test function we check for next). The outerWidth starts as 0, so a sufficient delay
// after attempting to pop is needed.
result = true;
}
else if (poppedWindow && poppedWindow.test) {
// This is the actual test. The client window should be fine.
result = false;
}
else {
// Else we'll assume the window is not OK
result = true;
}
} catch (err) {
//if (console) {
// console.warn("Could not access popup window", err);
//}
}
if(result)
alert("The popup was blocked. You must allow popups to use this site.");
}
To use it just do this:
var popup=window.open('location',etc...);
checkPopupBlocked(popup);
If the popup get's blocked, the alert message will display after the 5 second grace period (you can adjust that, but 5 seconds should be quite safe).
This fragment incorporates all of the above - For some reason - StackOverflow is excluding the first and last lines of code in the code block below, so I wrote a blog on it. For a full explanation and the rest of the (downloadable) code have a look at
my blog at thecodeabode.blogspot.com
var PopupWarning = {
init : function()
{
if(this.popups_are_disabled() == true)
{
this.redirect_to_instruction_page();
}
},
redirect_to_instruction_page : function()
{
document.location.href = "http://thecodeabode.blogspot.com";
},
popups_are_disabled : function()
{
var popup = window.open("http://localhost/popup_with_chrome_js.html", "popup_tester", "width=1,height=1,left=0,top=0");
if(!popup || popup.closed || typeof popup == 'undefined' || typeof popup.closed=='undefined')
{
return true;
}
window.focus();
popup.blur();
//
// Chrome popup detection requires that the popup validates itself - so we need to give
// the popup time to load, then call js on the popup itself
//
if(navigator && (navigator.userAgent.toLowerCase()).indexOf("chrome") > -1)
{
var on_load_test = function(){PopupWarning.test_chrome_popups(popup);};
var timer = setTimeout(on_load_test, 60);
return;
}
popup.close();
return false;
},
test_chrome_popups : function(popup)
{
if(popup && popup.chrome_popups_permitted && popup.chrome_popups_permitted() == true)
{
popup.close();
return true;
}
//
// If the popup js fails - popups are blocked
//
this.redirect_to_instruction_page();
}
};
PopupWarning.init();
Wow there sure are a lot of solutions here. This is mine, it uses solutions taken from the current accepted answer (which doesn't work in latest Chrome and requires wrapping it in a timeout), as well as a related solution on this thread (which is actually vanilla JS, not jQuery).
Mine uses a callback architecture which will be sent true when the popup is blocked and false otherwise.
window.isPopupBlocked = function(popup_window, cb)
{
var CHROME_CHECK_TIME = 2000; // the only way to detect this in Chrome is to wait a bit and see if the window is present
function _is_popup_blocked(popup)
{
return !popup.innerHeight;
}
if (popup_window) {
if (popup_window.closed) {
// opened OK but was closed before we checked
cb(false);
return;
}
if (/chrome/.test(navigator.userAgent.toLowerCase())) {
// wait a bit before testing the popup in chrome
setTimeout(function() {
cb(_is_popup_blocked(popup_window));
}, CHROME_CHECK_TIME);
} else {
// for other browsers, add an onload event and check after that
popup_window.onload = function() {
cb(_is_popup_blocked(popup_window));
};
}
} else {
cb(true);
}
};
Jason's answer is the only method I can think of too, but relying on position like that is a little bit dodgy!
These days, you don't really need to ask the question “was my unsolicited popup blocked?”, because the answer is invariably “yes” — all the major browsers have the popup blocker turned on by default. Best approach is only ever to window.open() in response to a direct click, which is almost always allowed.
HI
I modified the solutions described above slightly and think that it is working for Chrome at least.
My solution is made to detect if popup is blocked when the main page is opened, not when popup is opened, but i am sure there are some people that can modify it.:-)
The drawback here is that the popup-window is displayed for a couple of seconds (might be possible to shorten a bit) when there is no popup-blocker.
I put this in the section of my 'main' window
<script type="text/JavaScript" language="JavaScript">
var mine = window.open('popuptest.htm','popuptest','width=1px,height=1px,left=0,top=0,scrollbars=no');
if(!mine|| mine.closed || typeof mine.closed=='undefined')
{
popUpsBlocked = true
alert('Popup blocker detected ');
if(mine)
mine.close();
}
else
{
popUpsBlocked = false
var cookieCheckTimer = null;
cookieCheckTimer = setTimeout('testPopup();', 3500);
}
function testPopup()
{
if(mine)
{
if(mine.test())
{
popUpsBlocked = false;
}
else
{
alert('Popup blocker detected ');
popUpsBlocked = true;
}
mine.close();
}
}
</script>
The popuptest looks like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Popup test</title>
<script type="text/javascript" language="Javascript">
function test() {if(window.innerHeight!=0){return true;} else return false;}
</script>
</head>
<body>
</body>
</html>
As i call the test-function on the popup-page after 3500 ms the innerheight has been set correctly by Chrome.
I use the variable popUpsBlocked to know if the popups are displayed or not in other javascripts.
i.e
function ShowConfirmationMessage()
{
if(popUpsBlocked)
{
alert('Popups are blocked, can not display confirmation popup. A mail will be sent with the confirmation.');
}
else
{
displayConfirmationPopup();
}
mailConfirmation();
}
function openPopUpWindow(format)
{
var win = window.open('popupShow.html',
'ReportViewer',
'width=920px,height=720px,left=50px,top=20px,location=no,directories=no,status=no,menubar=no,toolbar=no,resizable=1,maximize:yes,scrollbars=0');
if (win == null || typeof(win) == "undefined" || (win == null && win.outerWidth == 0) || (win != null && win.outerHeight == 0) || win.test == "undefined")
{
alert("The popup was blocked. You must allow popups to use this site.");
}
else if (win)
{
win.onload = function()
{
if (win.screenX === 0) {
alert("The popup was blocked. You must allow popups to use this site.");
win.close();
}
};
}
}
As far as I can tell (from what I've tested) Chrome returns a window object with location of 'about:blank'.
So, the following should work for all browsers:
var newWin = window.open(url);
if(!newWin || newWin.closed || typeof newWin.closed=='undefined' || newWin.location=='about:blank')
{
//POPUP BLOCKED
}