My site has a page loading system to load only the necessary content of the next page that the user is navigating to, and it keeps the top navigation and footer, and only fetches the new content. In order to execute the script tags that are in the new HTML content that's loaded with fetch(), all the script tags are appended with document.body.appendChild(scriptElementToRun). The script elements are added to an array so that they can be removed when the user navigates to another page which will require different scripts. The problem is that removing the script elements from the DOM is not enough to terminate them. They still continue to execute. Is it possible to stop the execution of only the scripts that are in the array, even asynchronous code such as setInterval, setTimout, WebSockets etc. that don't run in the main event loop, without adding anything to the script tags I want to stop?
This is the code that adds the scripts:
let result = new TextDecoder("utf-8").decode(data); // decode the binary response data
$('mn').innerHTML = result; // a div that contains the main body content of the page
for (script of $('mn').getElementsByTagName('script')) {
let activeScriptElement = document.createElement('script');
pageScripts.push(activeScriptElement);
if (script.src) {
activeScriptElement.src = script.src;
console.log('run script: ' + script.src);
} else { // inline script
activeScriptElement.appendChild(document.createTextNode(script.innerHTML));
console.log('run script: inline');
}
document.body.appendChild(activeScriptElement);
}
I then remove the scripts when loading a new page:
for (script of pageScripts){
script.remove();
}
EDIT:
What I ended up doing is putting all the page HTML content in an iFrame and communicating between the iFrame and the parent window with window.postMessage()
It would be helpful to see the code you're talking about. I'm guessing that you are using event listeners, which will still be active, even if you remove script-tags from the DOM.
You can remove events like this:
document.getElementById("your-elements-id").removeEventListener();
var myTimeout = setTimeout(...);
clearTimeout(myTimeout);
var myInterval = setInterval(...);
clearInterval(myInterval);
Related
I'm looking for a way in the purpose to speed up site loading and postpone non-urgent scripts. I need to load a script on button click as it placed in Head tag <script ...>.
async function do_script_load() {
var script = document.createElement("script");
script.src = "https://web.webformscr.com/apps/fc3/build/loader.js";
script.type = "text/javascript";
script.setAttribute("async", "");
await item.parentElement.querySelector(".n-bell-bubble").appendChild(script);
do_other_stuff();
}
addEventListener( "click" , () => {
do_script_load()
})
The problem is that the script get loaded but it is not being started.
I'm looking for a way to run the script as a script added as used to.
<head>
<script src="..." async></script>
The script you are loading is listening for the window's load event to execute.
(from https://web.webformscr.com/apps/fc3/build/loader.js)
window.addEventListener("load",e,!1)
This event is long fired when you do append the script, and hence it will wait for something that already did happen.
You can quick-fix this by adding a load event handler on your <script>and fire a new load event on the window when this happens:
script.addEventListener("load", e => window.dispatEvent(new Event("load")));
Though it seems we need some other elements in the page with sp-form-id attributes that I don't know of and thus couldn't test myself.
But if you can reach to the author of this script you load, then you should ask them to expose a method to initialize it without having to fire this event, since other parts of your page may very well rely on the fact that this event is only supposed to fire once.
Also note that you may want to pass a { once: true } option to your click listener to avoid loading this script every time the user clicks.
I'm writing an extension for chrome. The problem is I need to insert an iframe of loading page into DOM instead of loading itself.
Here is a code of the content script which is not working:
window.stop();
var body = document.createElement("body");
body.src = window.location.href;
document.documentElement.appendChild(body);
Why browser generate body in DOM but don't show it on the page?
Have got no idea why is it happen, but window.stop blocks the DOM from changes after calling. In order to reset this state I should do this:
document.documentElement.innerHTML = "";
And as a bonus head and body will be appended automatically)
Essentially what I'd like to do is something to the effect of this:
window.location.href = "some_location";
window.onload = function() {
alert("I'm the new location and I'm loaded!");
};
Is there any way to have a callback when the window's new location is loaded? (The above code doesn't work.)
No, you cannot do it the way you want. Loading a new page closes the current document and starts loading a new document. Any code in your current document will no longer be active when the new page starts to load.
To have an event handler when the new document loads, you would either need to insert code into the new page's document or load the new document into an iframe and monitor the loading of the iframe from your current document.
Setting window.location.href to a new value tells the browser to simply go to the next page.
Like so:
window.location.href = "http://www.google.com/";
Once the browser goes to google.com, that's it, your Javascript is no longer executing, and there is no way for you to have any sort of callback, because the browser is no longer running your page.
So, the answer is no.
in regards to callback and js execution in new context - no (as per orig answer), but there are ways to access loaded elements without callback. For e.g. scrollIntoView on a loaded element, you can append the selector:
const href = '#someElem'
if(window.location.pathname !== '/') window.location.href = `/${href}`
else document.querySelector(href).scrollIntoView()
I have a page that has an iframe with external content. I don't want infinite loops in the external content to crash my whole page. Is there any way to get around this.
I tried to set something up where the parent postMessages the child iframe every so often and if the child Iframe doesn't respond for too long a time the parent changes the iframes src, but this doesn't seem to work. The parent's setTimeout functions no longer execute once the iframe starts looping. See my code here (note that it will crash your tab if you execute it, open the console before execution to view the logging):
<html>
<head>
</head>
<body>
<script type="text/javascript">
var scr = 'script';
var html = '<html><head><script>\n' +
' window.addEventListener("message", answer, false);' +
' function answer() { console.log("answered"); parent.postMessage(\'hi\', \'*\');}' +
' setTimeout("while(1){console.log(\'in loop\')};", 3000)' +
"</" + scr + "></head><body>IFRAME</body</html>";
var lastAnswer = (new Date()).getTime();
var iframe = document.createElement('iframe');
queryChild();
window.addEventListener("message", receive, false);
function receive() {
lastAnswer = (new Date()).getTime();
console.log('got answer');
}
function queryChild() {
console.log('querying');
if((new Date()).getTime() - lastAnswer > 5000) {
console.log('killing');
iframe.src = '';
} else if(iframe.contentWindow){
iframe.contentWindow.postMessage('hi', '*');
}
setTimeout(queryChild, 2000);
};
document.body.appendChild(iframe);
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(html);
iframe.contentWindow.document.close();
</script>
</body>
</html>
Any suggestions on how to solve this problem?
My experience with this kind of problem is that unless you can access the external code before feeding it to the iframe (either as an URL or via the srcdoc attribute), the loop will completely interrupt any JavaScript execution.
Whatever kind of timeout functionality you implement, it will not be called due to the iframe code execution consuming 100% resources until the browser reports a crash.
Your options are:
Sanitize the code automatically before adding it to the iframe, which proves impractical since there are infinite ways to loop infinitely, and you will not be able to catch them all. You would have to write a scanner script that could detect infinite loops while not crashing in the course of scanning the code.
Use a sandboxing solution like Google Caja to sanitize the code. However, this will change the code structurally if not configured heavily.
In case of an application that has capabilites of creating virtual environments and monitoring them, you could execute the iframe code (let's say on a virtual machine of sorts), check if the process locks up and use that outcome to determine if you can safely set the iframe.src property to your code's URL. This might be the only solution that can guarantee some sort of guarantee that this code will not lock up immediately (however, there are many ways to have race conditions at some later point of execution, so there will not be a sure way to say it will never lock up the browser).
Summary: Unless you can find a way to test the code extensively before showing it in the iframe, you can not guarantee that the iframe code will not lock up the browser tab.
It depends highly on the browser, some browser uses one thread for each page (or iframe), in this case your script cannot be executed until the iframe execution is over (the infinite loop). Some others have one thread per page (or iframe) and maybe you are able to do it.
What I'm sure is than if you expect to support enterprise browsers (like IE8) you can't.
This
console.log('killing');
iframe.src = '';
Will not kill the iframe. according to same origin policy you can not manipulate external domains from your domain. Just changing the src of an iframe doesn't trigger a navigation in the iframe. src will change, but iframe will not get navigated.
If your get the message from inner iframe you should remove the iframe from your document tree and insert a new one in document tree in order to kill the iframe using removeChild
document.body.removeChild(iframe);
document.body.appendChild(newiframe);
Look at this simple demonstration I've created here: http://jsbin.com/avodeb/1/
Try this:
<script>
document.getElementById('iframeID').onload= function() { //When the iframe loads quickly
clearTimeout(killerTimer); // Stop the Killer Timer
};
var killerTimer = setTimeout( function() {
document.getElementById("iframeID").setAttribute("src",""); //Otherwise, kill the iframe
}, 3000 );
</script>
I did a simple auto form filler, by sending info from a created html to the background and then the content script, so the injected script can change the info on the form.
I know the content script run once the page is load. I want to know if I can run the content script again, without the need of reloading the page.
I got sendRequest function in the content script, that I use to make sure it gets the info, only when the page is ready. It then add the info to the form, and wait for me to send it.
In the content script, I added a onRequest and it works (it get the info). but, I don't see the changes on the form, unless I am realoding the page.
I want to know if it is possible to do and if it does what subjects should I learn to implent this.
I am new to chrome extentions and I am still learning :)
in 1 of the pages, I use jQuery, so an answer with jQuery would be good too.
i found out that if we create a chrome.tabs.sendRequest from background we can use chrome.extestion.onRequest from content script and it will execute every time becuse they both run allmost in the same time.
so i did from background:
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.sendRequest(tabs[i].id, {...requests u want to send }, function (response) {
});
}
});
from content script:
chrome.extension.onRequest.addListener(function (request, sender, sendRespons) {
//get requested info here
//call functions.
sendResponse({}); //send info back to background page.
});
form's target could be an iframe which would avoid page reload. not sure how useful it'd be.
The correct way to execute a content script again is by using the chrome.tabs.executeScript method. It receives two arguments. The first argument is the tabId, which can be obtained in many ways, such as one of the chrome.tabs events. Use null to execute the content script in the currently selected tab (caution: this may also be an active dev tools window!).
Examples:
// Reloads the current tab
chrome.tabs.executeScript(null, {code:'location.reload();'});
// Executes contentscript.js in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js'});
// Executes contentscript.js in all frames in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js', allFrames: true});
// Receives message from content script, and execute a content script:
chrome.extension.onMessage.addListener(function(details) {
if (details.message === 'load a content script') {
chrome.tabs.executeScript(details.sender.tab.id, {file: 'a_script.js'});
}
});
// The previous one is activated from a content script, as follows:
chrome.extension.sendMessage('load a content script');
(onMessage and sendMessage have to be used instead of onRequest and sendRequest, since Chrome 20)