Can we prevent assignment to innerHTML from requesting resources? - javascript

The following code results in an HTTP request for an image resource in both Firefox and Chrome.
var el = document.createElement('div');
el.innerHTML = "<img src='junk'/>";
As a programmer, I may or may not want el to be rendered. If I don't, then maybe I don't want a request being sent for the src.
dojo.toDom() shows the same behaviour.
Is there anyway to get a document fragment from a string, without referenced resources from being requested?

Use the DOMParser to create a full document structure from a given string.
Alternatively, use the beforeload event to intercept requests.

Much lighter memory to use strings to create DOM elements instead of creating documentFragments and working with them:
var div = document.createElement('div');
div.innerHTML = 'some text';
document.getElementById('someparent').appendChild('div');
Can be replaced with:
var div = '<div>some text</div>';
document.getElementById('someparent').innerHTML += div;

Related

Is DOMParser().parseFromString() worth using?

https://developer.mozilla.org/en-US/docs/Web/API/DOMParser#DOMParser_HTML_extension
It looks like the DOMParser uses innerHTML to add stringified elements to the DOM. What's the advantage of using it?
I have compared the difference between using DOMParser().parseFromString() and using element.innerHTML below. Am I overlooking something?
Using DOMParser
const main = document.querySelector('main');
const newNodeString = '<body><h2>I made it on the page</h2><p>What should I do now?</p><select name="whichAdventure"><option>Chill for a sec.</option><option>Explore all that this page has to offer...</option><option>Run while you still can!</option></select><p>Thanks for your advice!</p></body>';
// Works as expected
let newNode = new DOMParser().parseFromString(newNodeString, 'text/html');
let div = document.createElement('div');
console.log('%cArray.from: ', 'border-bottom: 1px solid yellow;font-weight:1000;');
Array.from(newNode.body.children).forEach((node, index, array) => {
div.appendChild(node);
console.log('length:', array.length, 'index: ', index, 'node: ', node);
})
main.appendChild(div);
Using innerHTML
const main = document.querySelector('main');
const newNodeString = '<h2>I made it on the page</h2><p>What should I do now?</p><select name="whichAdventure"><option>Chill for a sec.</option><option>Explore all that this page has to offer...</option><option>Run while you still can!</option></select><p>Thanks for your advice!</p>';
// Works as expected
let div = document.createElement('div');
div.innerHTML = newNodeString;
main.appendChild(div);
I expect that DOMParser().parseFromString() provides some additional functionality that I'm unaware of.
Well, for one thing, DOMParser can parse XML files. It also validates that the XML is well formed and produces meaningful errors if not. More to the point, it is using the right tool for the right job.
I've used it in the past to take an uploaded XML file and produce HTML using a predefined XSLT style sheet, without getting a server involved.
Obviously, if all you're doing is appending the string to an existing DOM, and innerHTML (or outerHTML) works for you, continue using it.

what's the difference between appending a child element and setting innerHTML

In the first example, the script was executed, but not in the second example, the Dom results are the same.
// executable
var c = 'alert("append a div in which there is a script element")';
var div = document.createElement('div');
var script_2 = document.createElement('script');
script_2.textContent = c;
div.appendChild(script_2);
document.body.appendChild(div);
// unexecutable although the dom result is same as above case
var d = '<script>alert("append a div that has script tag as innerHTML")';
var div_d = document.createElement('div');
div_d.innerHTML = d;
document.body.appendChild(div_d);
.innerHTML allows you to add as much HTML as you want in one easy call.
.appendChild allows you to add a single element (Or multiple elements if you append a DocumentFragment).
If you use .innerHTML then you need to include the opening and closing tags correctly. Your HTML must be proper.
When elements that were created using document.createElement then auto generate the appropriate opening and closing tags.
Your example for .innerHTML is not properly formed. Instead of:
var d = '<script>alert("append a div that has script tag as innerHTML")';
it should be:
var d = '<script>alert("append a div that has script tag as innerHTML")</script>';
UPDATE:
Interesting!!
I know that, in the past, your second example would have worked. But it seems that, probably for security reasons, the browser no longer allows you to insert <script> through .innerHTML.
I tried on Chrome 62 and it fails. Firefox 57 fails and Safari 11.0.2 fails.
My best guess is that this is a security update.
Look here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
And go down to the Security considerations section.
It reads:
It is not uncommon to see innerHTML used to insert text in a web page. This comes with a security risk.
const name = "John";
// assuming 'el' is an HTML DOM element
el.innerHTML = name; // harmless in this case
// ...
name = "<script>alert('I am John in an annoying alert!')</script>";
el.innerHTML = name; // harmless in this case
Although this may look like a cross-site scripting attack, the result is harmless. HTML5 specifies that a tag inserted via innerHTML should not execute.

How to execute document.querySelectorAll on a text string without inserting it into DOM

var html = '<p>sup</p>'
I want to run document.querySelectorAll('p') on that text without inserting it into the dom.
In jQuery you can do $(html).find('p')
If it's not possible, what's the cleanest way to to do a temporary insert making sure it doesn't interfere with anything. then only query that element. then remove it.
(I'm doing ajax requests and trying to parse the returned html)
With IE 10 and above, you can use the DOM Parser object to parse DOM directly from HTML.
var parser = new DOMParser();
var doc = parser.parseFromString(html, "text/html");
var paragraphs = doc.querySelectorAll('p');
You can create temporary element, append html to it and run querySelectorAll
var element = document.createElement('div');
element.insertAdjacentHTML('beforeend', '<p>sup</p>');
element.querySelectorAll('p')

How can I manipulate the DOM from a string of HTML in JavaScript?

I'm developing a Windows 8 Metro App using JavaScript. I need to manipulate a string of HTML to select elements like DOM.
How can I do that?
Example:
var html = data.responseText; // data.response is a string of HTML received from xhr function.
// Now I need to extract an element from the string like document.getElementById("some_element")...
Thanks!
UPDATE:
I solved!
var parser = new DOMParser();
var xml = parser.parseFromString(data.responseText);
I think your approach to the problem isn't the best, you could return JSON or xml. But if you need to do it that way:
To my knowledge you wont be able to use getElementById without inserting a new element in the document (in the example below, doing inserting div in document, for example document.appendChild(div)), but you could do this:
var div = document.createElement("div");
div.innerHTML = '<span id="rawr"></span>'; //here you would put data.responseText
var elements = div.getElementsByTagName("span"); // [<span id="rawr"></span>], there you could ask elements[0].id === "rawr" or whatever you like

construct a DOM tree from a string without loading resources (specifically images)

So I am grabbing RSS feeds via AJAX. After processing them, I have a html string that I want to manipulate using various jQuery functionality. In order to do this, I need a tree of DOM nodes.
I can parse a HTML string into the jQuery() function.
I can add it as innerHTML to some hidden node and use that.
I have even tried using mozilla's nonstandard range.createContextualFragment().
The problem with all of these solutions is that when my HTML snippet has an <img> tag, firefox dutifully fetches whatever image is referenced. Since this processing is background stuff that isn't being displayed to the user, I'd like to just get a DOM tree without the browser loading all the images contained in it.
Is this possible with javascript? I don't mind if it's mozilla-only, as I'm already using javascript 1.7 features (which seem to be mozilla-only for now)
The answer is this:
var parser = new DOMParser();
var htmlDoc = parser.parseFromString(htmlString, "text/html");
var jdoc = $(htmlDoc);
console.log(jdoc.find('img'));
If you pay attention to your web requests you'll notice that none are made even though the html string is parsed and wrapped by jquery.
The obvious answer is to parse the string and remove the src attributes from img tags (and similar for other external resources you don't want to load). But you'll have already thought of that and I'm sure you're looking for something less troublesome. I'm also assuming you've already tried removing the src attribute after having jquery parse the string but before appending it to the document, and found that the images are still being requested.
I'm not coming up with anything else, but you may not need to do full parsing; this replacement should do it in Firefox with some caveats:
thestring = thestring.replace("<img ", "<img src='' ");
The caveats:
This appears to work in the current Firefox. That doesn't meant that subsequent versions won't choose to handle duplicated src attributes differently.
This assumes the literal string "general purpose assumption, that string could appear in an attribute value on a sufficiently...interesting...page, especially in an inline onclick handler like this: <a href='#' onclick='$("frog").html("<img src=\"spinner.gif\">")'> (Although in that example, the false positive replacement is harmless.)
This is obviously a hack, but in a limited environment with reasonably well-known data...
You can use the DOM parser to manipulate the nodes.
Just replace the src attributes, store their original values and add them back later on.
Sample:
(function () {
var s = "<img src='http://www.google.com/logos/olympics10-skijump-hp.png' /><img src='http://www.google.com/logos/olympics10-skijump-hp.png' />";
var parser = new DOMParser();
var dom = parser.parseFromString("<div id='mydiv' >" + s + "</div>", "text/xml");
var imgs = dom.getElementsByTagName("img");
var stored = [];
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
stored.push(img.getAttribute("src"));
img.setAttribute("myindex", i);
img.setAttribute("src", null);
}
$(document.body).append(new XMLSerializer().serializeToString(dom));
alert("Images appended");
window.setTimeout(function () {
alert("loading images");
$("#mydiv img").each(function () {
this.src = stored[$(this).attr("myindex")];
})
alert("images loaded");
}, 2000);
})();

Categories

Resources