Usage of MutationObserver in a page that is not dynamically modified - javascript

This seems like an obvious question but I can't find any definitive answer in any documentation.
I want to make sure that a function is ran when the page is loaded at first and when the page is modified by adding or removing a node.
Is a MutationObserver supposed to trigger on page load, at least once, when the DOM is parsed, even if there is no JS that adds or removes nodes?
My testing seems to indicate that it is. The Observer always gets at least one record, that includes the whole body of the document, when I get it to observe document.body with childList on true. However, I'd like to have a definitive answer, ideally from documentation (which I can't find), since perhaps this is browser dependant in some way.
This is the simple code I'm using to test:
<!DOCTYPE html>
<head>
<title>Observer test</title>
</head>
<body>
<script type='text/javascript'>
var config = { childList: true, subtree: true };
function mutationCallback (mutationsList, observer) {
console.log('Triggered');
for(let mutation of mutationsList) {
console.log(mutation.type);
console.log(mutation.target);
for (let node of mutation.addedNodes) {
console.log(node)
}
}
}
observer = new MutationObserver(mutationCallback);
observer.observe(document.body, config);
</script>
</body>
</html>

Is a MutationObserver supposed to trigger on page load, at least once, when the DOM is parsed, even if there is no JS that adds or removes nodes?
No, page load event is not related.
The observer is triggered only by DOM mutations (either in JS or in HTML parser) and you happen to have one here as well. To see it easily add a console.log for the entire list: console.log(mutationsList)
You will see that the only mutation is the addition of a text node containing spaces/newlines between the tags </script> and </body>.

It depends on where you invoke the observe() function when you observe document.body.
If you invoke it in the head, it will observe the whole loading process of the body.
If you invoke it at the end of the body, it will not observe the part of the body already loaded.

Related

Extension issue when elements are accessed directly by their id, not getElementById

Even though it is not recommended, JavaScript allows you to access elements by their id without the use of getElementById.
<iframe id="myframe" />
<script>
var i = window.myframe;
</script>
This presents a problem for extensions that need to access said elements when they themselves are being accessed. The way to do this is to call Object.defineProperty() on the prototype for getElementById so that when it is called, you can then access the element. But when referencing an element directly getElementById is not called so consequently, they are a blind spot for the extension. I've tried the following but without any luck.
Iterating over the DOM in the content script doesn't seem to work because the full DOM isn't loaded yet.
Setting a listener for 'DOMContentLoaded' isn't an option because by that time the page's scripts would have already run and potentially accessed the elements.
Attempting to insert the script before the first script on the page but again, at that point nothing really exists yet.
Does anyone know the internal mechanism that JavaScript uses when an element is referenced like this? I'm curious if there is another function that it calls internally which could possibly be hooked like getElementById. Or is there a way to ensure the content script code is run after the DOM loads but before the page scripts runs?
Or is there a way to ensure the content script code is run after the DOM loads but before the page scripts runs?
A problem is that page scripts can run before the DOM is fully loaded. For example, below:
<div id="foo"></div>
<script>
foo.textContent = 'foo';
</script>
<div id="bar"></div>
the script will run before the bar element is created (before the DOM is loaded).
It's possible to find all elements with IDs currently in the DOM with [id], and it's possible to reassign those properties on the window by simply reassigning the property - or, if you want to run code when the element is retrieved, you can use Object.defineProperty to turn it into a getter. You need to be able to find the IDs and reassign them before scripts load, which can be accomplished with a subtree MutationObserver - right before a <script> is inserted, iterate over the [id]s in the DOM, and reassign them:
console.log('(empty JS block inserted by Stack Snippet editor)');
<script>
new MutationObserver((mutations) => {
// Check to see if a script was inserted
if (mutations.every(
mutation => [...mutation.addedNodes].every(
addedNode => addedNode.tagName !== 'SCRIPT'
)
)) {
// No scripts inserted
return;
}
console.log('id reassignment running');
for (const elm of document.querySelectorAll('[id]')) {
Object.defineProperty(window, elm.id, {
get() {
console.log('Custom code running');
return elm;
},
configurable: true
});
}
})
.observe(document.body, { childList: true, subtree: true });
</script>
<div id="foo"></div>
<script>
console.log('Lower script about to reference foo');
foo.textContent = 'foo';
console.log('Lower script has finished referencing foo');
</script>
<div id="bar"></div>
It can benefit from some polishing, but that's the general idea.

How to execute code before window.load and after DOM has been loaded?

Here is the circumstance:
I have 2 pages:
1 x html page
1 x external Javascript
Now in the html page, there will be internal Javascript coding to allow the placement of the window.onload, and other page specific methods/functions.
But, in the external Javascript I want certain things to be done before the window.onload event is triggered. This is to allow customized components to be initialized first.
Is there a way to ensure initialization to occur in the external Javascript before the window.onload event is triggered?
The reason I have asked this, is to attempt to make reusable code (build once - use all over), to which the external script must check that it is in 'order/check' before the Javascript in the main html/jsp/asp/PHP page takes over. And also I am not looking for a solution in jQuery #_#
Here are some of the links on Stack Overflow I have browsed through for a solution:
Javascript - How to detect if document has loaded (IE 7/Firefox 3)
How to check if page has FULLY loaded(scripts and all)?
Execute Javascript When Page Has Fully Loaded
Can someone help or direct me to a solution, your help will be muchness of greatness appreciated.
[updated response - 19 November 2012]
Hi all, thanks for you advice and suggested solutions, they have all been useful in the search and testing for a viable solution.
Though I feel that I am not 100% satisfied with my own results, I know your advice and help has moved me closer to a solution, and may indeed aid others in a similar situation.
Here is what I have come up with:
test_page.html
<html>
<head>
<title></title>
<script type="text/javascript" src="loader.js"></script>
<script type="text/javascript" src="test_script_1.js"></script>
<script type="text/javascript" src="test_script_2.js"></script>
<script type="text/javascript">
window.onload = function() {
document.getElementById("div_1").innerHTML = "window.onload complete!";
}
</script>
<style type="text/css">
div {
border:thin solid #000000;
width:500px;
}
</head>
<body>
<div id="div_1"></div>
<br/><br/>
<div id="div_2"></div>
<br/><br/>
<div id="div_3"></div>
</body>
</html>
loader.js
var Loader = {
methods_arr : [],
init_Loader : new function() {
document.onreadystatechange = function(e) {
if (document.readyState == "complete") {
for (var i = 0; i < Loader.methods_arr.length; i++) {
Loader.method_arr[i]();
}
}
}
},
load : function(method) {
Loader.methods_arr.push(method);
}
}
test_script_1.js
Loader.load(function(){initTestScript1();});
function initTestScript1() {
document.getElementById("div_1").innerHTML = "Test Script 1 Initialized!";
}
test_script_2.js
Loader.load(function(){initTestScript2();});
function initTestScript2() {
document.getElementById("div_2").innerHTML = "Test Script 2 Initialized!";
}
This will ensure that scripts are invoked before invocation of the window.onload event handler, but also ensuring that the document is rendered first.
What do you think of this possible solution?
Thanking you all again for the aid and help :D
Basically, you're looking for this:
document.onreadystatechange = function(e)
{
if (document.readyState === 'complete')
{
//dom is ready, window.onload fires later
}
};
window.onload = function(e)
{
//document.readyState will be complete, it's one of the requirements for the window.onload event to be fired
//do stuff for when everything is loaded
};
see MDN for more details.
Do keep in mind that the DOM might be loaded here, but that doesn't mean that the external js file has been loaded, so you might not have access to all the functions/objects that are defined in that script. If you want to check for that, you'll have to use window.onload, to ensure that all external resources have been loaded, too.
So, basically, in your external script, you'll be needing 2 event handlers: one for the readystatechange, which does what you need to be done on DOMready, and a window.onload, which will, by definition, be fired after the document is ready. (this checks if the page is fully loaded).
Just so you know, in IE<9 window.onload causes a memory leak (because the DOM and the JScript engine are two separate entities, the window object never gets unloaded fully, and the listener isn't GC'ed). There is a way to fix this, which I've posted here, it's quite verbose, though, but just so you know...
If you want something to be done right away without waiting for any event then you can just do it in the JavaScript - you don't have to do anything for your code to run right away, just don't do anything that would make your code wait. So it's actually easier than waiting for events.
For example if you have this HTML:
<div id=one></div>
<script src="your-script.js"></script>
<div id=two></div>
then whatever code is in your-script.js will be run after the div with id=one but before the div with id=two is parsed. Just don't register event callbacks but do what you need right away in your JavaScript.
javascript runs from top to bottom. this means.. if you include your external javascript before your internal javascript it would simply run before the internal javascript runs.
It is also possible to use the DOMContentLoaded event of the Window interface.
addEventListener("DOMContentLoaded", function() {
// Your code goes here
});
The above code is actually adding the event listener to the window object, though it's not qualified as window.addEventListener because the window object is also the global scope of JavaScript code in webpages.
DOMContentLoaded happens before load, when images and other parts of the webpage aren't still fully loaded. However, all the elements added to the DOM within the initial call stack are guaranteed to be already added to their parents prior to this event.
You can find the official documentation here.

How do I know when DOM “body” element is available?

As soon as body DOM node is available, I'd like to add a class to it with JavaScript.
I want this to happen as soon as possible, before any of body's children are loaded.
Right now, I'm using an inline script right after opening body tag. Is there a less obtrusive way?
Might be a bit late to the party but...
You can just tap into the browser rendering cycle. Thus you don't have to deal with timeouts which leak memory (if improperly used).
var script = document.createElement('script');
script.src = '//localhost:4000/app.js';
(function appendScript() {
if (document.body) return document.body.appendChild(script);
window.requestAnimationFrame(appendScript);
})();
I would imagine this will differ between browsers.
One solution may be to test for it by placing a script immediately inside the opening <body> tag, then running your code at an interval to add the class.
<body>
<script>
function add_class() {
if(document.body)
document.body.className = 'some_class';
else
setTimeout(add_class, 10); // keep trying until body is available
}
add_class();
</script>
<!-- rest of your elements-->
</body>
jQuery does something similar internally to deal with a particular IE bug.
There isn't a guarantee that the descendant elements won't be loaded though, since again it will depend on when the particular implementation makes the body available.
Here's the source where jQuery takes a similar approach, testing for the existence of the body in its main jQuery.ready handler, and repeatedly invoking jQuery.ready via setTimeout if the body isn't available.
And here's an example to see if your browser can see the <body> element in a script at the top of the element, before the other elements. (Open your console)
Here's the same example without needing the console.

Access/Copy/Clone an Element’s Event Listeners (or Edit the lineNumber and sourceName)

Scenario
I’m writing a Chrome extension / userscript to add a little usability to a third-party site. The page that the extension is made for has a few elements that have `click` event listeners attached (per-element, no bubbling) via `addEventListener` (the `onclick` and other properties are empty). My extension clones (`cloneNode`) one of the elements and appends it to the list.
For example with this,
<div id="list">
<div id="d1">A</div>
<div id="d2">B</div>
<div id="d3">C</div>
</div>
my extension would add a D element.
Problem
Extending the list works fine, but when the original nodes are clicked, they perform the expected action, while clicking the new one does nothing.
Tests
Test 1
I examined the event listeners of the elements in Chrome’s Developer Tools and tried copying the anonymous function to my new elements with `addEventListener` (making sure to duplicate the parameters), but that did not work. It did perform some of the expected actions, but not all of them.
Test 2
I tried anfilat’s suggestion of using the trick from [this question][1]. I inserted a `script` block that then called `addEventHanlder` for the new node, and it did indeed have the new handler (with a `sourceName` referring to the site—the page, not the `.JS` file—instead of the extension), however it still threw a variable not found error.
Hypothesis
I suspect that it is a domain issue because the click-handler calls a function in an external `.JS` as referenced in the `sourceName` and `lineNumber` of the event listener as seen below. Note that the `listenerBody` is identical, but the sources differ.
Question
Is there a way to access, copy, or clone the handlers of an element and/or edit the `lineNumber` and `sourceName`?
Appendix A: Diagrams
Figure 1: Handlers of original elements referring to a .JS on the site (with slight filename edits):
Figure 2: Handlers of new elements referring to the extension:
I wrote the small working test.
Crome extension inject script:
var myScriptElement = document.createElement('script');
myScriptElement.innerHTML =
'b=document.getElementById("button");' +
'c=b.cloneNode(true);' +
'b.parentElement.appendChild(c);' +
'c.addEventListener("click", function(e){foo("from new button")}, false);';
document.querySelector('head').appendChild(myScriptElement);
test html:
<html>
<script type='text/javascript' src='test.js'></script>
<body>
<button id='button'>test</button>
<script>
document.getElementById('button').addEventListener('click', function (event) {
foo('from page');
}, false);
</script>
</body>
</html>
and test.js:
function foo(text) {
console.log(text);
};

when and where to put javascript in html

I am new to Java script. I am practicing code.When i put my code in the head section, then i get element null, and when i put it inside body, but before element, then i also get null, but if i put it inside body, but after element then i get the element. I want to ask why i am getting null in case of the first two cases. Here is my code
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="js/attributes.js"></script> // null
</head>
<body>
<script type="text/javascript" src="js/attributes.js"></script> // null
<a id="braingialink"
onclick="return showAttributes();"
href="http://www.braingia.org" >Steve Suehring's Web Site
</a>
<script type="text/javascript" src="js/attributes.js"></script> // ok
</body>
Here is my javascript
var a1 = document.getElementById("braingialink"); //get null in first two cases
window.alert(a1.getAttribute("href"));
a1.setAttribute("href", "www.google.com");
window.alert(a1.getAttribute("href"));
function showAttributes() {
var e = document.getElementById("braingialink");
var elementList = "";
for (var element in e) {
/**
* Sometimes, especially when first programming with JavaScript, you might not know what
* attributes are available for a given element. But you don’t have to worry about that, because
* of a loop that calls the getAttribute() method.
*/
var attrib = e.getAttribute(element);
elementList = elementList + element + ": " + attrib + "\n";
} //end of for()
alert(elementList);
} //end of function showAttributes
And also tell me, placing <script type="text/javascript" src="js/attributes.js"></script>
after the a element, is the same as i write script in the script tag , like
Steve Suehring's Web Site
<script type="text/javascript">
var a1 = document.getElementById("braingialink");
alert(a1.getAttribute("href"));
a1.setAttribute("href","http://www.microsoft.com");
alert(a1.getAttribute("href"));
</script>
Are both things mean to same?
Thanks
The browser parses the document from top to bottom, and if it encounters a <script> block (whether inline script or inclusion of an external JS file) it runs that JavaScript before parsing any more of the document. If that particular code block tries to refer to any elements it can only access the ones above it in the source, i.e., the ones already parsed.
The document.getElementById() method returns null if no element is found for the id you supply, so if you try to use it to access elements below it in the source they've not yet been parsed and can't be found.
The two most common practices to deal with this are:
Put all of your script at the bottom of the <body> such that when it runs all of the elements will have been parsed.
Create an "onload" handler, that is, define a function that will be run as soon as the document finishes loading. You can do this from a script block in the <head> - the JavaScript that defines the onload function is run immediately, but then the function is executed later after everything has loaded.
Following is the simplest way to do option 2:
window.onload = function() {
var x = document.getElementById("x");
// other element manipulation here
};
There is nothing stopping you doing 1 and 2 in the same document, along with throwing some <script> blocks in the middle of the document, but most people find it neater to keep all their code in the one spot.
You're getting null in the head because the DOM has not loaded - your objects are nonexistent at that time. Use this:
window.onload = function () {
// Your code
}
Oh and also take a look at the .ready() function of jQuery here. It would certainly help the headache later on.
Normally you should put script blocks inside the head tag. You can put them in the body tag if you have a special reason, for example to make the script load later because it comes from a slow server.
The reason that you can't access the element, is that the code runs before the browser has parsed the code for the element, so the element simply doesn't exist yet.
You use the load event to run the code after the document is loaded:
window.onload = function() {
// here you put the code that needs to access the elements
}
see http://www.w3schools.com/js/ and http://www.w3schools.com/js/js_whereto.asp
You can place an unlimited number of scripts in your document, and you can have scripts in both the body and the head section at the same time.
It is a common practice to put all functions in the head section, or at the bottom of the page. This way they are all in one place and do not interfere with page content.
You need to understand how web browsers load resources into a page. Firefox -> Firebug add-on Net tab shows the timeline of how resources are loaded. If you are using jQuery or something like it (and you aught to) - then stick your code inside $(document).ready(function() { .. } - that will ensure the page has fully loaded.
Also, it's a good practise to to include your custom js last thing before </body> tag - that way page DOM would have loaded.
Have a read if you want to understand this deeper:
http://www.goodreads.com/book/show/6438581-even-faster-web-sites
and
http://www.goodreads.com/book/show/1681559.High_Performance_Web_Sites
Best would be right before the closing body tag, to not disturb the page loading and rendering at all! It's also recommended by google, for example for analytics snippet and also by facebook!
you get nulls because your script executes while the browser is still loading the page. Since the page might not yet have all elements rendered, you get nulls. you need to run the script when the page has finished loading.
put your script in to the HEAD element, and invoke it on body's onload event.

Categories