Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setTimeout is not working #24

Open
ahmadalibaloch opened this issue Apr 19, 2019 · 10 comments
Open

setTimeout is not working #24

ahmadalibaloch opened this issue Apr 19, 2019 · 10 comments

Comments

@ahmadalibaloch
Copy link

setTimeout, setInterval and other interval built in methods do not work, even if I expose them in context.

console.log('start');
setTimeout(()=> {
     console.log('hello timeout');
 }, 2000);
 
 console.log('end');

output:

start
end

It is to be noted that when I add .bind(this) , then timeout works and breaks at that line saying .bind is not a function.

console.log('start');
setTimeout(()=> {
     console.log('hello timeout');
 }, 2000).bind(this);
 
 console.log('end');

output:

start
hello timeout
// and an error in console saying setTimeout(...).bind is not a function
@goto-bus-stop
Copy link
Member

yeah, this is because vm-browserify creates an iframe to run the code in, and immediately destroys it after running the code. so if you schedule async callbacks, they will not get called.

it would be nice to find a solution for this, i'm not sure how to do it because we don't want to keep the iframes around forever.

@ahmadalibaloch
Copy link
Author

But why does it work when I write .bind(this), and script stops working at that line too, because of the errro .bind is not a function.

@goto-bus-stop
Copy link
Member

goto-bus-stop commented Apr 20, 2019 via email

@ahmadalibaloch
Copy link
Author

The issue is same for builtin vm module but I tested this in node.js and it works fine. I think there is some problem of this of the setTimeout, but I dont know how to solve it.

This issue is also discussed here, any workarounds are appreciated.
setTimeout and/or context issue (runInContext)
setTimeout not run in webview's Node vm

@goto-bus-stop
Copy link
Member

The .bind error is fixed by putting it on the function instead of on setTimeout:

setTimeout(
  function () {
    // ...
  }.bind(this)
)

But it's just not possible to use setTimeout in vm-browserify. async code doesn't work, because we destroy the iframe immediately.

@ahmadalibaloch
Copy link
Author

If you say that the iFrame is destroyed in which the code was running then how my custom implementation of setTimeout and setInterval is working.

const setTimeouts = [];
function customSetTimeout(cb, interval) {
  const now = window.performance.now();
  const index = setTimeouts.length;
  setTimeouts[index] = () => {
    cb();
  };
  setTimeouts[index].active = true;
  const handleMessage = (evt) => {
    if (evt.data === index) {
      if (window.performance.now() - now >= interval) {
        window.removeEventListener('message', handleMessage);
        if (setTimeouts[index].active) {
          setTimeouts[index]();
        }
      } else {
        window.postMessage(index, '*');
      }
    }
  };
  window.addEventListener('message', handleMessage);
  window.postMessage(index, '*');
  return index;
}

function customClearTimeout(setTimeoutId) {
  if (setTimeouts[setTimeoutId]) {
    setTimeouts[setTimeoutId].active = false;
  }
}

Even if I add customSetTimeout for 1 minute, it works

 const codeToEval = `console.log('start)
 var a = setTimeout(function() {
    console.log('setTimeout  async done');
 }, 10000);
 console.log('sync');`
 const sandbox = {
      setTimeout: customSetTimeout,
      clearTimeout: customClearTimeout,
    };
vm.runInNewContext(codeToEval, sandbox);

@goto-bus-stop
Copy link
Member

It's because there are multiple windows at play. If you call window.setTimeout inside the iframe, a new callback is scheduled inside the iframe. If the iframe is destroyed, this callabck will not run.

If you call your custom setTimeout, or pass the setTimeout function like so:

vm.runInNewContext('code', { setTimeout: window.setTimeout })

this calls out to the parent window; so in fact your callback is scheduled in the parent window, not inside the iframe. Then if the iframe is destroyed the callback still lingers and will be called by the parent window.

@ahmadalibaloch
Copy link
Author

I tried checking your suggestion
vm.runInNewContext('code', { setTimeout: window.setTimeout })
but it doesn't work. Custom setTimeout do work.

@ahmadalibaloch
Copy link
Author

ahmadalibaloch commented Sep 11, 2019

I ended up creating a custom setTimeout, setInterval, clearTimeout, clearInterval, explained in the link below. And it plays very well!
https://gist.github.com/ahmadalibaloch/6c7d70244c83b90aa77bb83fa28cd0df

@Yarflam
Copy link

Yarflam commented Jul 18, 2023

I've found a faster solution:

vm.runInNewContext('code', {
    setTimeout: (callback, wait) => setTimeout(() => callback(), wait),
    clearTimeout: handle => clearTimeout(handle)
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants