How to use MutationObserver on a newly created window - javascript

I want to use MutationObserver to track DOM changes in a window I create with window.open. I have the following code:
var newWin = window.open('/somepath');
var observerConfig = {
childList: true,
subtree: true,
attributes: false,
characterData: false
};
var obs = new newWin.MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (!mutation.addedNodes) return;
mutation.addedNodes.forEach(function(addedNode) {
console.log(addedNode);
});
})
});
obs.observe(newWin.document, observerConfig);
I would expect to see some newly added nodes logged in the console (as I got when I tracked the original window the same way, same observer), but I get nothing. Am I missing something?

This is the same problem you would face when using JavaScript (from a script in <head> for instance) to modify the DOM of the current document, before the DOM has been loaded: you would be trying to access elements that do not exist in memory at this time.
You would use the DOMContentLoaded event to trigger a callback when the DOM has been loaded and is ready to manipulate.
Similarly, when you are trying to access the DOM of the new window, that window may not have finished loading yet.
You should wrap your obs.observe call in a load event listener:
newWin.addEventListener('load', function() {
obs.observe(newWin.document.body, observerConfig);
}, true);
This way when you begin observing the body you are sure that it actually exists!

Related

Simulating a click on a div added dynamically not existing in DOM

As I understand an ajax callback adds a new div to the html on my end. I can see this element added in the html however I can't access it through DOM for some reason. I tried calling it from the console in chrome and a js bookmarklet after the element was already visible/added to the html. I am not sure why the DOM is not picking it up but that's another question I guess...
This is the div:
<div class="recaptcha-checkbox-checkmark" role="presentation"></div>
And I used the below to grab a reference to it but 90% of the time it returns a null (occasionally, it actually returns a reference but I honestly don't know /understand why):
document.querySelector(".recaptcha-checkbox-checkmark");
I have been looking at different ways to bind to this new div so I can execute a click on it after it appears in the html/dom but I am having trouble putting this together as it seems you can bind to a click event but you can't bind to something like a shown event :/
So this is what I currently have but it doesn't work as (I guess) it's based on a click event and I am not actually clicking it?
var host = document.querySelector("head");
if(host){
var importJquery = function(){
var jquery = document.createElement('script');
jquery.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js")
host.appendChild(jquery);
}
importJquery();
var bindToCaptcha = function(){
var script = document.createElement("script");
script.innerHTML = '$(document).on("click", ".recaptcha-checkbox-checkmark", function(e) {alert("clicked: %o", this);});';
host.appendChild(script);
}
bindToCaptcha();
}
So just to be clear I want to identify the moment this div shows up in the html and execute a click on it but can't since I am missing a reference to it.
I was considering running a loop at an interval checking whether the div existed but I would rather like to stay away from that approach (and also I am not sure this would work because the DOM doesn't always seem to return a reference this div).
Any help greatly appreciated.
You can use the MutationObserver API to detect when a child is added to a parent element:
// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');
// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };
// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
for(var mutation of mutationsList) {
if (mutation.type == 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();
Example taken from https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#Example_usage
I couldn't get the reference because the element I was after was located within an iframe. I feel like this question would have been answered instantly by someone else had I mentioned the iframe in the first place but I am new to this so I wasn't even aware of it until I have done some further digging.

How to detect whether an dom element has loaded on the page

I have a single page application where a div with a class "abc" is getting loaded dynamically. I want to run a script only after that particular element and all its children have been loaded in the DOM. I don't want to use a timer which calls the function again and again. How do I go about this. This is probably related to mutation observer but I am not understanding how to use that.
Example with mutation observer
var target = document.querySelector('.class');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true }
// pass in the target node, as well as the observer options
observer.observe(target, config);
So for each mutation of the target element
this code console.log(mutation.type);
will be execute
$(document).on('DOMNodeInserted', '.abc', function() {
});
This will wait until the DOM element is ready and then run your code.

Call back on window object initialization (dynamic inclusion of iframes in page)

What I need:
My requirement is to inject script in to all windows that are presented in a web page.
I know we can find all windows by using window.frames property, but that won't be sufficient in my case since new windows can be added later to the page via iframes (inclusion of iframes in to the DOM)
So I need a mechanism to track windows in a page, something like callback on new window initialization.
What I tried:
I used Object.observe API to track the window.frames object changes. But I came to know that Object.observe API is going to be removed as per this link (https://esdiscuss.org/topic/an-update-on-object-observe).
So, is it good to use this API. Or if any alternate way is there please let me know
Here is a way using MutationObserver API, you can use the api to detect any element injected into your target element, even when a text is change or element is appended somewhere in your target tree
function createIframeOnDemand(wait) {
var iframe = document.createElement('iframe')
iframe.src = 'about:blank';
setTimeout(function() {
document.body.appendChild(iframe)
}, wait || 2000);
}
var body = document.body;
var observer = new MutationObserver(function(mutations) {
for(mutationIdx in mutations) {
var mutation = mutations[mutationIdx];
for(childIndex in mutation.addedNodes) {
var child = mutation.addedNodes[childIndex];
if(child.tagName && child.tagName.toLowerCase() == 'iframe') {
console.log(child);
//child is your iframe element
//inject your script to `child` here
}
}
}
});
var config = { attributes: false, childList: true, characterData: false };
observer.observe(body, config);
createIframeOnDemand();

Modify elements immediately after they are displayed (not after page completely loads) in greasemonkey script?

I have this script. It applies on RottenTomatoes movie pages , and modifies 3 elements
(1 visible text and 2 texts inside tooltips).
Currently, (due to greasemonkey's #run-at document-end) it modifies the elements only after the page has completely loaded.
Additionally, there are many times where RottenTomates pages delay loading, up to 20 sec(!),
so that's additional delay to my script.
This causes two things:
a delay in displaying the modified visible text, and that,
if you hover your mouse in either of the tooltips before the page is completely loaded, then, after it's loaded, it will probably only contain the initial text without my addition, or just my addition.
To see this, install the script and then visit this typical target page.
The RottenTomatoes pages load various content via XHR (as I've seen in Firefox Netowork Monitor),
but the 3 elements (that I want to modify) are displayed immediately, not when page has completely loaded.
I have tried using MutationObserver to watch for the specific text value to appear on screen, but it doesn't seem to work. Its structure is like this:
var target = selector_for_element_containing_text ; // the selector is: document.querySelector('.audience-info > div:nth-child(1)');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
modifyElements();
});
});
var config = { attributes: true, childList: true, characterData: true, subtree: true };
observer.observe(target, config);
function modifyElements(){ // This is just the current code of my script
// modify element 1
// modify element 2
// modify element 3
}
How can I modify the elements immediately after they are displayed?
Make the script run at document-start by adding this code to script metablock:
..............
// #run-at document-start
..............
// ==/UserScript==
Now when the script runs there's nothing in the document so we'll attach the observer to the document root and will scan the added nodes for the container element .audience-info:
var observer = new MutationObserver(function(mutations) {
for (var i=0; i<mutations.length; i++) {
var mutationAddedNodes = mutations[i].addedNodes;
for (var j=0; j<mutationAddedNodes.length; j++) {
var node = mutationAddedNodes[j];
if (node.classList && node.classList.contains("audience-info")) {
node.firstElementChild.innerHTML = node.firstElementChild.innerHTML
.replace(/[\d.]+/g, function(m) { return 2 * m });
// don't hog resources any more as we've just done what we wanted
observer.disconnect();
return;
}
}
}
});
observer.observe(document, {childList: true, subtree: true});
When the observer is called the tooltips are already present in the document tree so you can simply move the code from "load" function into the observer before return without changing anything.
N.B. It's best to make the observer as fast as possible, particularly by using plain for-loops instead of function callbacks because of the overhead to call them, which might become an issue on a slow PC/device when opening a very complex page that generates thousands (quite a common case) or hundreds of thousands mutations (extremely rare but still!). And disconnect it once the job is done.
P.S. With setMutationHandler function the observer code would be:
// #require https://greasyfork.org/scripts/12228/code/setMutationHandler.js
// ==/UserScript==
setMutationHandler(document, '.audience-info div:first-child', function(nodes) {
this.disconnect();
nodes.forEach(function(n) {
n.innerHTML = n.innerHTML.replace(/[\d.]+/g, function(m) { return 2*m });
});
});

Timeago plugin to always be on()

How would I change the following code (which makes the timeago plugin work) to use the on() function (or others) so that it can be used live? I can't quite seem to figure it out (I'm fairly new to JQuery).
jQuery(document).ready(function() {
jQuery("a.timeago").timeago();
});
How would this be done?
You can use MutationObserver to check when the DOM was modified: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
You pick a node to observe. For your project you should observe the container the a.timeago elements will be created into. <body> is fine but will incur more work for the browser. Then, whenever the dom changes, a callback will be fired. Catch the elements you care about.
Modified from the documentation:
// select the target node
var target = document.querySelector('#your-container');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(addedNode) {
// if the addedNode matches $('a.timeago')
// $(addedNode).timeago();
});
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
// later, you can stop observing
observer.disconnect();

Categories