Greetings,
my xul app needs to load scripts dynamically, for this I derived a function that works in regular html/js apps :
function loadScript(url)
{
var e = document.createElement("script");
e.src = url;
e.type="text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
}
to something that ought work in XUL :
function loadScript( url)
{
var e = document.createElement("script");
//I can tell from statically loaded scripts that these 2 are set thru attributes
e.setAttribute( 'type' , "application/javascript" ); //type is as per MDC docs
e.setAttribute( 'src' , url );
//XUL apps attach scripts to the window I can tell from firebug, there is no head
document.getElementsByTagName("window")[0].appendChild(e);
}
The script tags get properly added, the attributes look fine,but it does not work at all, no code inside these loaded scripts is executed or even parsed.
Can any one give a hint as to what might be going on ?
T.
Okay,
as usual whenever I post on stack overflow, the answer will come pretty soon thru one last desperate Google search.
This works :
//Check this for how the url should look like :
//https://developer.mozilla.org/en/mozIJSSubScriptLoader
function loadScript( url)
{
var loader = Components.classes["#mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
//The magic happens here
loader.loadSubScript( url );
}
This will only load local files, which is what I need for my app.
I am fairly disappointed by Mozilla, why not do this the same way like html, in a standard way ?
I've tried this, and I think you're right - I can't seem to get XUL to run dynamically appended script tags - perhaps it's a bug.
I'm curious as to why you would want to though - I can't think of any situation where one would need to do this - perhaps whatever you're trying could be achieved another way. Why is it they need to be dynamically loaded?
Off-topic: on the changes you made to the script.
e.setAttribute('src',url); is valid in normal webpages as well, and is actually technically more "correct" than e.src=url; anyway (although longer and not well supported in old browsers).
Types application/javascript or application/ecmascript are supposed to work in normal webpages and are more "correct" than text/javascript, but IE doesn't support them so they're not normally used.
Inside xul environment you are only allowed to use XHR+eval like the following:
function loadScript (url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false); // sync
xhr.send(null);
if (xhr.status && xhr.status != 200)
throw xhr.statusText;
try {
eval(xhr.responseText, window);
} catch (x) {
throw new Error("ERROR in loadScript: Can't load script '" + url+ "'\nError message is:" + x.message);
}
};
Related
I currently have the following Javascript as part of my HTML
function guide1(i) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
guideload1(this, i);
}
};
xmlhttp.open("GET", "tvguidedata.xml", true);
xmlhttp.send();
}
function guideload1(xml, i) {
var i
var xmlDoc = xml.responseXML;
x = xmlDoc.getElementsByTagName("programme");
for (i = 1; i <x.length; i++)
if (*blah blah blah, the script starts working here, I assume its not necessary posting it in full*)
This script works as intended and so far so good.
However I would like to improve a small part of it for (what I consider) better performance of the overall intend.
Currently I have another script that is calling the above script at an set interval to make sure the data displayed inside my HTML is current (TV Guide airings)
Checking the chrome dev tools, I can see each time the script is running it re-downloads the full xml.
While the XML file is small, I would prefer it to only load/download the xml once and then be able to loop my script with the data being processed from a cached xml.
Is this even possible at all? And if so how?
I already tried my setInterval script to directly call the "guideload1" function instead of "guide1, however then I get an error and the script is not loading data correctly.
Maybe there is a script that first loads the XML into cache, local storage or whatever would be used in this case and any subsequent calls to "guide1" function and/or "guideload1" then take the cached file and don't make any download requested.
And the script that caches the XML, I would most likely set it to only reload/download once after 12 hours.
Any help is appreciated, I should also note that I know almost zero about coding (I got most of my coode by google search and similar) so if an explanation is necessary feel free to phrase it like I'm a baby.
Other information, I believe could be useful:
Cross-Browser compatibility is not required/necessary.
My whole HTML is only opened on Chrome browswer on Windows in kiosk mode.
There is never any user interaction, as the page is displayed to a HDMI-OUT/Modulator and certain sub-HTML elements are reloaded every day at midnight
below code is in one javascript file
==> test.js:
function test(){ window.open('http://example.com/abc/c4adbeb687f77b48a041d70395803358','','toolbar=yes,statusbar=yes,location=yes,menubar=yes,scrollbars=yes,resizable=no,fullscreen=yes');}
how can i get http://example.com/abc/c4adbeb687f77b48a041d70395803358 from a html file with below structure
==>test.html:
<script type="text/javascript" src="test.js"></script>
<script>
alert(window.open.value);
</script>
If you have control over both the HTML and the JS, you can intercept the window.open function:
<script>
var oldWindowOpen = window.open;
var windowOpenArgs = [];
window.open = function() {
windowOpenArgs = arguments;
oldWindowOpen.call(window, arguments);
};
</script>
<script type="text/javascript" src="test.js"></script>
<script>
window.open = oldWindowOpen;
// do stuf with variable windowOpenArgs
console.log(windowOpenArgs[0]); //the url
</script>
You could get more advanced, by throwing and catching a error to find the stack trace and only record the parameters when the call comes from a specific function, but it all depends on the nature of your scripts:
window.open = function() {
try { throw new Error(); } except(e) {
var stack = e.stack;
if (/test#.*?test.js:/.test(stack)) {
windowOpenArgs = arguments;
}
}
}
However notice that the variable will only be set after test() is executed!
Your question seems a bit unclear.... to me.
I presume you are trying to rip the url that is located in a .js file. In your example you want the url http://example.com/abc/c4adbeb687f77b48a041d70395803358 to be ripped out of test.js.
Well, in your example, you have test.js loaded as its in the script tag as you showed here:
<script type="text/javascript" src="test.js"></script>
Therefore, if you know the name of the function that has the url data. You can simply:
alert(test.toSource());
to see its contents. However, it will need to be parsed. One could write a weird looking RegEx expression for it. But, here is a workaround:
alert("http"+test.toSource().split("http")[1].split("',")[0]);
This is a ROUGH workaround. Parsing heavily depends on how people write code. And, in this example, if they used a " instead of ' then it would not display the url correctly. There could be other issues, a nice RegEx could knock it out cleanly tho.
However, if you dont know the functions name. And you just want to blindly search the whole js file for a url (specific or in general), then I would suggest it best to load it with XMLHttpRequest. I guess there is a HttpRequest you could use as well, never looking into it.
var sUrl = "test.js";
var httpRequest = new XMLHttpRequest();
httpRequest.open("GET",sUrl, true);
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState == 4 && httpRequest.status == 200) { // ok
alert ( "http"+httpRequest.responseText.split("http")[1].split("',")[0] );
} else if ( httpRequest.readyState == 4 && httpRequest.status != 200 ) {
// failed
}
};
httpRequest.send(null);
That mess should show an alert box containing the first appearing http url in a function argument from any URL put into sUrl.
If you want more... then that means more parsing. But you can store it from here to do whatever.
But, once you open a window from javascript, you cannot access this new window's contents from within the opening javascript. Cross Domain violation
(some sort of extension of the Same Origin Policy). You can, however, change where its pointing. And, grab the location its pointed to, which might also be what your asking here.
In that case, if the window is already open you can do this
var w = window.open("http:\\example.com"); // some time before
alert(w.location);
However, if you just opened it. Like above. It might be about:blank, because it hasn't loaded yet. In which case you would have to look for it later.. like:
window.open("http:\\example.com", "MyName");
setTimeout('var w = window.open(,"MyName"); alert(w.location);', 1000);
Here, you have to use the window name parameter for window.open. Then, the second time you call window.open you leave the URL parameter blank and only use the window name parameter to get a reference to an existing open window... instead of opening a new one.
Then, if you want to ubiquitously grab all urls passed into window.open, there is a hijack method. It all just depends on what your asking here... but Kroltan beat me to it! lol! So check his answer for that one.
PT
I'm trying to load some data from Reddit via a Safari extension. I'm using a JSONP pattern to create the callback function and attach the new src to the script. However, it looks like there are two window namespaces, and the function that I dynamically create is not available to the context of the dynamically added script.
This link seems to detail the problem for chrome, which I'm guessing is similar to mine in Safari.
JSONP request in chrome extension, callback function doesn't exist?
Here's the code (works outside of extension):
function jsonp(url, callback) {
var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
window[callbackName] = function(data) {
delete window[callbackName];
callback(data);
};
console.log('about to create script');
var script = document.createElement('script');
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'jsonp=' + callbackName;
document.body.appendChild(script);
console.log('script should be appended');
}
function getImages(){
jsonp('http://www.reddit.com/r/cats/.json', function(data) {
data.data.children.forEach(function(el){
console.log(el.data.url);
});
});
}
Any ideas, work arounds?
Thanks!
You're right about there being two namespaces in the same window. Injected scripts cannot access a web page's globals, and vice versa. When you initiate JSONP in an injected script, the script of the inserted <script> tag runs in the web page's namespace.
I know of two ways to work around this limitation in a Safari extension.
The probably better way is to use your extension's global page to communicate with the external API through standard XHR or jQuery Ajax methods (which are supported in global page scripts) and use messages to pass the fetched data to an injected script.
The other way is to go ahead and use JSONP from the injected script, but have the JSONP callback function add the fetched data to the page's DOM, which is accessible by the injected script. For example, you could put the data in a hidden <pre> element and then use the element's innerHTML property to retrieve it. Of course, this technique does make the content visible to the page's own scripts, so exercise caution.
I want to develop a Chrome extension, just imagine when Facebook loads you are allowed to add extra JS on it.
But my problem is I can't modify the DOM of the later content, which means the newly loaded content that appear when the user scrolled down.
So I want to detect XHR using JavaScript.
I tried
send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
/* Wrap onreadystaechange callback */
var callback = this.onreadystatechange;
this.onreadystatechange = function() {
if (this.readyState == 4) {
/* We are in response; do something, like logging or anything you want */
alert('test');
}
callback.apply(this, arguments);
}
_send.apply(this, arguments);
}
But this is not working.. any ideas?
Besides Arun's correct remark that you should use _send for both, your approach doesn't work because of how Content Scripts work.
The code running in the content script works in an isolated environment, to prevent it from conflicting with page's own code. So it's not like you described - you're not simply adding JS to the page, you have it run isolated. As a result, your XHR replacement only affects XHR calls from your extension's content scripts and not the page.
It's possible to inject the code into the page itself. This will affect XHR's from the page, but might not work on all pages, if the Content Security Policy of the page in question disallows inline code. It seems like Facebook's CSP would allow this. Page's CSP should not be a problem according to the docs. So, this approach should work, see the question I linked.
That said, you're not specifically looking for AJAX calls, you're looking for new elements being inserted in the DOM. You can detect that without modifying the page's code, using DOM MutationObservers.
See this answer for more information.
to detect AJAX calls on a webpage you have to inject the code directly in that page and then call the .ajaxStart or .ajaxSuccess
Example:
// To Successfully Intercept AJAX calls, we had to embed the script directly in the Notifications page
var injectedCode = '(' + function() {
$('body').ajaxSuccess(function(evt, request, settings) {
if (evt.delegateTarget.baseURI == 'URL to check against if you want') {
// do your stuff
}
});
} + ')();';
// Inserting the script into the page
var script = document.createElement('script');
script.textContent = injectedCode;
(document.head || document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
I can detect when the content of an iframe has loaded using the load event. Unfortunately, for my purposes, there are two problems with this:
If there is an error loading the page (404/500, etc), the load event is never fired.
If some images or other dependencies failed to load, the load event is fired as usual.
Is there some way I can reliably determine if either of the above errors occurred?
I'm writing a semi-web semi-desktop application based on Mozilla/XULRunner, so solutions that only work in Mozilla are welcome.
If you have control over the iframe page (and the pages are on the same domain name), a strategy could be as follows:
In the parent document, initialize a variable var iFrameLoaded = false;
When the iframe document is loaded, set this variable in the parent to true calling from the iframe document a parent's function (setIFrameLoaded(); for example).
check the iFrameLoaded flag using the timer object (set the timer to your preferred timeout limit) - if the flag is still false you can tell that the iframe was not regularly loaded.
I hope this helps.
This is a very late answer, but I will leave it to someone who needs it.
Task: load iframe cross-origin content, emit onLoaded on success and onError on load error.
This is the most cross browsers origin independent solution I could develop. But first of all I will briefly tell about other approaches I had and why they are bad.
1. iframe That was a little shock for me, that iframe only has onload event and it is called on load and on error, no way to know it is error or not.
2. performance.getEntriesByType('resource'). This method returns loaded resources. Sounds like what we need. But what a shame, firefox always adds Resource in resources array no matter it is loaded or failed. No way to know by Resource instance was it success. As usual. By the way, this method does not work in ios<11.
3. script I tried to load html using <script> tag. Emits onload and onerror correctly, sadly, only in Chrome.
And when I was ready to give up, my elder collegue told me about html4 tag <object>. It is like <iframe> tag except it has fallbacks when content is not loaded. That sounds like what we are need! Sadly it is not as easy as it sounds.
CODE SECTION
var obj = document.createElement('object');
// we need to specify a callback (i will mention why later)
obj.innerHTML = '<div style="height:5px"><div/>'; // fallback
obj.style.display = 'block'; // so height=5px will work
obj.style.visibility = 'hidden'; // to hide before loaded
obj.data = src;
After this we can set some attributes to <object> like we'd wanted to do with iframe. The only difference, we should use <params>, not attributes, but their names and values are identical.
for (var prop in params) {
if (params.hasOwnProperty(prop)) {
var param = document.createElement('param');
param.name = prop;
param.value = params[prop];
obj.appendChild(param);
}
}
Now, the hard part. Like many same-like elements, <object> doesn't have specs for callbacks, so each browser behaves differently.
Chrome. On error and on load emits load event.
Firefox. Emits load and error correctly.
Safari. Emits nothing....
Seems like no different from iframe, getEntriesByType, script....
But, we have native browser fallback! So, because we set fallback (innerHtml) directly, we can tell if <object> is loaded or not
function isReallyLoaded(obj) {
return obj.offsetHeight !== 5; // fallback height
}
/**
* Chrome calls always, Firefox on load
*/
obj.onload = function() {
isReallyLoaded(obj) ? onLoaded() : onError();
};
/**
* Firefox on error
*/
obj.onerror = function() {
onError();
};
But what to do with Safari? Good old setTimeout.
var interval = function() {
if (isLoaded) { // some flag
return;
}
if (hasResult(obj)) {
if (isReallyLoaded(obj)) {
onLoaded();
} else {
onError();
}
}
setTimeout(interval, 100);
};
function hasResult(obj) {
return obj.offsetHeight > 0;
}
Yeah.... not so fast. The thing is, <object> when fails has unmentioned in specs behaviour:
Trying to load (size=0)
Fails (size = any) really
Fallback (size = as in innnerHtml)
So, code needs a little enhancement
var interval = function() {
if (isLoaded) { // some flag
return;
}
if (hasResult(obj)) {
if (isReallyLoaded(obj)) {
interval.count++;
// needs less then 400ms to fallback
interval.count > 4 && onLoadedResult(obj, onLoaded);
} else {
onErrorResult(obj, onError);
}
}
setTimeout(interval, 100);
};
interval.count = 0;
setTimeout(interval, 100);
Well, and to start loading
document.body.appendChild(obj);
That is all. I tried to explain code in every detail, so it may look not so foolish.
P.S. WebDev sucks
I had this problem recently and had to resort to setting up a Javascript Polling action on the Parent Page (that contains the IFRAME tag). This JavaScript function checks the IFRAME's contents for explicit elements that should only exist in a GOOD response. This assumes of course that you don't have to deal with violating the "same origin policy."
Instead of checking for all possible errors which might be generated from the many different network resources.. I simply checked for the one constant positive Element(s) that I know should be in a good response.
After a pre-determined time and/or # of failed attempts to detect the expected Element(s), the JavaScript modifies the IFRAME's SRC attribute (to request from my Servlet) a User Friendly Error Page as opposed to displaying the typical HTTP ERROR message. The JavaScript could also just as easily modify the SRC attribute to make an entirely different request.
function checkForContents(){
var contents=document.getElementById('myiframe').contentWindow.document
if(contents){
alert('found contents of myiframe:' + contents);
if(contents.documentElement){
if(contents.documentElement.innerHTML){
alert("Found contents: " +contents.documentElement.innerHTML);
if(contents.documentElement.innerHTML.indexOf("FIND_ME") > -1){
openMediumWindow("woot.html", "mypopup");
}
}
}
}
}
I think that the pageshow event is fired for error pages. Or if you're doing this from chrome, then your check your progress listener's request to see if it's an HTTP channel in which case you can retrieve the status code.
As for page dependencies, I think you can only do this from chrome by adding a capturing onerror event listener, and even then it will only find errors in elements, not CSS backgrounds or other images.
Doesn't answer your question exactly, but my search for an answer brought me here, so I'm posting just in case anyone else had a similar query to me.
It doesn't quite use a load event, but it can detect whether a website is accessible and callable (if it is, then the iFrame, in theory, should load).
At first, I thought to do an AJAX call like everyone else, except that it didn't work for me initially, as I had used jQuery. It works perfectly if you do a XMLHttpRequest:
var url = http://url_to_test.com/
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status != 200) {
console.log("iframe failed to load");
}
};
xhttp.open("GET", url, true);
xhttp.send();
Edit:
So this method works ok, except that it has a lot of false negatives (picks up a lot of stuff that would display in an iframe) due to cross-origin malarky. The way that I got around this was to do a CURL/Web request on a server, and then check the response headers for a) if the website exists, and b) if the headers had set x-frame-options.
This isn't a problem if you run your own webserver, as you can make your own api call for it.
My implementation in node.js:
app.get('/iframetest',function(req,res){ //Call using /iframetest?url=url - needs to be stripped of http:// or https://
var url = req.query.url;
var request = require('https').request({host: url}, function(response){ //This does an https request - require('http') if you want to do a http request
var headers = response.headers;
if (typeof headers["x-frame-options"] != 'undefined') {
res.send(false); //Headers don't allow iframe
} else {
res.send(true); //Headers don't disallow iframe
}
});
request.on('error',function(e){
res.send(false); //website unavailable
});
request.end();
});
Have a id for the top most (body) element in the page that is being loaded in your iframe.
on the Load handler of your iframe, check to see if getElementById() returns a non null value.
If it is, iframe has loaded successfully. else it has failed.
in that case, put frame.src="about:blank". Make sure to remove the loadhandler before doing that.
If the iframe is loaded on the same origin as the parent page, then you can do this:
iframeEl.addEventListener('load', function() {
// NOTE: contentDocument is null if a connection error occurs or if
// X-Frame-Options is not SAMESITE (which could happen with
// 4xx or 5xx error pages if the corresponding error handlers
// do not specify SAMESITE). If error handlers do not specify
// SAMESITE, then networkErrorOccurred will incorrectly be set
// to true.
const networkErrorOccurred = !iframeEl.contentDocument;
const serverErrorOccurred = (
!networkErrorOccurred &&
!iframeEl.contentDocument.querySelector('#well-known-element')
);
if (networkErrorOccurred || serverErrorOccurred) {
let errorMessage;
if (networkErrorOccurred) {
errorMessage = 'Error: Network error';
} else if (serverErrorOccurred) {
errorMessage = 'Error: Server error';
} else {
// Assert that the above code is correct.
throw new Error('networkErrorOccurred and serverErrorOccurred are both false');
}
alert(errorMessage);
}
});