dakov
dakov

Reputation: 1139

setTimeout keeps firing

I have a simple chrome extension, which works with page's DOM (it looks for "something" what looks like login forms). Problem is, that many pages has "dynamic" login forms - its code is loded when needed. That's why body's onload event is not enough.

So I've created mutation observer and execute my procedure each time DOM changes (there will be some restrictions in the future - many of these changes does not affect my purpose)

Because of some intervals I have to implement delayed execution of my procedure over updated DOM (the time 500 is only for testing purpose)

var target = document.body;
var timer;

var observer = new MutationObserver(function(mutations) {

  mutations.forEach(function(mutation) {
    var count = mutation.addedNodes.length;

    if( count > 0) { 

        timer = setTimeout(function (){

                   console.log("executed");
                   my_procedure();

               }, 500);

    }
  });    
});

var config = { attributes: true, childList: true, characterData: true };

observer.observe(target, config);

The problem is, that setTimeout keeps firing to infinity, which makes the page pretty lazy after some time. In Chrome JS console is number of 'executed' messages still increasing.

I'm aware of clearTimeout(timer), but I'm not able to use it right - so my_procedure is executed only once for each DOM change.

EDIT: my_procedure is actually 3rd-party library function, so I don't want (or can) to change it - the function starts the whole form-classification logic.

Thank yout for advice.

Upvotes: 0

Views: 1753

Answers (3)

Mauno Vähä
Mauno Vähä

Reputation: 9788

The reason why setTimeout seems to fire infinity, is that it actually fires as many times as there is e.g. new elements added to the DOM. So basically, it assigns 3 setTimeout calls if 3 divs are added to the DOM. The reason being, that you are doing it inside mutations.forEach(function(mutation) { ... } loop.

Therefore, the solution is to remove the loop and only assign one setTimeout call at higher level which will fire when the DOM changes:

// create an observer instance
var observer = new MutationObserver(function(mutations) {

   // fired when a mutation occurs
   timer = setTimeout(function () {

      console.log("executed");
      // my_procedure();

   }, 500);  

});

Below is the full example, it should display "executed" once (after 3 second) to console when the DOM is actually changed, even though we add 3 new divs.

js fiddle example

The code:

// select the target node
var target = document.body,
    timer;

// create an observer instance
var observer = new MutationObserver(function(mutations) {

   // fired when a mutation occurs
   timer = setTimeout(function () {

      console.log("executed");
      // my_procedure();

   }, 500);  

});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };

// pass in the target node, as well as the observer options
observer.observe(target, config);

// Testing our observer
setTimeout(function() {
    document.body.innerHTML =  "<div>lol 1</div>";
    document.body.innerHTML += "<div>lol 2</div>";
    document.body.innerHTML += "<div>lol 3</div>";
}, 3000);

/* Comment out if you want to add more divs..
setTimeout(function() {
    document.body.innerHTML += "<div>lol 4</div>";
    document.body.innerHTML += "<div>lol 5</div>";
    document.body.innerHTML += "<div>lol 6</div>";
}, 5000);
*/

Cheers.

Upvotes: 1

V31
V31

Reputation: 7666

In my_procedure() you can use

clearTimeout(timer); 

in the first line to avoid multiple occurence

UPDATED CODE:

var target = document.body;
var timer;
var counter = 0;

var observer = new MutationObserver(function(mutations) {

  mutations.forEach(function(mutation) {
    var count = mutation.addedNodes.length;
    clearTimeout(timer);
    if( count > 0) { 
        if(counter == 0){
        counter = 1;
        timer = setTimeout(function (){
                   console.log("executed");
                   my_procedure();

               }, 500);
        }else {
            clearTimeout(timer);
         }

    }
  });    
});

var config = { attributes: true, childList: true, characterData: true };

observer.observe(target, config);

Upvotes: 2

antoinestv
antoinestv

Reputation: 3306

You have got only 1 var "timer" and then you use ".forEach()" on your "mutations" !

So you overwrite timer each time, that's why if you set clearTimeout in my_procedure it doesn't work (it will stop only 1 timer, the last of the mutations).

Doesn't it ?

Upvotes: 0

Related Questions