Bug 14877 - Define [[Class]] of interface prototype objects, exception interface objects, and exception interface prototype objects
Define [[Class]] of interface prototype objects, exception interface objects,...
Status: CLOSED FIXED
Product: WebAppsWG
Classification: Unclassified
Component: WebIDL
unspecified
All All
: P2 enhancement
: ---
Assigned To: Cameron McCormack
public-webapps-bugzilla
see comment #16
:
Depends on:
Blocks: 15348 15349
  Show dependency treegraph
 
Reported: 2011-11-18 18:36 UTC by Aryeh Gregor
Modified: 2011-12-27 20:03 UTC (History)
9 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Aryeh Gregor 2011-11-18 18:36:02 UTC
Currently this is left undefined.  Behavior of existing browsers, testing with Node and NodeList, using both String(Foo.prototype) and {}.toString.call(Foo.prototype):

IE9 and Opera 12.00: "NodePrototype", "NodeListPrototype"
Firefox 11a1: "DOM Constructor.prototype" for Node (old bindings), "Object" for NodeList (new bindings)
Chrome 17 dev: "Object"

So the reasonable choices for the [[Class]] of Foo.prototype are FooPrototype and Object.  FooPrototype appeals to me more, personally, but it makes no real difference as long as it's defined.
Comment 1 Garrett 2011-11-18 19:43:14 UTC
Please see: http://lists.w3.org/Archives/Public/public-script-coord/2011OctDec/0110.html

Brendan writes:

| Note that [[Class]] is going away in ES.next.
| 
| The internal methods and properties of ECMA-262 are not arbitrary extension 
| points for other specs to use without consultation.


Scripts do not need to know about host objects' [[Class]]. Instead, scripts can use feature detection. So from my perspective as a script writer, [[Class]] is not needed.
Comment 2 Aryeh Gregor 2011-11-18 19:50:12 UTC
I just care that we have interop on what toString() does.  Should {}.toString.call(Node.prototype) be "[object NodePrototype]" or "[object Object]"?  In ES5 it's determined by [[Class]]; if ES.next defines toString some other way, [[Class]] definitions in WebIDL can be ported to the new definition at that point.  For now we're working with ES5 and need to use [[Class]].  I agree that the information isn't useful to scripts, but we still need interop.
Comment 3 Travis Leithead [MSFT] 2011-11-18 19:52:51 UTC
+1 for FooPrototype. This design was chosen for IE9 because it helps the web developer easily determine a random DOM instance object's prototype via:
alert(Object.getPrototypeOf(instance));

...and helps web developers learn the prototype hierarchy of the DOM.

Note that it's sometimes (but not always) possible to get the related name of a prototype instance through:
instance.constructor.toString();

because some instances may be associated with an interface marked as [NoInterfaceObject] (which won't have a 'constructor' property.
Comment 4 David Flanagan 2011-11-18 21:42:45 UTC
If you standardize anything other than "Object", it will be impossible for implementations written in JavaScript (like this one: https://github.com/andreasgal/dom.js) to conform, at least in ES5.  

Of course, WebIDL already has [[Class]] and other requirements that are impossible to conform to in ES5 implementations.

I tend to think that simpler (Object) is better.  But Travis is right that having interesting values of [[Class]] is nice when debugging.
Comment 5 Cameron McCormack 2011-11-19 08:02:40 UTC
(In reply to comment #4)
> Of course, WebIDL already has [[Class]] and other requirements that are
> impossible to conform to in ES5 implementations.

Right, we've already crossed the bridge into ES5+proxies land.  I am not strongly disposed in either direction, but if we do choose something related to the interface name, it should "<Interface>Prototype" makes sense to me.
Comment 6 Aryeh Gregor 2011-11-20 16:06:41 UTC
Per comment 3, FooPrototype makes the most sense to me.  Descriptive stringification is better.

(In reply to comment #4)
> If you standardize anything other than "Object", it will be impossible for
> implementations written in JavaScript (like this one:
> https://github.com/andreasgal/dom.js) to conform, at least in ES5.  

You can mostly fake it by adding a custom toString, no?  That doesn't work if someone uses Object.prototype.toString, but it works for alert().
Comment 7 Aryeh Gregor 2011-11-20 17:01:18 UTC
I just discovered that exception interface objects and exception interface prototype objects also don't have [[Class]] defined.  Browser behavior for DOMException:

* IE9: DOMException and DOMExceptionPrototype, like regular interfaces
* Firefox 11a1: ???
  * {}.toString.call(DOMException) == "[object DOMPrototype]"
  * String(DOMException) == "[object DOMException]"
  * {}.toString.call(DOMException.prototype) == "[object XPC_WN_ModsAllowed_NoCall_Proto_JSClass]"
* Chrome 17 dev: Function for the exception interface object, Object for the exception interface prototype object
* Opera 12.00: DOMException for both objects

(Of course, String(DOMException.prototype) throws in all browsers because it invokes Error.prototype.toString.)

I'd go with IE9 here, unless it has compat issues.
Comment 8 Garrett 2011-11-21 02:50:26 UTC
(In reply to comment #7)
> I just discovered that exception interface objects and exception interface
> prototype objects also don't have [[Class]] defined.  Browser behavior for
> DOMException:
> 
> * IE9: DOMException and DOMExceptionPrototype, like regular interfaces
> * Firefox 11a1: ???
>   * {}.toString.call(DOMException) == "[object DOMPrototype]"
>   * String(DOMException) == "[object DOMException]"
>   * {}.toString.call(DOMException.prototype) == "[object
> XPC_WN_ModsAllowed_NoCall_Proto_JSClass]"
> * Chrome 17 dev: Function for the exception interface object, Object for the
> exception interface prototype object
> * Opera 12.00: DOMException for both objects
> 
> (Of course, String(DOMException.prototype) throws in all browsers because it
> invokes Error.prototype.toString.)
> 
> I'd go with IE9 here, unless it has compat issues.

If DOMException is spec changed to be a function then the Interface
Object's prototype -- DOMException.prototype -- can then be defined to be
an instance of DOMException. This is how all the built-ins work, e.g.

 Function.prototype instance of Function; // true
 String.prototype == ""; // true
 Error.prototype instanceof Error; // true

And with host objects I am suggesting a standardized behavior like this:

 DOMException.prototype intanceof DOMException;

 - with a standardized result of `true`. The concept might be applied here, but probably would be complicated for Interface Objects that can't by any rational means be constructed, e.g.
  Element.prototype instanceof Element;
  Node.prototype instanceof Node;

Those Interface Objects can't be new'd. What type of Element would Element.prototype be? What of its ownerDocument? 

An Object's toString() doesn't indicate what the object can do so programs shouldn't rely on information from that. 

However programs should be able to expect a toString method on host objects. By spec'ing Object.prototype as a base, Object.prototype.toString would be guaranteed available, instead of an error or some strange host object behavior (Asen raises some issues on "Updated FAQ" thread: http://groups.google.com/group/comp.lang.javascript/msg/fe387847c719119a?hl=en&dmode=source )
Comment 9 David Flanagan 2011-11-21 17:55:43 UTC
(In reply to comment #5)
> (In reply to comment #4)
> > Of course, WebIDL already has [[Class]] and other requirements that are
> > impossible to conform to in ES5 implementations.
> 
> Right, we've already crossed the bridge into ES5+proxies land.  I am not
> strongly disposed in either direction, but if we do choose something related to
> the interface name, it should "<Interface>Prototype" makes sense to me.

Right, but even with proxies, we still can't specify [[Class]] for arbitrary objects. 

(In reply to comment #6)
> You can mostly fake it by adding a custom toString, no?  That doesn't work if
> someone uses Object.prototype.toString, but it works for alert().

The method would have to check the value of this to see if it was being called on the prototype or on an instance and return a different string in each case. But defining a toString() method on the prototype object would itself be a violation of WebIDL; possibly a worse violation than the [[Class]] violation. And I can't put it on a prototype object higher up the inheritance chain, since WebIDL doesn't allow that, either.

But you tangentially raise an interesting question. Instead of having WebIDL standardize the ES-internal [[Class]] attribute, how about having it standardize the behavior of toString() on the objects it defines?
Comment 10 Brendan Eich 2011-11-21 19:42:34 UTC
(In reply to comment #9)
> (In reply to comment #5)
> > (In reply to comment #4)
> > > Of course, WebIDL already has [[Class]] and other requirements that are
> > > impossible to conform to in ES5 implementations.
> > 
> > Right, we've already crossed the bridge into ES5+proxies land.  I am not
> > strongly disposed in either direction, but if we do choose something related to
> > the interface name, it should "<Interface>Prototype" makes sense to me.
> 
> Right, but even with proxies, we still can't specify [[Class]] for arbitrary
> objects.

You mean for non-proxies? True, in those cases overriding toString may be necessary.

Also, it's important to say the role of [[Class]], since ES6 drafts remove that internal property in favor of generic (class-independent) semantics, or else a more precise and less over-loaded internal nominal tagging scheme. I'm assuming here the issue is Object.prototype.toString.

> (In reply to comment #6)
> > You can mostly fake it by adding a custom toString, no?  That doesn't work if
> > someone uses Object.prototype.toString, but it works for alert().
> 
> The method would have to check the value of this to see if it was being called
> on the prototype or on an instance and return a different string in each case.
> But defining a toString() method on the prototype object would itself be a
> violation of WebIDL; possibly a worse violation than the [[Class]] violation.
> And I can't put it on a prototype object higher up the inheritance chain, since
> WebIDL doesn't allow that, either.

Ok, hmm. Gecko does have custom toString IIRC. We should make WebIDL map to JS without requiring proxies if there is no need other than the O.p.toString one.

> But you tangentially raise an interesting question. Instead of having WebIDL
> standardize the ES-internal [[Class]] attribute, how about having it
> standardize the behavior of toString() on the objects it defines?

This seems strictly better, all else equal.

/be
Comment 11 Aryeh Gregor 2011-11-21 20:07:34 UTC
(In reply to comment #8)
> If DOMException is spec changed to be a function then the Interface
> Object's prototype -- DOMException.prototype -- can then be defined to be
> an instance of DOMException. This is how all the built-ins work, e.g.
> 
>  Function.prototype instance of Function; // true
>  String.prototype == ""; // true
>  Error.prototype instanceof Error; // true

Function.prototype instanceof Function is false in Firefox 11a1, Chrome 17 dev, and Opera 12.00, and that appears to me correct per spec:

http://es5.github.com/#x15.3.5.3

Regardless, the behavior is out of WebIDL's control, unless it wants to define a special [[HasInstance]] internal method for its functions (I don't think it does).  WebIDL gets to say what everything's [[Prototype]] is, and [[HasInstance]] (= instanceof) behavior follows from that.

> Those Interface Objects can't be new'd. What type of Element would
> Element.prototype be? What of its ownerDocument? 

Element.prototype is an object with all the properties you'd expect on any Element.  However, the descriptors of properties like ownerDocument are accessor descriptors, not data descriptors, and the getters/setters are defined to throw if you call them on something that's not an instance.  So Element.prototype.ownerDocument just throws, as do Element.prototype.nodeType and all the rest.

> However programs should be able to expect a toString method on host objects. By
> spec'ing Object.prototype as a base, Object.prototype.toString would be
> guaranteed available, instead of an error or some strange host object behavior

Everything should be inheriting eventually from Object.  That's not the issue under discussion.

(In reply to comment #10)
> Also, it's important to say the role of [[Class]], since ES6 drafts remove that
> internal property in favor of generic (class-independent) semantics, or else a
> more precise and less over-loaded internal nominal tagging scheme. I'm assuming
> here the issue is Object.prototype.toString.

Yes, exactly.  That's all I actually care about.  The only way ES5 gives us to define Object.prototype.toString behavior is via [[Class]], so that's how WebIDL has to define it.  The other places in the ES5 where [[Class]] is used don't matter for this bug -- none of them care what [[Class]] is except if it's "Array", "Boolean", "Function", "Number", "RegExp", or "String", if I didn't miss any.  Any other [[Class]] is entirely arbitrary except for Object.prototype.toString.
Comment 12 Brendan Eich 2011-11-21 20:15:19 UTC
(In reply to comment #11)
> (In reply to comment #8)
> > If DOMException is spec changed to be a function then the Interface
> > Object's prototype -- DOMException.prototype -- can then be defined to be
> > an instance of DOMException. This is how all the built-ins work, e.g.
> > 
> >  Function.prototype instance of Function; // true
> >  String.prototype == ""; // true
> >  Error.prototype instanceof Error; // true
> 
> Function.prototype instanceof Function is false in Firefox 11a1, Chrome 17 dev,
> and Opera 12.00, and that appears to me correct per spec:
> 
> http://es5.github.com/#x15.3.5.3

Yes, note how instanceof always skips one up on the [[Prototype]] chain of the LHS object before testing for whether the current object on that chain is the same as the value of the .prototype property of the RHS.

Garrett's comment is in error on the first and third lines (correcting for the stray space on the first).

> (In reply to comment #10)
> > Also, it's important to say the role of [[Class]], since ES6 drafts remove that
> > internal property in favor of generic (class-independent) semantics, or else a
> > more precise and less over-loaded internal nominal tagging scheme. I'm assuming
> > here the issue is Object.prototype.toString.
> 
> Yes, exactly.  That's all I actually care about.  The only way ES5 gives us to
> define Object.prototype.toString behavior is via [[Class]], so that's how
> WebIDL has to define it.

Allen should weigh in here. As noted, [[Class]] is not a supported extension mechanism for other specs. We should negotiate a more future-proof way to spec.

/be
Comment 13 Garrett 2011-11-22 03:20:50 UTC
(In reply to comment #12)
> (In reply to comment #11)
> > (In reply to comment #8)
> > > If DOMException is spec changed to be a function then the Interface
> > > Object's prototype -- DOMException.prototype -- can then be defined to be
> > > an instance of DOMException. This is how all the built-ins work, e.g.
> > > 
> > >  Function.prototype instance of Function; // true
> > >  String.prototype == ""; // true
> > >  Error.prototype instanceof Error; // true
> > 
> > Function.prototype instanceof Function is false in Firefox 11a1, Chrome 17 dev,
> > and Opera 12.00, and that appears to me correct per spec:
> > 
> > http://es5.github.com/#x15.3.5.3
> 
> Yes, note how instanceof always skips one up on the [[Prototype]] chain of the
> LHS object before testing for whether the current object on that chain is the
> same as the value of the .prototype property of the RHS.
> 
Right, thanks.
Though what I was getting at was that "The Function prototype object is itself a Function object". 


> Garrett's comment is in error on the first and third lines (correcting for the
> stray space on the first).
> 
The third line is a suggestion of what might happen if the suggested change is made. As you've pointed out with Function.prototype, that too would not make sense.  The idea suggested was what if DOMException.prototype is a DOMException object. My mistake was using the instanceof operator here.
Comment 14 Aryeh Gregor 2011-11-23 13:58:44 UTC
(In reply to comment #12)
> Allen should weigh in here. As noted, [[Class]] is not a supported extension
> mechanism for other specs. We should negotiate a more future-proof way to spec.

It's not?  ES5 says:

"""
The value of the [[Class]] internal property of a host object may be any String value except one of "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", and "String".
"""

That suggests to me that specs defining the behavior of host objects are allowed to give them any [[Class]] they want except for that fixed list.  If this isn't the intent, the definition of String.prototype.toString has to change to add a different extension point.

(In reply to comment #13)
> Right, thanks.
> Though what I was getting at was that "The Function prototype object is itself
> a Function object". 

The Function constructor object (Function) is a Function object -- Function instanceof Function is true.  The Function prototype object (Function.prototype) is not a Function object, it's a regular Object object -- Function.prototype instanceof Function is false, Function.prototype instanceof Object is true.  See section 15.3 in ES5: http://es5.github.com/#x15.3

> The third line is a suggestion of what might happen if the suggested change is
> made. As you've pointed out with Function.prototype, that too would not make
> sense.  The idea suggested was what if DOMException.prototype is a DOMException
> object. My mistake was using the instanceof operator here.

It amounts to the same thing.  The DOMException prototype is, as the name says, only a prototype for DOMException objects, in that all DOMException objects inherit its properties.  It is not a DOMException itself, and treating it as one would be inconsistent with how ES5 built-ins work.
Comment 15 Garrett 2011-11-23 17:47:10 UTC
(In reply to comment #14)
> (In reply to comment #12)
> > Allen should weigh in here. As noted, [[Class]] is not a supported extension
> > mechanism for other specs. We should negotiate a more future-proof way to spec.
> 
> It's not?  ES5 says:
> 
> """
> The value of the [[Class]] internal property of a host object may be any String
> value except one of "Arguments", "Array", "Boolean", "Date", "Error",
> "Function", "JSON", "Math", "Number", "Object", "RegExp", and "String".
> """
> 
> That suggests to me that specs defining the behavior of host objects are
> allowed to give them any [[Class]] they want except for that fixed list.  If
> this isn't the intent, the definition of String.prototype.toString has to
> change to add a different extension point.
> 
> (In reply to comment #13)
> > Right, thanks.
> > Though what I was getting at was that "The Function prototype object is itself
> > a Function object". 
> 
> The Function constructor object (Function) is a Function object -- Function
> instanceof Function is true.  The Function prototype object
> (Function.prototype) is not a Function object, it's a regular Object object --

No, the Function prototype object is itself a Function object. (sic)

> Function.prototype instanceof Function is false, Function.prototype instanceof
> Object is true.  See section 15.3 in ES5: http://es5.github.com/#x15.3
> 
That's on the FUnction constructor. Instead, see the section on FUnction.prototype: http://es5.github.com/#x15.3.3.1 Which states "The Function prototype object is itself a Function object". 

Function.prototype is useful for a reusable noop function and I've used it this way a lot in my code! Instead of having : var noop = function(){}; Saves some clutter. Though beware, while Function.prototype 
 is a function, it is not specified to implement [[Construct]] (same with parseInt, etc). "None of the built-in functions described in this clause that are not constructors shall implement the [[Construct]] internal method ".

> > The third line is a suggestion of what might happen if the suggested change is
> > made. As you've pointed out with Function.prototype, that too would not make
> > sense.  The idea suggested was what if DOMException.prototype is a DOMException
> > object. My mistake was using the instanceof operator here.
> 
> It amounts to the same thing.  The DOMException prototype is, as the name says,
> only a prototype for DOMException objects, in that all DOMException objects
> inherit its properties.  It is not a DOMException itself, and treating it as
> one would be inconsistent with how ES5 built-ins work.
No, you're wrong on that one. That quote I wrote is from the same spec.  I had a blunder with instanceof, sorry, but I was his is how all the built-ins work. I already quoted Function.prototype twice. Here's a few other built ins, followed by my proposed suggestion.

15.5.4 Properties of the String Prototype Object # &#9417; &#9398;
The String prototype object is itself a String object (its [[Class]] is "String") whose value is an empty String.

15.6.3.1 Boolean.prototype # &#9417; &#9415;
The initial value of Boolean.prototype is the Boolean prototype object (15.6.4).

15.11.4 Properties of the Error Prototype Object # &#9417; 
The Error prototype object is itself an Error object (its [[Class]] is "Error").

What I was trying to suggest was a proposal as such:
The DOMException prototype object is itself a DOMException object (its [[Class]] is "DOMException").
Comment 16 Garrett 2011-11-23 17:51:00 UTC
(In reply to comment #15)

> 
> 15.6.3.1 Boolean.prototype # &#9417; &#9415;
> The initial value of Boolean.prototype is the Boolean prototype object
> (15.6.4).
> 
15.6.4 Properties of the Boolean Prototype Object # &#9417; 
The Boolean prototype object is itself a Boolean object (its [[Class]] is "Boolean") whose value is false.
Comment 17 Aryeh Gregor 2011-11-25 20:01:19 UTC
(In reply to comment #15)
> No, the Function prototype object is itself a Function object. (sic)

Ah, I see.  We were using different definitions.  By "is a Function object" I meant "is instanceof Function"/"has Function.prototype in its prototype chain", while you meant "has [[Class]] 'Function'".  You're quite correct, I misunderstood.  Your point is that

  {}.toString.call(Object.prototype) === "[object Object]"
  {}.toString.call(Function.prototype) === "[object Function]"
  {}.toString.call(Boolean.prototype) === "[object Boolean]"

so for consistency we also want

  {}.toString.call(NodeList.prototype) === "[object NodeList]"

and so on -- not NodeListPrototype or Object or whatever.

That's a really good point, and now that you point it out, I agree.  For consistency with ES, we should define the [[Class]] of an interface prototype object to be the identifier of the interface, and the [[Class]] of an exception interface prototype object to be the identifier of the exception.  The [[Class]] of interface objects and exception interface objects should remain "Function", as now.  (I was confused in comment 9 when I talked about the [[Class]] of exception interface objects not being defined -- it clearly is.)
Comment 18 Garrett 2011-11-25 20:28:44 UTC
(In reply to comment #17)
> (In reply to comment #15)
> > No, the Function prototype object is itself a Function object. (sic)
> 
> Ah, I see.  We were using different definitions.  By "is a Function object" I
> meant "is instanceof Function"/"has Function.prototype in its prototype chain",
> while you meant "has [[Class]] 'Function'".  You're quite correct, I
> misunderstood.  Your point is that
> 
>   {}.toString.call(Object.prototype) === "[object Object]"
>   {}.toString.call(Function.prototype) === "[object Function]"
>   {}.toString.call(Boolean.prototype) === "[object Boolean]"
> 
> so for consistency we also want
> 
>   {}.toString.call(NodeList.prototype) === "[object NodeList]"
> 
> and so on -- not NodeListPrototype or Object or whatever.
> 
> That's a really good point, and now that you point it out, I agree.  For
> consistency with ES, we should define the [[Class]] of an interface prototype
> object to be the identifier of the interface, and the [[Class]] of an exception
> interface prototype object to be the identifier of the exception.  The
> [[Class]] of interface objects and exception interface objects should remain
> "Function", as now.  (I was confused in comment 9 when I talked about the
> [[Class]] of exception interface objects not being defined -- it clearly is.)

(In reply to comment #17)
> (In reply to comment #15)
> > No, the Function prototype object is itself a Function object. (sic)

[...]
> That's a really good point, and now that you point it out, I agree.  For
> consistency with ES, we should define the [[Class]] of an interface prototype
> object to be the identifier of the interface, and the [[Class]] of an exception
> interface prototype object to be the identifier of the exception.  The
> [[Class]] of interface objects and exception interface objects should remain
> "Function", as now.  (I was confused in comment 9 when I talked about the
> [[Class]] of exception interface objects not being defined -- it clearly is.)

Doing so would be consistent. 

However in contrast, Brendan advises against other specs using [[Class]] and that [[Class]] would be going away: "Note that [[Class]] is going away in ES.next." I provided a link to that in comment 1. Here it is again:
http://lists.w3.org/Archives/Public/public-script-coord/2011OctDec/0110.html

So if [[Class]] is going away, then Brendan or Allen should figure out what other specs like WebIDL can use to specify the behavior. Taking a step back, this bug could be renamed to describe the desired behavior instead of the desired implementation details ([[Class]] is an implementation detail). E.g. instead of 

"Define [[Class]] of interface prototype objects, exception interface objects, and exception interface prototype objects"

- change it to define the publicly observable behavior:-

"Define toString of interface prototype objects, exception interface objects, and exception interface prototype objects."

So as to not rely on specification mechanisms but rather to work with a defined contract. Or maybe mark the bug as INVALID and start over with a new or refined bug.
Comment 19 Aryeh Gregor 2011-12-01 18:57:47 UTC
Current WebIDL depends on ES5.  In ES5, we have no way to affect toString except via [[Class]], which we're explicitly allowed to define for host objects (see comment #14, first quote).  If ES6 gets rid of [[Class]] and gives some other mechanism for affecting .toString, then we can change WebIDL at that point.  But for now, we only have ES5, and we have to work with its definition of .toString.  There is no way to futureproof this -- if ES6 gets rid of [[Class]], we need to just update WebIDL then.
Comment 20 Cameron McCormack 2011-12-09 05:52:33 UTC
(In reply to comment #19)
> Current WebIDL depends on ES5.  In ES5, we have no way to affect toString
> except via [[Class]], which we're explicitly allowed to define for host objects
> (see comment #14, first quote).  If ES6 gets rid of [[Class]] and gives some
> other mechanism for affecting .toString, then we can change WebIDL at that
> point.  But for now, we only have ES5, and we have to work with its definition
> of .toString.  There is no way to futureproof this -- if ES6 gets rid of
> [[Class]], we need to just update WebIDL then.

We have already defined [[Class]] to be something host object-y for actual instances of Node etc., even though Proxies don't let us specify that.  It's no worse to extend this to interface prototype objects, etc.

It's not an uncommon pattern for script to do Object.prototype.toString.call(obj) and then extract out the bit between the "[object " and "]".  For this, simply overriding toString on HTMLDivElement.prototype isn't going to cut it.

What if we *cough* have Web IDL say that as soon as an ECMAScript environment is created, that Object.prototype.toString is overridden with something that makes it look like HTMLDivElements have [[Class]] == "HTMLDivElement"?

That, or proxies need to gain the ability to not necessarily fake [[Class]], but to affect what Object.prototype.toString returns.
Comment 21 Cameron McCormack 2011-12-15 06:04:36 UTC
Here's a test expanding on Aryeh's comment 0 tests for Object.prototype.toString() and String() on Node, Node.prototype, NodeList, NodeList.prototype and Image:

http://people.mozilla.org/~cmccormack/tests/constructor-class.html

I agree with Aryeh that choosing ({}).toString.call(Node.prototype) == "[object Node]" is best, since that allows for the debugging that Travis wants as well as being consistent with ES builtins as Garrett points out.

Do people think there's much advantage to having String(Node) == "function Node() { ... }", now that interface objects are function objects?  It would be consistent with the builtin constructors like Object, String, etc.  OTOH  "[object Node]" is a slight winner from the results.

So my preferred proposal is to make:

1. ({}).toString.call(Node) == "[object Node]"
2. String(Node) == "[object Node]"
3. ({}).toString.call(Node.prototype) == "[object Node]"
4. String(Node.prototype) == "[object Node]"
5. ({}).toString.call(Image) == "[object Image]"
6. String(Image) == "[object Image]"

but I would also be OK with this, if there are no compat issues:

1. ({}).toString.call(Node) == "[object Node]"
2. String(Node) == "function Node() { ... }"
3. ({}).toString.call(Node.prototype) == "[object Node]"
4. String(Node.prototype) == "[object Node]"
5. ({}).toString.call(Image) == "[object Image]"
6. String(Image) == "function Image() { ... }"

For (2), (4) and (6), we'd need to have toString functions on interface objects to override the one from Function.prototype.


Results from the test:

Node
====

      {}.toString.call()        String()
  -----------------------------------------------------------------
  Fx  [object DOMPrototype]     [object Node]
  Cr  [object Function]         function Node() { [native code] }
  Sa  [object NodeConstructor]  [object NodeConstructor]
  Op  [object Node]             [object Node]
  IE  [object Node]             [object Node]

Node.prototype
==============

      {}.toString.call()                  String()
  -----------------------------------------------------------------
  Fx  [object DOM Constructor.prototype]  [object Node]
  Cr  [object Object]                     [object Object]
  Sa  [object NodePrototype]              [object NodePrototype]
  Op  [object NodePrototype]              [object NodePrototype]
  IE  [object NodePrototype]              [object NodePrototype]

NodeList
========

      {}.toString.call()        String()
  -----------------------------------------------------------------
  Fx  [object NodeList]         [object NodeList]
  ... rest as for Node ...

NodeList.prototype
==================

      {}.toString.call()        String()
  -----------------------------------------------------------------
  Fx  [object Object]           [object Object]
  ... rest as for Node.prototype ...

Image
=====

      {}.toString.call()                  String()
  -----------------------------------------------------------------
  Fx  [object DOMConstructor]             [object Image]
  Cr  [object Function]                   [object Function]
  Sa  [object ImageConstructor]           [object ImageConstructor]
  Op  [object Image]                      function Image() { [native code] }
  IE  [object Function]                   function Image() { [native code] }


And this is what the spec currently says to do:

                  {}.toString.call()                  String()
  ----------------------------------------------------------------------------
  Node            [object Node]                       function Node() { ... }
  Node.prototype  [object Object]                     [object Object]
  Image           [object Function]                   function Image() { ... }
Comment 22 Travis Leithead [MSFT] 2011-12-15 20:55:28 UTC
(In reply to comment #17)
> That's a really good point, and now that you point it out, I agree.  For
> consistency with ES, we should define the [[Class]] of an interface prototype
> object to be the identifier of the interface, and the [[Class]] of an exception
> interface prototype object to be the identifier of the exception.  The
> [[Class]] of interface objects and exception interface objects should remain
> "Function", as now.  (I was confused in comment 9 when I talked about the
> [[Class]] of exception interface objects not being defined -- it clearly is.)

Makes sense to me, and is the most aligned with ECMAScript.

Note that the [object Function] ambiguity is a side-effect of switching everything over to be a function instance. We should either accept the consequence of this change, or go back to interface objects as instances of Object (and new-able constructors as Function instances)--which I wouldn't mind a bit, since that what IE9 does today; however that re-opens a closed issue.


(In reply to comment #21)
> I agree with Aryeh that choosing ({}).toString.call(Node.prototype) == "[object
> Node]" is best, since that allows for the debugging that Travis wants as well
> as being consistent with ES builtins as Garrett points out.

Why not "[object NodePrototype]" since that has the most interop according to your results? It also allows differentiation between String(Node) and String(Node.prototype) which given your proposals are indistinguishable?
 
> Do people think there's much advantage to having String(Node) == "function
> Node() { ... }", now that interface objects are function objects?  It would be
> consistent with the builtin constructors like Object, String, etc.  OTOH 
> "[object Node]" is a slight winner from the results.

Having String(Node) return "function Node() { ... }" is the default ECMAScript behavior when passing any function instance into the String constructor:

function foo() { }
String(foo) == "function foo() {}"

I don't think we should change that.

> For (2), (4) and (6), we'd need to have toString functions on interface 
> objects to override the one from Function.prototype.

This would really be a waste for IE, because it would add a signficant start-up performance cost to our engine for every web page load (doubling the cost to init every interface object into the script engine due to the extra method). I'd have a hard time convincing our perf-concious developers to make this change only for the debugging value; furthermore, the unique-named value is already provided by the String() behavior as noted (without additional changes) and works for the majority of APIs that perform object/function-to-string coercion like console.log/alert/etc.

Your comparisons use Node but that's not [currently] represented as a function instance in IE, so to make a more apples-to-apples comparison of browsers, I make sure that the reported object types are the same via typeof. XHR worked best for this:

name="XMLHttpRequest"

Browser typeof(name) {}.toString.call(name)   String(name)
======= ==========   ======================   ============
Fx      function     [object DOMConstructor]  [object XMLHttpRequest]
Cr      function     [object Function]        function XMLHttpRequest() {..}
Sa      object*      n/a                      n/a
Op      function     [object XMLHttpRequest]  function XMLHttpRequest() {..}
IE      function     [object Function]        function XMLHttpRequest() {..}

(I couldn't find any interface or prototype DOM objects in Safari that reported as function for typeof in my quick tests, therefore their results aren't comperable in this table).

There's pretty good consistency for the String() behavior of interface objects that are functions.

Firefox seems to be contradicting itself by reporting typeof "function" but stringifying as "[object ...]".

Comparing these browser's behavior against native ECMAScript functions, where
function Foo() { } is created before running the tests shows good alignment for all and [I think] sets a baseline:

name="Foo"

Browser typeof(name) {}.toString.call(name)   String(name)
======= ==========   ======================   ============
Fx      function     [object Function]        function Foo() {}
Cr      function     [object Function]        function Foo(){}
Sa      function     [object Function]        function Foo() { }
Op      function     [object Function]        function Foo() { }
IE      function     [object Function]        function Foo() {}

It looks to me like Chrome and IE (from the first table) have the closest behvior to ECMAScript handling of function interface objects.

And finally, comparing the results of an interface Operation (getElementById) and how it behaves under the same conditions (becuase why have different behavior if we're talking about function instances?)

name="document.getElementById"

Browser typeof(name) {}.toString.call(name)   String(name)
======= ==========   ======================   ============
Fx      function     [object Function]        function getElementById() {..}
Cr      function     [object Function]        function getElementById() {..}
Sa      function     [object Function]        function getElementById() {..}
Op      function     [object Function]        function getElementById() {..}
IE      function     [object Function]        function getElementById() {..}

Wow. Not bad. Someday we'll get there for interface objects and prototypes too :)

I think that also sets a pretty consistent baseline.

So, here's my counter-proposal. We expect {}.toString.call() and String() to behave as follows:

1. ({}).toString.call(Node)           == "[object Function]"
2. String(Node)                       == "function Node() { [native code] }"
3. ({}).toString.call(Node.prototype) == "[object NodePrototype]"
4. String(Node.prototype)             == "[object NodePrototype]"
5. ({}).toString.call(Image)          == (same as Node)
6. String(Image)                      == (same as Node)
Comment 23 Travis Leithead [MSFT] 2011-12-15 20:57:08 UTC
(In reply to comment #22)
> (In reply to comment #17)
> > That's a really good point, and now that you point it out, I agree.  For
> > consistency with ES, we should define the [[Class]] of an interface prototype
> > object to be the identifier of the interface, and the [[Class]] of an exception
> > interface prototype object to be the identifier of the exception.  The
> > [[Class]] of interface objects and exception interface objects should remain
> > "Function", as now.  (I was confused in comment 9 when I talked about the
> > [[Class]] of exception interface objects not being defined -- it clearly is.)
> 
> Makes sense to me, and is the most aligned with ECMAScript.

(I think I was reading that wrong. Please disregard)
Comment 24 Cameron McCormack 2011-12-16 04:08:14 UTC
The reason I preferred "[object Node]" over "[object NodePrototype]" was for the consistency with JS that Garrett pointed out.  For knowing what prototype you're looking at, "[object Node]" seems sufficient, too.

OTOH, the reason that this is the case for JS builtins is because String.prototype, RegExp.prototype, etc. are all actual String, RegExp, etc. instances (differing only by their [[Prototype]] value).  In our case, Node.prototype isn't an actual Node.

I see now (I wasn't reading closely enough before) that Garrett proposes this for exception interface prototype objects, not necessarily interface prototype objects in general, since it would be possible to have DOMException.prototype be an actual DOMException instance (unlike Node, which is abstract).  I am not sure if there is any concrete benefit from doing this, though.  I think we can either have exceptions be consistent with interfaces or with builtin Errors.

So I guess I'm not really opposed to "[object NodePrototype]" and "[object DOMExceptionPrototype]".

As for String(Node) == "function Node() { ... }", if people prefer that then I'm happy with it.  That Chrome already does this gives me a small amount of reassurance that it's safe.
Comment 25 Cameron McCormack 2011-12-19 04:29:11 UTC
I've done this now, along with my comment 20 suggestion of having custom Object.prototype.toString behavior in place of requirements to have particular [[Class]] values.

http://dev.w3.org/cvsweb/2006/webapi/WebIDL/Overview.xml.diff?r1=1.426;r2=1.427;f=h

Aryeh, can you please indicate whether this change is satisfactory.  Thanks!
Comment 26 Aryeh Gregor 2011-12-27 20:03:14 UTC
Looks okay to me.  I filed two follow-up bugs, since I think there were a couple of mistakes in your commit: bug 15348 and bug 15349.