Race condition when hooking appendChild - javascript

I've encountered a really weird race condition when hooking appendChild.
consider this code:
var RealAppend = IframeWindow.Element.prototype.appendChild;
IframeWindow.Element.prototype.appendChild = function(){
RealAppend.apply(this, arguments); //some more code here --- >};
I'm hooking some iframe appendchild that's why I'm doing "IframeWindow.Element.prototype.appendChild" with the IframeWindow as the iframe content window.
Now after I'm hooking the appendchild then I start to append nodes to the iframe , one of the nodes is a simple div element with id="somediv" , and another node is a javascript element and as soon as the javascript is executed it will use document.createElement to create an iframe and set its attributes and than it will execute document.getElementById('somediv').appendChild(CreatedIframe);
which will append the created iframe to the "somediv" , my goal here is to intercept the last appendchild and hook the appendchild of that iframe also.
now a first I thought that maybe I need to actually override the appendchild like this:
someDiv.appendChild = function(){.....
but after more tests i can see that sometimes my code does succeed and hooks the last iframe and sometimes the SomeDiv.appendChild is the native code and my code fails.
Any ideas why?
EDIT:
I think its might related to the fact that i'm using
document.createRange().createContextualFragment(strHTML);
}
to parse html as string and then append the nodes to the iframe so maybe the div (somediv) is getting a clean appendChild from there? but why sometimes it does work and sometimes not?
one other thing is that after the page loads and i'm using dev tools to get the somediv.appendchild i'm getting the hooked code every time so the problem happens only when the javascript code executes

So i managed to figure out what was the problem, so if anyone will ever encounter this here is the solution:
When i was parsing the html string to nodes so i can append them to the iframe i was first using the "DOMParser" API and i missed the fact that the DOMParser uses the document from the caller contentWindow so instead of calling DOMParser like this:
new DOMParser();
i needed to call it like that:
new HookedIframeContentWindow.DOMParser();

Related

Event handler stuck after changing body.innerHTML

I would like to change some text in my body html with code below. Replacing works fine, but after that it seems to stuck, can't get out of function, so next parts of my script cannot be executed.
Nothing will happen after clicking .another-button. I think problem is with line that change body.innerHTML because when I delete this line everything works fine.
$(".do-replace-button").on("click", function () {
//some different code
document.body.innerHTML = document.body.innerHTML.replace(regExp, newWord);
}
$(".another-button").on("click", function (event) {
//some code that won't execute
}
You are effectively replacing the innerHTML of your <body> with something else, which is a string, the result of your replace(). That string is parsed and built into new DOM elements and the old ones are getting replaced by the new ones.
Hence, the events you bound on the old ones are gone, because the elements are gone. One way to solve this would be to:
a) bind on document itself:
$(document).on('click', '.do-replace-button', function() {
// whatever...
})
b) find another, less aggressive way to achieve whatever it is you are achieving with that replace.
Do note it's not only your events that get removed, but any event bound by any library/plugin you might have loaded and already initialized by the time your script runs. I strongly recommend against running replace on innerHTML, especially on <body>.
The call to document.body.innerHTML and the right hand assignment that comes after it is completely replacing the html inside the document, or more specifically, the parser is building a completely new node tree inside the body so all of your HTMLElement nodes that may have previously had event listeners assigned to them are no longer there. I recommend you go another route of only replacing the the HTML that matches your regex instead of the entire HTML contents of the body element. Or you could have a function that is called inside the first event-listener callback that fires after the replacement of the body HTML, that will re-initialize the event listeners.

Is necessary to call empty() before html() or before innerHTML?

I have a popup plugin which sets the html content of a <div/> with the response of an AJAX call.
Every time i open the popup, in the code i just call $("#popup").html(response). This overrides the existing html content with the new content.
Should i call $("popup").empty() before i call $("popup").html(response) so i release the browser memory used by the objects which were previously in the $("popup") div? (eventually prevent memory leaks)
PS: what if i call $("popup")[0].innerHTML = response ? should i call .empty() method?
Short answer no.
jQuery.fn.html uses DOMNode.innerHTML = after doing several other things. One is to remove any stored data on nodes (stored via $.fn.data), see http://james.padolsey.com/jquery/#v=git&fn=jQuery.fn.html for full source overview.
.innerHTML removes the children and replaces with the new html. But beware of memory leaks. I would use jQuery.fn.empty before setting innerHTML. eg.
var a = document.createElement('div'),
b = document.createElement('div');
b.appendChild(a);
b.innerHTML = 'new innerHMTL'.
You would think everything is ok. But the node replaces/removed is still captured in the varible a and therefore isn't thrown to the garbage collector. I'm not sure wether or not that jQuery stores DOMNodes internal until you call jQuery.removeData etc.
JQuery documentation of html method says:
When .html() is used to set an element's content, any content that was in that element is completely replaced by the new content. Additionally, jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
So you don't need to call empty().
No need to call empty().html(response) method ovverides the existing content.
No difference except .empty() runs faster and .html() can overwrite the content of the context
http://jsperf.com/jquery-empty-vs-html/17

Targeting an ID that is rendered by a script

I'm using a nifty little script called Tabifier (http://www.barelyfitz.com/projects/tabber/)
Now, long story short, this script, which I run in my head tag, creates a <ul> with <li>s containing <a>s. Also in the head tag it creates IDs for these <a>s. When I inspect the loaded site I can clearly see the ID tags present. However, I cannot call them using getElementById. I've been using
<script>
document.getElementById('rightpanelnav1').style.padding='200px';
</script>
as a sample script in different parts of my code but to no avail. I'm wondering wether it's the placement or order in which these things are defined in my code that's causing it not to recognize the ID. What do you think?
EDIT: I recieved a great answer below, but I still can't get 'rightpanelnav1' to register onclick events...? It's an , there shouldn't be a problem, right? And when I click it, the entire page has been loaded for several seconds...
Firstly, in order to access an element in the DOM, the element must be a part of the DOM (document). So if you place your <script> with getElementById in the page at a place prior to where the element is loaded, it will not see the element in the DOM.
Secondly, it is highly probable that this library you use does its modification on page load, which would mean that no matter where you place your <script> it would have no chance of seeing these elements before running.
As a result, you should have your script wait as well, and do this:
window.onload = function(){
document.getElementById('rightpanelnav1').style.padding='200px';
};
Or for a click event
window.onload = function(){
document.getElementById('rightpanelnav1').onclick = function(){
alert("clicked!");
};
};

Modifying innerHTML of an element created with innerHTML

Basically, I have a div that I insert content into with innerHTML. A segment of the HTML inserted is:
<p style="text-align:center;"><span class="bold" id="timer">00:00:00:0</span></p>
This is a timer that should increment using a setTimeout. Later, I call the following function to modify the timer block:
document.getElementById("timer").innerHTML = output;
When checking the innerHTML property with Firebug or an alert, the innerHTML of the span element is incrementing (e.g. it will say "00:00:10:6" or "00:01:01:4"). However, the page does not reflect the changes; the browser still shows 00:00:00:0.
I tried placing the span directly on the page as opposed to through modifying the div with innerHTML, and only then does it work. Is there anyway to make it work with innerHTML on the div?
EDIT: The page is -----
Try using Firebug or the Chrome console to check the follow:
document.getElementById('bunny-timer').innerHTML
EDIT 2: URL removed.
Without seeing the code, my best guess is that you're accidentally creating duplicate ids, and the element you're seeing is different than the one you're inspecting.
Look at this: http://plugins.jquery.com/project/timers. As you're already using jQuery quite a lot, why not play with their timer functions? They seem to be quite well-made.

How to read all child elements with tag names and their value from a xml file?

I've a xml file in which I'm storing some HTML content in an element tag called <body>. Now I'm trying to read all the HTML content of body tag using XML DOM in JavaScript.
I tried this code:
var xmlDoc=loadXMLDoc('QID_627.xml');
var bodytag = xmlDoc.getElementsByTagName("body");
document.write(bodytag);
but it is showing [object HTMLCollection] message on the browser screen.
Try this:
var xmlDoc=loadXMLDoc('QID_627.xml');
var bodytags = xmlDoc.getElementsByTagName("body");
document.write(bodytags[0]);
getElementsByTagName returns an array of elements (even if just one is found) so you need to subscript the array to retrieve your element.
Andrew Hare pointed out that getElementsByTagName() always returns an array, so you have to use bodytag[0] to get the element you want. This is correct, but not complete since even when you do that you'll still get an equally useless "[object ElementName]" message.
If you're set on using document.write() you can try to serialize out the content of the body tag with
document.write(bodytag[0].innerHTML);
Better yet would be directly attaching the source DOM nodes into your destination DOM.
You'd use something like
document.getElementById("destinationNodeId").appendChild(bodytag[0]);
There may be some issues with attaching DOM nodes from another document that may require you to copy the nodes, or jump through some other hoops to have it work.
You need to use document.write(bodytag.toXMLString());
EDIT: Andrew Hare also points out you need to subscript first. I think you may still need to use the toXMLString call as well.

Categories

Resources