Problem in brief
Setting of (function () {}).__proto__ is a no-op in recent versions of HtmlUnit breaking some libraries.
Setting of new Object().__proto__ still works as expected.
This problem affects the Knockout library of at least v3.3.0 and the current version v3.5.1.
Details
When Rhino is running in ES6 mode, setting of _proto__ on a function object is denied by this condition in SpecialRef that only allows setting on objects. This behaviour is incompatible with major browsers (Chrome / FF / Edge / IE11) and breaks libraries that rely on this.
Test case
<html> <head> <script> function test() { var proto = { foo: function (n) { console.log('foo'); } } proto.foo(); var obj = function () {} console.log("Original __proto__:", obj.__proto__); obj.__proto__ = proto; console.log("Modified __proto__:", obj.__proto__); console.log("obj.foo:", obj.foo); try { obj.foo(); } catch (e) { console.log(e); } } </script> </head> <body onload="test()"> </body> </html>
Expected:
foo
Original __proto__: function () {[native code]}
Modified __proto__: ({foo: (function (n) { console.log("foo"); })})
obj.foo: (function (n) { console.log("foo"); })
foo
HtmlUnit 2.46.0 fails with the following:
foo
Original __proto__: function () {[native code]}
Modified __proto__: function () {[native code]}
obj.foo: net.sourceforge.htmlunit.corejs.javascript.Undefined@0
TypeError: Cannot find function foo in object function () {...}.
Suggested fix
The documentation for Function's __proto__ (Object.prototype__proto__) doesn't seem to make any distinction between the state of support for objects and function objects so it seems they should be equally supported, if supported at all.
Out of the possible values returned by typeof, it seems "function" and "object" are the only ones that make sense:
[undefined, null, true, 123, 123n, "abc", Symbol(), function () {}, {}].forEach(x => { try { var proto = {} var r = (x.__proto__ = proto); console.log(typeof x + ": returned=" + (r === proto) + " changed=" + (x.__proto__ === proto)) } catch (e) { console.log(typeof x + ": " + e); } }) /* Output of Chrome / FF: undefined: TypeError: Cannot set property '__proto__' of undefined object: TypeError: Cannot set property '__proto__' of null boolean: returned=true changed=false number: returned=true changed=false bigint: returned=true changed=false string: returned=true changed=false symbol: returned=true changed=false function: returned=true changed=true object: returned=true changed=true */
(This can be run in HtmlUnit 2.46.0 by removing 123n (BigInt(123)) which is not supported.)
In lieu of this, my suggestion is for this condition in SpecialRef be changed as follows:
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
- if ((value != null && !"object".equals(ScriptRuntime.typeof(value))) ||
- !"object".equals(ScriptRuntime.typeof(target))) {
- return Undefined.instance;
- }
+ if (value != null && !"object".equals(ScriptRuntime.typeof(value))) {
+ return obj;
+ }
+ switch (ScriptRuntime.typeof(target)) {
+ case "object":
+ case "function":
+ break;
+ default:
+ return obj;
+ }
target.setPrototype(obj);
} else {
target.setPrototype(obj);
}