Functions/Fields of derived ObjectInstance not showing up in for-in loop

Mar 1, 2011 at 8:35 PM

Hi again Paul,

I have some C# code for a class that derives from an ObjectInstance, and when I use a for-in loop on said object, I never see the values from PopulateFields and PopulateFunctions, but I do see a value for SetPropertyValue.  I'm not sure if this is standards-compliant or not, but it was not the behavior I expected. Here's a failing test case that demonstrates the issue.  The first assertion passes, but the other two fail.  I'm using the Jurassic 2.1 release.


        [TestMethod]
        public void containedInEnumeration()
        {
            var engine = new ScriptEngine();
            engine.SetGlobalValue("obj", new ClrObject(engine.Object.Prototype));
            Assert.AreEqual(true, engine.Evaluate("x = false; {for (var name in obj) { if (name == 'number') x = true; } x;}"));
            Assert.AreEqual(true, engine.Evaluate("x = false; {for (var name in obj) { if (name == 'field') x = true; } x;}"));
            Assert.AreEqual(true, engine.Evaluate("x = false; {for (var name in obj) { if (name == 'method') x = true; } x;}"));
        }

        public sealed class ClrObject : Jurassic.Library.ObjectInstance
        {
            public ClrObject(Jurassic.Library.ObjectInstance prototype)
                : base(prototype)
            {
                this.PopulateFields();
                this.PopulateFunctions();
                this.SetPropertyValue("number", 25, true);
            }

            [Jurassic.Library.JSField]
            public string field = "just a string field";

            [Jurassic.Library.JSFunction]
            public void method()
            {
            }
        }

Coordinator
Mar 1, 2011 at 11:37 PM

PopulateFields() and PopulateFunctions() create properties that are marked as PropertyAttributes.NonEnumerable (i.e. readable and writable but not enumerable).  This is because the spec mandates that built-in methods and properties should be non-enumerable.  SetPropertyValue on the other hand defaults to PropertyAttributes.FullAccess.

There is no way to change the behavior of PopulateFunctions() (unless you change the source) but you can change the access level of a property using Object.defineProperty (from script):

Object.defineProperty(obj, 'method', { enumerable: true });

You can also do it from .NET using the equivalent method (ObjectConstructor.DefineProperty).

Mar 2, 2011 at 2:03 PM

Thanks Paul, I didn't realize that the spec defined this behavior.  I'm still new to JavaScript, so I may have just had a non-standard approach to what I really wanted to do.


In the contrived example in my original post in this thread, perhaps users know and expect that there will be a JS property named "number" and they will read/write it as necessary.  They know that the object can do some other things as well, but they don't know what the names are of the other things.  So in short, how would consumers of my ClrObject example discover the "field" and "method" properties without me specifically writing it out in the documentation?  My original assumption was that they could say "Hmm... how do I Foo with this Bar?  ... for (var x in bar) ... Oh, I just call method()."

Perhaps we could get away with a "ForceEnumerable" named parameter on the JSField and JSFunction attributes?  I made a patch with tests for said functionality, and it is on Pastebin.  The patch actually also includes the diff to generate XML documentation. Take it or leave it :)

Coordinator
Mar 2, 2011 at 6:52 PM

The spec defines this behavior, but only for built-in functions.  Any extensions, like yours, are undefined and thus any behavior is permissable.  The various browsers take advantage of this by being wildly inconsistant with how they expose DOM methods.  Some make these properties full access (writable/deletable/enumerable), others make them non-configurable (writable/enumerable), still others non-enumerable (writable/deletable).  So I can see how you might be confused :-)

Generally it is expected that functions are discovered through the documentation - but you can view even non-enumerable properties using Object.getOwnPropertyNames.  For example:

console.log(Object.getOwnPropertyNames(JSON).toString());

Thanks for the patch but I'd rather not restrict the options to just enumerable/non-enumerable.  Making functions read-only is also somewhat useful.

Mar 2, 2011 at 8:26 PM
paulbartrum wrote:
console.log(Object.getOwnPropertyNames(JSON).toString());

Well that's exactly what I was looking for this whole time.  I saw several posts on StackOverflow that all culminated in "there's no way to find non-enumerable properties without knowing the name."  Thanks for helping me solve the problem.

Coordinator
Mar 2, 2011 at 11:13 PM
MarkLIC wrote:

Well that's exactly what I was looking for this whole time.  I saw several posts on StackOverflow that all culminated in "there's no way to find non-enumerable properties without knowing the name."  Thanks for helping me solve the problem.


That's probably because Object.getOwnPropertyNames is new in ECMAScript 5; I'm guessing the StackOverflow answers were written before any browser supported that version.

And I'm happy to help :-)