I heared that simply to say document.getElementById('divId').innerHTML = ""; is not safe for future divId contents editing via JS so how to clean up divs contents safely?
What should always work is:
var div = document.getElementById('divId');
while(div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
innerHTML is not part of any specification but is widely supported by browsers.
I wrote something similar to Felix Kling, only diffirence is that I wanted to pass the id or the reference. So I wrote it like this:
function removeAllChildNodes(node) {
i = (typeof(node) == "object") ? node : document.getElementById(node);
while (i.hasChildNodes()) {
i.removeChild(i.firstChild);
}
}
Setting the innerHTML attribute to nothing will eliminate any DOM nodes withing the specified DIV. There is no risk of "safety".
You can do this safely :P
I believe this is where JS frameworks, such as jQuery, come to light... they are optimized for cross-browser security - which includes secure DOM element removal, although you will loose a bit of computing speed (setting innerHTML is always the fastest way)
as for others wondering about security of this, some browsers (notably MSIE) have flaws that would crash the browser or allow malicious procedures to be executed on client's side under certain conditions, so that's why setting innerHTML might not be as secure as people use to think ;-)
Related
Im am trying to keep my CSP policy as strict as possible. I need to include 3d party component in my bundle. But it uses element.setAttribute('style'...) method which breaks CSP. Is there a way to allow this particular script to inline styles in that manner?
Yes, there is a way.
There is much discussion about this here: https://github.com/w3c/webappsec-csp/issues/212
Which is succinctly summarised towards the end:
CSP is checked at parsing and blocks parsing the style attribute. Any direct operations go through.
Using setAttribute invokes the HTML parser and CSP is triggered.
So, instead of:
.setAttribute("style","background:red"); // needs HTML parsing
You would need:
.style.background = "red"; // direct manipulation
It may sound odd that one method works and the other does not, I think the understanding here is that there is a subtle difference between HTML attributes and DOM properties. https://joji.me/en-us/blog/html-attribute-vs-dom-property/
2018-10-06 update
The original answer here is still correct for now — because with CSP as currently implemented in browsers at least, there’s still no way to have dynamically injected styles at all without specifying unsafe-inline, and specifying unsafe-inline basically negates the whole purpose of CSP.
However, CSP3 adds a new unsafe-hashes expression for enabling you to allow particular inline scripts/styles. See https://w3c.github.io/webappsec-csp/#unsafe-hashes-usage, and see Explainer: ‘unsafe-hashes’, ‘unsafe-inline-attributes’ and CSP directive versioning. It hasn’t shipped in any browsers yet, though. So for the time being, the answer below still fully applies.
The only way to allow style attributes is to use unsafe-inline. It doesn’t matter whether the style attributes are coming from a different origin or from self—they’re still going to be considered a CSP violation unless you have unsafe-inline.
Specifically, one solution that won’t work for style attributes is to use a nonce or hash—because in CSP, nonce and hash usage are only defined for style and script elements; the spec has a Hash usage for style elements section that explicitly omits defining hash use for style attributes.
So even if in your policy you specify the correct hash for the contents of a style attribute, your browser will still handle it as a violation.
The bottom line is that since unsafe-inline is the only way to allow style attributes—but using unsafe-inline pretty much completely defeats the purpose of having any CSP policy to begin with—the only safe solution from a CSP perspective is just to never use style attributes—neither directly from your own markup/code nor by way of any third-party code.
For anyone looking for a jQuery patch to change setting the style attrib into setting the proper css values, here is one I use (sourced from this Github but with an important bug fix to make it work correctly):
var native = jQuery.attr;
jQuery.attr = function (element, attr, value) {
if (attr === 'style') {
resetStyles(element);
return applyStyles(element, value);
} else {
//native.apply(jQuery, arguments);
return native(element, attr, value);
}
};
function applyStyles(element, styleString) {
if (styleString) {
var styles = styleString.split(';');
styles.forEach(function (styleBit) {
var parts = styleBit.split(':');
var property, value;
if (parts.length === 2) {
property = parts[0].trim();
value = parts[1].trim();
element.style[property] = value;
}
});
return styleString;
}
}
function resetStyles(element) {
var styleList = [].slice.call(element);
styleList.forEach(function (propertyName) {
element.style.removeProperty(propertyName);
});
}
It is strange why the '3 years old' question appeared in the new ones, and why the topicstarter's issue still unsolved.
The issue of using element.setAttribute('style', ...) without causing a CSP violation is easily solved with a little hack that globally replaces the problematic element.setAttribute('style') with the safe element.style.ptop = '...'. After that you can use setAttribute('style') without breaking CSP, this will fix jQuery and other libs too.
Solutions using only the CSP itself will be ineffective because:
the 'unsafe-hashes' token is still not supported in all browsers.
in the case of third-party JavaScript libraries that actively use dynamic styles through 'setAttribute('style')', it is technically impossible to maintain a list of dozens of hashes. In addition, CSP headers are limited in size.
cakeboeing727 started on the right track, but unfortunately the idea of the new contributor was not heard by society. It's a pity.
because of some problems with joomla "in-content javascript" I have to give all my js logic to one file, but there are problems with inconsistence of dom elements across my site (it is ajax driven, so there is only one script and various DOMs).
What is the best solution to make some conditionals solving this problem..
Is it checking $(selector).length, or is there any better solution..
And in case of the $(selector).length , is there a way to save this selector to variable (performance issues)
for example some kind of
var selector = ($(selector).length !== 0) ? this : false ;
if(selector) { makeSomething; }
The this is actually pointing to Window object..So is there any way to make it like this without need of reselection?
Thanks
var $obj = $('selector');
if ($obj.length) { makeSomething(); }
Actually, this is only meaningful if you are searching for the existence of a certain element (that might identify a whole page) and running several operations based on that.
If you just want to do something on the elements like
$('selector').append('x');
the condition might be useless, because if the jQuery collection is empty, the methods won't run anyways (as pointed out by #Gary Green).
I am actually making a Sidebar Gadget, (which is AJAX-based) and I am looking for a way to extract a single element from an AJAX Request.
The only way I found yet was to do something like that:
var temp = document.createElement("div");
temp.innerHTML = HttpRequest.innerText;
document.body.appendChild(temp);
temp.innerHTML = document.getElementByID("WantedElement").innerText;
But it is pretty ugly, I would like to extract WantedElement directly from the request without adding it to the actual document...
Thank you!
If you're in control of the data, the way you're doing it is probably the best method. Other answers here have their benefits but also they're all rather flawed. For instance, the querySelector() method is only available to Windows Desktop Gadgets running in IE8 mode on the host machine. Regular expressions are particularly unreliable for parsing HTML and should not be used.
If you're not in control of the data or if the data is not transferred over a secure protocol, you should be more concerned about security than code aesthetics -- you may be introducing potential security risks to the gadget and the host machine by inserting unsanitized HTML into the document. Since gadgets run with user or admin level privileges, the obvious security risk is untrusted source/MITM script injection, leaving a hole for malicious scripts to wreak havoc on the machine it's running on.
One potential solution is to use the htmlfile ActiveXObject:
function getElementFromResponse(divId)
{
var h = new ActiveXObject("htmlfile");
h.open();
// disable activex controls
h.parentWindow.ActiveXObject = function () {};
// write the html to the document
h.write(html);
h.close();
return h.getElementById("divID").innerText;
}
You could also make use of IE8's toStaticHTML() method, but your gadget would need to be running in IE8 mode.
One option would be to use regular expressions:
var str = response.match(/<div id="WantedElement">(.+)<\/div>/);
str[0]; // contents of div
However, if your server response is more complex, I'd suggest you to use a data format like JSON for the response. Then it would be much cleaner to parse at the client side.
You could append the response from XMLHttpRequest inside a hidden div, and then call getElementById to get the desired element. Later remove the div when done with it. Or maybe create a function that handles this for you.
function addNinjaNodeToDOM(html) {
var ninjaDiv = document.createElement("div");
ninjaDiv.innerHTML = html;
ninjaDiv.style.display = 'none';
return ninjaDiv;
}
var wrapper = addNinjaNodeToDOM(HttpRequest.innerText);
var requiredNode = wrapper.getElementById("WantedElement");
// do something with requiredNode
document.body.removeChild(wrapper); // remove when done
The only reason for appending it to the DOM was because getElementById will not work unless its part of the DOM tree. See MDC.
However, you can still run selector and XPath queries on detached DOM nodes. That would save you from having you to append elements to the DOM.
var superNinjaDiv = document.createElement('div');
superNinjaDiv.innerHTML = html;
var requiedNode = superNinjaDiv.querySelector("[id=someId]");
I think using getElementById to lookup the element in this case is not a good approach. This is because of extra steps you have to take to use it. You wrap the element in a DIV, inject in DOM, lookup your element using getElementById and then remove the injected DIV from DOM.
DOM manipulation is expensive and injection might cause unnecessary reflow as well. The problem is that you have a document.getElementById and not a element.getElementById which would allow you to query without injection in the document.
To solve this, using querySelector is an obvious solution which is far more easier. Else, I would suggest using getElementsByClassName if you can and if your element has a class defined.
getElementsByClassName is defined on ELEMENT and hence can be used without injecting the element in DOM.
Hope this helps.
It's somewhat unusual to pass HTML through an AJAX request; normally you pass a JSON string that the client can evaluate directly, and work with that
That being said, I don't think there's a way to parse HTML in javascript the way you want that's cross-browser, but here's a way to do it in Mozilla derivatives:
var r = document.createRange();
r.selectNode(document.body);
var domNode = r.createContextualFragment(HTTPRequest.innerText);
I know getElementsByName('something') that returns the elements with name="something", but I want to return a list of elements where custom="something", how would I do that?
There are no standard API in the DOM to do this.
If you do not mind adding jQuery to your project, you could query your elements using the jQuery attribute selector:
$("[custom='something']")
To answer my own question, it seems it was easier than I thought.
elements = document.getElementsByTagName('pre');
for (elem = 0;elem < elements.length;elem++)
{
element = elements[elem];
if (element.lang != 'php')
break;
...
}
The above happened to work in my situation. :)
This page lists all the functions of the Document object in the JavaScript available in browsers. Thus it provides getElementById(), getElementByName() and getElementByTagName().
I guess need to use something like JQuery to gain more freedom as it allows you to express more complex "queries". I'm not sure, but that might also be slower, depending on how often you have to look up things.
Hi I would like to do dom selection and manipulation out of the dom.
The goal is to build my widget out of the dom and to insert it in the dom only once it is ready.
My issue is that getElementById is not supported on a document fragment. I also tried createElement and cloneNode, but it does not work either.
I am trying to do that in plain js. I am used to do this with jQuery which handles it nicely. I tried to find the trick in jQuery source, but no success so far...
Olivier
I have done something similar, but not sure if it will meet your needs.
Create a "holding area" such as a plain <span id="spanReserve"></span> or <td id="cellReserve"></td>. Then you can do something like this in JS function:
var holdingArea = document.getElementById('spanReserve');
holdingArea.innerHTML = widgetHTMLValue;
jQuery will try to use getElementById first, and if that doesn't work, it'll then search all the DOM elements using getAttribute("id") until it finds the one you need.
For instance, if you built the following DOM structure that isn't attached to the document and it was assigned to the javascript var widget:
<div id="widget">
<p><strong id="target">Hello</strong>, world!</p>
</div>
You could then do the following:
var target;
// Flatten all child elements in the div
all_elements = widget.getElementsByTagName("*");
for(i=0; i < all_elements.length; i++){
if(all_widget_elements[i].getAttribute("id") === "target"){
target = all_widget_elements[i];
break;
}
}
target.innerHTML = "Goodbye";
If you need more than just searching by ID, I'd suggest installing Sizzle rather than duplicating the Sizzle functionality. Assuming you have the ability to install another library.
Hope this helps!
EDIT:
what about something simple along these lines:
DocumentFragment.prototype.getElementById = function(id) {
for(n in this.childNodes){
if(id == n.id){
return n;
}
}
return null;
}
Why not just use jQuery or the selection API in whatever other lib youre using? AFAIK all the major libs support selection on fragments.
If you wan tto skip a larger lib like jQ/Prototype/Dojo/etc.. then you could jsut use Sizzle - its the selector engine that powers jQ and Dojo and its offered as a standalone. If thats out of the question as well then i suppose you could dive in to the Sizzle source and see whats going on. All in all though it seems like alot of effort to avoid a few 100k with the added probaility that the code you come up with is going to be slower runtime wise than all the work pulled into Sizzle or another open source library.
http://sizzlejs.com/
Oh also... i think (guessing) jQ's trick is that elements are not out of the DOM. I could be wrong but i think when you do something like:
$('<div></div>');
Its actually in the DOM document its just not part of the body/head nodes. Could be totally wrong about that though, its just a guess.
So you got me curious haha. I took a look at sizzle.. than answer is - its not using DOM methods. It seems using an algorithm that compares the various DOMNode properties mapped to types of selectors - unless im missing something... which is entirely possible :-)
However as noted below in comments it seems Sizzle DOES NOT work on DocumentFragments... So back to square one :-)
Modern browsers ( read: not IE ) have the querySelector method in Element API. You can use that to get and element by id within a DocumentFragment.
jQuery uses sizzle.js
What it does on DocumentFragments is: deeply loop through all the elements in the fragment checking if an element's attribute( in your case 'id' ) is the one you're looking for. To my knowledge, sizzle.js uses querySelector too, if available, to speed things up.
If you're looking for cross browser compatibility, which you probably are, you will need to write your own method, or check for the querySelector method.
It sounds like you are doing to right things. Not sure why it is not working out.
// if it is an existing element
var node = document.getElementById("footer").cloneNode(true);
// or if it is a new element use
// document.createElement("div");
// Here you would do manipulation of the element, setAttribute, add children, etc.
node.childNodes[1].childNodes[1].setAttribute("style", "color:#F00; font-size:128px");
document.documentElement.appendChild(node)
You really have two tools to work with, html() and using the normal jQuery manipulation operators on an XML document and then insert it in the DOM.
To create a widget, you can use html():
$('#target').html('<div><span>arbitrarily complex JS</span><input type="text" /></div>');
I assume that's not what you want. Therefore, look at the additional behaviors of the jQuery selector: when passed a second parameter, it can be its own XML fragment, and manipulation can happen on those documents. eg.
$('<div />').append('<span>').find('span').text('arbitrarily complex JS'). etc.
All the operators like append, appendTo, wrap, etc. can work on fragments like this, and then they can be inserted into the DOM.
A word of caution, though: jQuery uses the browser's native functions to manipulate this (as far as I can tell), so you do get different behaviors on different browsers. Make sure to well formed XML. I've even had it reject improperly formed HTML fragments. Worst case, though, go back and use string concatenation and the html() method.