I'm doing a toy library in which the user can modify the DOM with AJAX, basically around:
fetch("/newpage").then(r => r.text()).then(html => {
var tmp = document.createElement('html');
tmp.innerHTML = html;
document.querySelector("#container").outerHTML = tmp.querySelector("#container").outerHTML;
// insert in the DOM
The user of the library can decide to have <script> blocks in the inserted HTML <div>, so after insertion I do:
for (const scr of document.querySelectorAll("#container script"))
eval(scr.innerHTML);
as discussed in Executing <script> injected by innerHTML after AJAX call.
This works fine. But then, if another AJAX call is done and #container is replaced again by something else, and the <script> is removed, we need to be able to stop all running scripts that originated from the <script> inside #container.
How to do this?
Example:
after AJAX call #1, #container contains a <script> with code running periodically (setInterval, etc.). It is now running thanks to eval (see above)
after AJAX call #2, #container is replaced by something else, and this <script> is no longer here. But the setInterval task is still running... How to automatically kill this when <script> disappears after AJAX call #2?
Ideas: could we associate the return value of eval(..) to a variable, so that we can keep track of this, and kill its execution later?
Or when inserting <script> with element.outerHTML = ..., should we add an identifier (id or class) to the <script>, so that we can later take action on the code that this script block launched?
Note: neither Is it possible to stop JavaScript execution? nor How to terminate the script in JavaScript? nor Executing <script> injected by innerHTML after AJAX call solve this question.
You can wrap around the setTimeout before eval() the script. When removed the script, you can unregister the tasks.
function mockInternal() {
const setTimeout = window.setTimeout;
const setInterval = window.setInterval;
// window.setImmediate
// window.addEventListener
const setTimeoutHandlers = [];
const setIntervalHandlers = [];
window.setTimeout = function mock_setTimeout(callback, delay) {
const h = setTimeout(callback, delay);
setTimeoutHandlers.push(h);
return h;
}
window.setInterval = function mock_setInterval(callback, delay) {
const h = setInterval(callback, delay);
setIntervalHandlers.push(h);
return h;
}
return function clearMockInternal() {
window.setTimeout = setTimeout;
window.setInterval = setInterval;
setTimeoutHandlers.forEach(h => clearTimeout(h));
setIntervalHandlers.forEach(h => clearInterval(h));
}
}
const clearMockInternal = mockInternal();
eval(`setInterval(() => console.log(new Date()), 1000);`);
setTimeout(() => {
clearMockInternal();
console.log("should not log Date from now");
}, 6000);
This answer just answer your one question(for interval) but I just wanna share it. You can return values from your eval and use it for later. For example:
index.html
<html>
<head>
</head>
<body>
<button type="button" onclick="run1()">Run first method</button>
<button type="button" onclick="run2()">Run second method</button>
<div id="container">
</div>
<script>
var pro1, pro2;
function run1() {
if (pro2 != undefined && pro2 != null) {
clearInterval(pro2);
}
fetch("page1.html").then(r => r.text()).then(html => {
var tmp = document.createElement('html');
tmp.innerHTML = html;
document.querySelector("#container").outerHTML = tmp.querySelector("#container").outerHTML;
for (const scr of document.querySelectorAll("#container script")) {
pro1 = eval(scr.innerHTML);
}
});
}
function run2() {
if (pro1 != undefined && pro1 != null) {
clearInterval(pro1);
}
fetch("page2.html").then(r => r.text()).then(html => {
var tmp = document.createElement('html');
tmp.innerHTML = html;
document.querySelector("#container").outerHTML = tmp.querySelector("#container").outerHTML;
for (const scr of document.querySelectorAll("#container script")) {
var data = eval(scr.innerHTML);
pro2 = data;
}
});
}
</script>
</body>
</html>
page1.html
<html>
<head>
</head>
<body>
<div id="container">
<script>
test();
function test() {
var i = 0;
const interval =
setInterval(function () {
console.log(i);
i++;
}, 1000);
return interval;
};
</script>
</div>
</body>
</html>
page2.html
<html>
<head>
</head>
<body>
<div id="container">
<script>
test();
function test() {
var i = 9999;
const interval =
setInterval(function () {
console.log(i);
i--;
}, 1000);
return interval;
};
</script>
</div>
</body>
</html>
Everytime you clicked a button it will stop other interval
Screenshot for sample:
I hope this helps.