how can I access iframe.contentDocument to get response after cross-origin request?
Solution 1
After digging around to try and figure this out, I finally came across a solution that seems to work for me. However, it isn't an exact answer to my question.
In summary, I'm working on supporting <form>
based file uploads. For browsers that do not support file upload via XHR
, we have to resort to the traditional <form>
submission, using a hidden <iframe>
to avoid page refresh. The form redirects the reload action into the hidden <iframe>
, and then the HTTP response is written to the body of the <iframe>
after the file has been transferred.
Due to the same-origin policy, and hence the reason I asked this question, I don't have access to the <iframe>
's content. The same-origin policy for <iframe>
s restricts how a document or script loaded from one origin can interact with a resource from another origin. Since we can't get access to the <iframe>
's document, which is where the file upload HTTP response gets written to, the server will return a redirect response to which the server will append the upload response JSON. When the <iframe>
loads, the JS will parse out the response JSON, and write it to the <iframe>
's body. Finally, since the redirect was to the same origin, we can access the <iframe>
's contents :)
A huge thanks to jQuery File Uploader; they did all the hard work ;)
https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads
Setting Up...
JS
function setupForm() {
// form is declared outside of this scope
form = document.createElement('form');
form.setAttribute('id', 'upload-form');
form.setAttribute('target', 'target-iframe');
form.setAttribute('enctype', 'multipart/form-data');
form.setAttribute('method', 'POST');
// set the 'action' attribute before submitting the form
form.setAttribute('action', 'invalid');
};
function setupIframe() {
// iframe is declared outside of this scope
iframe = document.createElement('iframe');
/*
* iframe needs to have the 'name' attribute set so that some versions of
* IE and Firefox 3.6 don't open a new window/tab
*/
iframe.id = 'target-iframe';
iframe.name = 'target-iframe';
/*
* "javascript:false" as initial iframe src to prevent warning popups on
* HTTPS in IE6
*/
iframe.src = 'javascript:false;';
iframe.style.display = 'none';
$(iframe).bind('load', function() {
$(iframe)
.unbind('load')
.bind('load', function() {
try {
/*
* the HTTP response will have been written to the body of the iframe.
* we're assuming the server appended the response JSON to the URL,
* and did the redirect correctly
*/
var content = $(iframe).contents().find("body").html();
response = $.parseJSON(content);
if (!response) {
// handle error
return;
}
uploadFile(...); // upload the next file
}
catch (e) {
// handle error
}
});
});
/*
* insert the iframe as a sibling to the form. I don't think it really
* matters where the iframe is on the page
*/
$(form).after(iframe);
};
HTML - redirect page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
// grabs the JSON from the end of the URL
document.body.innerHTML = decodeURIComponent(window.location.search.slice(1));
</script>
</body>
</html>
The only thing left to do is set the action
attribute on the <form>
to the cross-domain URL that we're sending the upload to:
form.setAttribute('action', 'http://sub.example.com:8080/upload?id=ab123');
form.submit();
Meanwhile, on the server...
// send redirect to the iframe redirect page, where the above HTML lives
// generates URL: http://example.com/hack?%7B%22error%22%3Afalse%2C%22status%22%3A%22success%22%7D
response.sendRedirect("http://example.com/hack?{\"error\":false,\"status\":\"success\"}");
I know this is a massive hack to get around the same-origin policy with <iframe>
s, but it seems to work, and I think it's pretty good with cross-browser compatibility. I haven't tested it in all browsers, but I'll get around to doing that, and post an update.
Solution 2
Well, I came to solution also. It's a bit hard coded atm, but still looks neat.
First I made an html file on the server. (I'll modify it to ejs template later, that includes my data).
<!DOCTYPE html>
<html>
<title>Page Title</title>
<script>
function myFunction() {
parent.postMessage('Some message!!!', 'http://192.168.0.105:3001'); // hard coded, will change this later
}
window.onload=myFunction;
</script>
<body>
</body>
</html>
Important part here is the use of parent.
Than from my node server I'm uploading the file and sending back to the client the html file:
res.sendFile('file.html');
On the client I have the same html like you.
'<form id="{id}_form" action="http://192.168.0.105:3011/private/profile_picture/upload" enctype="multipart/form-data" method="post" target="{id}_uploadframe">',
'<span id="{id}_wrapper" class="file-wrapper">',
'<input id="{id}_real" type="file" accept="image/*" name="photo" />',
'<span class="button">{0}</span>',
'</span>',
'</form>',
'<iframe id="{id}_uploadframe" name="{id}_uploadframe" class="mc-hidden"></iframe>',
This template I rendered on the page. I added the following event Handler also
window.addEventListener('message',function(event) {
//if(event.origin !== cross_domain) return;
console.log('message received: ' + event.data,event);
},false);
As you know addEventListener is not working on all browsers. And this solution will not work on IE8 < 8, which does not support postMessage. Hope this is hapefull to you
Comments
-
Hristo about 4 years
I'm successfully sending a file from
localhost:8888
tolocalhost:8080
(different domain in production), but I can't read the HTTP response after the transfer finishes.Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "http://localhost:8888" from accessing a frame with origin "http://localhost:8080". The frame requesting access set "document.domain" to "localhost", but the frame being accessed did not. Both must set "document.domain" to the same value to allow access.
To send the file, for compatibility support, I'm trying to get this to work for
<form>
based file uploads; notXHR
based. This is the basic HTML structure:<form target="file-iframe" enctype="multipart/form-data" method="POST" action="invalid"> <input type="file" id="file-input" class="file-input" title="select files"> </form> <iframe src="javascript:false;" id="file-iframe" name="file-iframe"></iframe>
To insert the
<iframe>
element into the DOM, I do the following:document.domain = document.domain; var domainHack = 'javascript:document.write("<script type=text/javascript>document.domain=document.domain;</script>")'; var html = '<iframe id="file-iframe" name="file-iframe"></iframe>'; var parent = document.getElementById('wrapper'); var iframe = UTILS.createDomElement(html, parent); iframe.src = domainHack; UTILS.attachEvent(iframe, 'load', function(e) { // this throws the above SecurityError var doc = iframe.contentDocument || iframe.contentWindow.document; // do other stuff... });
Before I submit the form, I set the
action
attribute on the<form>
to be the target cross-domain URL:action="http://localhost:8080/"
After submitting the
<form>
, the<iframe>
'sload
event is fired, and I try to access the<iframe>
's content to read the HTTP response. However, doing so throws the above error since this is is a cross-origin request, and I don't have access to the<iframe>
's content.I thought the
document.domain
hack would work, but the error message is telling me that theiframe
did not set the domain tolocalhost
, even though I set theiframe
'ssrc
attribute to thedomainHack
variable, which seems to execute.Any ideas as to what I might be doing wrong? How can I set
document.domain
to belocalhost
for both the<iframe>
and its parent (which is the current page).
I've read through several StackOverflow questions, some MDN articles, and other random results on Google, but I haven't been able to get this to work. Some stuff I've looked through already: