Update: Since this is unanswered, I'm changing the question slightly. Comments on the post on Dean's blog linked below indicate this technique does not work in Safari.
My question now is: does the technique described below work* in modern browsers, and in particular can someone confirm whether it works in Safari?
Here's a more recent blog post. It says at one point:
Sandboxed natives ... are supported in a variety of browsers, including ... Safari 2.0+
...but later says that the iframe technique is "supported by all major browsers except Safari," and the fallback he shows involves doing some weird stuff with faked constructors and __proto__
that seems a bit hacky.
I almost find it hard to believe that two different windows could actually share the same, say, Object.prototype. What happens with cross-domain iframes? If I modify prototypes in one frame, do the prototypes in the other frame get modified? This seems like an obvious security concern. Someone please shed some light on this situation.
* By "work" I mean My.Object != Object
, so the prototypes can be modified in one window without affecting the other.
Original post
I know this has been asked before, but I have a specific solution in mind, and I want to know if this type of solution has been discussed before and where I might learn how reliable and well-accepted it is.
The question is how to extend native types in javascript without actually messing with the types themselves, so just altering Array.prototype is no good (maybe other code is using for..in with arrays). Creating a fake constructor that returns a native array with some functions tacked on doesn't seem like a good solution either, actually extending the native objects seems better. But you can't do the normal javascript dummy function prototype switcharoo style extension with native types either, because you'll get errors like "push is not generic" when you try to call native functions.
So, the solution I have in mind works like this: create another window, add functionality to prototypes of native constructors in that window, and use those constructors in your program.
This example extends Array
as My.Array
with an each
function and String
as My.String
with an alert
function.
var My = (function(){
// create an iframe to get a separate global scope
var iframe = document.createElement('iframe');
iframe.style.height = '0px';
iframe.style.width = '0px';
iframe.style.border = 'none';
iframe.style.position = 'absolute';
iframe.style.left = '-99999px';
document.documentElement.appendChild(iframe);
var My = iframe.contentWindow;
My.String.prototype.alert = function(){
alert(this);
}
My.Array.prototype.each = function(callback){
for (var i=0, l=this.length; i<l; i++) {
callback(this[i], i);
}
}
return My;
}());
Again, my question is whether this approach has been discussed before, what it's called, where I can find more information, etc. I'd like to know if there is a cleaner way to get another global scope without using an iframe, or if it's possible this will fail for some reason in certain javascript engines, or if anyone thinks it's a particularly bad idea or whatever.
Update: I guess people are calling this kind of thing an iframe sandbox, not to be confused with the HTML5 iframe sandbox attribute.
related:
http://dean.edwards.name/weblog/2006/11/hooray/
http://webreflection.blogspot.com/2008/03/javascript-arrayobject.html
This technique is quite dangerous because it can be misused to infect the user with some unwanted or corrupted software or malicious program. Such things sometimes prompt users to download or click on things that will bring harm to their system.
The iFrame contains a malicious form that can lead the user to submit sensitive information. This threat can be solved by using sandbox with removing allow-forms . The iFrame may unintentionally download malware to the user's computer.
The sandbox attribute enables an extra set of restrictions for the content in the iframe. When the sandbox attribute is present, and it will: treat the content as being from a unique origin. block form submission.
iframe injection is a very common cross-site scripting attack. iframes use multiple tags to display HTML documents on web pages and redirect users to different web addresses. This behavior allows 3rd parties to inject malicious executables, viruses, or worms into your application and execute them in user's devices.
I ran this page in various browsers (Safari, Opera, IE7-9, Chrome, Firefox) and got consistent results with everything but firefox, in firefox the prototypes are sandboxed so that's good but the second test fails for some reason in firefox. The iframe prototype isn't immediately augmented. But this doesn't matter if you didn't mean to augment it anyway. You could try running it in more browsers to test.
Note that this doesn't really test any of the quirks for example (My.Array().slice
would return the main window
arrays depending on browser...) and there could be more. So I would say it's pretty unsafe.
It's an overkill anyway and seems too much work for no real gain.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script type="text/javascript">
(function(){
var ifr = document.createElement("iframe"),
callbacks = [],
hasReadyState = "readyState" in ifr;
ifr.style.display = "none";
document.body.appendChild(ifr);
ifrDoc = ifr.contentWindow.document;
ifrDoc.open();
ifrDoc.write( "<!DOCTYPE html><html><head></head><body>"+"<"+"script"+">var w = this;"+"</"+"script"+">"+"</body></html>");
ifrDoc.close();
if( hasReadyState ) {
ifr.onreadystatechange = function(){
if( this.readyState === "complete" ) {
fireCallbacks();
}
};
}
function fireCallbacks(){
var i, l = callbacks.length;
window.My = ifr.contentWindow.w;
for( i = 0; i < l; ++i ) {
callbacks[i]();
}
callbacks.length = 0;
}
function checkReady(){
if( hasReadyState && ifr.readyState === "complete" ) {
fireCallbacks();
}
else if( !hasReadyState ) {
fireCallbacks();
}
}
window.MyReady = function(fn){
if( typeof fn == "function" ) {
callbacks.push( fn );
}
};
window.onload = checkReady; //Change this to DOMReady or whatever
})()
MyReady( function(){
My.Object.prototype.test = "hi";
var a = new My.Object(),
b = new Object();
console.log( Math.random(), My.Object !== Object && b.test !== "hi", a.test === "hi" );
});
</script>
</body>
</html>
If you have two different frames with content loaded from different domains, then no modern browser will allow any interaction on the JavaScript level between them for obvious security reasons. Your best bet of course would be to setup a test and see what happens for yourself, but I'm pretty sure that what you're describing should be safe on most browsers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With