Ways to increase performance when set big value to innerHTML - javascript

I'm trying to set a huge (200K) response to innerHTML of a node. The goal is to get better time than 2.7 sec in Internet Explorer 6.
Any ideas?

This won't make it happen any faster but it will stop the browser from locking up on the user; they can continue to use the page while this happens in the background:
function asyncInnerHTML(HTML, callback) {
var temp = document.createElement('div'),
frag = document.createDocumentFragment();
temp.innerHTML = HTML;
(function(){
if(temp.firstChild){
frag.appendChild(temp.firstChild);
setTimeout(arguments.callee, 0);
} else {
callback(frag);
}
})();
}
Using it:
var allTheHTML = '<div><a href="#">.............</div>';
asyncInnerHTML(allTheHTML, function(fragment){
myTarget.appendChild(fragment); // myTarget should be an element node.
});
This technique will take longer than plain innerHTML but the user will be able to carry on using your site without noticing a delay.

First off, I would make sure your HTML is as simple as possible. As minimal structure markup as possible. Use CSS everywhere, including its cascading feature so you're not setting class attribute everywhere, etc.
Then try some further research in the best methods per browser. For example, some do better by building DOM objects, some setting all of it to the innerHTML of a single non-tree DOM node then adding it to the tree, etc. I suggest you read Quirksmode.org's Benchmark - W3C DOM vs. innerHTML.
You'll also want to probably use a script like JimmyP suggested to add in chunks, so the browser UI doesn't appear to hang. Although, you don't want to split it up too much, or you'll be unnecessarily triggering too many page refreshes when you don't need them.

why not bring the response in json format and process it on the client side, for example here a big amount of data is driven each time you change any navigation option, and all rows are rendered by the client.

Make the HTML as simple as possible, or use XML with the shortest possible element / attribute names.
Style with CSS (Don't use XML with XSLT, that could end up being even slower to parse / build).
The handling of XML in different browsers is not compatible unfortunately.

With a large HTML payload you run the risk of the "Operation Aborted" error in IE 6. Take some time and review the following StackOverflow questions, as the error arises from updating the DOM using innerHTML and to date MS does not have a complete fix in place:
Why Does Asp.net cause the operation aborted error in ie7?
How To Overcome IE Bug When Dynamically Attaching DIV To Body From Script?

Make sure your JSON/HTML/Plain Text is as simple as possible. The easiest solution would be to do less work, but if you really do need to stream 200k, make sure the transport to the browser is compressed, which should be configurable in your app and/or web server. Gzip'ping content (assuming you're using an AJAX-friendly service) really does help out, especially with simple text.
Look at other things like any loops that can be simplified, etc., but it sounds like making sure the data is ready and making sure that data can be sent across the wire efficiently will be the most help.
If IE is the outlier and all other browsers work well, you might need to move on. There's only so much you can do, but I suspect it's not the main problem.

I've heard that innerText is about two times faster than innerHTML in IE. Not sure if it's true, though, but it might be worth a try.
if (window.ActiveXObject) { // we're using IE
document.getElementById('myElement').innerText = 'Bla bla bla bla';
// or create a textnode:
var txt = document.createTextNode('Lorem ipsum');
document.getElementById('myElement').appendChild(txt);
} else {
// other code here
}
Update: Note that if you want to modify the HTML of myElement, don't use innerText – the HTML will be visible in plain text, like if you were using < and >.

Related

Is there a technique to use a W ^ X mechanic in HTML5 to fight XSS?

I wonder if it is possible to tell the Browser to only Execute JS Code that is in the tags in the initial loading of the page. Thus not executing any tags that were inserted dynamically by JS Code with
element.innerHTML = "<script>XSSCode</script>"
I think this might make many XSS attacks impossible.
Edit: el.innerHTML is only one example of adding a new script
tag to a Webpage.
There are two different proposals in your question.
...if it is possible to tell the browser to only execute JS code that
is in the tags in the initial loading of the page
You would then prevent code splitting and force everyone to bundle full scripts. Also, there is document.head.appendChild(...), where child is document.createElement("script") - this functionality kind of creates a script for "initial loading of the page" right in the <head/>. It is a bad idea in many ways to prevent appending scripts to document's DOM.
tags that were inserted dynamically
...it is a different proposal. If you are saying browser still allows JS to create script tags from within the code, but el.innerHTML should not allow script tag at all, this might not be too limiting for certain cases. You can achieve it e.g. by overriding Element.prototype's innerHTML. It is still a bad idea, but might help prevent a certain attack.
An example of the code is in accepted answer here:
Change innerHTML set on the fly
In essence, you would do:
var originalSet = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set;
Object.defineProperty(Element.prototype, 'innerHTML', {
set: function (value) {
// change it (ok)
var new_value = value.toString().replace(/<script/g,"");
//Call the original setter
return originalSet.call(this, new_value);
}
});
It is not very robust and I would not use it in production. But I could imagine it might help to detect a problem with third party script.

How can I speedup adding large amounts of complex HTML to the DOM

I have a plain webpage that loads the majority of it's content through an AJAX call that returns a large amount of complex HTML. When I place the retrieved HTML in the DOM, it cripples the browser for quite a while (between 5 seconds on Chrome and 35 seconds on Edge).
Example how I append the HTML to the DOM:
$.ajax("example.php").done(function (response) {
const contentElement = document.getElementById('results');
contentElement.innerHTML = response;
});
I want to avoid having to return JSON and converting it to HTML at all cost because of the complexity of the application.
The odd thing is that the browser gets crippled a little while after the inserted HTML is already visible. See the timeline below, where I can see the HTML on my screen (with proper styling) before the ~5 sec long Parse HTML events happen.
How do I speed up the parsing and appending of the HTML to the DOM?
Edit: I have tried multiple browsers and multiple methods of injecting the HTML (documentFragments, innerHTML, jquery .html(), append()). All methods are roughly as slow.
Edit2: The exact HTML injected can be seen in this gist: https://gist.github.com/Rhinni/3032e74bab0de8f40e08a3392c0243b1
Part 1 - It's not the way the code is loaded, it's just the code is invalid and wouldn't work even if hardcoded on the page.
"The odd thing is that the browser gets crippled a little while after the inserted HTML is already visible. See the timeline below, where I can see the HTML on my screen (with proper styling) before the ~5 sec long Parse HTML events happen."
There are some things that should be addressed concerning the practicality of the HTML (its preposterousness speaks for itself), its validity (which it isn't) and functionality, (which it isn't and probably never had any).
You should validate your HTML because it is very invalid, but before we get into that, when you decide to validate that mess, you'll need to separate it into about 16 parts because most online services will fall or cut the validation process early if given that much to process at one time.
The following is a list of issues that are not isolated problems due to a typo. These issues are repeated multiple times. What concerns me the most is that the values and the majority of the variables appear to be customized by hand. Hopefully I am mistaken and you didn't spend hours on customizing values that will hinder rather than be of any real use.
1. #IDs must be unique -- under no circumstances should there ever be a duplicated #ID on the same page.
14 #accordion- fixed, 14 #headingOne - fixed, 7 #model, 7 #type, 7#brand,...
There's more duped #IDs, I changed the #accordion to #acordion1 to 14 because it was necessary for each #accordion to function rather than just the first one. All related attributes that has a direct relationship with #accordion needs to be changed as well, I managed to change toggle-parent="#accodion for the sake of functionality once again. So there's 15 functioning accordions, I added a Home tab with a properly designed accordion which you can use as a template if you decide to redesign the other 14 accordions.
2. In order to use Bootstrap components, you make them according to the document.
The OP code wasn't even close to having any tabs, if you referred to the Bootstrap documents or even W3School's short tutorials, you'd know that you are required to have an <a> for each tab, so your code was short of 16 <a> to toggle 16 tabs. This is why your page is only showing the first tab of 16 tabs, it isn't because the browser just fails midway.
3. Another invalid thing I noticed was that the attribute readonly (and required to a lesser extent) was applied to almost every form control.
Why would you need the readonly attribute on a <select> tag? When assigning attributes to elements, don't start adding a ton of attributes to everything. The clutter makes readability, maintenance, and debugging impossible.
4. There are 2 Plunks:
Plunk 1 is the solution to the OP (Original Post) question which is explained in detail in Part 2 of this answer. The HTML has been partially fixed, I don't have enough time to fix everything.
It has 16 tabs and 15 accordions that work.
Load time has been reduced from 34 sec to 2 sec. with Edge. It appears that Edge heroically tries to make sense of the HTML that was parsed and then fails. The real browsers like Firefox and Chrome justs dumps it and leaves it there.
Plunk 2 is the HTML from the OP code and my solution loading it.
The results are the same, OP code is failing due to the code itself, not because of a loading problem.
Part 2 - A stable way to parse a huge string into HTML. Not needed if OP code actually worked.
OP experiencing heavy latency when attempting to add a huge amount of markup to DOM by innerHTML. Up to 34 seconds to render it completely using Edge while other browsers OP reported at 3 seconds.
I got the load time down to 2 to 3 seconds on Edge and instantly on the real browsers (Chrome and Firefox).
Although OP had tried using createDocumentFragment() already, I believe it is key to a quick load and parse of said HTML. The other key components that the OP probably didn't use are:
insertAdjacentHTML() and Immediately Invoked Function Expression
Using insertAdjacentHTML() method instead of innerHTML property. insertAdjacentHTML() is a powerful and versatile version of innerHTML.
Similarities:
Both will take a given string and parse as HTML.
Both are quick.
Differences:
insertAdjacentHTML() inserts HTML into the DOM, it doesn't overwrite any existing HTML in an element or anywhere in the DOM. innerHTML overwrites the inside of an element.
innerHTML is directed by reference to an element to which it will take a string and overwrite all content of said element with the given string. If innerHTML is just directed to an element without a string, then it will return with the HTML content of said element. innerHTML ability to GET is the only thing insertAdjacentHTML() it can't do. In contrast, insertAdjacentHTML() ability to SET is powerful as explained: insertAdjacentHTML() is directed not only by reference to an element it is told exactly where to go in relation to the referenced element by its first parameter which is one of the 4 DOMStrings that correlate to a position:
"beforebegin" places the string right before the beginning of the element.
`$elector.before(str)`★
"afterend" places the string right after the end of the element.
`$elector.after(str)`★
"afterbegin" places the string within the element right after the brginning. In other words the string is inserted before the element's contents.
`$elector.prepend(str)`★
"beforeend" places the string within the element right before the end. Basically the string is placed after the element's content. This position is the most optimized for speed since the are no proceeding siblings to slow things down.
`$elector.append(str)`★
insertAdjacentHTML() second parameter is the string that will be parsed into HTML. Using Template Literals instead of literal strings allows us to a whole extra level of easy string manipulation.
`element.insertAdjacentHTML("beforeend", <input id="${ID+i}" type="${typeArr[i]}" value="${Math.floor(Math.random() * i}">)`
Immediately Invoked Function Expression is a function with a special pattern.
It is usually two anonymous functions:
The outer function is wrapped in parenthesis.
The inner function usually forms a closure.
Anonymous/expression functions are created when evaluated and then they are immediately invoked due to the extra parenthesis wrapped around them.
They have no name and the variables that the inner function uses can only be accessed by the outer function because they are local scope.
These conditions make an IIFE a one time thing. The signature of an IIFE have slight variances in their signature but the gist of one goes like this:
`(function() { var x = function() {...} x})();`
DOM manipulation is processor intensive and the more we avoid it the better. DocumentFragment was made in order for us to do all of the menial yet numerous tasks involving the DOM -- off of the DOM. We can add as many elements, text, attributes, set event handlers, etc. to the DocumentFragment and its descendants without touching the DOM. Once everything is complete, only one DOM operation needs to be done:
`document.body.appendChild(frag);`
Demo - if you'd like to test an actual working Demo, review this Plunk
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Monstrosity</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script>
(function() {
const str = `Huge Disaster of HTML`
const frag = document.createDocumentFragment();
const node = document.createElement('div');
let build = function(node, str, frag) {
node.insertAdjacentHTML('beforeend', str);
frag.appendChild(node);
document.body.appendChild(frag);
}
build(node, str, frag);
}());
</script>
</body>
</html>
Simply appending or inserting the HTML you have provided into the browser does not seem to have any adverse affects. At least for me on my browser/computer. (chrome)
Run this example and see if you experience any delays or pauses..
See Example
document.getElementById("test").innerHTML = toAppend;
Obviously this is not a complete test because I am missing your CSS and I have modified your html slightly by removing line breaks so that I could assign the text to a variable in the text editor.
If the example works for you then we need to investigate further the data coming from the server, and try adding css to the equation etc..
If the example is causing delays then the problem is likely hardware related, maybe you don't have enough available memory and or cpu which is crippling the browser.
I fixed the issue with the help of the answers of Trevor and Zer00ne, but it was something completely different.
The issue was caused by the Laravel Debugbar which tracks AJAX requests by default, and parses the response for debugging purposes. Disabling AJAX request tracking in the Debugbar config solved the issue.

Use one file to display to lots of webpages

I'm building a simple webpage. However there are a few dozen sub pages. The way this guy organises his business means the phone number is constantly changing between those in charge of taking calls any given week. Is there a way I can change a single line of text (say in a css file) and have the phone number posted on all the sub pages change every week according to who is in charge of taking the calls?
Since then I've learned just enough to change the template from a messy html/table code to a more streamlined look to the code using css.
You COULD potentially do it via the ::after pseudo-element if this fits your browser support profile: http://caniuse.com/#feat=css-gencontent
Note that IE8 (the only 'common' browser without support of ::after instead of :after) is EOL early next year (and there will be much rejoicing).
While this is an absolutely terrible way of doing this- you're supposed to use CSS for presentation not information, I'm not gonna tell you don't if this is just a temporary hack to save yourself a headache while you implement something less terrible.
Sample of how-to: http://dabblet.com/gist/b4bd30443cdbd810d8a8
Call <span class="data-onCallPhnNum"></span> for help.
.data-onCallPhnNum::after{
content:"(555)-555-5555";
}
Should note that the primary disadvantage of this is that there is no fallback if the browser cannot render the ::after pseudoelement.
Better yet, you could include a Javascript file like this:
[....]
<script type="text/javascript" src="phonenum.js"></script>
</body>
</html>
and having the js in phonenum.js (and its expected HTML use) be:
(function(){
var phnNum = "(555)-555-5555";
var phnLnks = document.getElementsByClassName("data-onCallPhnNum");
for(var i = phnLnks.length >>> 0; i--;){
phnLnks[i].href = "tel:" + phnNum;
phnLnks[i].innerHTML = phnNum;
}
})();
Call the number listed on our contact page for assistance.
This would accomplish the same thing, only not work on browsers with js turned off, has a natural fallback, has a clickable phone number for mobile viewers, and isn't using CSS for information.
You should still eventually move this into a database and have the number pulled server side, but for a hack to save on headaches before that real solution's ready, either'll do.
EDIT NOTE: Beers go to CBroe for suggesting the tel: protocol and the formalization of the fallback.
There are two other options, neither on is strictly an HTML solution.
Server Side Includes are one option, though they are falling out of favor.
Another option is using a server-side template system, such as PHP.

Cross-browser JavaScript to alert on DOM being modified within a DIV?

I am a basic JavaScript hacker and not an advanced programmer and I would appreciate some pointers.
I am after a JavaScript (or JQuery) function that can monitor the DOM and alter if the content within a specified DIV has been changed. I want this new content captured in a variable for further processing (but for now should be echoed to console.log or alert to demonstrate success).
The DIV is content that will be updated by a separate AJAX process or may contain an iFrame, neither of which I will have full control over. The content may be updated multiple times and on an infrequent and unstructured basis. The contents of the DIV may also change format and could contain any sort of content.
I believe I need a JS event to handle this (rather than any sort of interval based check) and I have been looking at the DOMsubtreeModified function, but not only can't I make it work consistently, it appears that this is not reliable across browsers and I need this to work regardless of the client.
Am I barking up the wrong tree? Is this possible in a cross-browser way? Should I continue to hack on DOMSubtreeModified to try and get it working or is there a better method?
DOMSubtreeModified is the right event.
This might help you, I created it after reading your question. I think it does what you want it to do.
<div class="change_event_box">
1234
</div>
<script>
$(document).ready(function() {
$('.change_event_box').bind("DOMSubtreeModified",function(){
alert('changed');
});
setTimeout(function() {
$('.change_event_box').text("4321");
}, 5000);
});
</script>
http://jsfiddle.net/F4FMk/

JavaScript - controlling the insertion point for document.write

I would like to create a page that runs a 3rd party script that includes document.write after the DOM was already fully loaded.
My page is not XHTML. My problem is that the document.write is overwriting my own page. (which is what it does once the DOM was loaded).
I tried overriding the document.write function (in a way similiar to http://ejohn.org/blog/xhtml-documentwrite-and-adsense/) but that doesn't cover cases where the document.write contains partial tags.
An example that would break the above code is:
document.write("<"+"div");
document.write(">"+"Done here<"+"/");
document.write("div>");
Is there some way to modify the document.write insertion point through JavaScript? Does anyone have a better idea how to do this?
If you're dealing with 3rd party scripts, simply replacing document.write to capture the output and stick it in the right place isn't good enough, since they could change the script and then your site would break.
writeCapture.js does what you need (full disclosure: I'm the author). It basically rewrites the script tags so that each one captures it's own document.write output and puts it in the correct place. The usage (using jQuery) would be something like:
$(document.body).writeCapture().append('<script type="text/javascript" src="http://3rdparty.com/foo.js"></script>');
Here I'm assuming that you want to append to the end of the body. All jQuery selectors and manipulation methods will work with the plugin, so you can inject it anywhere and however you want. It can also be used without jQuery, if that is a problem.
It is possible to override the document.write method. So you can buffer the strings sent to document.write and output the buffer wherever you like. However changing a script from synchronous to asynchronous can cause errors if not handled correctly. Here's an example:
Simplified document.write replacement
(function() {
// WARNING: This is just a simplified example
// to illustrate a problem.
// Do NOT use this code!
var buffer = [];
document.write = function(str) {
// Every time document.write is called push
// the data into buffer. document.write can
// be called from anywhere, so we also need
// a mechanism for multiple positions if
// that's needed.
buffer.push(str);
};
function flushBuffer() {
// Join everything in the buffer to one string and put
// inside the element we want the output.
var output = buffer.join('');
document.getElementById("ad-position-1").innerHTML = output;
}
// Inject the thid-party script dynamically and
// call flushBuffer when the script is loaded
// (and executed).
var script = document.createElement("script");
script.onload = flushBuffer;
script.src = "http://someadserver.com/example.js";
})();
Content of http://someadserver.com/example.js
var flashAdObject = "<object>...</object>";
document.write("<div id='example'></div>");
// Since we buffer the data the getElementById will fail
var example = document.getElementById("example");
example.innerHTML = flashAdObject; // ReferenceError: example is not defined
I've documented the different problems I've encountered when writing and using my document.write replacement: https://github.com/gregersrygg/crapLoader/wiki/What-to-think-about-when-replacing-document.write
But the danger of using a document.write replacement are all the unknown problems that may arise. Some are not even possible to get around.
document.write("<scr"+"ipt src='http://someadserver.com/adLib.js'></scr"+"ipt>");
adLib.doSomething(); // ReferenceError: adLib is not defined
Luckily I haven't come across the above problem in the wild, but that doesn't guarantee it won't happen ;)
Still want to try it out? Try out crapLoader (mine) or writeCapture:
You should also check out friendly iframes. Basically it creates a same-domain iframe and loads everything there instead of in your document. Unfortunately I haven't found any good libraries for handling this yet.
Original Answer before the edit:
Basically the problem of document.write is that it does not work in XHTML documents. The most broad solution then (as harsh as it may seem) is to not use XHTML/xml for your page. Due to IE+XHTML and the mimetype problem, Google Adsense breaking (may be a good thing :), and the general shift towards HTML5 I don't think it's as bad as it seems.
However if you'd really like to use XHTML for your page, then John's script that you linked to is the best you've got at this point. Just sniff for IE on the server. If the request is from IE, don't do anything (and don't serve the application/xhtml+xml mimetype!). Otherwise drop it into the <head> of your page and you're off to the races.
Re-reading your question, is there a specific problem you have with John's script? It is known to fail in Safari 2.0 but that's about it.
You may be interested in the Javascript library I developed which allows to load 3rd party scripts using document.write after window.onload. Internally, the library overrides document.write, appending DOM elements dynamically, running any included scripts which may use document.write as well.
Unlike John Resig's solution (which was part of the inspiration for my own code), the module I developed supports partial writes such as the example you give with the div:
document.write("<"+"div");
document.write(">"+"Done here<"+"/");
document.write("div>");
My library will wait for the end of the script before parsing and rendering the markup. In the above example, it would run once with the full string "<div>Done here</div>" instead of 3 times with partial markup.
I have set up a demo, in which I load 3 Google Ads, an Amazon widget as well as Google Analytics dynamically.
FWIW, I found postscribe to be the best option out there these days - it handles wrapping a pesky ad rendering module like a charm allowing our page to load without being blocked.
In order to alter the content of the page after the DOM has rendered you need to either use a javascript library to append HTML or text at certain points (jQuery, mootools, prototype, ...) or just use the innerHTML property of each DOM element to alter/append text to it. This works crossbrowser and doesn't require any libraries.
There are better ways to do this.
2 ways
1) Append
<html><head>
<script>
window.onload = function () {
var el = document.createTextNode('hello world');
document.body.appendChild(el);
}
</script></head><body></body></html>
2) InnerHTML
<html><head><script>
window.onload = function () {
document.body.innerHTML = 'hello world';
}
</script></head><body></body></html>

Categories

Resources