Hello Javascript ninjas ! I have a pretty tough issue to solve and did not find any satisfying solution.
For a very specific Javascript framework I am developping, I need to be able to set the __proto__ property of a dynamically created function. I have some kind of generic function factory and need to have common definitions for the created functions.
I'd like not to argue wether or not this is a good practice as I really need to achieve this for perfectly valid reasons.
Here is a small QUnit sample that runs perfectly on Chrome latest version that shows what I need :
var oCommonFunctionProto = {};
var fnCreateFunction = function () {
var fnResult = function () {};
fnResult.__proto__ = oCommonFunctionProto; // DOES NOT WORK WITH IE9 OR IE10
return fnResult;
};
var fn1 = fnCreateFunction();
oCommonFunctionProto.randomMethod = function() { return 10; };
equal(fn1.randomMethod(), 10, "__proto__ has been set properly");
var oInstance = new fn1(); // fn1 is instantiable
As you can see on this code, anything added to oCommonFunctionProto will be available directly on any function returned by fnCreateFunction method. This allows to build prototype chain on Function objects (like it's often done on prototype chains for objects.
Here is the problem : __proto__ property is immutable in IE9 and IE10 and sadly, I really need to be compatible with those browsers.
Moreover :
I cannot use any third party. I need a fully functional code that do not depend on anything else.
As you can see, the randomMethod was added after the creation of the function. I really need the prototype chaining as in my scenarios, this objects will me modified after function creations. Simply duplicating oCommonFunctionProto properties on the function prototype will not work.
I'm perfectly okay with suboptimal code as long as it does the job. This will be a compatibility hack just for IE9/IE10. AS long as it does the job, I'll be happy.
It could be okay to set the __proto__ at function creation. It's better if I can do it afterwards, but if I have no choice, this can be acceptable.
I tried every hack I could but did not find any way to bypass this limitation on IE9/IE10.
TL;DR : I have to be able to set __proto__ on a javascript function without the help of any third party in IE9 and IE10.
Based on other answers and discussions, it appears this is just not possible for IE<11.
I finally dropped prototype chains, be it for Objects or Functions, in favor of flattened prototype and notification when a logical "parent" prototype changes to update "child" prototype accordingly.
Related
Google JavaScript Style Guide advises against extending the Array.prototype.
However, I used Array.prototype.filter = Array.prototype.filter || function(...) {...} as a way to have it (and similar methods) in browsers where they do not exist. MDN actually provides similar example.
I am aware about Object.prototype issues, but Array is not a hash table.
What issues may arise while extending Array.prototype that made Google advise against it?
Most people missed the point on this one. Polyfilling or shimming standard functionality like Array.prototype.filter so that it works in older browsers is a good idea in my opinion. Don't listen to the haters. Mozilla even shows you how to do this on the MDN. Usually the advice for not extending Array.prototype or other native prototypes might come down to one of these:
for..in might not work properly
Someone else might also want to extend Array with the same function name
It might not work properly in every browser, even with the shim.
Here are my responses:
You don't need to use for..in on Array's usually. If you do you can use hasOwnProperty to make sure it's legit.
Only extend natives when you know you're the only one doing it OR when it's standard stuff like Array.prototype.filter.
This is annoying and has bit me. Old IE sometimes has problems with adding this kind of functionality. You'll just have to see if it works in a case by case basis. For me the problem I had was adding Object.keys to IE7. It seemed to stop working under certain circumstances. Your mileage may vary.
Check out these references:
http://perfectionkills.com/extending-native-builtins/
http://blip.tv/jsconf/jsconf2011-andrew-dupont-everything-is-permitted-extending-built-ins-5211542
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
https://github.com/kriskowal/es5-shim
Good luck!
I'll give you the bullet points, with key sentences, from Nicholas Zakas' excellent article Maintainable JavaScript: Don’t modify objects you don’t own:
Dependability: "The simple explanation is that an enterprise software product needs a consistent and dependable execution environment to be maintainable."
Incompatible implementations: "Another peril of modifying objects that you don’t own is the possibility of naming collisions and incompatible implementations."
What if everyone did it?: "Simply put: if everyone on your team modified objects that they didn’t own, you’d quickly run into naming collisions, incompatible implementations, and maintenance nightmares."
Basically, don't do it. Even if your project is never going to be used by anyone else, and you're never going to import third party code, don't do it. You'll establish a horrible habit that could be hard to break when you start trying to play nice with others.
As a modern update to Jamund Ferguson's answer:
Usually the advice for not extending Array.prototype or other native prototypes might come down to one of these:
for..in might not work properly
Someone else might also want to extend Array with the same function name
It might not work properly in every browser, even with the shim.
Points 1. and 2. can now be mitigated in ES6 by using a Symbol to add your method.
It makes for a slightly more clumsy call structure, but adds a property that isn't iterated over and can't be easily duplicated.
// Any string works but a namespace may make library code easier to debug.
var myMethod = Symbol('MyNamespace::myMethod');
Array.prototype[ myMethod ] = function(){ /* ... */ };
var arr = [];
// slightly clumsier call syntax
arr[myMethod]();
// Also works for objects
Object.prototype[ myMethod ] = function(){ /* ... */ };
Pros:
For..in works as expected, symbols aren't iterated over.
No clash of method names as symbols are local to scope and take effort to retrieve.
Cons:
Only works in modern environments
Slightly clunky syntax
Extending Array.prototype in your own application code is safe (unless you use for .. in on arrays, in which case you need to pay for that and have fun refactoring them).
Extending native host objects in libraries you intend others to use is not cool. You have no right to corrupt the environment of other people in your own library.
Either do this behind an optional method like lib.extendNatives() or have [].filter as a requirement.
Extending Natives and Host Objects
Prototype does this. It's evil. The following snippet demonstrates how doing so can produce unexpected results:
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script>
<script language="javascript">
a = ["not", "only", "four", "elements"];
for (var i in a)
document.writeln(a[i]);
</script>
The result:
not only four elements function each(iterator, context) { var index = 0; . . .
and about 5000 characters more.
I want to add an additional answer that allows extending the Array prototype without breaking for .. in loops, and without requiring use of hasOwnPropery:
Don't use this bad approach which causes prototype values to appear in for .. in:
Array.prototype.foo = function() { return 'foo'; };
Array.prototype.bar = function() { return 'bar'; };
let a = [ 1, 2, 3, 4 ];
console.log(`Foo: ${a.foo()}`);
console.log(`Bar: ${a.bar()}`);
console.log('==== Enumerate: ====');
for (let v in a) console.log(v);
Instead use Object.defineProperty, with enumerable: false - it exists for pretty much exactly this reason!
Object.defineProperty(Array.prototype, 'foo', {
value: function() { return 'foo'; },
enumerable: false
});
Object.defineProperty(Array.prototype, 'bar', {
value: function() { return 'bar'; },
enumerable: false
});
let a = [ 1, 2, 3, 4 ];
console.log(`Foo: ${a.foo()}`);
console.log(`Bar: ${a.bar()}`);
console.log('==== Enumerate: ====');
for (let v in a) console.log(v);
Note: Overall, I recommend avoiding enumerating Arrays using for .. in. But this knowledge is still useful for extending prototypes of classes where enumeration is appropriate!
Some people use for ... in loops to iterate through arrays. If you add a method to the prototype, the loop will also try to iterate over that key. Of course, you shouldn't use it for this, but some people do anyway.
You can easily create somekind of sandbox with poser library.
Take a look on https://github.com/bevacqua/poser
var Array2 = require('poser').Array();
// <- Array
Array2.prototype.eat = function () {
var r = this[0];
delete this[0];
console.log('Y U NO .shift()?');
return r;
};
var a = new Array2(3, 5, 7);
console.log(Object.keys(Array2.prototype), Object.keys(Array.prototype))
I believe this question deserves an updated ES6 answer.
ES5
First of all, as many people have already stated. Extending the native prototypes to shim or polyfill new standards or fix bugs is standard practice and not harmful. For example if a browser doesn't support the .filter method if (!Array.prototype.filter) you are free to add this functionality on your own. In-fact, the language is designed to do exactly this to manage backwards compatibility.
Now, you'd be forgving for thinking that since JavaScript object use prototypal inheritance, extending a native object like Array.prototype without interfering should be easy, but up until ES6 it's not been feasible.
Unlike objects for example, you had to rely and modifying the Array.prototype to add your own custom methods. As others have pointed out, this is bad because it pollutes the Global namespace, can interfere with other code in an unexpected way, has potential security issues, is a cardinal sin etc.
In ES5 you can try hacking this but the implementations aren't really practically useful. For more in depth information, I recommend you check out this very informative post: http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/
You can add a method to an array, or even an array constructor but you run into issues trying to work with the native array methods that rely on the length property. Worst of all, these methods are going to return a native Array.prototype and not your shiny new sub-class array, ie: subClassArray.slice(0) instanceof subClassArray === false.
ES6
However, now with ES6 you can subclass builtins using class combined with extends Array that overcomes all these issues. It leaves the Array.prototype intact, creates a new sub-class and the array methods it inherits will be of the same sub-class! https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/
See the fiddle below for a demonstration:
https://jsfiddle.net/dmq8o0q4/1/
Extending the prototype is a trick that only works once. You do and you use a library that also does it (in an incompatible way) and boom!
The function you are overriding could be used by the internal javascript calls and that could lead to unexpected results. Thats one of the reasons for the guideline
For example I overrode indexOf function of array and it messed up accessing array using [].
I'm trying (perhaps in vain) to come up with a way to use the publish-subscribe pattern while a) using no libraries and b) minimizing boilerplate code in modules that use it. So far the best I've come up with is this:
var handle = document.createElement();
var unsubscribe = AwesomeModule.subscribe(handle);
handle.addEventListener('awesome', function() {
console.log('awesome');
});
This will work pretty well, except that people using AwesomeModule might be confused by having to provide a random DOM element that isn't used as an element.
I tried the following and it doesn't work too well:
var handle = Object.create(EventTarget);
var unsubscribe = AwesomeModule.subscribe(handle);
handle.addEventListener('awesome', function(){
console.log('awesome')
});
I get TypeError: Object [object Object] has no method 'addEventListener'. Interestingly enough, it doesn't seem to look in the prototype chain even though handle has EventTarget as its prototype.
Why doesn't this work? Is there a way implement EventTarget with pure JS? Can it be done in a single line of code that won't horrify users of AwesomeModule?
EDIT: I don't know why it didn't occur to me last night, but I suppose EventTarget being an interface means that it doesn't have implemented code. What's confusing is that in the Chrome debugger console Object.create(EventTarget) makes an object that appears to have addEventListener in is prototype chain. Maybe its lying. Can anyone explain this behavior? Can anyone explain why W3 chose not to make EventTarget a concrete "class"?
It looks like the answer to my original question is "yes." Since JavaScript doesn't have an inheritance model like Java which does compile-time checks for legal implementation, I suppose any Object can implement an interface merely by having methods with the same name. However, doing this would constitute making a library since the addEventListener code isn't implemented in EventTarget (I had previously assumed it was). Since there seems to be no cross-browser way to get a vanilla EventTarget, I will probably use window.addEventListener in conjunction with custom events.
The source is located here: https://code.google.com/p/chromium/codesearch#chromium/src/third_party/trace-viewer/src/base/event_target.js&sq=package:chromium&type=cs&l=18
If you can't modify it, you can always replicate it.
Here's a simple set of routines that works well.
with a small polyfill for IE9 and 10, support is decent.
you can incorporate these functions into your project as needed, i don't think it constitutes a library, or i wouldn't post this.
var on = addEventListener.bind(window),
off = removeEventListener.bind(window),
emit = function(name, val) {
dispatchEvent(new CustomEvent(name, {
detail: val
}));
};
// demo:
on("test", function(e){ alert(e.detail);});
emit("test", "Number is " + Math.random());
i don't think it can get much simpler (~180 chars) without sacrificing speed or library compatibility.
Can I replace s standard DOM functions like removeChild to show for example alert before some node removed from parent? Something like that but my example is with errors
var original = node.removeChild;
node.removeChild = function(node, original){
alert('message');
original(node);
}
If you want to apply this across the document do this
var original = Node.prototype.removeChild;
Node.prototype.removeChild = function(node) {
//custom logic
original.apply(this, arguments);
}
If you want to apply the change only to a selected node then
var original = node.removeChild;
node.removeChild = function(node){
//custom logic
original.apply(this, arguments);
}
First of all, the usage of the new keyword is completely incorrect. That will severely change the behaviour. function is an instance of Function, the "mothership" of all JavaScript functions.
When using the new keyword, the function will be immediately executed and the behaviour very different from what you expect. See Section 13 of the ECMA Language Specification for more details on how to create function objects. Read it here, at page 98.
Second, it is strongly discouraged to alter any native object prototype. It leads to the most tedious and painful bugs in human history. Anybody coming in after you to edit the code will spend a long time before figuring out where that alert originates from.
Keep those two actions separate, they are completely unrelated. Wrapping them up in a prototype function is very bad design, for the above reason any many more, such as:
Using for in loops. You will iterate through more properties that you should if you forget to use hasOwnProperty.
Yourself and other developers will have a hard time figuring out why random things are happing with a basic DOM Node remove operation occurs.(you will forget, happens to everybody).
I am going to be radical an just say no. While it may technically work on the browsers you care about and be the basis for prototype.js, you should never modify DOM objects, or their prototypes, like this.
There is a long post on the topic: http://perfectionkills.com/whats-wrong-with-extending-the-dom/ but the TL;DR is that these are "hosted objects" and modification to their behavior is not guaranteed. It may work today on browser x but there's no guarantee about browser y or even x tomorrow.
Your implementation looks fine, except that you don't need to pass original as an argument there.
var original = node.removeChild;
node.removeChild = function(node) {
alert('message');
original(node);
}
We're using a custom JavaScript library at work where we do this:
DOM.__proto__ = Library.prototype;
to quickly transfer internal functions/properties DOM objects so that it can be used similar to how jQuery for example does it.
For a new project we need to implement it for Internet Explorer but unfortunately __proto__ is not supported by IE.
Any ideas, workarounds of polyfills for this? The requirement is IE10 only (but IE9 would be nice too).
Instead of assigning to __proto__ after creating the DOM object, use Object.create (MSDN, supported since IE9) to create the object with the correct prototype, and then afterwards assign properties to it.
var DOM = {};
DOM.__proto__ = Library.prototype;
var DOM = Object.create(Library.prototype);
If you wanted to modify the prototype of existing (and maybe even foreign) objects, just avoid it. It's a bad practise anyway.
Instead of directly extending the DOM element as you are, you could wrap it with your API;
// Wrapping Constructor
function Library(element) {
this.element = element;
}
// Whatever it is your library does
Library.prototype = {
// some example method
html: function(markup) {
// refer to "this.element" instead of "this"
this.element.innerHTML = markup;
}
};
// example
var wrappedElement = new Library(document.getElementById('unique'));
// refer to the API rather than the Element directly
wrappedElement.html('<span>Hello World</span>');
You can also extend Library some more by safely subclassing Array with this technique.
Hope this helps, thanks.
What about extending it:
$.extend(Element.constructor.prototype, Library.prototype);
EDIT: due to public outcry, I'll note that extend is a very familiar concept in the context of JS programming in the year of 2014. You may find online numerous examples of what it is and how it works, and there's a pretty damn good chance at least one library you use already in your project implements it (jQuery, Lo-Dash/Underscore, AngularJS, Ember.js...)
I would really like to provide the user some scripting capabilities, while not giving it access to the more powerful features, like altering the DOM. That is, all input/output is tunneled thru a given interface. Like a kind of restricted javacsript.
Example:
If the interface is checkanswer(func)
this are allowed:
checkanswer( function (x,y)={
return x+y;
}
but these are not allowed:
alert(1)
document.write("hello world")
eval("alert()")
EDIT: what I had in mind was a simple language that was implemented using javascript, something like http://stevehanov.ca/blog/index.php?id=92
(Edit This answer relates to your pre-edit question. Don't know of any script languages implemented using Javascript, although I expect there are some. For instance, at one point someone wrote BASIC for Javascript (used to have a link, but it rotted). The remainder of this answer is therefore pretty academic, but I've left it just for discussion, illustration, and even cautionary purposes. Also, I definitely agree with bobince's points — don't do this yourself, use the work of others, such as Caja.)
If you allow any scripting in user-generated content, be ready for the fact you'll be entering an arms race of people finding holes in your protection mechanisms and exploiting them, and you responding to those exploits. I think I'd probably shy away from it, but you know your community and your options for dealing with abuse. So if you're prepared for that:
Because of the way that Javascript does symbol resolution, it seems like it should be possible to evaluate a script in a context where window, document, ActiveXObject, XMLHttpRequest, and similar don't have their usual meanings:
// Define the scoper
var Scoper = (function() {
var rv = {};
rv.scope = function(codeString) {
var window,
document,
ActiveXObject,
XMLHttpRequest,
alert,
setTimeout,
setInterval,
clearTimeout,
clearInterval,
Function,
arguments;
// etc., etc., etc.
// Just declaring `arguments` doesn't work (which makes
// sense, actually), but overwriting it does
arguments = undefined;
// Execute the code; still probably pretty unsafe!
eval(codeString);
};
return rv;;
})();
// Usage:
Scoper.scope(codeString);
(Now that uses the evil eval, but I can't immediately think of a way to shadow the default objects cross-browser without using eval, and if you're receiving the code as text anyway...)
But it doesn't work, it's only a partial solution (more below). The logic there is that any attempt within the code in codeString to access window (for instance) will access the local variable window, not the global; and the same for the others. Unfortunately, because of the way symbols are resolved, any property of window can be accessed with or without the window. prefix (alert, for instance), so you have to list those too. This could be a long list, not least because as bobince points out, IE dumps any DOM element with a name or an ID onto window. So you'd probably have to put all of this in its own iframe so you can do an end-run around that problem and "only" have to deal with the standard stuff. Also note how I made the scope function a property of an object, and then you only call it through the property. That's so that this is set to the Scoper instance (otherwise, on a raw function call, this defaults to window!).
But, as bobince points out, there are just so many different ways to get at things. For instance, this code in codeString successfully breaks the jail above:
(new ('hello'.constructor.constructor)('alert("hello from global");'))()
Now, maybe you could update the jail to make that specific exploit not work (mucking about with the constructor properties on all — all — of the built-in objects), but I tend to doubt it. And if you could, someone (like Bob) would just come up with a new exploit, like this one:
(function(){return this;})().alert("hello again from global!");
Hence the "arms race."
The only really thorough way to do this would be to have a proper Javascript parser built into your site, parse their code and check for illegal accesses, and only then let the code run. It's a lot of work, but if your use-case justifies it...
T.J. Crowder makes an excellent point about the "arms race." It's going to be very tough to build a watertight sandbox.
it's possible to override certain functions, though, quite easily.
Simple functions:
JavaScript: Overriding alert()
And according to this question, even overriding things like document.write is as simple as
document.write = function(str) {}
if that works in the browsers you need to support (I assume it works in all of them), that may be the best solution.
Alternative options:
Sandboxing the script into an IFrame on a different subdomain. It would be possible to manipulate its own DOM and emit alert()s and such, but the surrounding site would remain untouched. You may have to do this anyway, no matter which method(s) you choose
Parsing the user's code using a white list of allowed functions. Awfully complex to do as well, because there are so many notations and variations to take care of.
There are several methods to monitor the DOM for changes, and I'm pretty sure it's possible to build a mechanism that reverts any changes immediately, quite similar to Windows's DLL management. But it's going to be awfully complex to build and very resource-intensive.
Not really. JavaScript is an extremely dynamic language with many hidden or browser-specific features that can be used to break out of any kind of jail you can devise.
Don't try to take this on yourself. Consider using an existing ‘mini-JS-like-language’ project such as Caja.
Sounds like you need to process the user entered data and replace invalid mark-up based on a white list or black-list of allowed content.
You can do it the same way as Facebook did. They're preprocessing all the javascript sources, adding a prefix to all the names other than their own wrapper APIs'.
I got another way: use google gears WorkerPool api
See this
http://code.google.com/apis/gears/api_workerpool.html
A created worker does not have access
to the DOM; objects like document and
window exist only on the main page.
This is a consequence of workers not
sharing any execution state. However,
workers do have access to all
JavaScript built-in functions. Most
Gears methods can also be used,
through a global variable that is
automatically defined:
google.gears.factory. (One exception
is the LocalServer file submitter,
which requires the DOM.) For other
functionality, created workers can ask
the main page to carry out requests.
What about this pattern in order to implement a sandbox?
function safe(code,args)
{
if (!args)
args=[];
return (function(){
for (i in window)
eval("var "+i+";");
return function(){return eval(code);}.apply(0,args);
})();
}
ff=function()
{
return 3.14;
}
console.log(safe("this;"));//Number
console.log(safe("window;"));//undefined
console.log(safe("console;"));//undefined
console.log(safe("Math;"));//MathConstructor
console.log(safe("JSON;"));//JSON
console.log(safe("Element;"));//undefined
console.log(safe("document;"));//undefined
console.log(safe("Math.cos(arguments[0]);",[3.14]));//-0.9999987317275395
console.log(safe("arguments[0]();",[ff]));//3.14
That returns:
Number
undefined
undefined
MathConstructor
JSON
undefined
undefined
-0.9999987317275395
3.14
Can you please provide an exploit suitable to attack this solution ? Just to understand and improve my knowledge, of course :)
THANKS!
This is now easily possible with sandboxed IFrames:
var codeFunction = function(x, y) {
alert("Malicious code!");
return x + y;
}
var iframe = document.createElement("iframe");
iframe.sandbox = "allow-scripts";
iframe.style.display = "none";
iframe.src = `data:text/html,
<script>
var customFunction = ${codeFunction.toString()};
window.onmessage = function(e) {
parent.postMessage(customFunction(e.data.x, e.data.y), '*'); // Get arguments from input object
}
</script>`;
document.body.appendChild(iframe);
iframe.onload = function() {
iframe.contentWindow.postMessage({ // Input object
x: 5,
y: 6
}, "*");
}
window.onmessage = function(e) {
console.log(e.data); // 11
document.body.removeChild(iframe);
}