13

So this is our scenario... First thing we do is, we append a piece of javascript code which adds external script to document like this:

(function() {
     var e = document.createElement('script'); e.type = 'text/javascript'; e.async = true; e.src = 'http://blabla.com/script.js';
     var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(e, s);
})();

Then in script.js following happens:

function ajaxCall() { 
    //... some script to send ajax request which calls createDiv() after success
   if (window.XMLHttpRequest){
      xmlhttp=new XMLHttpRequest();
   }
   else{
      xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
   }
   xmlhttp.onreadystatechange=function(){
      if(xmlhttp.readyState==4&&xmlhttp.status==200){
         createDiv(xmlhttp.responseText);
      }
   };
   xmlhttp.open("GET","http://blabla.com/api/");
   xmlhttp.send(); 
}
function parseResponse(response) {
    var parser = new DOMParser();
    var dom = parser.parseFromString(response, "text/html");
    return dom;
}
function createDiv(responsetext)
{
   var dom = parseResponse(responsetext);

   var _ws = dom.getElementById('styles');
   var h = document.getElementsByTagName('head')[0];
   h.appendChild(document.importNode(_ws, true));

   var _jr = dom.getElementById('script1').outerHTML;
   var _vc = dom.getElementById('script2').outerHTML;
   var _rv = dom.getElementById('script3').outerHTML;

   var _rw = dom.getElementById('my_div');
   var _b = document.getElementsByTagName('body')[0];
   var _d = document.createElement('div'); _d.id = 'just_id';
   _d.innerHTML = _jr + _vc + _rv;
   _d.appendChild(document.importNode(_rw, true));
   _b.appendChild(_d);
}
ajaxCall();

Everything works fine, script's and a div are being appended as expected and where expected, but appended script doesn't get executed and doesn't affect appended div. Why is that? And how can I make it execute? We also don't get any warnings/errors in console. Using latest firefox.

EDIT 1

Comment: The example script you showed just defines function, but it never actually calls them

It actually calls a function which makes an ajax request, please check edited code snippet.

EDIT 2

Comment: Have you tried to put the logic in a callback and then call it in createDiv? May be add some logs to test if its getting called but it unable to find div

I just tried console.log('hello world'); call in one of scripts which are being appended by createDiv() function, but it does nothing.

EDIT 3

For more details. When page is loaded, I can see

<script type="text/javascript" id="script1" src="http://blabla.com/script1.js"></script>
<script type="text/javascript" id="script2" src="http://blabla.com/script2.js"></script>
<script type="text/javascript" id="script3" src="http://blabla.com/script3.js"></script>
<div id="just_id">Content here</div>

In DOM-Inspector and as mentioned above, there is console.log('hello world'); to test it, but it doesnt get executed.

EDIT 4

Added some CSS through createDiv() function to document's <head></head> and it affects my div with id 'just_id'. JS still isn't working.

EDIT 5

Also tried to append inline javascript instead of external one in my function, but still no success. Can see following in DOM-Inspector, but code doesn't log anything.

<script type="text/javascript">
   console.log('test');
</script>

EDIT 6

I also tried to import nodes from as suggested here

This method is not allowed to move nodes between different documents. If you want to append node from a different document the document.importNode() method must be used.

and also tried to changed order. 1.css 2.div 3.js like this but still no success... Anyone has an idea what is going on here?

Check the code above

EDIT 7

As suggested in How do you execute a dynamically loaded JavaScript block? I set innerHTML of my div to scripts + content and then appended to DOM like this:

Check the code above

But still without any success.

EDIT 8

Added ajax function code. 3rd paramter in xmlhttp.open("GET","http://blabla.com/api/"); true (asynchronous) and false (synchronous) already tried. Still no success.

EDIT 9

function createDiv() as suggested in answer:

var dom = parseResponse(responsetext);

var _jr = dom.getElementById('script1'); //<script> tag with src attribute
var _vc = dom.getElementById('script2'); //inline <script> for test purposes only, it should actualy be external script also
var _rv = dom.getElementById('script3'); //inline <script>

var _rw = dom.getElementById('external_div');
var _b = document.getElementsByTagName('body')[0];
var _d = document.createElement('div'); _d.id = 'internal_div';
_d.appendChild(document.importNode(_rw, true));
_d.appendChild(document.importNode(_jr, true));
_d.appendChild(document.importNode(_rv, true)); //without second parameter TRUE, I dont get script content
_d.appendChild(document.importNode(_vc, true)); //without second parameter TRUE, I dont get script content
_b.appendChild(_d);

Doesn't work. Work's only if I take innerHTML of inline scripts from other document, create elements like suggested in answer, set their innerHTML and then append do current document. External script's wont load at all.

EDIT 10

.htaccess from localhost... :

<Files *.html>
    Order Deny,Allow
    Deny from all
    Allow from 127.0.0.1
</Files>

<Files *.php>
    Order Deny,Allow
    Deny from all
    Allow from 127.0.0.1
</Files>

<Files index.php>
    Order Allow,Deny
    Allow from all
</Files>

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /page1/
#RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
</IfModule>

.htaccess from 192.*** ... :

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /direktexpress/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /page2/index.php [L]
</IfModule>
# END WordPress

EDIT 11

So I am probably going to give bounty @Drakes, just for the effort and his spent time on my question and going to show some screenshots what is going on. It is definitely firefox bug, I just tested on chrome and it works.

1st picture: source code of 192.168.2.197/testing/whatever/etc. .htaccess remains the same as in example above

Source Code for 192.168.2.197

2nd picture is source code for script.js, which is being loaded on 192.168.2.197/testing/whatever/etc. .htaccess remains the same as in example above:

Script.js Source

3rd picture is a screenshot of inspector, how does my dom look like:

Dom

4th picture.... nothing happens. Just a warning which has nothing to do with my problem (I hope so), because even when I remove all scripts, it still appears.

Console

Is it a firefox bug?

Community
  • 1
  • 1
ksno
  • 487
  • 1
  • 4
  • 21
  • Most likely due to [Same-origin policy](https://en.wikipedia.org/wiki/Same-origin_policy) – hindmost Jul 20 '16 at 08:53
  • 1
    @hindmost But that should give a warning/error message. – str Jul 20 '16 at 08:54
  • _Error messages for syntax errors are only available for same-origin scripts._ ([source](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Cross-origin_network_access)) – hindmost Jul 20 '16 at 08:59
  • If there is a *syntax* error in the script you are right. But it will still show an warning/error message when a script cannot be loaded because of the same-origin policy. – str Jul 20 '16 at 09:01
  • @kso: The example script you showed just *defines* functions, but it never actually *calls* them. – str Jul 20 '16 at 09:03
  • @downvoter, please add some comments and explain why this question should be closed – ksno Jul 20 '16 at 09:10
  • _it will still show an warning/error message when a script cannot be loaded because of the same-origin policy._ I didn't claim that. I explained why the OP doesn't get warnings/errors in console. – hindmost Jul 20 '16 at 09:13
  • Have you tried to put the logic in a callback and then call it in `createDiv`? May be add some logs to test if its getting called but it unable to find div – Rajesh Jul 20 '16 at 09:13
  • @hindmost what if I add inline js to document with lets say simple `console.log('so');` through my `createDiv()` function. Would it still be SOP problem? – ksno Jul 20 '16 at 09:50
  • If `console.log('test');` in a script tag in the HTML doesn't work, all comments about AJAX/DOM make no sense. What browser? Does it work in incognito mode? Any JS-blocking extensions? If you type `console.log('test');` in the *console* does it immediately log? – David Gilbertson Jul 29 '16 at 07:07
  • Firefox 47.0 for Mac. No blocking extensions. Yes it does log immediately. – ksno Jul 29 '16 at 09:17
  • You shouldn't transmutate questions like this. If you find out that you'd need to modify the question a lot it's better to just close the question and create another. This question has been modified 11 times and the final state doesn't match the original question. A careful reader would need review all versions of the question to match any given answer to historical state of the question. – Mikko Rantalainen Jun 28 '21 at 09:29

7 Answers7

12

The problem is that the code above is mixing appendChild with innerHTML. It's not intuitively obvious that the two perform differently in some cases. The former allows newly inserted script nodes to be executed as they are attached to the DOM. The latter doesn't. This has been a head-scratcher for many people. Kindly Google "script and innerHTML" and you will find that you are not alone facing this similar problem.

If you (1) change

_d.innerHTML = _jr + _vc + _rv

to

_d.appendChild(document.importNode(_jr));
_d.appendChild(document.importNode(_vc));
_d.appendChild(document.importNode(_rv));

and additionally (2) change

var _jr = dom.getElementById('script1').outerHTML;
var _vc = dom.getElementById('script2').outerHTML;
var _rv = dom.getElementById('script3').outerHTML;

to

var _jr = dom.getElementById('script1');
var _vc = dom.getElementById('script2');
var _rv = dom.getElementById('script3');

then your scripts will get executed. Here is a demonstration:

var b = document.getElementsByTagName('body')[0];

/* Works */
var s1 = document.createElement('script');
s1.innerHTML = "console.log('I will execute')";
b.appendChild(s1);

/* Fails */
var s2 = document.createElement('script');
s2.innerHTML = "while(1){alert('Do not worry! I will not execute');}";
b.innerHTML = s2.outerHTML;
console.log("It didn't execute");

Tested

I ran the OP's code on my server to replicate the problem as best I can. Given the supplied code I can make it work as intended on my server. I only changed the AJAX URL from http://blabla.com/api/ to api.html and supplied my own file as none was provided by the OP, but I pieced together what it should contain from the OP's JavaScript). Here are sample results:

index.html

<html lang="en">
<body>
    <script>
        (function() {
            var e = document.createElement('script'); e.type = 'text/javascript'; e.async = true; e.src = 'script.js';
            var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(e, s);
        })();
    </script>
</body>
</html>

script.js

This is the OP's code modified to my specifications above (using appendChild instead of innerHTML, and the AJAX call returns the contents of api.html below)

function ajaxCall() {
    //... some script to send ajax request which calls createDiv() after success
    if (window.XMLHttpRequest){
        xmlhttp=new XMLHttpRequest();
    }
    else{
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange=function(){
        if(xmlhttp.readyState==4&&xmlhttp.status==200){
            createDiv(xmlhttp.responseText);
        }
    };
    xmlhttp.open("GET","api.html");
    xmlhttp.send();
}
function parseResponse(response) {
    var parser = new DOMParser();
    var dom = parser.parseFromString(response, "text/html");
    return dom;
}
function createDiv(responsetext)
{
    var dom = parseResponse(responsetext);

    var _ws = dom.getElementById('styles');
    var h = document.getElementsByTagName('head')[0];
    h.appendChild(document.importNode(_ws, true));

    var _jr = dom.getElementById('script1');
    var _vc = dom.getElementById('script2');
    var _rv = dom.getElementById('script3');

    var _rw = dom.getElementById('my_div');
    var _b = document.getElementsByTagName('body')[0];
    var _d = document.createElement('div'); _d.id = 'just_id';

    _d.appendChild(document.importNode(_jr, true));
    _d.appendChild(document.importNode(_vc, true));
    _d.appendChild(document.importNode(_rv, true));

    _d.appendChild(document.importNode(_rw, true));
    _b.appendChild(_d);
}
ajaxCall();

api.html (returned by the AJAX call)

<!DOCTYPE html>
<html>
<body>
    <style type="text/css" id="styles"></style>
    <script type="text/javascript" id="script1" src="script1.js"></script>
    <script type="text/javascript" id="script2">console.log("incline script 2")</script>
    <script type="text/javascript" id="script3">console.log("incline script 3")</script>
    <div id="my_div"></div>
</body>
</html>

script1.js

console.log("src script1");

Here are the DevTools results to show this is working.

Devtools inspection

Console results

Drakes
  • 23,254
  • 3
  • 51
  • 94
  • Thanks for an answer, but you didn't check edit history. If you take a look on 3rd edit and 5th edit, you can see I started to add inline scripts for testing purposes. How could I execute external scripts? This works with inline scripts in the way you showed us, but the actual problem lays in external scripts, script tags with src attribute. And also we have to create `s1`, `s2` and etc elements, append innerHTML from other document inline script, and then append elements to document, but if I append them from other document in straight way, it doesn't work tho. – ksno Jul 25 '16 at 07:31
  • @ksno I took your code and ran it on my server (I updated my answer with screnshots). The external scripts execute when my modifications are made, BOTH inline scripts and the external scripts, and there is no COORS problem either as this is essentially JSONP as long as the initial ajax GET is to a domain that you control or has COORS enabled. – Drakes Jul 25 '16 at 10:12
  • Does it makes difference if 1.initial script is running on lets say http://192.168.1.194/index.html 2.loads script from http://localhost/js/script.js 3.script.js loads scripts from http://localhost/js2/script.js ? 192.168.1.194 and localhost are same machines. I really dont understand why does it work on your server and here it doens't. And yes, COORS is enabled on http://localhost. very first time I got COORS error in console, so I fixed it. – ksno Jul 26 '16 at 08:07
  • Scripts 1 through 3 can come from anywhere, any domain as they are not subject to CORS (it's CORS, not COORS as I typo'd in my above comment). It's only `script.js` that is subject to the CORS restriction. Do you have any other code? Htaccess rules? What browser are you running? It shouldn't make a difference, but there could be a bug report worth checking into. – Drakes Jul 26 '16 at 08:16
  • 192..... is just a simple wordpress page and localhost is... lets call it self-made framework. htaccess update coming on my question. Check edit 10. – ksno Jul 26 '16 at 08:31
  • And yes running lots of code of course, jQuery included, other scripts for wordpress and self-made framework but it doesn't affect the JS im debuggin atm. Running latest firefox. Debugging with standard inspector, but Firebug also installed. – ksno Jul 26 '16 at 08:37
  • What happens when you run my test code exactly as I've presented it in this answer? Can you get similar outputs? – Drakes Jul 26 '16 at 09:13
  • Unfortunately nothing. Scripts are not being executed. – ksno Jul 27 '16 at 06:46
  • @ksno Thanks for edits. This may not be a bug per se. I went searching more into this problem, and maybe firefox just doesn't display the console messages even though the scripts execute. Try adding `alert("hi");` into one of the scripts and see if it pops up. Here is a full discussion on this: http://stackoverflow.com/questions/610995/cant-append-script-element – Drakes Jul 29 '16 at 09:26
4

The JavaScript of the page has already been parsed, you need to eval the result to put it into the window context.

function evalRequest(url) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            eval(xmlhttp.responseText);
        }
    }
    xmlhttp.open("GET", url, true);
    xmlhttp.send(null);
}; 

UPDATE : you might need to get the script as text and then eval it )

function getScriptsAsText() {
    var div = document.createElement('div');
    var scripts = [];
    var scriptNodes = document.getElementsByTagName('script');

    for (var i = 0, iLen = scriptNodes.length; i < iLen; i++) {
        div.appendChild(scriptNodes[i].cloneNode(true));
        scripts.push(div.innerHTML);
        div.removeChild(div.firstChild);
    }
    return scripts;
};
tnt-rox
  • 5,400
  • 2
  • 38
  • 52
  • Ye, thought already about it, its good for inline script execution. But, unfortunately, response is html not js and there are several extern scripts, which would leed me to multiple ajax requests. – ksno Jul 29 '16 at 08:05
1

The problem is your method is asynchronous not synchronous execution. Meaning the page is still loading while you insert your dynamical scripts. There are few ways to do so:

  1. The easiest way is to use jQuery $(document).append instead of document.createElement, if you don't mind using the library.

  2. You can also use jQuery $.getScript.

  3. If you prefer not to include 3rd libraries, you have to think of a way to delay page load while you insert your scripts, something like an alert to creat a prompt window will work, but user experience not so good, think of a more smooth way.

  4. Use XMLHTTPRequest to retrieve your scripts. This is synchronous and I can see you are familiar with it because you are using it in your script already.

Good luck and happy coding.

Godinall
  • 2,280
  • 1
  • 13
  • 18
1

Have you tried getting the URL of the src file and excuting it with jQuery's getScript() function?

Change your creatediv() function :

var _jr = dom.getElementById('script1').outerHTML;

var srcScript1Left = _jr.split('src="');
srcScript1Right = srcScript1Left[1].split('"');
srcScript1 = srcScript1Right[0];

console.log(srcScript1);

$.getScript(srcScript1, function()
{
    // The external script has been executed.
});

EDIT

Similar method in pure Javascript :

var _jr = dom.getElementById('script1').outerHTML;

var srcScript1Left = _jr.split('src="');
srcScript1Right = srcScript1Left[1].split('"');
srcScript1 = srcScript1Right[0];

console.log(srcScript1); // Display the name of the external script file

if(window.XMLHttpRequest) {
    xmlhttp=new XMLHttpRequest();
} else {
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function() {
    if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
        eval(xmlhttp.responseText); // Execute the external script
    }
};
xmlhttp.open("GET",srcScript1);
xmlhttp.send();
0

You are currently loading a script into your page. This happens AFTER the page js was already executed (since is async and usually loading an external resource takes longer than the script execution).

So... basically you have the right function definitions and everything, and calling them from console later should work (I think you already tried that and was fine).

Still, the calls to your functions, even thought they are there won't execute since will not be computed after load.

To work around this, you can create an ajax request to a page that has the script, and in its callback to call whatever function you need to call.

zozo
  • 8,230
  • 19
  • 79
  • 134
0

I would say remove async, as the action comes after the initial page load

0
    (function () {
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(CreateScriptTag('http://blabla.com/script.js',true), s);
    })();
    function CreateScriptTag(src, isAsync) {
        var e = document.createElement('script')
        e.type = 'text/javascript';
        e.async = isAsync;
        e.src = src;
    return e;
    }
    function ajaxCall() {
        //... some script to send ajax request which calls createDiv() after success
        if (window.XMLHttpRequest) xmlhttp = new XMLHttpRequest();
        else  xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        xmlhttp.open("GET", "http://blabla.com/api/");
        xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) createDiv(xmlhttp.responseText); };
        xmlhttp.send();
    }
    function parseResponse(response) {
        var parser = new DOMParser();
        var dom = parser.parseFromString(response, "text/html");
        return dom;
    }
    function createDiv(responsetext) {
        var head=document.getElementsByTagName('head')[0],dom = parseResponse(responsetext),_div=document.createElement('div');
        head.appendChild(document.importNode(dom.getElementById('styles'), true)); //Assuming stylesheets are working fine.
        _div.id = 'just_id';
        //div.innerHTML = _script1 + _script2 + _script3;
        document.getElementsByTagName('body')[0].appendChild(_div.appendChild(dom.getElementById('my_div')));

        head.appendChild(CreateScriptTag(dom.getElementById('script1').getAttribute("src"),true));
        head.appendChild(CreateScriptTag(dom.getElementById('script2').getAttribute("src"),true));
        head.appendChild(CreateScriptTag(dom.getElementById('script3').getAttribute("src"),true));

    /* or another idea;
        document.write(_script1 + _script2 + _script3);
    */
    }
    ajaxCall();
c-sharp
  • 573
  • 1
  • 9
  • 25