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();
Related
I am using Web Application to automate using Javascript and selenium. There is a dropdown which has different portals and if I change the value from the dropdown entire page will get changed based on what portal we select. Say, Developer portal, Tester portal etc,.. but URL is same.
I can change the portal easily, but if I try to fetch the elements after changing the portal, I am getting the previous portal elements.
I am using JS with selenium,
String id = (String)jse.executeScript("var x = document.getElementsByTagName(\""+ tag +"\");var e=''; for(i=0; i<x.length; i++) { e+=x[i].innerText + \",\";if(x[i].innerText.trim()===\""+searchObject+"\"){x[i].click();}} return e;");
Note: If I use the refresh action, application navigates back to the previous portal(It is an application behavior).
Is there a way to get the elements?
Use mutation observer for the above quest.
// select the target node
var target = document.querySelector('#some-id');
// In your case this will body or the parent where you are adding elements
// 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);
The observer will notify you when some element added or any attribute changed or you have changed the text. If you want to observe only children just remove the other two.
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.
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!
I have an iframe that's supposed to load different modules of a web application.
When the user clicks a navigation menu in the top window, it's passes a new url to the iframe. The trouble is, the new url doesn't actually point to a new page, it only uses a changed hash.
i.e.:
User clicks "dashboard", iframe src set to application.html#/dashboard
User clicks "history", iframe src set to application.html#/history
This means that the iframe does not actually load the src url again because hash changes don't require it to. The application inside the iframe is an angular app which loads the required modules dynamically using requireJS. We need this functionality to remain.
I need to force the frame source to load again even though only the hash changed. It's possible that I instead find a way to rewrite our angular app to dynamically unload/load the modules on push state events but that introduces several layers of issues for the app, plus some IE trouble.
I've tried:
Setting iframe src and calling it's location.reload, but that reloads the originally loaded url
Setting the iframe location.href/hash and calling reload, same issue
Blanking the src attribute and then setting the new url - no effect
The only solution I can find is to set the src to a blank screen, then onload set it to the new url:
var appIFrame = document.getElementById('appIFrame');
appIFrame.src = 'about:blank';
appIFrame.onload = function(){
appIFrame.src = '// set the real source here';
appIFrame.onload = false;
}
This works, yet it seems inefficient because there's an extra step.
Maybe add a dynamic GET parameter – f.e. the current timestamp, which you can get from the JavaScript Date object – to the iframe URL.
Instead of assigning application.html#/dashboard as src value, assign application.html?1234567890#/dashboard from your outside page (with 1234567890 replaced by the current timestamp, obviously).
I don't have a specific answer for you. However, the following script may proved useful (I wrote this about a year or so ago). The following script deals with re-adjusting iframe height when the document changes. This script was tested cross-browser. It does deal with the issues you're experience but indirectly. There is a lot of commenting with the Gist:
https://gist.github.com/say2joe/4694780
Here my solution (based on this stackoverflow answer):
var $ = function(id) { return document.getElementById(id); };
var hashChangeDetector = function(frame, callback) {
var frameWindow = frame.contentWindow || frame.contentDocument;
// 'old' browser
if (! "onhashchange" in window) {
var detecter = function(callback) {
var previousHash = frameWindow.location.hash;
window.setTimeout(function() {
if (frameWindow.location.hash != previousHash) {
previousHash = frameWindow.location.hash;
callback(previousHash);
}
}, 100);
};
}
else // modern browser ?
{
var detecter = function(callback) {
frameWindow.onhashchange = function () {
callback(frameWindow.location.hash);
}
};
}
detecter(callback);
};
hashChangeDetector($('myframe'), function(hash) {
alert ('detecting hash change: ' + hash);
});
You can test this here: http://paulrad.com/stackoverflow/iframe-hash-detection.html
I've been working on a scrollspy module for Angularjs. I've run into the problem where if the page is dealing with dynamic content, the scrollspy data (element positions) quickly becomes outdated. What is the angularjs way of dealing with issues such as this?
Should any directive that performs DOM manipulation $broadcast an event that the scrollspy module looks out for - allowing it to refactor its position data?
Should the scrollspy module check every x seconds for a change in scrollHeight with $timeout?
Or even better, is there a way to bind and watch for DOM attribute value changes (attributes such as offsetTop, offsetHeight, scrollHeight, not data attributes)?
Update: Added code base to GitHub
Mutation Observers seem to be the facility needed, unfortunately they are only supported by the latest browsers.
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
if( MutationObserver ) {
for ( var i = 0; i < spyService.spies.length; i++ ) {
var spy = spyService.spies[i].scope.spy;
var target = document.getElementById(spy);
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.warn('mutation observation');
});
}).observe(target, config);
}
}
else {
console.warn('no mutation observers here');
$interval(function() {
angular.element( document ).ready( function() {
console.log('refreshing');
});
}, 2000);
}
Currently searching for a polyfill that actually works.
EDIT: Added polling as a fallback if Mutation Observers aren't supported.
Something like this should work... doesn't seem to update with CSS transitions though.
scope.$watch(function(){
return elem[0].offsetLeft;
}, function(x){
console.log('ITEM AT '+x);
});
Without the code I'm stabbing in the dark a bit, but I would suggest your scrollspy module check every time $rootScope receives a $digest call. So use $rootScope.$watch.
$rootScope.$watch(function() {
// Update scrollspy data here
}