I have the website example.com and on this website is a button with the id "button1" and by clicking on this button, it opens an unknown url "example.com/unknownUrl" with the known button "buttonOnUnknownPage" on it. How can i be sure that the unknown page has finished loading and i can click "buttonOnUnknownPage"?
NOTE: "example.com" is opened in another window than the script. that means, the script don't stops running after "example.com" reloads.
I have until now used this:
// open example.com and get button1
exampleWindow = window.open("http://example.com", "_blank");
button1 = exampleWindow.document.getElementById('button1');
// clicking on button 1 opens example.com/unknownUrl
button1.click();
//and now wait 1000ms until page might be loaded
setTimeout(function() {
buttonOnUnknownPage = exampleWindow.document.getElementById('buttonOnUnknownPage');
buttonOnUnknownPage.click()
}, 1000);
The problem of this is, that i need to wait everytime 1000ms and can still not be sure, that "example.com/unknownUrl" was loaded.
Is there a more efficient method, to be sure that "example.com/unknownUrl" has loaded ? Something like document.onload ?
The monitoring of some other window as it changes location is fairly complicated and the reason it's complicated is that each time the other window loads a new document, the entire window state is cleared and all event listeners are wiped out and a whole new document is created. So, you can't install an event listener once and keep using it because it gets wiped out each time a new link is clicked and the page location is changed.
It is further complicated by the fact that the process of creating a new window for a particular URL will (in some browsers) first load a URL called about:blank and then load the real URL causing your monitoring to sometimes detect the loading of the about:blank internal URL, not the real URL you want to monitor. IE (even new versions) is particular bad for this (no surprise).
So, it is possible to track the loading of these external windows, but it takes some hacking to get it to work. The hacking requires these steps:
Get the original URL of the window (what it was before you told it to load something new).
Wait until the window.location.href value for that window is no longer the original URL. This signifies that the window has now started to load its new URL.
Once it is loading the new URL, then wait until the window shows that it has a .addEventListener property. For some unknown reason, newly created windows in IE don't yet have this property. That means you can't install a load event handler on a newly create window. Instead, you have to wait until that property is available and then you can install the load event handler.
When the .addEventListener property is available, see if the document is already done loading. If so, proceed with your DOM operation. If not, then register an event handler for when the document is done loading.
I've created a function call monitorWindowLoad() for carrying out these steps above:
function monitorWindowLoad(win, origURL, fn) {
log("monitorWindowLoad: origURL = " + origURL);
function windowInitiated() {
// at this point, we know the new URL is in window.location.href
// so the loading of the new window has started
// unfortunately for us, IE does not necessarily have a fully formed
// window object yet so we have to wait until the addEventListener
// property is available
checkCondition(function() {
return !!win.addEventListener;
}, windowListen);
}
// new window is ready for a listener
function windowListen() {
if (win.document.readyState === "complete") {
log("found readyState");
fn();
} else {
log("no readyState, setting load event handler");
win.addEventListener("load", fn);
}
}
if (origURL) {
// wait until URL changes before starting to monitor
// the changing of the URL will signal that the new loading window has been initialized
// enough for us to monitor its load status
checkCondition(function() {
return win.location.href !== origURL;
}, windowInitiated);
} else {
windowInitiated();
}
}
// Check a condition. If immediately true, then call completeFn
// if not immediately true, then keep testing the condition
// on an interval timer until it is true
function checkCondition(condFn, completeFn) {
if (condFn()) {
completeFn();
} else {
var timer = setInterval(function() {
if (condFn()) {
clearInterval(timer);
completeFn();
}
}, 1);
}
}
This function can then be used to click successive links in several loading pages like this:
function go() {
// open new window
var exampleWindow = window.open("window2.html");
monitorWindowLoad(exampleWindow, "about:blank", function() {
var loc = exampleWindow.location.href;
clickButton(exampleWindow, "button2");
monitorWindowLoad(exampleWindow, loc, function() {
loc = exampleWindow.location.href;
clickButton(exampleWindow, "button3");
monitorWindowLoad(exampleWindow, loc, function() {
// last page loaded now
});
});
});
}
There's actually working demo of this concept here. This loads a file named window1a.html. The Javascript in that page opens a new window for window2.html, and when that is loaded, it clicks a specific link in that window. Clicking that link opens window3.html and when that is loaded, it clicks a link in that window which then opens window4.html. You should end up with two windows open (window1a.html and window4.html). window1a.html will contain a log of the various events it carried out.
The script in window1.html doesn't know any of the URLs. It's just clicking links and monitoring when the newly loaded window has loaded so it can click the next link and so on.
Related
So lately I have been learning JS and trying to interact with webpages, scraping at first but now also doing interactions on a specific webpage.
For instance, I have a webpage that contains a button, I want to press this button roughly every 30 seconds and then it refreshes (and the countdown starts again). I wrote to following script to do this:
var klikCount = 0;
function getPlayElement() {
var playElement = document.querySelector('.button_red');
return playElement;
}
function doKlik() {
var playElement = getPlayElement();
klikCount++;
console.log('Watched ' + klikCount);
playElement.click();
setTimeout(doKlik, 30000);
}
doKlik()
But now I need to step up my game, and every time I click the button a new window pops up and I need to perform an action in there too, then close it and go back to the 'main' script.
Is this possible through JS? Please keep in mind I am a total javascript noob and not aware of a lot of basic functionality.
Thank you,
Alex
DOM events have an isTrusted property that is true only when the event has been generated by the user, instead of synthetically, as it is for the el.click() case.
The popup is one of the numerous Web mechanism that works only if the click, or similar action, has been performed by the user, not the code itself.
Giving a page the ability to open infinite amount of popups has never been a great idea so that very long time ago they killed the feature in many ways.
You could, in your own tab/window, create iframes and perform actions within these frames through postMessage, but I'm not sure that's good enough for you.
Regardless, the code that would work if the click was generated from the user, is something like the following:
document.body.addEventListener(
'click',
event => {
const outer = open(
'about:blank',
'blanka',
'menubar=no,location=yes,resizable=no,scrollbars=no,status=yes'
);
outer.document.open();
outer.document.write('This is a pretty big popup!');
// post a message to the opener (aka current window)
outer.document.write(
'<script>opener.postMessage("O hi Mark!", "*");</script>'
);
// set a timer to close the popup
outer.document.write(
'<script>setTimeout(close, 1000)</script>'
);
outer.document.close();
// you could also outer.close()
// instead of waiting the timeout
}
);
// will receive the message and log
// "O hi Mark!"
addEventListener('message', event => {
console.log(event.data);
});
Every popup has an opener, and every different window can communicate via postMessage.
You can read more about window.open in MDN.
Firstly, I see this question asked a few times but no answers seem satisfactory. What I am looking for is to be able to call a script at anytime and determine whether or not an iframe has loaded - and to not limit the script to require being added to the iframe tag itself in an onload property.
Here's some background: I have been working on an unobtrusive script to try and determine whether or not local iframes in the dom have loaded, this is because one of our clients includes forms on their website in iframes and many of them open in lightboxes - which dynamically add the iframes into the dom at any time. I can attach to the open event of the lightbox, but its hit or miss as to whether I can "catch" the iframe before it has loaded.
Let me explain a little more.
In my testing I've determined that the onload event will only fire once - and only if it is bound before the iframe actually loads. For example: This page should only alert "added to iframe tag" and the listener that is attached afterward does not fire - to me that makes sense. (I'm using the iframe onload property for simple example).
https://jsfiddle.net/g1bkd3u1/2/
<script>
function loaded () {
alert ("added to iframe tag");
$("#test").load(function(){
alert("added after load finished");
});
};
</script>
<iframe onload="loaded()" id="test" src="https://en.wikipedia.org/wiki/HTML_element#Frames"></iframe>
My next approach was to check the document ready state of the iframe which seems to work in almost all of my testing except chrome which reports "complete" - I was expecting "Access Denied" for cross domain request. I'm ok with a cross domain error because I can disregard the iframe since I am only interested in local iframes - firefox reports "unintialized" which I'm ok with because I know I can then attach an onload event.
Please open in Chrome:
https://jsfiddle.net/g1bkd3u1/
<iframe id="test" src="https://en.wikipedia.org/wiki/HTML_element#Frames"></iframe>
<script>
alert($("#test").contents()[0].readyState);
</script>
I've found that if I wait just 100ms - then the iframe seems to report as expected (a cross domain security exception - which is what I want - but I don't want to have to wait an arbitrary length).
https://jsfiddle.net/g1bkd3u1/4/
<iframe id="test" src="https://en.wikipedia.org/wiki/HTML_element#Frames"></iframe>
<script>
setTimeout(function () {
try {
alert($("#test").contents()[0].readyState);
} catch (ignore) {
alert("cross domain request");
}
}, 100);
</script>
My current workaround / solution is to add the onload event handler, then detach the iframe from the dom, then insert it back into the dom in the same place - now the onload event will trigger. Here's an example that waits 3 seconds (hoping thats enough time for the iframe to load) to show that detaching and re-attaching causes the iframe onload event to fire.
https://jsfiddle.net/g1bkd3u1/5/
<iframe id="test" src="https://en.wikipedia.org/wiki/HTML_element#Frames"></iframe>
<script>
setTimeout(function(){
var placeholder = $("<span>");
$("#test").load(function(){
alert("I know the frame has loaded now");
}).after(placeholder).detach().insertAfter(placeholder);
placeholder.detach();
}, 3000);
</script>
While this works it leaves me wondering if there are better more elegant techniques for checking iframe load (unobtrusively)?
Thank you for your time.
Today I actually ran into a bug where my removing and re-inserting of iframes was breaking a wysiwyg editor on a website. So I created the start of a small jQuery plugin to check for iframe readiness. It is not production ready and I have not tested it much, but it should provide a nicer alternative to detaching and re-attaching an iframe - it does use polling if it needs to, but should remove the setInterval when the iframe is ready.
It can be used like:
$("iframe").iready(function() { ... });
https://jsfiddle.net/q0smjkh5/10/
<script>
(function($, document, undefined) {
$.fn["iready"] = function(callback) {
var ifr = this.filter("iframe"),
arg = arguments,
src = this,
clc = null, // collection
lng = 50, // length of time to wait between intervals
ivl = -1, // interval id
chk = function(ifr) {
try {
var cnt = ifr.contents(),
doc = cnt[0],
src = ifr.attr("src"),
url = doc.URL;
switch (doc.readyState) {
case "complete":
if (!src || src === "about:blank") {
// we don't care about empty iframes
ifr.data("ready", "true");
} else if (!url || url === "about:blank") {
// empty document still needs loaded
ifr.data("ready", undefined);
} else {
// not an empty iframe and not an empty src
// should be loaded
ifr.data("ready", true);
}
break;
case "interactive":
ifr.data("ready", "true");
break;
case "loading":
default:
// still loading
break;
}
} catch (ignore) {
// as far as we're concerned the iframe is ready
// since we won't be able to access it cross domain
ifr.data("ready", "true");
}
return ifr.data("ready") === "true";
};
if (ifr.length) {
ifr.each(function() {
if (!$(this).data("ready")) {
// add to collection
clc = (clc) ? clc.add($(this)) : $(this);
}
});
if (clc) {
ivl = setInterval(function() {
var rd = true;
clc.each(function() {
if (!$(this).data("ready")) {
if (!chk($(this))) {
rd = false;
}
}
});
if (rd) {
clearInterval(ivl);
clc = null;
callback.apply(src, arg);
}
}, lng);
} else {
clc = null;
callback.apply(src, arg);
}
} else {
clc = null;
callback.apply(this, arguments);
}
return this;
};
}(jQuery, document));
</script>
The example waits until the window has loaded to dynamically add an iframe to the DOM, it then alerts its document's readyState - which in chrome displays "complete", incorrectly. The iready function should be called after and an attempt to output the document's readyState proves cross domain exception - again this has not been thoroughly tested but works for what I need.
I encountered a similar issue in that I had an iframe and needed to modify its' document once it had finished loading.
IF you know or can control the content of the loaded document in the iFrame, then you could simply check for/add an element that you could check the existence of in order to then update the iframe document.
At least then you know the elements you want to work with are loaded in to the document.
In my case, I called a function, which itself checked for the existence of my known element that would always be found after the elements I needed to update had already been loaded - in the case it was not found, it called itself again through setTimeout().
function updateIframeContents() {
if ($("#theIframe").contents().find('.SaveButton').length > 0) {
// iframe DOM Manipulation
} else {
setTimeout(updateIframeContents, 250);
}
}
updateIframeContents();
Is it possible.. to have my javascript, example as in below; to simply continue to execute with my click function but the changes are only reflective in a new window - while current page (non-new window does not change via the JS) is this possible?
$('.download-pdf').click(function() {
$(this).attr('target', '_blank');
notChecked = $("input[type=checkbox]:not(:checked)").parent();
notChecked.hide();
yesChecked = $("input[type=checkbox]:checked").parent();
$.each(yesChecked, function( index, el ) {
$(el).show().html(texts[$(el).attr('id')]);
});
You can use the postMessage API which is perfectly described in this SO Answer. You can do this only, if the new window has the same origin.
Probably you'd need to wait some time for the new frame to be fully loaded.
I am trying to write a Firefox extension for Android that will fire an event every time the web page changes. It is monitoring which URLs are being loaded (all URLs) and the contents of the page loaded (via DOM inspection). My problem is that the window load event using the code below only gets loaded when a tab is opened, if you navigate away from the page, no events get fired.
How do I hook into every page load event for any URL?
This code is the entire contents of bootstrap.js:
Components.utils.import("resource://gre/modules/Services.jsm");
var windowListener = {
onOpenWindow: function(aWindow) {
console.log('vipro.openWindow');
let domWindow = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal || Components.interfaces.nsIDOMWindow);
domWindow.addEventListener("UIReady", function onLoad() {
domWindow.removeEventListener("UIReady", onLoad, false);
console.log('vipro.openWindow.loaded');
// ** ONLY EVER FIRED ONCE ** //
try {
var browser = Services.wm.getMostRecentWindow("navigator:browser");
} catch(e) {
console.log('vipro.openWindow.error.' + e.toString());
}
console.log('vipro.openWindow.loaded.DONE');
});
console.log('vipro.openWindow.DONE');
},
onCloseWindow: function(aWindow) {},
onWindowTitleChange: function(aWindow, aTitle) {},
};
function startup(data, reason) {
console.log('vipro.startup');
try {
let wm = Components.classes["#mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
wm.addListener(windowListener);
} catch(e) {
console.log('vipro.startup.error.' + e.toString());
}
console.log('vipro.startup.DONE');
}
function shutdown() {
console.log('vipro.shutdown');
}
function install(aData, aReason) {}
function uninstall(aData, aReason) {}
I'm not fussed that the code above only works for new tabs and not existing (I've intentionally kept it simple), it's just the fact that when I navigate away from the initial new tab page, I don't get the opportunity to hook into the other pages.
Found a slightly different approach on this page:
https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Mobile_development
Using the layout that cfx provides and not using the bootstrap... bootstrap i am able to achieve what i need using page-mod.
I'm developing a web application that opens a popup using windows.open(..). I need to call a function on the opened window using the handle returned by "window.open", but I'm always getting the error message "addWindow.getMaskElements is not a function", as if it couldn't access the function declared on child window. This is the behavior in both IE and FF. My code looks like this:
function AddEmail(target,category)
{
if(addWindow == null)
{
currentCategory = category;
var left = getDialogPos(400,220)[0];
var top = getDialogPos(400,220)[1];
addWindow = window.open("adicionar_email.htm",null,"height=220px, width=400px, status=no, resizable=no");
addWindow.moveTo(left,top);
addWindow.getMaskElements ();
}
}
I've googled and read from different reliable sources and apparently this is supposed to work, however it doesn't.
One more thing, the functions in child window are declared in a separate .js file that is included in the adicionar_email.htm file. Does this make a difference? It shouldn't..
So, if anyone has ran into a similar problem, or has any idea of what I'm doing wrong, please, reply to this message.
Thanks in advance.
Kenia
The window creation is not a blocking operation; the script continues to execute while that window is opening and loading the HTML & javascript and parsing it.
If you were to add a link on your original page like this:
Test
You'd see it works. (I tried it just to be sure.)
**EDIT **
Someone else posted a workaround by calling an onload in the target document, here's another approach:
function AddEmail()
{
if(addWindow == null) {
addWindow = window.open("test2.html",null,"height=220px, width=400px, status=no, resizable=no");
}
if(!addWindow.myRemoteFunction) {
setTimeout(AddEmail,1000);
} else { addWindow.myRemoteFunction(); }
}
This keeps trying to call addWindow.myRemoteFunction every 1 second til it manages to sucessfully call it.
The problem is that window.open returns fairly quickly, the document that is requested and then any other items that that document may subsequently refer to will not yet have been loaded into the window.
Hence attempting to call this method so early will fail. You should attach a function to the opened window's load event and attempt to make you calls from that function.
The problem with the below one is :
When the javascript is being executed in the parent window, the child window is not loading. Hence, the invoking function from parent window is in the infinite loop and it is leading to crashing the window.
The window creation is not a blocking operation; the script continues
to execute while that window is opening and loading the HTML &
javascript and parsing it.
If you were to add a link on your original page like this:
Test
You'd see it works. (I tried it just to be sure.)
**EDIT **
Someone else posted a workaround by calling an onload in the target
document, here's another approach:
function AddEmail()
{
if(addWindow == null) {
addWindow = window.open("test2.html",null,"height=220px, width=400px, status=no, resizable=no");
}
if(!addWindow.myRemoteFunction) {
setTimeout(AddEmail,1000);
} else { addWindow.myRemoteFunction(); }
}
This keeps trying to call addWindow.myRemoteFunction every 1 second
til it manages to sucessfully call it.
You are calling the function immediately after opening the window; the page on the popup may not be loaded yet, so the function may not be defined at that point.