Solving the "how do I tell whether I have an HTML element?" (or image element, or whatever) problem

# Boris Zbarsky (11 years ago)

Note: es-discuss is bcced, but please reply to public-script-coord.

The fact that instanceof for "standard classes" is completely broken when multiple globals are involved has come up yet again, as Gecko converts to WebIDL bindings.

The reason for that is that currently in Gecko's DOM bindings instanceof where the RHS is a DOM interface object does magic that returns true if the LHS is a platform object implementing that interface (in the WebIDL sense). We initially tried to not do that for the WebIDL bindings and have had to put it back in for now because it turned out that there was a fair bit of Gecko-specific content that relied on this behavior.

That said, I would like to figure out a way forward here, and figure it out sooner rather than later so we don't have to change said Gecko-specific content more than once.

The obvious options seem to be:

  1. Hack instanceof as Gecko has in the past. This is actually a bit of a pain to implement even in Gecko, without changes to SpiderMonkey, because function objects can't have the magic behavior the RHS needs above in SpiderMonkey at the moment.

  2. Add something like Array.isArray to all WebIDL interface objects. HTMLImageElement.isHTMLImageElement is a bit wordy, though... HTMLImageElement.isInstance() would be simpler, but doesn't match the existing isArray pattern.

  3. Do something else. What?

Thoughts? I'm particularly interested in things that other UAs are willing to implement (e.g. if #1 above is a non-starter for them, there's no point even discussing it in terms of specs).

-Boris

P.S. The obvious "foo instanceof foo.ownerDocument.defaultView.HTMLImageElement" hack only works for nodes, and even then fails if foo.ownerDocument is an XHR response document or something else without a Window, so it's not a viable solution even if it were too not convoluted to really recommend with a straight face.

# Allen Wirfs-Brock (11 years ago)

On Dec 31, 2012, at 8:33 PM, Boris Zbarsky wrote:

Note: es-discuss is bcced, but please reply to public-script-coord.

The fact that instanceof for "standard classes" is completely broken when multiple globals are involved has come up yet again, as Gecko converts to WebIDL bindings.

I'd argue that this is an ECMAScript issue rather than a Web API issue. If one indeed considers instanceof broken when multiple globals is involved then it is broken for everything, not just Web APIs. It isn't clear that fixing it for Web APIs while leaving it broken for everything else actually benefits JavaScript programmers. Rather than trying to fix it via WebIDL bindings perhaps it would be better to lobby TC39 to fix (or replace it) for everything.

The reason for that is that currently in Gecko's DOM bindings instanceof where the RHS is a DOM interface object does magic that returns true if the LHS is a platform object implementing that interface (in the WebIDL sense). We initially tried to not do that for the WebIDL bindings and have had to put it back in for now because it turned out that there was a fair bit of Gecko-specific content that relied on this behavior.

That said, I would like to figure out a way forward here, and figure it out sooner rather than later so we don't have to change said Gecko-specific content more than once.

The obvious options seem to be:

  1. Hack instanceof as Gecko has in the past. This is actually a bit of a pain to implement even in Gecko, without changes to SpiderMonkey, because function objects can't have the magic behavior the RHS needs above in SpiderMonkey at the moment.

  2. Add something like Array.isArray to all WebIDL interface objects. HTMLImageElement.isHTMLImageElement is a bit wordy, though... HTMLImageElement.isInstance() would be simpler, but doesn't match the existing isArray pattern.

  3. Do something else. What?

Thoughts? I'm particularly interested in things that other UAs are willing to implement (e.g. if #1 above is a non-starter for them, there's no point even discussing it in terms of specs).

In the current ES6 drafts, instanceof is extensible on a per class (really constructor) basis.

This works as follows:

 (obj instanceof Ctor)   is defined to produce the boolean result of calling:
                 Ctor[hasInstance_privateSymbol](obj)

In the spec. we refer to the method that is called as @@hasInstance

The default @@hasInstance behavior is the traditional ES instanceof check. However, all it takes to change that is to define an alternative @hasInstance method on a constructor.

For example, let say for constructor Foo you want hasInstance to work cross frames and to be insensitive to the prototype chain configuration. You might do this by defining a private symbol, let's refer to it as @@FooBrand and make sure that it is known to the Foo implementation that is used in all frames. Whenever a Foo instance is created by Foo it would create a frozen @@FooBrand keyed property on the instance.

The definition of @@hasInstance for Foo could then be:

Foo[hasInstance_privateSymbol] = function (obj) { if (typeof obj !== "object") return false; if (obj ===null) return false; return Object.getOwnPropertyDescriptor(obj,FooBrand_privateSymbol) !== undefined };

(I've gloss over the details of how hasInstance_privateSymbol and FooBrand_privateSymbol) would be imported. If Foo was implemented natively, they would be communicated at that level.)

# Boris Zbarsky (11 years ago)

On 12/31/12 11:33 PM, Boris Zbarsky wrote:

  1. Hack instanceof as Gecko has in the past. This is actually a bit of a pain to implement even in Gecko, without changes to SpiderMonkey, because function objects can't have the magic behavior the RHS needs above in SpiderMonkey at the moment.

We have decided to go this route in Gecko, I believe, given that Allen says this will be describable in ES6.

In the short term that means that our WebIDL interface objects will probably not be function objects but reasonable facsimiles. In the longer term we'll probably add a way to have functions have custom instanceof behavior to SpiderMonkey.

The next step is presumably getting WebIDL changed accordingly. With that in mind, I would welcome one single representative of another browser or browser engine commenting here... ;)

# Brandon Benvie (11 years ago)

Indeed, this is described by @@hasInstance in recent drafts of the ES6 spec. It's a property of Function.prototype, and adding custom functionality for it simply requires access to the @@hasInstance symbol in order to the set your own custom functionality.

# Boris Zbarsky (11 years ago)

On 1/17/13 4:27 PM, Travis Leithead wrote:

This grabbed my attention :)

Oh, good. This is only my third try at getting a response... ;)

Is the idea to change the interface object such that instanceof can answer true for any instance of that type (from any Javascript engine instance)? For example:

x instanceof Element === true (where x and Element are from different engine instances?

I'm not sure what you mean by "engine instance".

The proposal is that the expression "x instanceof Foo" where "Foo" is a WebIDL interface object for a non-callback interface as defined at dev.w3.org/2006/webapi/WebIDL/#interface-object will return true if and only "x" is a platform object implementing the relevant interface.

So "x instanceof Element" would be true if x is an Element, no matter what its owner document is.

# Boris Zbarsky (11 years ago)

On 1/17/13 4:48 PM, Travis Leithead wrote:

So "x instanceof Element" would be true if x is an Element, no matter what its owner document is.

I don't think that's quite what you want. For multiple documents inside of a single window instance this is already true:

Sure, but it's not true for documents in general.

I think you are asking that when comparing instances of elements originating from two different window objects the instanceof check can return true.

Yes, indeed. Saying that it's true for all ownerDocuments implies that it's also true for ones from other windows.

Will this be new ECMAScript 6 behavior for instanceof

ES6 has a new trap that allows the RHS of instanceof to return whatever value it wants, basically. I'm proposing that WebIDL define this trap for WebIDL interface objects to return whether the LHS implements the given interface.

# Anne van Kesteren (11 years ago)

On Thu, Jan 17, 2013 at 11:09 PM, Travis Leithead <travis.leithead at microsoft.com> wrote:

I think this sounds fine. Most web developers don't expect the current spec'd behavior, even though to me it seems more natural.

While we have your attention: www.w3.org/Bugs/Public/show_bug.cgi?id=20567

(And every other browser vendor not using Gecko really.)

Thanks! :-)

# Cameron McCormack (11 years ago)

On 18/01/13 9:09 AM, Travis Leithead wrote:

I think this sounds fine. Most web developers don't expect the current spec'd behavior, even though to me it seems more natural.

IE has the current behavior and I expect we would migrate to the new behavior at some point once it was defined and agreed upon.

At one point a long time ago, Web IDL did require [[HasInstance]] to be overridden to return true even for objects from different windows. If proxies now support trapping that, I'll just add it back.

(And hey, it was still there just commented out! I must have known it'd return.)

dev.w3.org/cvsweb/2006/webapi/WebIDL/Overview.xml.diff?r1=1.612;r2=1.613;f=h

I guess this will change to the @hasInstance that Allen mentions, once ES6 stabilises and I actually understand what "@hasInstance" means. :)

# Tom Van Cutsem (11 years ago)

2013/1/24 Cameron McCormack <cam at mcc.id.au>

On 18/01/13 9:09 AM, Travis Leithead wrote:

I think this sounds fine. Most web developers don't expect the current spec'd behavior, even though to me it seems more natural.

IE has the current behavior and I expect we would migrate to the new behavior at some point once it was defined and agreed upon.

At one point a long time ago, Web IDL did require [[HasInstance]] to be overridden to return true even for objects from different windows. If proxies now support trapping that, I'll just add it back.

Just to clarify: we've previously talked about proxies trapping [[HasInstance]], but instead settled on an approach that does not require proxies (see below).

I guess this will change to the @hasInstance that Allen mentions, once ES6 stabilises and I actually understand what "@hasInstance" means. :)

@hasInstance denotes a unique symbol. If a JavaScript object (any object, doesn't have to be a proxy) defines a property with that symbol as a key, it will be able to intercept instanceof. My understanding of how this would work is roughly as follows:

// get the hasInstance symbol from somewhere (e.g. import from a std module) var hasInstance = ...;

function Foo() {...}; Foo[hasInstance] = function(subject) { return <boolean-expression>; }

obj instanceof Foo // calls the above function, passing obj as the subject