This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 12458 - Interface objects should be Functions
Summary: Interface objects should be Functions
Status: RESOLVED FIXED
Alias: None
Product: WebAppsWG
Classification: Unclassified
Component: WebIDL (show other bugs)
Version: unspecified
Hardware: All All
: P2 normal
Target Milestone: ---
Assignee: Cameron McCormack
QA Contact: public-webapps-bugzilla
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-04-08 21:41 UTC by Cameron McCormack
Modified: 2011-07-01 01:43 UTC (History)
9 users (show)

See Also:


Attachments

Description Cameron McCormack 2011-04-08 21:41:36 UTC
It's inconsistent with how constructors are defined in ES5 that Web IDL interface objects (e.g. window.Node) are not functions -- using typeof on them returns "object", unless the interface is defined with [Constructor].  If it is safe to do so, we should make them real functions, where if the IDL interface is not also annotated with [Constructor], a TypeError is thrown.
Comment 1 Cameron McCormack 2011-04-08 21:45:21 UTC
(In reply to comment #0)
> If it is safe to do so, we should make them real functions, where if the
> IDL interface is not also annotated with [Constructor], a TypeError is
> thrown.

...if you try to call/new it.
Comment 2 Jonas Sicking (Not reading bugmail) 2011-04-08 23:02:35 UTC
If we're going to make things like |new HTMLDivElement| work, then it *might* make sense to make that change at the same time as switching over to having the constructors be functions. That way authors can feature detect by checking |typeof|.
Comment 3 Simon Pieters 2011-04-11 12:35:15 UTC
Aren't there compat constraints here?
Comment 4 Travis Leithead [MSFT] 2011-04-12 00:11:13 UTC
I also worry about compat problems (though in IE9, we never tried implementing interface objects as functions, so I have no specific examples).

In general, I'm not sure why we should consider this change, since there is great interop here, and as Jonas mentions, you can discover "new-able" interface objects by their type quite easily.

It may be obvious, but I'll also point out that not all objects created in the global scope in ES5 are "constructors", for example "Math" is not a function. (Of course, Math doesn't have a prototype either, so its another class of object altogether.)
Comment 5 Jonas Sicking (Not reading bugmail) 2011-04-12 01:12:50 UTC
Sorry, I did not mean to imply that I did not want to do this change.

The inconsistency is that currently all javascript objects have a .constructor property which is a Function object. The one exception to this is DOM objects. This makes the DOM inconsistent with the rest of the JavaScript world, which is why we should fix this bug.
Comment 6 Brendan Eich 2011-04-12 01:24:51 UTC
What Jonas said. Math is not a constructor and it has no .prototype property, so there is no .prototype.constructor pointing back to Math.

The DOM crazyland differences are bugs, not features. Whether the Web depends on any of them is subject to testing. It may be that web content wants typeof not to be 'function' but I haven't seen enough evidence to throw in the towel.

/be
Comment 7 Simon Pieters 2011-04-13 13:02:01 UTC
Actually it seems that in Opera, most (all?) interface objects are in fact typeof 'function'. Known bug (CORE-35511), but I didn't find any bug referencing a page being broken because of it.
Comment 8 Travis Leithead [MSFT] 2011-04-13 21:56:01 UTC
So, here's the interop story at the moment for Node:

IE9:      typeof Node // "object"
FF4:      typeof Node // "object"
Safari5:  typeof Node // "object"
O11:      typeof Node // "function"
Chrome10: typeof Node // "function"

(It's not as interoperable as I'd hoped.)

There's certainly an argument to be made consistency with ES5 (i.e., change to "function"), and I get that.

However, interface objects really are not typical constructors--and I think it's OK for them to be different in this regard. Case-in-point, interface prototype objects are also not the same as any of the prototypes in native JavaScript. For example, a "prototype" should be a prototypical instance of the constructor (e.g., Number.prototype _is_ a number instance, Function.prototype _is_ a function instance, etc., but clearly the interface prototype object does not follow these rules either, nor should it IMHO.

Again, I think it's OK for interface objects to be different from a typeof perspective.

I think the best path forward is to get Opera's bug fixed and Chrome should be able to respond quickly given their release cadence. At that point the high-order bit is interoperability.
Comment 9 Brendan Eich 2011-04-14 01:24:39 UTC
High order bit being interop does not automatically mean majority rules. There is no interoperable de-facto standard now. We can get one by at least two different paths.

The <builtin>.prototype object is a special case in JS already. It is never instanceof <builtin>. It has a .constructor property referencing <builtin> but that does not make it "is-a" in relation to the underlying class or interface "type".

We need more detail about what "but clearly the interface prototype object does
not follow these rules either, nor should it IMHO" means. How does the prototype of a DOM interface object *observably* differ from instances accessed via that interface object or created by new'ing it?

/be
Comment 10 Cameron McCormack 2011-05-23 03:40:56 UTC
Another point is that interface objects also have to respond to [[HasInstance]] (so that you can do `document instanceof Node`), which for native objects is only defined for Functions.  The current Proxy proposal does not support a trap for [[HasInstance]], nor does it have trap that would allow an interface object implemented as a Function object to return "object" when typeof is applied to it.  This is another issue that would prevent pure JS implementations of the DOM, which some people want to do.
Comment 11 Mark S. Miller 2011-05-23 03:48:32 UTC
(In reply to comment #10)
> Another point is that interface objects also have to respond to [[HasInstance]]
> (so that you can do `document instanceof Node`), which for native objects is
> only defined for Functions.  The current Proxy proposal does not support a trap
> for [[HasInstance]], 

http://wiki.ecmascript.org/doku.php?id=strawman:proxy_instanceof
is a strawman that may enable that. Not accepted yet, so just FYI.


> nor does it have trap that would allow an interface object
> implemented as a Function object to return "object" when typeof is applied to
> it.  This is another issue that would prevent pure JS implementations of the
> DOM, which some people want to do.
Comment 12 Cameron McCormack 2011-06-19 02:12:25 UTC
Does anyone have any data on the compatibility constraints?  If changing interface objects to be typeof "function" is not going to break pages, I think I'd rather decide on the side of consistency with JS.

(I'm not sure I find discoverability of newable interface objects to be a compelling argument; authors can simply do the new call and if it fails, use their fallback code.)
Comment 13 Cameron McCormack 2011-06-30 03:16:22 UTC
In lieu of evidence of compatibility problems, I've made interface objects always be function objects.  When not declared with [Constructor], they will throw a TypeError (which is the same as if you tried to "new" one in an implementation where it is not a function).
Comment 14 Garrett 2011-06-30 19:22:25 UTC
In response to Comment 3 Simon Pieters 2011-04-11, there are compatibility constraints.

Changing behavior of existing methods and properties generally adds complexity.  That complexity is augmented by variance in browsers, versions, and existing scripts. 


The CSSOM/offsetTop changes comes to mind (see long threads on www-style c2008).

The problem with changing behavior that it adds complexity to web page authors. It makes feature detection much harder because there are multiple cases to deal with:

1) Browser has existing native Event interface object: (typeof Event == "object")
2) Browser does not have native Event interface object:
(typeof Event == "undefined").
3) Browser has new native Event interface object: (typeof Event == "function" (new)).
4) App is using a framework that adds/replaces global `Event`
a) Prototype.js 
| if (!window.Event) var Event = { }; (
  i. Older Event interface obj (see 1).
  ii. No Event interface obj: (creates a new user-defined Object object (typeof Event == "object") (different from (2)).
  iii.  New Event interface obj: (see 3).
b) MooTools:
| var Event = new Type(... (typeof Event == "function" (always)).

As the Event interface evolves, the new functionality must also be feature tested. This situation of feature testing the global Event constructor becomes more difficult.

For a copy'n'paste coder who tests in three browsers, a changed Event object will probably lead to compatibility bugs.

Now, OTOH, a *new* method can avoid dealing with legacy code issues:

| if(window.Event && typeof window.Event.createInitedEvent == "function") {
|   Event.createInitedEvent(type, optionObj)
| }

It should be a safe bet to assume that if `createInitedEvent` is present, it's not something that MooTools added. This avoids compatibility issues. Copy'n'pasters can drop that on into a web page and browser vendors should not see "TypeError Event is not a constructor", etc.
Comment 15 Cameron McCormack 2011-07-01 01:43:30 UTC
(In reply to comment #14)
> It makes feature detection much harder because there are multiple cases to deal
> with:
> 
> 1) Browser has existing native Event interface object: (typeof Event ==
> "object")
> 2) Browser does not have native Event interface object:
> (typeof Event == "undefined").
> 3) Browser has new native Event interface object: (typeof Event == "function"
> (new)).

Is it so that you can tell whether you can construct an Event object with new or not?  You can do that with:

  var canConstructEvent = false;
  try {
    new Event("a");
    canConstructEvent = true;
  } catch (e) {
  }

> 4) App is using a framework that adds/replaces global `Event`
> a) Prototype.js 
> | if (!window.Event) var Event = { }; (
>   i. Older Event interface obj (see 1).
>   ii. No Event interface obj: (creates a new user-defined Object object (typeof
> Event == "object") (different from (2)).
>   iii.  New Event interface obj: (see 3).

I'm not sure what the point of 4a is here.  (I checked out the source to Prototype.js and couldn't find this code.)

> b) MooTools:
> | var Event = new Type(... (typeof Event == "function" (always)).

I'm not sure what you're saying with this one.  Do you need to be able to distinguish between a native Event interface object and the one MooTools creates?  If so, doing `typeof Event == "object"` doesn't seem to be the best way to do this.

> As the Event interface evolves, the new functionality must also be feature
> tested. This situation of feature testing the global Event constructor becomes
> more difficult.

I don't understand why this becomes more difficult.

> For a copy'n'paste coder who tests in three browsers, a changed Event object
> will probably lead to compatibility bugs.

It really depends on whether they're copying-and-pasting code that checks `typeof Something == "object"`.  I find a few but not many instances of this with:

http://www.google.com/codesearch#search/&q=typeof%5Cs*%5C%28?%5Cs*%5BA-Z%5D%5BA-Za-z%5D%2B%5Cs*%5C%29?%5Cs*%5B!=%5D==?%5Cs*.object%20lang:%5Ejavascript$%20case:yes&sq=&type=cs

> Now, OTOH, a *new* method can avoid dealing with legacy code issues:
> 
> | if(window.Event && typeof window.Event.createInitedEvent == "function") {
> |   Event.createInitedEvent(type, optionObj)
> | }
> 
> It should be a safe bet to assume that if `createInitedEvent` is present, it's
> not something that MooTools added. This avoids compatibility issues.
> Copy'n'pasters can drop that on into a web page and browser vendors should not
> see "TypeError Event is not a constructor", etc.

This sounds like an issue for the DOM Core/Events specs, not Web IDL.