Chrome extension, javascript: Why is this firing twice? - javascript

I have a very very simple bit of code in my (test) Chrome extension:
function test()
{
alert("In test!");
}
chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined) {
test();
}
});
My question is, why is test() firing twice? And more importantly, how do I make it fire just once?

Have a look at what the different states are when the event is dispatched. I presume, that it is getting dispatched once when the state is "loading" or when the state is "complete". If that is the case, then your problem would be fixed with:
function test()
{
alert("In test!");
}
chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined && changeinfo.status == "complete") {
test();
}
});

I was also confused by this and tried to find the answer. Only after some experimentation did I figure out why I was receiving multiple "complete" update events for what I thought was a single page "update".
If your page has iframes, each will trigger a "complete" event and bubble up to the parent content script. So more complex pages will trigger a slew of onUpdated events if they have iframes.

When you write the following code:
chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined) {
test();
}
});
You're calling addListener and telling it to call test() not immediately but rather whenever the tab is updated. Tab update events are broadcast by the Chrome browser itself, which in turn, causes your test() code to run.

I know this is old but anyway... it's happening to me too and maybe this can help someone. Looking into the Tab object that the handler function is receiving, I saw that the favIconUrl property is different in both calls so I guess it has something to do with that although I have no idea about the reason behind this.
I thought it was a bug but after a second thought and some testing I discard the bug theory.
What I know so far is that if two properties are changed, the event is triggered twice with the changeInfo object containing one property each time. In other words if for instance the properties that change are status and favIconUrl, then
changeInfo = {status:"complete"};
and then in the next call
changeInfo = {favIconuUrl : ".... whatever ..."};

Although I'm not sure why our code is firing twice, I had a similar problem and this answer helped me out a lot.
https://stackoverflow.com/a/49307437/15238857
Vaibhav's answer was really smart in incorporating validation in the form of the details object of the request.
When logging the details object I noticed a trend that the original firing had a frameId of 0, and the second firing was always something else. So instead of positively validating that you're in the correct submission of the URL, I like using an if statement to return and bail out when you have the duplicate entry.
I'm using onCompleted here, but it should work for onUpdated as well.
chrome.webNavigation.onCompleted.addListener(details => {
//need to make sure that this only triggers once per navigation
console.log(details);
if (details.frameId !== 0) return;
});

I solved this problem by checking the title of the tab when updated:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
var title = changeInfo.title;
if (title !== undefined) {
doSomething();
}
});

Twice is due to status being loading once and status being completed once.
if you are unconcerned about the status, you may also try the onCreated event. That will be fired just once!

Related

Firefox return false; (preventDefault();) and window.location.reload(); together

I think it very stupid question, but after hours of google it - i have no idea or solution.
Point is that i need reload page after handling "click" event on my web-site. In chrome\opera it's works without problems. But in firefox i have some bug. My JS code with comments:
$('#project_create').click(function() {
var projectname = $("#project_name").val();
var projectdescription = $("#project_description").val();
$.post("projects/add_project.php", {
project_name: projectname,
project_description: projectdescription
});
$("#create_project_form").hide("slow");
$("#project_list").show("slow");
//return false; - if it uncomment, all work, but next page reloader doesn't work.
window.location.reload(); //but if it's work, FireFox doesn't send $.post-query
});
I need to work booth methods, because after click - script put in $_SESSION['my_var'] some variable, and it variable is avaliable after reload page only. Maybe there are other ways to do it? As I understand the problem here in features with firefox and preventDefault();
Thanks!
The issue is just you reload the page before performing the ajax request.
Try to reload page in the ajax success callback handler :
$.post("projects/add_project.php", {
project_name: projectname,
project_description: projectdescription
}, function(){
window.location.reload();
});
And remove your old window.location.reload()
When you do a return, code after that line will not be reached anymore and is considered "dead code". One does not simply put code after a return.
Another is that there's and issue when using return false to prevent default default actions. It prevents delegation/bubbling. Event handlers hooked higher up in the DOM tree (especially ones hooked with on()) won't be executed. If delegation matters, don't use it.
If your goal is to prevent the default action of the link and do stuff in JS, use event.preventDefault instead. The event object is passed in as the first argument in the handler.
$('#project_create').click(function(event) {
event.preventDefault();
// rest of the code
});
In addition to what the other answers suggest, you can also execute the location.reload method asynchronously using setTimeout:
setTimeout(function() { location.reload(); }, 1);
return false;
EDIT: The entire idea of running an asynchonous AJAX request and reloading the page immediately afterwards is flawed, of course, since the AJAX request may not have been completed by the time you reload the page.
You should therefore use a callback, as suggested by the accepted answer. Alternatively, you could use a synchronous AJAX request, but that would freeze execution until the request has completed, which is generally not desirable.

set window.location after wait

I have a callback function that changes
window.location.href = url;
however, this callback is invoked after checking whether an image exists in a different page (and different domain).
My problem is that IE9 won't let me do the following:
$(testImage)
.bind("load readystatechange", function (e) {
if (this.complete || (this.readyState === 'complete' && e.type === 'readystatechange')) {
callbackFunction(true);
}
})
.bind("error abort", function () {
abort();
})
.attr("src", testUrl);
IE9 WILL however allow me to do this:
callbackFunction(true);
but as I want to wait and see that my image exists, I need the above to work. (I don't want to change IE settings) Of course, it all works just fine in Chrome.
Anyone got any ideas?
Edit: I think I should clarify: "when I say ie9 wont let me do.." I mean that ie9 executes the callback BUT the code inside (changing url) don't work.
IE also supports the load event on images, you don't have to explicitly check the .readyState property unless you have another reason you didn't mention.
So it should absolutely be enough to go like
$( testImage ).on('load', function( e ) {
callbackFunction( true );
});

Send information about clicked link to the server before redirect

We're creating a click tracking app, that builds heatmaps. I'm writing a script which users are suppose to insert into their pages for tracking to work.
It works fine on elements, which doesn't require a redirect or form submit. For example, if I click on h1 or p or whatever, it works perfectly correct. But, if I click on a a, request to our server never happens before the normal redirect.
In the last couple of days I tried a lot of ways to do that. First of, I tried a normal AJAX call, since it was a cross-domain request I had to use JSONP, but again, that AJAX call did not have time to execute before the redirect. Adding async: false would have solved the problem, but it doesn't work with JSONP requests. So I decided to add a flag variable which indicates that it is safe to move on with redirect and used an empty while loop to wait until it becomes try in the ajax callback. But the while loop was blocking the execution flow, so callback never got a chance to set that variable to true. Here is some simplified code:
$(document).on('click', function (e) {
//part of the code is omitted
$.ajax({
url: baseUrl,
data: data,
type: "get",
dataType: "jsonp",
crossDomain: true,
complete: function (xhr, status,) {
itsSafeToMoveOn = true;
}
});
while(!itsSafeToMoveOn){}
return true;
});
The next thing I tried is to use unload page event to wait until total ajax calls in progress would become zero (I had a counter implemented) and then to move on with redirect. It worked in Firefox and IE, but in WebKit there was this error:
Error: Too much time spent in unload handler
After that I realized that I don't care about the server response and using img.src for the request would be an ideal fit for this case. So at this point code looks like this:
$(document).click(function (e) {
//part of the code is ommited
(new Image).src = baseUrl + '?' + data;
if (tag === "a" || clickedElement.parents().has("a")) {
sleep(100);
}
return true;
});
That way I increased the overall script performance slightly, but problem with links remains unchanged. The sleep function appears to be also blocking the execution flow and request never happens.
The only idea left is to return false from the event handler and than redirect manually to the clicked element's href or to call submit() on the form, but it will complicate things to much and believe me it's already a huge pain in the ass to debug this script in different browsers.
Does anyone have any other ideas?
var globalStopper = true;
$(document).on('click', function (e) {
if (globalStopper === false)
return true; //proceed with click if stopper is NOT set
else {
globalStopper = false; //release the breaks
$.ajax({
//blahblah
complete: function (xhr, status,) {
$(elem).click(); //when ajax request done - "rerun" the click
}
});
return false; //DO NOT let browser process the click
}
});
Also, instead of adding image, try adding script. And then add the script to the HEAD section. This way the browser will "wait" until it's loaded.
$(document).on('click', function (e) {
var scriptTag = document.createElement("script");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(scriptTag);
return true;
}
I would take a look at the navigator sendBeacon API mentioned in this stack overflow answer or directly linked to here.
From the description on the site
navigator.sendBeacon(url, data) - This method addresses the needs of analytics and diagnostics code that typically attempts to send data to a web server prior to the unloading of the document.
You can save information to ajax request in cookies or localStorage and make any worker that will send information. Saving to cookies or localStorage is faster then ajax-request. You can do next:
$(document).click(function (e) {
var queue = localStorage.getItem('requestQueue');
queue.push(data);
localStorage.setItem('requestQueue',queue);
});
$(function(){
setInterval(function(){
var queue = localStorage.getItem('requestQueue');
while (queue.length > 0) {
var data = queue.pop();
$.ajax({
...
success: function(){
localStorage.setItem('requestQueue', queue);
}
});
}
},intervalToSendData);
});
So, when user click on link or send a form, data will be saved to storage and after user go to next page, this worker starts and send data to your server.
The JavaScript is basically executed in single thread. It is not possible to have your callback function executed and at the same time have an infinite loop waiting for a flag variable from it. The infinite loop will occupy the single execution thread and the callback will never be called.
Best approach is to cancel the default handler of your event and bubbling for it (basically return false if you are really building your tracking code with jQuery), and do the necessary actions (redirect page to the necessary address if a link was clicked or trigger other default actions), but this would take a lot of careful work to recreate all the possible combinations of actiona and callbacks.
Another approach is to:
1) Look for something specific to your code in the event data
2) If it is not present - make an AJAX call and in its callback re-trigger the same even on the same element, but this time with your specific bit added to the even data; after the AJAX call return false
3) If your specific bits are present in the data - simply do nothing, allowing the default event processing to take place.
The either approach may bite, however.
So if I understand right, you want your ajax logs completed before the page unloads and follows a link href. This sounds like a perfect case where you could consider using Deferreds in jQuery.
When your user clicks on anything that's supposed to take him away from the page, just check your promise status. If it's not resolved, you could throw a modal window over the page, and ask the user to wait til the progress is complete. Then, add a new pipe to your deferred, telling it to change the location href once everything is complete.
Let me know if this is the scenario. If it is, I'll explain in more detail. No use continuing if I didn't understand your requirement properly

jQuery .load() not firing on images (probably caching?)

I have some pretty basic jQuery code:
...
$(this).find('img').load(function(){
loadedImages++;
if(loadedImages == $this.find('img').length){
...
However, thats not firing consistently. It fires if i do a hard refresh or close my browser, but a normal refresh, or just hitting the same URL twice at any time without erasing the cache makes the .load() never fire.
Any ideas on how to fix this?
I think this has been discussed before. It’s not the caching per se that is the problem but the timing: the images may already have been loaded by the time the event handler is attached (so it never gets fired). This may also occur if no caching happens, for example in a multithreaded browser on a very fast connection. Fortunately, there is a property .complete that you can use:
var load_handler = function() {
loadedImages++;
…
}
$(this).find('img').filter(function() {
return this.complete;
}).each(load_handler).end().load(load_handler);
You can also create your own event attach function:
jQuery.fn.extend({
ensureLoad: function(handler) {
return this.each(function() {
if(this.complete) {
handler.call(this);
} else {
$(this).load(handler);
}
});
}
});
And then call it as follows:
$(this).find('img').ensureLoad(function(){
loadedImages++;
if(loadedImages == $this.find('img').length){
…
});
A way would be to add a "dummy variable" at the end of the URL that you use to grab the image... such as the time in milliseconds appended as a query string param.
Edit: Preventing the Browser to Cache images is a very bad idea in 99% of the cases as it will slow down your application. Instead you should check if an image is already loaded, and if not wait for the load event to complete.
As Ron and El Guapo said, the answer is to add a query at the end of the URL. I did this like this:
$(this).find('img').each(function(){
$(this).attr('src',$(this).attr('src')+'?'+new Date().getTime())
}).load(function(){
//This will always run now!

gBrowser.addEventListener: "load" event fired three times

I have installed the "hello world" dev example for Firefox extensions as described here:
http://blog.mozilla.com/addons/2009/01/28/how-to-develop-a-firefox-extension/
I have modified the anonymous function that gets passed to gBrowser.addEventListener:
gBrowser.addEventListener("load", function (event) {
var t = event.target;
alert("Content title: " + t.contentTitle);
}, false);
This function is getting called three times for every page load. When I click a link, it fires twice for the current (already loaded page) and once for the new page.
I have uninstalled all other addons (including Firebug) and still it fires 3 times. Does anyone know why this might be?
Thanks Richard
I would recommend you to do something like this:
window.addEventListener("load", function load() {
window.removeEventListener("load",load,false); //no longer needed
window.gBrowser.addEventListener('DOMContentLoaded', function load(event) {
your_addon.init_function(event);
}, false);
In my addon it works. :-)
Hope this helps.
Michał

Categories