Cross browser alternatives to eval - javascript

My html page dynamically loads pages via Ajax for dynamic panels on the page. I need the script tags in the dynamically loaded pages to be executed. I fixed the code from a SO question. It works fine on FF/Safari/Chrome.
But dom nodes of type script work differently on IE. -- I can't seem to add text to a script node in IE 7:
// variable "data" holds the script element's content from an
// incoming html page loaded via ajax
var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode(data)); // doesn't work on ie
// also doesn't work on IE 7:
script.innerHTML = data;
script.innerText = data;
Any ideas for getting the sw to work on IE? (Other than using eval.)

You should simple call eval(data).
Although it is true that eval should usually be avoided, this is one of the few exceptions.
EDIT: Without eval, you can do it like this:
var scriptNode = document.createElement('script');
scriptNode.type = 'text/javascript';
scriptNode.text = data;
document.head.appendChild(scriptNode);
document.head.removeChild(scriptNode); //Optional

You have a few options I can think of (other than using eval).
The script could be served from a separate path; setting the src of the script element instead of its content should work, even in IE.
The script to be executed could be attached to the onload listener of an image or other element, which can be appended to the document as you are doing with the script element.
use Function instead of eval. This will at least keep the evaluated code out of the local scope: new Function(data)();

Related

How to remove inline styles added by external script?

I have a <script> that generates a both <style> and inline style attributes with !important tags. I'd like to remove all this styling.
My plan was to use a javascript onload callback (and some jQuery) to remove the <style> block and all inline style attributes — but I can't seem to select any of these elements. Here's what I've been toying with:
var script = document.createElement("script");
script.src = "//script.path.js";
script.onload = function(){
$(this).parent().find("style").remove();
$(this).parent().find("[style]").removeAttr("style");
};
$(target).append(script);
UPDATE
It seems that the elements generated by the <script> just aren't available in the DOM right away. If I use setInterval to check if the elements exist first, I can get this to work. I imagine there's a better way to do this though...
According to this other question, you must append the script tag to the DOM before setting onload.
var script = document.createElement("script");
$(target).append(script);
script.src = "//script.path.js";
script.onload = function(){
$(this).parent().find("style").remove();
$(this).parent().find("[style]").removeAttr("style");
};
https://jsbin.com/minoyeyicu/edit?html,js,output
UPDATE: Having clarified that the issue is that the style tag/attributes haven't yet been applied to the DOM until after the downloaded script has executed, one alternative (depending on whether the loaded script is under your control), is to pass a callback parameter to the loaded script and have the loaded script execute the callback when it finishes executing (which is how the Google Maps API works). E.g.
script.src = '//script.path.js?callback=removeStyles'
In order to use the callback parameter from within script.path.js, something like this could be done.

Can't execute inline script with Element.insertAdjacentHTML()

Can I use insertAdjacentHTML to execute inline javascript?
What works in the browser console:
$('body').append('<script>alert(1)</script>')
What I need to work in browser console:
document.body.insertAdjacentHTML('beforeend', '<script>alert(1)</script>');
The VanillaJS solution does not work. I would be glad about a reason
Using insertAdjacentHTML, although the script tag is added to the page, it won't be parsed or executed.
For the script to actually run you need to use createElement:
var script = document.createElement('script');
script.innerText = "console.log('Hello!');";
document.body.append(script);
var script = document.createElement('script'); // create a new script element
script.innerText = "alert('Hello!');"; // InnerText property html-encodes the content,
document.body.append(script); //append innterText to script

Count number of async scripts in the DOM

I have a script script.js that calls in advertisements into the DOM. It is invserted after #closeImage if some test is true:
<div id="overlay">
<img id="closeImage" src="close100x100.png">
</div>
If the test is true I call my script.
if (test) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'script.js';
$("#closeImage").append(script);
script.js might not find an advertisement. In this case from within script.js, script01.js which will be inserted into the DOM to look for different advertisements. If nothing is found script03.js will be called from within script02.js and so my div might end looking like this:
<div id="overlay">
<img id="closeImage" src="close100x100.png">
<script src="script.js"></script>
<script src="script01.js"></script>
<script src="script02.js"></script>
<!-- actual banner html -->
</div>
Only the first script is being inserted by the original document.createElement(). How do I count the number of scripts in #overlay? - This did not work:
$(document).ready(function () {
var loaded = $('div#ADF_overlay script').length;
});
EDIT (Restated the problem differently based on feedback)
My main guess here is that you have a timing issue. You are inserting a script, which has to load and then execute, which may then insert another script which has to load and then execute and you don't actually know when that process is done so you can check how many scripts were loaded in total.
$(document).ready() will not wait for that process to be done - it only waits for the original HTML of the document to be parsed (and any inline scripts that were there in the original HTML and don't have the defer or async attributes will run).
The only way to know when a cascade of dynamically loaded scripts are actually done is to have the last script somehow mark when it's done (either by calling a function, triggering an event, setting a variable or marking something in the DOM). Without the script telling you when it's done inserting new script tags, you can't know whether the next script is still loading and waiting to run which might insert some more scripts, etc...
We could probably help better with ideas for solving your overall problem (what you are actually trying to accomplish) if you described the overall problem rather than just this one piece that you're trying to use.
If you just want to count how many dynamically insert scripts there are, then it would be simplest to just maintain a javascript counter as you insert them and then you can use that counter sometime later.
var scriptsInserted = 0;
if (seenOverlay('served') === 'false') {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'script.js';
$("#closeImage").append(script);
++scriptsInserted;
}
Then, some time later, you can just refer to your variable scriptsInserted to access the count.
Alternatively, you can put a class name on your script elements and just query for that:
if (seenOverlay('served') === 'false') {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'script.js';
script.className = "adScript";
$("#closeImage").append(script);
++scriptsInserted;
}
And, some time later:
$(".adScript").length;
If you're dynamically inserting a cascade of scripts (insert one, which loads, runs and then inserts another, and so on), then when do you know to check to see how many scripts were actually inserted? The timing of when to check may also be an issue for you because if you're checking that in $(document).ready(), then that process may not be done yet as each dynamically inserted script is loaded asynchronously and $(document).ready() can easily fire before that process is done because it doesn't wait for dynamically inserted scripts to load or run.
It appears that you may potentially have other issues because inserting a script into a particular place in an already loaded document will usually not insert content at that place in the document because a dynamically loaded script element can't use document.write() to insert content into the existing document in the same way that a normal inline <script> tag can.

How do I inject a userscript into a site that uses CSP?

I'm currently in the process of writing a userscript for a certain website. What I'm trying to accomplish requires access to the content scope with the page variables and functions.
Standard operating procedure for that sort of thing seems to be a function that injects a new <script> tag into the DOM, like so:
function injectScript(f) {
var target = document.head || document.documentElement;
var script = document.createElement("script");
script.type = "text/javascript";
script.text = "(" + f.toString() + ")();";
target.appendChild(script);
target.removeChild(script);
}
However, this approach doesn't work because the website has a Content Security Policy which prevents the injected script tag from executing! I've also tried the location hack (basically location.href="javascript:someCode()"), but that also counts as an inline script and won't work.
unsafeWindow would be an option that is unfortunately only available with Greasemonkey. Ideally my script should work with Chrome as well. Is there a better way to access page variables?

How to insert javascript using innerHTML (ie6 is giving me a unknown runtime error)

var head =document.getElementsByTagName("head")[0];
newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.innerHTML = '$(window).load(function(){ someFooo(); }); ';
head.appendChild(newScript);
This is causing an Unknown Runtime Error in IE6. Any other way around this?
Try the text property instead:
newScript.text = '$(window).load(function(){ someFooo(); });';
This works in non-IE browsers as well. I've used this in FF2, FF3, FF3.5, Safari 4 (win), Opera 9+, Chrome 2, Chrome 3, and they all work.
According to the spec (I have to say this otherwise I feel misleading), you're supposed to use appendChild:
var script = '$(window).load(function(){ someFooo(); });';
newScript.appendChild(document.createTextNode(script));
But that fails in IE (<script> elements are not allowed to have children or some other inane IE thing). So just go with the former.
Looks like this is an issue with innerHTML being used in a readonly element. This happens too when you try to set innerHTML in tbody. According to msdn documentation:
(...) Doing this can also be a little
tricky because you cannot use
innerHTML to inject into the HEAD or
STYLE element directly. (Both of these
tags are READ ONLY.) The best way to
dynamically add styles to a page is to
wait for the document to load, and
then create the rule in a new style
sheet.
Why is it a readonly attribute? Here is an explanation.
So, you need to use a DOM approach to do this dynamic loading. Check this link to load external javascript resources and this one to inline scripts which is your case.
newScript = document.createElement('script');
newScript.type = 'text/javascript';
var head = document.getElementsByTagName("head")[0];
var src = '$(window).load(function(){ someFooo(); }); ';
newScript.text = src;
head.appendChild(newScript);

Categories

Resources