Why doesn't window.open get blocked on a setTimeout <= 1000ms? - javascript

document.querySelector('#ontime').onclick = function() {
setTimeout(() => {
window.open('https://www.google.com');
}, 1000);
};
When using window.open after a user click with a timeout <= 1000ms (or a Promise.resolve().then(...)) it doesn't get blocked by the browser.
If you do the same using a timeout > 1000ms or requestAnimationFrame, the popup gets blocked.
Full example with the 4 cases is available clicking the link below:
https://jsfiddle.net/kouty79/rcwgbfxy/
Does anybody know why? Is there any documentation or w3c spec about this?

From HTML 5.2:
An algorithm is allowed to show a popup if any of the following conditions is true:
…
event listener for a trusted event
…
… queued by an algorithm that was allowed to show a popup, and the chain of such algorithms started within a user-agent defined timeframe.
onclick is a trusted event, but setTimeout put it in a queue (so it wasn't called directly) so the popup has to come within a certain time.
That time is up to the browser to decide.

Related

How can all the individual executions of a content.js on different frames of the same page communicate with each other?

So, I'm building an extension that autofills different types of forms. As it's not apparent from the url which form is used on a particular website, I need to match all the urls in my manifest. I'm trying to detect the form by the 'src'-attribute in the web page.
Some of the fields of a certain form are not in the first frame. So "all_frames" has to be true in my manifest. That means content.js fires once for each frame or iFrame.
**content.js:**
async function checkForPaymentType(value, attribute = 'src') {
return document.querySelectorAll(`[${attribute}*="${value}"]`);
}
let hasThisForm;
document.addEventListener('DOMContentLoaded', () => {
checkForPaymentType('formJs.js').then((value) => {
if(value.length) {
hasThisForm = true;
}
if(hasThisForm)
fillForm();
});
});
The problem now is, that that only the first frame has the "src='formJs.js" attribute in one of its elements. So it only fills out those fields in the first frame.
My solution idea
What I am trying to do is some sort of global boolean variable ('hasThisForm') that can only be set true. So once the first frame detected that there is this form on the website the other frames fire fillForm() as well.
Problems
1.I'm not able to set a variable that can be read from all of the executions.
2.I need the other executions of content.js to wait for the first one.
Another solution would be to have some sort of window.querySelectorAll, so every execution of content.js searches in the whole page and not just in its frame.
Thanks in advance:)
So I figured it out.
As pointed out in the comment from #wOxxOm or in this question (How to get all frames to access one single global variable) you need to manage everything via the background page.
You want to set up a listener in every Frame and send a message only from the top frame (to the background page which sends it back to the whole tab).
After hours of trying I noticed that the listeners weren't even ready when the message from the topFrame was sent. I put in a sleeper before sending the message which is not the ideal way I guess. But there is no "listenerIsSet-Event".
This is my code:
content.js
document.addEventListener('DOMContentLoaded', () => {
chrome.runtime.onMessage.addListener(
function (msgFromTopFrame) {
console.log(msgFromTopFrame)
});
if (window === top) {
Sleep(1000).then(() => {
const msgToOtherFrames = {'greeting': 'hello'};
chrome.runtime.sendMessage(msgToOtherFrames);
});
}
});
background.js
chrome.runtime.onMessage.addListener((msg, sender) => {
if(('greeting' in msg)) {
chrome.tabs.sendMessage(sender.tab.id, msg);
}
});
You probably want to execute some code depending on the value received. You can write it only once in the listener. It will execute in all frames including the top frame as the background.js sends it to all frames in the tab.
Note:
There may be some errors with the dicts/keys in the messages "sent around" in this code. Just log the message in all the listeners to get the right expressions.

Delaying window.open inside click event handler

For some days now I've been trying (without success) to open new window with delay without Chrome blocking it most of the time. Delay is crucial for my task, because some animation must take place before window is opened and I can not afford browsers to block new tabs from opening. Here is sample from my code:
element.on("click", function(e){
e.preventDefault();
var self = $(this),
url = self[0].href;
window.setTimeout(function(){
window.open(url);
}, 1000);
});
element is jQuery object (anchor element to be more precise). I have noticed that I have bigger success rate if I pass string directly like this
window.open("http://example.com");
I've tried this solution in Explorer(no problem), Chrome(problem), Firefox(no problem) and Opera(problem). I am loosing my mind, because I've tried everything I can remember. Synchronous ajax calls, artificially triggering click, creating fake anchor elements and artificially triggering click event. Nothing works fine.
Bonus question (in case someone knows how to solve problem): is there a way to call window.open anytime & anywhere from code?
This is working for me in Chrome http://jsbin.com/wuqopukayuwi/4/
var $element = $('#link');
$element.on('click', function () {
var url= $(this).attr('href');
window.setTimeout(function(){
var windowObjRef = window.open(url);
console.log(windowObjRef);
}, 2000);
});
Things to check:
Obviously, double check you've enabled popups for your domain in Chrome (it prompts you the first time it tries to pop up a new window
It's because Chrome expects to be able to access that window again programatically, so wants you to declare the response to the method as a variable. At least, that's how I got it working...
As long as you know how long the animation runs then you sleep for number of seconds
Add sleep(), msleep() and usleep() to Node.js, via a C++ binding.
This is mainly useful for debugging.
These calls will block execution of all JavaScript by halting Node.js' event loop!
Usage
`var sleep = require('sleep');
sleep.sleep(n): sleep for n seconds
sleep.msleep(n): sleep for n miliseconds
sleep.usleep(n): sleep for n microseconds (1 second is 1000000
microseconds)`

Google Analytics, track page unload event

I'm trying to accomplish tracking an event when a user leaves the page with Google Analytics (analytics.js). Though it is unknown how the user will leave, it may be because of an external link or just closing the tab. So my thought was to hook onto the beforeunload or unload event and then:
window.addEventListener("beforeunload", function() {
ga('send', 'event', 'some', 'other', 'data');
});
Now my question is, will the request to the GA server be synchronous or can I somehow force that behaviour with the hitCallback property? If that is not possible, how else can I achieve this? Preferably without having to set a timeout or fixed waiting time for the user!
There is a way to make sure the request will be sent to GA. Simo Ahava wrote a very good blog post titled - "Leverage useBeacon And beforeunload In Google Analytics".
Utilizing the brilliant sendBeacon solution. Here's quote which addresses the selected answer of this question:
User agents will typically ignore asynchronous XMLHttpRequests made in
an unload handler. To solve this problem, analytics and diagnostics
code will typically make a synchronous XMLHttpRequest in an unload or
beforeunload handler to submit the data. The synchronous
XMLHttpRequest forces the User Agent to delay unloading the document,
and makes the next navigation appear to be slower. There is nothing
the next page can do to avoid this perception of poor page load
performance.
There are other techniques used to ensure that data is submitted. One
such technique is to delay the unload in order to submit data by
creating an Image element and setting its src attribute within the
unload handler. As most user agents will delay the unload to complete
the pending image load, data can be submitted during the unload.
Another technique is to create a no-op loop for several seconds within
the unload handler to delay the unload and submit data to a server.
Not only do these techniques represent poor coding patterns, some of
them are unreliable and also result in the perception of poor page
load performance for the next navigation.
The request will not be synchronous, GA tracking calls never are.
The only way to ensure the call completes is to make sure the page stays open long enough - for an event on a link you would normally do this with a timeout potentially combined with a hitCallback, as you mentioned.
The only way to keep a window open when the user closes a tab is to return a value from your beforeunload handler, which will prompt a "Confirm Navigation" alert. That would be a really bad solution just to track a GA event, obviously.
Set transport to beacon, with ga:
window.addEventListener("beforeunload", function() {
ga('send', 'event', 'page_unload', 'bye bye', {transport: 'beacon'});
});
Or transport_type to beacon, with gtag:
window.addEventListener("beforeunload", function() {
gtag('event', 'page_unload', {
'event_category': 'my cat',
'event_label': 'my label',
'transport_type': 'beacon' // <--- important part here
});
});
For what is worth, beacon should become the default transport mode anyway. As of 2020-09:
Currently, gtag.js only uses navigator.sendBeacon if the transport
mechanism is set to 'beacon'. However, in the future, gtag.js will
likely switch to using 'beacon' as the default mechanism in browsers
that support it.
As pointed out by tomconnors, this does NOT work. I'm leaving the answer to help warn anyone thinking about doing it this way. Beacon transport is probably the way to go, but wasn't widely supported at the time of the original answer.
You can wait for a hit to be sent to Google Analytics in the page onunload, but it does require a busy loop. In my case this did not delay user navigation, as the page was a popup window that is dedicated to a webapp. I'd be more concerned about doing this inline with normal web page navigation. Still, I had to take 2 showers to get the filth off after committing code with a busy loop.
var MAX_WAIT_MS = 1000;
var _waitForFinalHit = false;
function recordFinalHit() {
_waitForFinalHit = true;
ga('send', 'event', 'some', 'other', 'data', {
'hitCallback': function() {
_waitForFinalHit = false;
}
});
}
function waitForFinalHit() {
var waitStart = new Date().getTime();
while (_waitForFinalHit
&& (new Date().getTime() - waitStart < MAX_WAIT_MS)) { }
}
function myOnUnload() {
recordFinalHit();
// Do your other final stuff here...
waitForFinalHit();
}
window.onunload = myOnUnload;

Javascript detect closing popup loaded with another domain

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

Javascript event for mobile browser re-launch or device wake

Morning all, I'm looking for some kind of Javascript event I can use to detect when a mobile browser window regains focus, after either a user closes/minimizes their browser (to go back to a home screen/different app), or if the device resumes from sleep (either the user powering it off, or it going to sleep after a screen timeout).
I'd like to be able to find a single event that works for everything, but I know that's unlikely! The pageshow event works for iOS devices, but it's rather sketchy for use with everything else. I've tried focus and DOMActivate but neither of them seem to have the desired effect.
The page may not always have form elements on it, and I don't really want the user to have to touch the page again to trigger the event.
The requirement for such an event is caused by our code periodically checking for new content by making XHR requests. These are never sent when the browser is asleep, so we never get new content to restart the timeouts.
Thanks for any help you guys may be able to provide!
We had a similar issue and solved it something like this:
var lastSync = 0;
var syncInterval = 60000; //sync every minute
function syncPage() {
lastSync = new Date().getTime(); //set last sync to be now
updatePage(); //do your stuff
}
setInterval(function() {
var now = new Date().getTime();
if ((now - lastSync) > syncInterval ) {
syncPage();
}
}, 5000); //check every 5 seconds whether a minute has passed since last sync
This way you would sync every minute if your page is active, and if you put your browser in idle mode for over a minute, at most 5 seconds will pass before you sync upon opening the browser again. Short intervals might drain the battery more than you would like, so keep that in mind when adapting the timings to you needs.
Better than an interval would be to add a window blur listener and a window focus listener. On blur, record current time. On focus, validate you are still logged in / sync'd / whatever you need to do.
Basically exactly the same thing but it runs only when necessary rather than slowing your entire page down with an interval.
Update
var $window = $(window),
$window.__INACTIVITY_THRESHOLD = 60000;
$window.add(document.body); //necessary for mobile browsers
$window.declareActivity = function () { $window.__lastEvent = new Date(); };
$window.blur($window.declareActivity);
$window.focus(function(){
var diff = (new Date()) - $window.__lastEvent;
if (diff > $window.__INACTIVITY_THRESHOLD) {
$window.trigger("inactivity");
}
});
$window.on("inactivity", "", null, function () {
//your inactivity code
});
Though that blur event seems sketchy if the phone is powering off and I don't know that I would trust it in all circumstances / mobile devices. So I'd probably throw in something like this:
$(document.body).on("click scroll keyup", "", null, $window.declareActivity);
so that my inactivity timer works for when the user just walks away as well. Depending on your site, you may want to adjust that exact event list - or simply throw in a $window.declareActivity(); into your existing scripts that respond to user inputs.

Categories

Resources