[Bug 28244] Requiring @@toStringTag on instances may have performance implications

# bugzilla at jessica.w3.org (2 years ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

Boris Zbarsky bzbarsky@mit.edu changed:

       What    |Removed                     |Added

             CC|                            |bzbarsky@mit.edu

--- Comment #1 from Boris Zbarsky bzbarsky@mit.edu ---

calls for a @@toStringTag own property on each object instance implementing an interface

This is just because we want different toString() results for Node instances and Node.prototype, right?

I agree that putting this on all instances is totally undesirable.

Hanging it off the prototype with an accessor means that the Object.prototype.toString value changes if you munge the proto chain. Maybe that's OK.

The pseudocode in comment 0 doesn't work that well for cross-origin invocation of various sorts, but that could be solved in spec prose.

In some ways, what would be ideal here is having a more extensible way to hook into toString via branding, the way ES6 does for the objects it defines.... Certainly that's how DOM toString stuff is implemented right now and has been for a long time, and I have some web compat worries about changing it. :(

Contact us to advertise here
# bugzilla at jessica.w3.org (2 years ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

Erik Arvidsson arv@google.com changed:

       What    |Removed                     |Added

             CC|                            |d@domenic.me,
               |                            |jsbell@google.com

--- Comment #2 from Erik Arvidsson arv@google.com ---

I think we should just have a data property on the prototype. The spec currently says that the Object.prototype.toString.call(Foo.prototype) should return "[object FooPrototype]". This legacy requirement is not needed for the web. (I assume this behavior came from Gecko's XPCOM bindings?) and Chrome never supported this. Chrome has always returned "[object Object]" for the prototype object.

My suggestion is to follow ES6 and just add a data property for the @@toStringTag to Foo.prototype with the value "Foo".

people.mozilla.org/~jorendorff/es6-draft.html#sec-map.prototype-@@tostringtag

This would mean that we get:

Object.prototype.toString.call(document.body) === '[object HTMLBodyElement]' Object.prototype.toString.call(HTMLBodyElement.prototype) === '[object HTMLBodyElement]'

# bugzilla at jessica.w3.org (2 years ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #3 from Domenic Denicola d@domenic.me ---

I agree with Arv. It is consistent with the ES built-ins, and more performant, and simpler. Given that Chrome provides evidence that it is not a compatibility issue, we should just do the simple thing.

# bugzilla at jessica.w3.org (2 years ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #4 from Boris Zbarsky bzbarsky@mit.edu ---

Given that Chrome provides evidence that it is not a compatibility issue

Sadly, not quite. Chrome provides evidence that returning "Object" as the string tag for HTMLBodyElement.prototype is not a compat issue. Returning "HTMLBodyElement" would break code that tries to work around cross-global instanceof not working in some browsers by checking Object.prototype.toString.call(). Doubly so because all of the methods/getters one would want to call on a <body> exist on HTMLBodyElement.prototype but throw

when called.

I'm game to try the simple thing in theory, but in practice I worry about compat issues. If we have data showing there is no issue (e.g. an implementation shipping the proposed behavior for a bit, or some other indication that people are not using Object.prototype.toString.call() in the way I worry about), I'd love to see it, now or in the future.

# bugzilla at jessica.w3.org (2 years ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #5 from Erik Arvidsson arv@google.com ---

My goal is to try to make this change in Blink. I will report back on possible issues.

# bugzilla at jessica.w3.org (a year ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #6 from Joshua Bell jsbell@google.com ---

We appear to be finally trying out the experiment (Object.prototype.toString.call(HTMLBodyElement.prototype) === '[object HTMLBodyElement]') in Chrome M50 which enters Dev shortly. We'll see if it sticks and report back here.

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #7 from Domenic Denicola d@domenic.me ---

FYI this stuck in Chrome and Firefox is intending to follow suit: groups.google.com/forum/#!searchin/mozilla.dev.platform/toStringTag%7Csort:relevance/mozilla.dev.platform/IZNh8QAXkFA/me59gpo5PgAJ We should change the spec as soon as feasible, to just a data property on the prototype.

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #8 from Boris Zbarsky bzbarsky@mit.edu ---

It's not clear what Firefox will be doing here, if anything. There was pushback to the intent to ship on general grounds of web developer confusion and there are unclear issues around compat. I personally have put the whole thing on indefinite hold for the moment and have no plans to pick it up.

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

Philip Jägenstedt philip@foolip.org changed:

       What    |Removed                     |Added

             CC|                            |philip@foolip.org

--- Comment #9 from Philip Jägenstedt philip@foolip.org ---

Mike Smith pointed me here. Does someone have a test that could show the different behavior in all engines? Trying to understand if there's already a majority behavior.

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #10 from Michael[tm] Smith mike@w3.org --- (In reply to Philip Jägenstedt from comment #9)

Does someone have a test that could show the different behavior in all engines?

(new Object()).toString.call(StorageEvent.prototype)

Per the current WebIDL spec that should give you "[object StorageEventPrototype]". And it does in Gecko and WebKit and (I think) in Edge.

But in Blink it instead now gives you "[object StorageEvent]"

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #11 from Boris Zbarsky bzbarsky@mit.edu ---

Indeed, what comment 10 says. And before Blink changed its behavior it used to give "[object Object]" in Blink.

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

Philip Jägenstedt philip@foolip.org changed:

       What    |Removed                     |Added

             CC|                            |haraken@chromium.org

--- Comment #12 from Philip Jägenstedt philip@foolip.org ---

I can confirm that Edge has the same behavior as Gecko here: (new Object()).toString.call(StorageEvent.prototype) === "[object StorageEventPrototype]" (Object.prototype.toString.call(HTMLBodyElement.prototype)) === "[object HTMLBodyElementPrototype]"

Where did things land in terms of how it's implemented?

crbug.com/239915 was closed as fixed with a different behavior, so adding Blink bindings team lead haraken@ to see if this can be prioritized. (Not sure if there's an open issue.)

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

Domenic Denicola d@domenic.me changed:

       What    |Removed                     |Added

             CC|                            |chris@dumez.cc,
               |                            |travil@microsoft.com

--- Comment #13 from Domenic Denicola d@domenic.me ---

It would be really good to work out the story here.

I strongly believe that @@toStringTag on the prototype, as a simple data property instead of a getter, is more correct:

  • It matches the ES built-ins
  • It fits better with JavaScript's prototypal inheritance model (now that toString behavior is something that prototypally inherits)
  • It's simpler
  • It's possibly more performant (although a sufficiently smart optimizer could probably equalize them)
  • It's very likely to be web-compatible given that it's been shipping in Chrome for several releases

Per comment #8, Boris doesn't quite agree, or at least doesn't want to be second-mover here.

Perhaps we could get other engines to comment? In particular, have any of them started moving their DOM to use ES6 @@toStringTag instead of ES5 [[Class]], such that they have an opinion on this matter?

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #14 from Boris Zbarsky bzbarsky@mit.edu ---

Per comment #8, Boris doesn't quite agree

Comment 8 was about what I'm doing (nothing, partly because I don't have time to pursue a change here in the face of probably reasonable opposition and partly because I have 0 interest in making changes that I will then have to revert because other UAs don't follow), not what I'm thinking.

If you want to know what I'm thinking...

  • It matches the ES built-ins

I think, personally, the ES built-ins are broken. The setup kinda sorta made sense for the ES3 builtins where the prototype was really an instance too (Array, Date). It's pretty nonsensical, imo, for the new built-ins ES6 adds (Map, Set, Promise, etc) where all the things you might want to do with an instance throw if you do them with the prototype. Now I'm obviously not going to change TC39's mind at this stage in the game, but I think they made a mistake here. The only saving grace is that at least Map.prototype instanceof Map tests true by default. But in a multi-global world instanceof is just broken, so you end up with Array.is (but refusals to add it for anything else) and people being told they should not worry about validating their inputs and instead just deal with exceptions if/when they arise. Except they don't, and you get broken websites when some idiot library walks the object graph and suddenly finds an object it did not expect; run into that a number of times as browsers have added new features. So people who want to avoid that pitfall and want to be careful use Object.prototype.toString to see what they're really working with, since instanceof is useless for that purpose. Of course with @@toStringTag this method of telling what you have might also become useless... we'll see.

I know this is getting rather far afield, but the theory of how people write JS and the practice of how at least a large minority do so are very much at odds, and it's the theory that's been driving TC39 decisions... As a result, I'm biased to be somewhat sceptical of adopting those decisions wholesale in other parts of the web platform, particularly when that involves changing long-standing behavior; it's bitten us before when we have.

  • It fits better with JavaScript's prototypal inheritance model (now that toString behavior is something that prototypally inherits)

I don't see how using an accessor property fails on this bullet point, for what that's worth, any more so than any other thing living on the prototype that only works on instances; ES6 is littered with that sort of stuff, as are the IDL-defiend parts of the web platform.

  • It's simpler

Granted. If we had no history here and had some other way of doing brand checking, I don't think anyone would even dream of suggesting that we should use Object.prototype.toString as a brand checking mechanism. Objectively speaking, it is, of course, daft.

  • It's possibly more performant (although a sufficiently smart optimizer could probably equalize them)

Yes on both counts. I question the performance sensitivity of Object.prototype.toString on the level we're talking about here, btw; just the string concatenations required for it already involve a bunch more work than either option here, I expect.

  • It's very likely to be web-compatible given that it's been shipping in Chrome for several releases

Maybe you meant this is the part I don't agree on?

My experience is that in today's market Chrome can get away with breaking sites when other browsers cannot; people will work around Chrome issues and just leave their sites broken in other browsers. Often enough the workaround involves sniffing for Chrome and doing something different there, so even if other browsers implement exactly what Chrome does the site will still be broken in them. This is unfortunate, but it does mean that "Chrome shipped it" is not as useful an indicator of web compat as it could be. It's better than nothing, of course, which is why this discussion is happening at all.

I, personally, happen to think this is web compatible enough, that it just screws over people trying to be careful, and that most people writing code on the web are not being very careful in this way and hence will be unaffected. I was not able to sell others within Mozilla on this viewpoint, though, at least not with the limited amount of effort I put into it.

Perhaps we could get other engines to comment?

That would be the most useful thing, yes.

P.S. Note that Chrome's devtools most definitely display event instances and Event.prototype differently; they are clearly making a branding decision somewhere in there. How they make it given the relative paucity of public facilities for this sort of thing, and why we want to deny web pages the ability to make similar distinctions, is an interesting question.

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #15 from Domenic Denicola d@domenic.me ---

Just to be clear, I sympathize with your desire for a better branding story. But I don't think making the behavior of @@toStringTag different from the ES built-ins is the right way to accomplish this. (I feel similarly for @@hasInstance; see heycam/webidl#129.)

# bugzilla at jessica.w3.org (9 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #16 from Boris Zbarsky bzbarsky@mit.edu ---

Sure, see the "no history" paragraph. The compat concern is whether in the face of a lack of better branding story people are using toString as poor-man's branding in ways that would break if this change got made. The fact that Chrome shipped the behavior it did is some indication that maybe in practice they are not, I agree.

# bugzilla at jessica.w3.org (8 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #17 from Philip Jägenstedt philip@foolip.org ---

Do we have a path forward here? As long as there is divergence, one engine or another will see a lot of noise in idlharness.js tests in web-platform-tests, making it harder to spot real issues (more tooling would help, we're trying that too).

# bugzilla at jessica.w3.org (5 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

Rick Byers rbyers@chromium.org changed:

       What    |Removed                     |Added

             CC|                            |rbyers@chromium.org

--- Comment #18 from Rick Byers rbyers@chromium.org ---

As long as there is divergence, one engine or another will see a lot of noise in idlharness.js tests in web-platform-tests, making it harder to spot real issues (more tooling would help, we're trying that too).

+1. I haven't fully parsed the whole history here, but I agree getting consensus on this is important to IDL tests. Is there any work / data the Chrome team could provide that would help with the compat argument?

# bugzilla at jessica.w3.org (5 months ago)

www.w3.org/Bugs/Public/show_bug.cgi?id=28244

--- Comment #19 from Domenic Denicola d@domenic.me ---

It's worth noting that nobody seems to actually follow the spec here. That is, Object.getOwnPropertySymbols(document.createElement("div")).length is 0 in all browsers I've tested. (I haven't yet tested Safari.)

Given that, I think we should at the very least remove the mention of class strings and @@toStringTag from Web IDL and from testharness.js. Or we could align with Blink's implementation, which at least is implemented in terms of @@toStringTag, whereas nobody else seems to have updated to ES6 at all.

Want more features?

Request early access to our private beta of readable email premium.