The short answer is that you can't really do what you appear to be trying to do. It doesn't make sense to have multiple body/head/html tags, if that is what you are trying to do.
Before I say anything more, I'll say that trying to parse and inject code into your site, especially code containing JS, can be very dangerous. If it is coming from a untrusted source it could contain a XSS attack. Even if it is coming from a trusted source it might be vulnerable to a reflected XSS attack.
What you are probably actually looking for would be <iframe>
s. <iframe>
s would allow you to present one or more pages embedded inside your page. Any code loaded in the <iframe>
is sand-boxed and can't effect your parent document, thus mitigating the risk of XSS.
let url = 'http://www.example.org/';
let iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.width = '90%';
iframe.style.height = '300px';
iframe.style.display = 'block';
iframe.style.margin = '1em auto';
document.body.appendChild(iframe);
Due to the Same-origin policy you unfortunately can't manipulate the page you loaded if it came from another server. What you could do is use a server-side script to grab the page and make the changes and then load it into an <iframe>
. (Doing so from another sub-domain, much as the snippet system here at Stack Overflow does, thus keeping the same-origin policy in effect, so your main domain can't be accessed by the proxied page).
If whatever you are doing is just for your own use, you could also just write an userscript to modify pages directly when browsing them instead of trying to parse them and load them in another page yourself.
You can kind of fake what you appear to be trying to do using this very hacky approach that I do not at all recommend. It parses the document twice, once with DOMParser
to extract the head
tag and the attributes of the body
tag, and then again with createContextualFragment
to create the actual nodes to insert. As I said in the warning above, THIS IS DANGEROUS (not to mention slow since you are parsing the document twice) and should be avoided.
// I'm just getting the HTML from the data attribute of an element in
// the page instead of using XHR...
// Can't just store it in a string here because when the browser sees a
// script tag inside of a string it assumes it is the end of the script
// and the script contains an unterminated string literal instead of a string
// containing a script tag.
let html = document.getElementById('data').dataset.html;
// parse the document with DOMParser to get the attributes of body
let parsedDoc = (new DOMParser()).parseFromString(html, "text/html");
let bodyAttr = [...parsedDoc.body.attributes];
// parse the html into a fragment
var frag = document.createRange().createContextualFragment(html);
frag.querySelector('h1').style.color = '#f00';
// avoid inserting the style tag from the head into the middle of the document
frag.querySelectorAll('style').forEach(tag => {
frag.removeChild(tag);
});
// insert the fragment
document.body.appendChild(frag);
// replace the document head with the parsed one
document.documentElement.replaceChild(parsedDoc.head, document.head);
// augment the body of the document with the attributes
// from the parsed document
bodyAttr.forEach(attr => {
document.body.setAttribute(attr.nodeName, attr.nodeValue);
});
// AGAIN, PLEASE don't do this unless you absolute control
// over the data that will be parsed with it, i.e. it is
// also coming from your server and you authored it, it is
// not user submitted. Again, this mostly works, but is
// slow and dangerous, it would be much better to use iframes
<div id="data" data-html="
<html>
<head>
<style>
body {color: #00f}
</style>
</head>
<body style='background: #000' lang='en-us' data-test='test data'>
<h1>Hello World</h1>
<p>Some text</p>
<script>
console.log('Hello JS');document.querySelector('h1').style.background = '#FF0';
</script>
</body>
</html>
"></div>