currently i am relying on a proxy script to handle this problem of Single Origin Policy. it is slow, and creates overhead. Not to mention, javascript is not rendered.
is there a working alternative out there?
If you can provide a callback name as a parameter to the service providing the JavaScript code in question, then you can append a script tag to your document, with a src attribute pointing to the service call. Otherwise, you're out of luck.
Use an iframe and try window.postMessage(message, origin) (it would be parent.postMessage from the iframe and iframeElement.contentWindow.postMessage from the top page) for all of the latest major browsers (Firefox, IE, Safari, Chrome, etc.) and changing/polling window.name for old browsers.
Oh dear, I think the solution you're looking for is with IFRAMEs. However the iframe approach is both a mental and technical undertaking. I suggest you start with this guide:
Cross-Domain Communication with IFrames
The alternative approach is getting data from another server asynchronously using script tags and json:
<script src="http://remotesite.com/path/to/script/blah.js"></script>
You can create a new SCRIPT tag element to pass and load data and append to DOM or insert the markup into an elements innerHTML.
I'm sure you can find some detailed examples and ways to implement but one thing you should keep a track of with the new SCRIPT method is adding so many tot he DOM. This might help and provide a starting point for you:
function require (url, callback) {
if (!isScriptLoaded(url)) {
document.write('<script src="' + url + '" type="text/javascript" charset="utf-8"><\/script>');
if (callback) {
callback();
}
}
}
function isScriptLoaded(src) {
var scriptsLoaded = {};
var scriptTags = document.getElementsByTagName("script");
for (var i = 0, script; script = scriptTags[i]; i++) {
if (script.src) {
scriptsLoaded[script.src] = 1;
}
};
if (scriptsLoaded[src]) {
return true;
}
return false;
}
(untested, but should work!)
Either way - best of luck.
JSON-P is pretty much ideal for this kind of thing. If you're using jQuery, or similar JavaScript libraries, your job is made even easier:
http://docs.jquery.com/Ajax/jQuery.getJSON#urldatacallback
Of course, it will depend on exactly what you are trying to do that will determine whether to use JSON-P, hidden iframes, postMessage, Flash proxies, or any other exotic solution.
If you control both domains and only care about Firefox 3.5+, you can use the XMLHttpRequest Object and set up permissions with Access Control.
Related
Background Information
I am working on a web application that utilizes GWT (v2.4). For the application, I am creating an iframe that will display some information from another website. I need to access some information from that iframe that is normally restricted via the Same Origin Policy (SOP). However, both sites (the parent and iframe) are hosted on the same super-domain, just under different sub-domains. So, something like this:
Parent: dev.app.mySite.com
frame: someOtherContent.mySite.com
I know the usual solution for this problem is to set the property: document.domain = 'mySite.com' on both parent and iframe site to allow passage of SOP. This works for all browsers (that I'm concerned with) except Internet Explorer 8 (and probably other versions).
The Problem
In IE, when I attempt to load my web application, I get a completely blank page with the following JS exception, "Access is denied." The source of this problem is in GWT's myGwtAppName.nochache.js where GWT generates some code during the compilation process that it needs (see below).
From the research I've done on this problem in general, the root cause of this issue seems to be that in IE, unlike all other browsers, iframes don't inherit their parent's document.domain settings. From what I understand, the code generated by GWT runs in an iframe (based on this comment: https://stackoverflow.com/a/5161888). So, what I think is happening based on my limited knowledge of JS:
I set document.domain = 'mySite.com' in the parent index page via JS and it is processed.
myGwtAppName.nochache.js is processed.
In nochache.js, code is ran to setup the GWT iframe sand-box environment
In that code, a call is being made to a SOP restricted property of the sand-box iframe
An exception is thrown because the site's parent document domain has been set to 'mySite.com' and the iframe's document.domain doesn't inherit that setting, so it's still 'dev.app.mySite.com'. This won't pass SOP because the domain has to be exactly the same.
The generated code that causes the exception
The below code, looks like it's setting up the GWT sandbox iframe environment.
var $intern_4 = 'myGwtAppName',
$intern_7 = 'body',
$intern_8 = 'iframe',
$intern_9 = 'javascript:""',
$intern_10 = 'position:absolute; width:0; height:0; border:none; left: -1000px; top: -1000px; !important',
$intern_11 = '<html><head><\/head><body><\/body><\/html>',
$intern_12 = 'script',
$intern_13 = 'javascript',
$intern_14 = 'var $wnd = window.parent;''
....
....
function setupInstallLocation(){
if (frameDoc) {
return;
}
var scriptFrame = $doc.createElement($intern_8);
scriptFrame.src = $intern_9;
scriptFrame.id = $intern_4;
scriptFrame.style.cssText = $intern_10;
scriptFrame.tabIndex = -1;
$doc.body.appendChild(scriptFrame);
frameDoc = scriptFrame.contentDocument;
if (!frameDoc) {
frameDoc = scriptFrame.contentWindow.document; //THIS CAUSES THE EXCEPTION
}
frameDoc.open();
frameDoc.write($intern_11);
frameDoc.close();
var frameDocbody = frameDoc.getElementsByTagName($intern_7)[0];
var script = frameDoc.createElement($intern_12);
script.language = $intern_13;
var temp = $intern_14;
script.text = temp;
frameDocbody.appendChild(script);
}
....
....
My Questions
Is my analysis of the situation completely off-base?
Has anyone seen a solution for this problem that will work in a GWT environment in IE?
Information Sources
IE doesn't inherit document.domain settings: https://stackoverflow.com/a/1888711 (and many other threads).
GWT runs in an iframe sand-box environment: https://stackoverflow.com/a/5161888
You may use html5 web messaging to communicate between iframe and parent.
Be aware that Internet Explorer has following bugs. You can send only string as messages. You can't send object like other browser support.
Some people advice to encode object into JSON if you wish to send more then just a string but sometimes it is cheaper to send URL encoded string just like query string in URL.
Here are examples 2 top results from my google
http://tutorials.jenkov.com/html5/messaging.html
https://thenewcircle.com/bookshelf/html5_tutorial/messaging.html
Take a look that they use different code to listen for messages
window.attachEvent("onmessage", handleMessage);
window.addEventListener("message", handleMessage, true);
First works with IE and old Opera and last works with a rest of world.
I've run into the same issue and have found no elegant solution, but...
Right now I have an ant task that manually alters 3 specific points in the GWT nocache.js file after compilation to workaround the issue. You have to use regular expressions to make sure the injected code can reference a couple specific variables in the obfuscated code. It's all terribly ugly...
If you've found a more elegant solution please do post, since my solution is a hack. Details below...
Note - This assumes you have compiled GWT in "PRETTY" mode, since that's the only way I could reference variable/method names.
The problem we really need to solve here is that IE does not inherit altered document.domain values. So IFrames will have invalid document.domains. You can, however, force an IFrame to set its own document.domain, so that it is in sync with the outer page. However - this method requires letting the IFrame load and execute first. Which means that further operations must be executed in a callback after the iframe has loaded.
1) You need to add the follow two methods to the gwt .js file:
function waitForSetupInstallLocation(callback){
if (frameDoc) {
return;
}
var scriptFrame = $doc_0.createElement('iframe');
scriptFrame.src="javascript:document.open();document.domain='<domainvalue>';document.close();document.body.contentEditable=true;";
scriptFrame.id = '<MyWidgetSetName>';
scriptFrame.style.cssText = 'position:absolute; width:0; height:0; border:none; left: -1000px;' + ' top: -1000px;';
scriptFrame.tabIndex = -1;
$doc_0.body.appendChild(scriptFrame);
var addedContent = false;
try {
setFrameDoc(scriptFrame);
callback();
}
catch(e){
scriptFrame.onload = function(){
if(!addedContent){
addedContent = true;
setFrameDoc(scriptFrame);
callback();
}
};
}
}
function setFrameDoc(scriptFrame){
frameDoc = scriptFrame.contentDocument;
if (!frameDoc) {
frameDoc = scriptFrame.contentWindow.document;
}
frameDoc.open();
var doctype = document.compatMode == 'CSS1Compat'?'<!doctype html>':'';
frameDoc.write(doctype + '<html><head><\/head><body><\/body><\/html>');
frameDoc.close();
}
These two methods allow GWT to inject code into the page while also waiting for IE to load an IFRAME which then changes its own document.domain. You can see that the first accepts a callback. The callback is executed only after the IFrame is loaded.
The next issue is that these are asynchronous methods, and only accept callbacks. GWT currently does all setup synchronously. So the next modification is that the two methods that need to use it must be altered. All of the inner content of the following methods:
function installCode(code_0){...}
<WidgetSetName>.__installRunAsyncCode = function(code_0){...}
Needs to be wrapped in a function, and passed to the waitForSetupInstallLocation method as a callback. So that essentially you have turned those methods into asynchronous methods.
An example of what this looks like is:
function installCode(code_0){
waitForSetupInstallLocation(function(){
<real method content>
});
}
Once you have done all this - it should work in IE, and should remain functional in other browsers, since youve only added the use of a callback.
In order to load some geojson data, I need to source scripts on an external domain, say http://www.stat.ucla.edu/~jeroen/files/la_county_simplified.min.json. I have no control over the contents of this script; all I know is the url, and the name of an object defined in the script that I am interested in. A dummy version of the script looks like:
var my_data = {"foo" : 123, "bar" : 456}
Now in my application, I would like to load the my_data object dynamically from its URL. Because it is cross domain, I can't use ajax. It isn't quite jsonp either, because my script defines an object, not a function. One way would be to insert it simply as a <script> in the head of the current document. However, I would like to avoid possible side effects.
What would be a cleaner solution? I was thinking of creating an <iframe> and then inserting the <script> tag in the iframe, and extracting the object once the iframe has loaded. However I am not sure this is a reliable method that will work cross browsers (especially binding a callback to extract the object after the script has been loaded in the iframe).
Is there some library or standard solution to load a script in a clean page, and extract copy over a particular object to the main page? I already have a dependency on jQuery so that would be fine.
If you plan to do this pure client-side and can't format your data, you could use JSONP with a twist. Instead of modifying the data to fit the callback, we refit the loader to adopt to the data.
We listen for the onload of the script. When the script loads, the variable should now be in the global scope and we execute our callback, which passes that global variable into our callback.
//your script loader
function loadData(url,varName,callback){
var script = document.createElement('script');
document.getElementsByTagName('head')[0].appendChild(script);
//when the script loads, we pass in `window.my_data`
script.onload = function(){
callback.call(this,window[varName]);
};
script.src = url;
}
//using it
loadData('http://example.com/somefile.js','my_data',function(data){
//executes when script is loaded, where data is `my_data`
});
The drawback of this approach is that every time you are loading the script, you are loading it into the global scope, and collisions could happen.
There is no other way around it since you have to beat the same origin policy you have to load the script in a new script tag, JSONP works this way too but jquery hides it for you.
Either that or the site has cors headers, if the site has no cors headers here is how you can load the data (not using jsonp because it isn't in jsonp format):
function loadJS(url){
var s=document.createElement("script");
s.src=url;
$(s).on("load",function(){
console.log("var abvailable");//do something with the variable here
$(s).remove();
});
document.head.appendChild(s);
}
loadJS("http://code.jquery.com/jquery-1.9.1.min.js");
The iframe method should work fine:
create an iframe
inject a script tag that points to the file
on script load, retrieve the object
The only cross-browser issue I can think of is that you'll need to use addEventListener in modern browsers and attachEvent in old IE.
This is a standard use of an iframe as sandbox - if I understand correctly you are worried about possible conflicts with global variable names.
[Update] To address some of your comments, here is some cross-browser code:
To add an event listener:
function addEvent(element,event,fn,capture){
// capture defaults to false if omitted
if (element.addEventListener) {element.addEventListener(event,fn,(capture||false));}
// else for old IE
else {element.attachEvent('on'+event,fn);}
};
To access the iframe document:
function iframeDocument(ifr){
var doc=ifr.contentWindow||ifr.contentDocument;
if (doc.document) doc=doc.document;
return doc;
};
If you use jQuery, .on("load") and $(ifr).contents() will take care of these cross-browser compatibility issues.
JSON-P is a way of loading JavaScript from a remote domain.
The return format of the JavaScript is to invoke a function with the response data as an parameter.
someGlobalFunctionName({/* your response data */});
function someGlobalFunctionName(data) { /* do something with data */ }
Since the data is contained in an object and passed to a function, there is no global leakage other than the global function itself, which is unavoidable.
More info: http://json-p.org/
I have site which uses $(selector).load(path) function in more than 300 pages. Now my client's requirement has changed and I need to access cross domain to call these pages.
For the purpose I have to replace all the .load( function to some cross-domain function with the help of YQL.
Is it possible to override my .load function and call prevent default and do my own code?
There is no clean way to do this, especially since $.fn.load does different things depending on the arguments and replacing it would affect all those subfunctions.
However, jQuery supports AJAX hooks which you might be able to achieve what you want.
In case all you need is support for IE's XDomainRequest, have a look at this plugin: https://github.com/jaubourg/ajaxHooks/blob/master/src/ajax/xdr.js
Anyway, if you really want to replace the ajax load function of jQuery, this code should do it:
var _load = $.fn.load;
$.fn.load = function(url, params, callback) {
if(typeof url !== "string") {
return _load.apply(this, arguments);
}
// do your ajax stuff here
}
This is exactly the same check jQuery uses to decide whether someone wants to bind the onload event or perform an AJAX load.
The most reasonnable way seems to me to not overload the jquery function but simply do a search and replace in your favorite editor to replace $(xxx).load( by yourpackage.load(xxx,.
This can be done in minutes even on 300 js files. Future changes will be easier and the code will be more readable as the reader never expects a jquery function to do something that isn't on the doc.
Yes, it's possible:
$.fn.load = yourFunc;
Is it recommended? I think not.
My site uses pushState to load pages. I have one issue, I want to use javascript on one of the pages but can't because it loads everything with AJAX. So what do I do? I've been told something about "parseScript" but I can't find enough information on it.
--Example--
I load using AJAX
On my page I have this script:
<script type="text/javascript">
function go(){
alert('1');
}
</script>
GO!!!
Nothing happens.
--Edit--
If I open up Google Chrome's debugger:
"Uncaught ReferenceError: go is not defined"
And the <script> tag is no where to be found
Browsers don't seem to parse <script> element content that's added to the document via targetElement.innerHTML. That's probably what you're running into.
The best solution is to use a well-tested framework like jQuery for solving problems like this. They've already figured out how to safely and correctly inject scripts into the DOM. There's no sense re-inventing the wheel unless you absolutely can't spare the bandwidth for the library.
One way you might fix this is by separating the JavaScript from the HTML in the Ajax response, either by issuing two requests (probably slower) or by structuring your JavaScript and HTML within a JSON object (probably harder to maintain).
Here's an example:
<script>
function load_content(){
var req = new XMLHttpRequest();
req.open("GET", "ajax.json", true);
req.onreadystatechange = function (e){
if (req.readyState === 4){
if (req.status === 200){
// these three lines inject your JavaScript and
// HTML content into the DOM
var json = JSON.parse(req.responseText);
document.getElementById("target").innerHTML = json.html;
eval(json.js);
} else {
console.log("Error", req.statusText);
}
}
};
req.send(null);
}
</script>
Load more stuff
<div id="target"></div>
The document ajax.json on the server looks like this:
{
"js": "window.bar = function (){ console.log(\"bar\"); return false; }",
"html": "<p>Log a message</p>"
}
If you choose this route, you must either:
namespace your functions: MyApp.foo = function (){ ... };, or
explicitly add your functions to the global namespace: window.foo = function (){ ... };.
This is because eval executes in the current scope, so your function definitions inherit that scope and won't be globally available. In my example, I chose the latter option since it's just a trivial example, but you should be aware of why this is necessary.
Please make sure to read When is JavaScript's eval() not evil? if you decide to implement this yourself.
I think it would be helpful to have a little more detail as to how the Ajax call is made and the content is loaded. That said, a few things of note:
the syntax for javascript:void() is invalid. It should be javascript:void(0). For that matter, using javascript:void() on the href of an anchor tag is generally bad practice. Some browsers do not support it. If you must use an tag, set the href to # and add "return false;" to the click event.
you should use a button tag instead of the a tag in this case anyway.
given what you have provided, it should work (aside from the syntax error with void())
If I were to do this I would use jquery's load call.
That takes care of putting an ajax call ,and parsing tags for script/no-script elements.
IF you dont wanna use jquery, I would suggest you go online and find what the jquery load method does and implement the same as an event handler for your ajax call.
I'm quite sure this a common question, but I'm pretty new to JS and am having some trouble with this.
I would like to load x.html into a div with id "y" without using iframes. I've tried a few things, searched around, but I can't find a decent solution to my issue.
I would prefer something in JavaScript if possible.
Wow, from all the framework-promotional answers you'd think this was something JavaScript made incredibly difficult. It isn't really.
var xhr= new XMLHttpRequest();
xhr.open('GET', 'x.html', true);
xhr.onreadystatechange= function() {
if (this.readyState!==4) return;
if (this.status!==200) return; // or whatever error handling you want
document.getElementById('y').innerHTML= this.responseText;
};
xhr.send();
If you need IE<8 compatibility, do this first to bring those browsers up to speed:
if (!window.XMLHttpRequest && 'ActiveXObject' in window) {
window.XMLHttpRequest= function() {
return new ActiveXObject('MSXML2.XMLHttp');
};
}
Note that loading content into the page with scripts will make that content invisible to clients without JavaScript available, such as search engines. Use with care, and consider server-side includes if all you want is to put data in a common shared file.
jQuery .load() method:
$("#y").load("x.html");
Using fetch
<script>
fetch('page.html')
.then(response=> response.text())
.then(text=> document.getElementById('elementID').innerHTML = text);
</script>
<div id='elementID'> </div>
fetch needs to receive a http or https link, this means that it won't work locally.
Note: As Altimus Prime said, it is a feature for modern browsers
2021
Two possible changes to thiagola92's answer.
async await - if preferred
insertAdjacentHTML over innerText (faster)
<script>
async function loadHtml() {
const response = await fetch("page.html")
const text = await response.text()
document.getElementById('elementID').insertAdjacentText('beforeend', text)
}
loadHtml()
</script>
<!-- ... -->
<div id='elementID'> </div>
I'd suggest getting into one of the JS libraries out there. They ensure compatibility so you can get up and running really fast. jQuery and DOJO are both really great. To do what you're trying to do in jQuery, for example, it would go something like this:
<script type="text/javascript" language="JavaScript">
$.ajax({
url: "x.html",
context: document.body,
success: function(response) {
$("#yourDiv").html(response);
}
});
</script>
document.getElementById("id").innerHTML='<object type="text/html" data="x.html"></object>';
There was a way to achieve this in the past, but it was removed from the specification, and subsequently, from browsers as well (e.g. Chrome removed it in Chrome 70). It was called HTML imports and it originally was part of the web components specs.
Currently folks are working on a replacement for this obviously lacking platform feature, which will be called HTML modules. Here's the explainer, and here's the Chrome platform status for this feature. There is no milestone specified yet as of when this feature will land.
Chances are the syntax is going to look similar to this:
import { content } from "file.html";
Resolving the remaining issues with HTML modules I assume might take quite some time, so until then the only viable options you have is to have
either your build stack resolve the issue for you (e.g. with webpack-raw-loader (Webpack 4), or with asset modules (Webpack 5)),
or to rely on async fetch to get the job done (which might result in a less-than-optimal performance experience).
We already have JSON modules and CSS module scripts (which both were sorely missing features for a long time as well).
http://www.boutell.com/newfaq/creating/include.html
this would explain how to write your own clientsideinlcude but jQuery is a lot, A LOT easier option ... plus you will gain a lot more by using jQuery anyways