-
Notifications
You must be signed in to change notification settings - Fork 165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace serializers by toJSON and [Default] extended attribute #323
Conversation
index.bs
Outdated
the closest [=inherited interface=] of |I| | ||
that declares a “toJSON” operation. | ||
1. Set |result| to [=?=] [=Call=](|super|, |O|). | ||
1. If Type(|result|) is not Object, return |result|. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior allows to set a [Default] extended attribute on a toJSON operation even it has ancestors that set a custom one.
If the custom toJSON operation defined on one of the ancestors returns an object, we just add the interface's attributes to it. Otherwise, we return whatever was returned by the ancestor's toJSON method.
Happy to hear comments as to why this is a bad idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here we're invoking the actual operation (as opposed to operation steps), right? But the default value of that operation, not whatever is on the prototype?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here we're invoking the actual operation (as opposed to operation steps), right?
Yes. At least that's the intent.
But the default value of that operation, not whatever is on the prototype?
That's also the intent, yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether this is observably different from invoking the operation steps... Do we use the |super| from O's relevant global? Or from the current global of the toJSON operation? That is, what is the current global while |super|'s steps are being performed? This may be observable with toJSON operations that return non-primitives...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm. I assumed that's what you had to do if you wanted to handle custom toJSON operations in inherited interfaces, e.g.:
interface Parent {
ParentJSON toJSON();
};
dictionary ParentJSON {
DOMstring bar;
};
interface Child {
attribute DOMstring foo:
[Default] toJSON();
};
But I guess you can invoke the steps of Parent.toJSON rather than the actual operation.
That's a bit over my head tbh.
index.bs
Outdated
* a [=sequence type=] whose parameterized type is a [=JSON value=], | ||
* a [=dictionary=] where all of its [=dictionary members|members=] are [=JSON values=], | ||
* a [=record=] where all of its [=map/values=] are [=JSON values=], and | ||
* an [=object type=], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if the object type needs further restrictions here (such as having all of its properties be JSON values or if we don't really care because JSON.stringify
will deal with it anyhow).
index.bs
Outdated
The “toJSON” operation of the <code class="idl">Transaction</code> interface | ||
must follow these steps: | ||
|
||
1. Let |json| be a new <code class="idl">TransactionJSONValue</code> dictionary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This screams for slots and a default behavior of slots and getters/setters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, this looks pretty reasonable...
index.bs
Outdated
<h5 id="idl-tojson-operation">toJSON</h5> | ||
|
||
By declaring a “toJSON” [=regular operation=], | ||
an [=interface=] specify how to convert the objects that implements it to [=JSON values=]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Specifies" and "implement".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
index.bs
Outdated
The “toJSON” [=regular operation=] is reserved for this usage. | ||
It must take zero arguments and return a [=JSON value=]. | ||
|
||
The list of <dfn id="dfn-json-values" export lt="JSON value">JSON values</dfn> is as follows: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a list of types, not values. Should we be talking about "JSON types"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is named after ECMA-404 which distinguishes between JSON text and JSON values (with the former being the stringified version of the latter). Deep-linking to ECMA-404 isn't possible, since the spec is a pdf, so I didn't.
I also considered calling this a type, but then I wasn't sure if we could define a type in prose which had restrictions the way this one does.
I'll go with whatever you suggest here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK with JSON values. Then we should say something like "The list of types that JSON values can have is:"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
index.bs
Outdated
the closest [=inherited interface=] of |I| | ||
that declares a “toJSON” operation. | ||
1. Set |result| to [=?=] [=Call=](|super|, |O|). | ||
1. If Type(|result|) is not Object, return |result|. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not very compatible with declaring toJSON
to return object
. Should the default toJSON
be required to return any
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's the behavior we want, than yes, absolutely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
index.bs
Outdated
the closest [=inherited interface=] of |I| | ||
that declares a “toJSON” operation. | ||
1. Set |result| to [=?=] [=Call=](|super|, |O|). | ||
1. If Type(|result|) is not Object, return |result|. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here we're invoking the actual operation (as opposed to operation steps), right? But the default value of that operation, not whatever is on the prototype?
index.bs
Outdated
that declares a “toJSON” operation, then | ||
1. Let |super| be the “toJSON” operation of | ||
the closest [=inherited interface=] of |I| | ||
that declares a “toJSON” operation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all does not play nicely with mixins, in our current mixin model. The idea should be that you consider your inherited interfaces and all their mixins, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point.
I think I need to rethink some of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bzbarsky: would you recommend doing this by climbing up the prototype chain in ES?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would really prefer we didn't do that actually, because that's mutable and can lead to weird behavior...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tentatively fixed. This was only missing consequential interfaces, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think just adding the consequential interfaces is what's needed for this bit.
index.bs
Outdated
run the the following steps: | ||
|
||
1. Let |O| be the <emu-val>this</emu-val> value. | ||
1. Let |result| be [=!=] [=ObjectCreate=]([=%ObjectPrototype%=]). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this move into a "if there is no inherited toJSON" branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Let/set algo-writing constraints pushed me to write it that way. An implementation obviously shouldn't create the object if it's not needed.
I guess I'll just either:
1. Let |result| be null;
1. If, … set |result| to …
1. Otherwise, set |result| to …
or:
1. If, … let |result| be …
1. Otherwise, let |result| be …
Which one would you favor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second one of those seems fine, with "let |result| be" or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Fixed the second example's copy/paste error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
index.bs
Outdated
1. If Type(|result|) is not Object, return |result|. | ||
1. For each [=exposed=] [=regular attribute=] |attr| that is an [=interface member=] of |I|: | ||
1. Let |id| be |attr|’s [=identifier=] [=converted to an ECMAScript value=]. | ||
1. Let |value| be [=?=] [=Get=](|O|, |id|). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is calling possibly-modified getters, not the original ones, right? Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't by design. I just didn't think about it. I must confess having no idea how to express what you're suggesting we do here. Is the following what you have in mind? Is there a clearer way to spec this?
1. Let |value| be the result of invoking the [=Get=] operation of interface |I|
passing it |O| and |id| as argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the right way to phrase this in the current world is. Maybe @domenic knows offhand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't had time yet to review this PR (hopefully today), but the two proposed phrasings are identical. If I understand the context correctly, I think you want something like "Let value be the result of running the steps specified for the getter of the IDL attribute id on object O". But please read that skeptically as I am jumping into the middle here without much context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tricky bit, which is also the one I'm conceptually struggling the most with (if my understanding of it is correct), is that you want to be calling the getters of interface |I|, which is not necessarily the one that |O| implements. Consider the following example (@bzbarsky, stop me if I'm wrong):
interface Grandparent {
[Default] any toJSON();
attribute DOMString x; // returns "gp-x"
};
interface Parent : Grandparent {
[Default] any toJSON();
attribute DOMString y; // returns "p-y"
};
interface Child : Parent {
attribute DOMString x; // returns "c-x"
attribute DOMString z; // returns "c-z"
};
new GrandParent().toJSON();
// { x: "gp-x" };
new Parent().toJSON();
// { x: "gp-x", y: "p-y" };
new Child().toJSON();
// { x: "gp-x", y: "p-y" };
In the last example, notice how we called Grandparent
's getter for x, not Child
's. Also notice how z isn't stringified as toJSON
isn't declared on Child
.
Overall I see how that makes sense from an implementor's perspective, but coming from dynamic languages, I find that super hard to grasp and reason about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, good catch. In general, what should happen if I subclass a thing with a [Default] toJSON? Seems to me like conceptually it shouldn't matter whether the subclassing is done via defining an interface inheriting from it or via a page just subclassing an interface via ES class syntax.
It seems like one fundamental question is whether toJSON should be getting properties or reading internal slots...
index.bs
Outdated
1. Let |id| be |attr|’s [=identifier=] [=converted to an ECMAScript value=]. | ||
1. Let |value| be [=?=] [=Get=](|O|, |id|). | ||
1. Let |created| be [=!=] [=CreateDataProperty=](|result|, |id|, |value|). | ||
1. Assert: |created| is <emu-val>true</emu-val>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't assert anything of the sort. You're calling CreateDataProperty on some random object that might have come from anywhere. It can totally fail to define the property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
index.bs
Outdated
1. For each [=exposed=] [=regular attribute=] |attr| that is an [=interface member=] of |I|: | ||
1. Let |id| be |attr|’s [=identifier=] [=converted to an ECMAScript value=]. | ||
1. Let |value| be [=?=] [=Get=](|O|, |id|). | ||
1. Let |created| be [=!=] [=CreateDataProperty=](|result|, |id|, |value|). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't use !
here. You don't control result
; it could just throw from [[DefineOwnProperty]]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looking pretty great. I guess the two big outstanding questions to me are:
- Whether we want the generic-ness of [Default] and default operations
- Whether we want the default algorithm to perform all these observable-from-ES steps, or go straight to the backing stuff
index.bs
Outdated
an [=interface=] specifies how to convert the objects that implement it to [=JSON values=]. | ||
|
||
The “toJSON” [=regular operation=] is reserved for this usage. | ||
It must take zero arguments and return a [=JSON value=]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the distinction here between values and types is a bit confusing. I think just renaming this to "JSON type" is the way to go. If you ever have to say "a value of a JSON type" then you can do so, but that should be rare...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON type it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
index.bs
Outdated
* a [=dictionary=] where all of its [=dictionary members|members=] are [=JSON values=], | ||
* a [=record=] where all of its [=map/values=] are [=JSON values=], and | ||
* an [=object type=], | ||
* an [=interface type=] that has a “toJSON” method declared on itself or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/method/operation, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
index.bs
Outdated
one of its [=inherited interface|inherited=] or | ||
[=consequential interface|consequential=] interface. | ||
|
||
How the “toJSON” [=regular operation=] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might just omit this paragraph (and the next one), as kind of the point here is that it behaves just like any other operation in terms of language bindings. The restrictions are there so that when you perform the usual exposure steps, the result is still usable by ECMAScript's JSON.stringify, but toJSON itself isn't really special, besides just having to follow some restrictions.
index.bs
Outdated
must follow these steps: | ||
|
||
1. Let |json| be a new <code class="idl">TransactionJSONValue</code> dictionary. | ||
1. Set |json|[“from”] to this <code class="idl">Transaction</code>'s associated "from" value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make these quotes not be curly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Man, I saw those elsewhere in the spec and duly copied them. Good thing we have a document convention section where all this stuff is clarified. :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the spec seems to really like using curly quotes for operation names, but for dictionary fields at least I assumed we were following the infra-style dictionaries-as-ordered-maps-with-string-keys and infra-strings don't use curly quotes.
In the future maybe it'd be cool to use <code>
for operation names instead of curly quotes. shrug
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right those are the infra ones. Sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@@ -8058,6 +7765,79 @@ for an interface is to be implemented. | |||
</div> | |||
|
|||
|
|||
<h4 id="Default" extended-attribute lt="Default">[Default]</h4> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we anticipate using this super-general "default operations" framework for anything else in the future? I'd be tempted to just go with [DefaultToJSON] and make it all specific to ToJSON here...Consider this resolved
index.bs
Outdated
<pre highlight="webidl"> | ||
[SecureContext] | ||
interface PaymentResponse { | ||
readonly attribute DOMString paymentRequestId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this is probably changing to an id
member in payment request. Maybe using a made-up example is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my second pass, yeah, we should probably get rid of this example. It seems redundant with the other example in the toJSON section... Maybe move that one here? Or split that one into two, so that the "In the ECMAScript language binding..." part goes here, and we link back to the previous example for the language-agnostic stage-setting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still need to address that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partially addressed this, still a bit more to fix.
index.bs
Outdated
then: | ||
1. Set |R| be the result of performing the actions listed in | ||
|operation|'s [=corresponding default operation=] | ||
on |O| if |O| is not <emu-val>null</emu-val>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should assert O is not null here instead of including this clause.
Similarly if we go for un-genericifying this we can assert that values is zero-length.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can still assert O is not null here, as [Default] is restricted to regular operations (not static operations)
index.bs
Outdated
may be declared with a [{{Default}}] [=extended attribute=]. | ||
|
||
|
||
<h6 id="es-default-tojson">default toJSON operation</h5> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capitalize "D"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
index.bs
Outdated
1. Let |id| be |attr|’s [=identifier=] [=converted to an ECMAScript value=]. | ||
1. Let |value| be [=?=] [=Get=](|O|, |id|). | ||
1. Perform [=?=] [=CreateDataProperty=](|result|, |id|, |value|). | ||
1. Return |result|. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I guess we have open questions as to whether this algorithm should observably call the super, or observably perform Get()s. I think it's easy enough to phrase it as doing neither. E.g. to be non-observable instead of
Let result be ? Call(super, O)
say something like
Let result be the result of performing the steps listed in the description of super on O (or the default steps defined for super if super is annotated with [Default])
(and switch around subsequent the type testing to work on IDL values instead of ES values).
And instead of
Let value be ? Get(O, id)
say something like
Let value be the result of performing the steps listed in the description the getter for id on O.
Finally adressing @domenic's comments here. Sorry for the delay in getting back to this.
The reason behind this was that (1) it turned out to be trivial to generalize, (2) it was more aesthetically pleasing to write: That said, I'm not attached to it at all and I'm happy to go with whatever people prefer.
I don't have a strong opinion here outside of saying that it is really hard to spec the ES route. As discussed above, ES Get relies on the prototype chain and, apparently, that's not what makes sense from an implementation perspective. We'd need to spec dedicated Get operations here that would call the getters defined higher up in the prototype chain. This means we'll need to track how ES Get is specified and edit our own version accordingly. I'd rather avoid that. The whole situation feels somewhat of a code smell. While the [Default] toJSON operation certainly makes sense in C++, it feels awkward in JS, where you'd probably implement that dynamically. If we're nonetheless sure this is the right algorithm to spec, we should probably got straight to the backing stuff and replace that with slots once we have those spec'ed. |
Re: [Default] vs. [DefaultToJSON]. I do not have strong opinions, and agree with your overview of the possible pros. I am just skeptical that we will ever use the generality. So I'm happy to leave it up to you; of course if @bzbarsky or anyone else has stronger opinions that would be good to hear. BTW I am quite happy with the extended attribute approach these days; notably it fits with other cases where we use extended attributes to "generate" the definition of the operation/attribute, like [PutForwards]. Re: overall strategy. I think going straight to the backing data is the better way to go. It matches the current spec (see e.g. #188 (comment) discussion) and thus will result in no extra implementation churn. |
* Remove all serializers. * Add support for JSON stringification through the toJSON operation. * Add the [Default] extended attribute. * Add a default JSON operation. Fixes whatwg#260. Fixes whatwg#188. Closes https://www.w3.org/Bugs/Public/show_bug.cgi?id=27428.
* Uses underlying values instead of converted to ES ones. * Relies on the underlying abstract operation instead of the exposed JS method. * No longer relies on the result of custom toJSON methods in inherited interfaces. * Explicit about how consequential and inherited interfaces are handled.
This is possible now that we no longer take into consideration custom toJSON operations in the [Default] algorithm.
|
@domenic this should be ready for another round of review right now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking quite good!! Just some minor stuff to finish up now.
index.bs
Outdated
* [=string types=], | ||
* [=nullable types=] whose [=nullable types/inner type=] is a [=JSON type=], | ||
* [=annotated types=] whose [=annotated types/inner type=] is a [=JSON type=], | ||
* [=union types=] where all of their [=member types=] are [=JSON types=], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: you switched from "whose" to "where". I think all "whose" might work well, e.g. "union types whose member types are all JSON types"
index.bs
Outdated
* [=sequence types=] whose parameterized type is a [=JSON type=], | ||
* [=dictionaries=] where all of their [=dictionary members|members=] are [=JSON types=], | ||
* [=records=] where all of their [=map/values=] are [=JSON types=], | ||
* [=object types=], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
object types doesn't seem correct; e.g. it includes interfaces, or object
could be used to return literally anything. I think just omitting this is OK; if someone really can't come up with a dictionary or record for their return type, we can address that in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I guess we need object
for the default case. So maybe this should just be object
, not object types? We could also prohibit object
unless you use [Default].
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially confused [=object=] with [=object types=]. Now fixed to include only the former.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be code
-ified like boolean
is
index.bs
Outdated
that has a “toJSON” method defined in prose, | ||
and an interface <code class="idl">Account</code>, | ||
which relies on the [=default toJSON operation=] | ||
(its “toJSON” operation is declared with a [{{Default}}] [=extended attribute=]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd get rid of the parenthetical; it's repetitive given you already stated this equivalence one paragraph ago.
index.bs
Outdated
is language binding specific. | ||
|
||
Note: In the ECMAScript language binding, | ||
this is done by exposing a “toJSON” method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume we're not consistent here, but maybe methods should be <code>toJSON</code>
, even if we keep the ugly convention of curly-quotes for operation identifiers? Not a big deal.
index.bs
Outdated
<pre highlight="webidl"> | ||
[SecureContext] | ||
interface PaymentResponse { | ||
readonly attribute DOMString paymentRequestId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my second pass, yeah, we should probably get rid of this example. It seems redundant with the other example in the toJSON section... Maybe move that one here? Or split that one into two, so that the "In the ECMAScript language binding..." part goes here, and we link back to the previous example for the language-agnostic stage-setting?
index.bs
Outdated
then: | ||
1. Set |R| be the result of performing the actions listed in | ||
|operation|'s [=corresponding default operation=] | ||
on |O| if |O| is not <emu-val>null</emu-val>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can still assert O is not null here, as [Default] is restricted to regular operations (not static operations)
index.bs
Outdated
with [=stack=] |stack| and [=ordered map=] |map| as arguments, | ||
run the the following steps: | ||
|
||
1. Let |I| be the result of invoking [=stack/pop=] from |stack|. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"the result of popping from stack"
index.bs
Outdated
1. Let |rhs| be the [=interface=] represented by the right-hand side [=identifier=]. | ||
1. Let |interfaces| be the result of [=create an inheritance stack|creating an inheritance stack=] | ||
for [=interface=] |rhs|. | ||
1. invoke [=collect attribute values=], passing it |interfaces| and |map|. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uppercase "invoke"
index.bs
Outdated
[=list|for each=] [=exposed=] [=regular attribute=] |attr| | ||
that is an [=interface member=] of |I|, in order: | ||
1. Let |id| be the [=identifier=] of |attr|. | ||
1. Let |value| be the underlying value of |attr|. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is better stated as "Let value be the result of performing the actions listed in the description of attr that occur on getting (or those listed in the description of the inherited attribute, if this attribute is declared to inherit its getter)".
This phrase appears in #dfn-attribute-getter, and maybe should be deduplicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would adding something like the below in §2.2.2 Attributes do?
To get the value of an [=attribute=] |attribute| declared in interface |I|,
implemented by object |O|, run the following steps:
1. Let |R| be the result of performing the actions listed in the
description of |attribute| that occur on getting
(or those listed in the description of the inherited attribute,
if this attribute is declared to [=inherit its getter=]),
on |O| if |O| is not <emu-val>null</emu-val>.
1. Return |R|.
How would you handle the "on |O| if |O| is not null" part keeping it in WebIDL, though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like in the latest spec revision includes two steps for getting value, one old and one new; so we should fix that.
As for the abstraction: I don't think there's a need for separate let/return steps, or for the interface I. So I'd phrase this as:
To get the underlying value of an attribute attr given a value target, return the result of performing the actions listed in the description of attr that occur on getting, or those listed in the description of the inherited attribute, if attr is declared to inherit its getter, on target if target is not null.
But we could leave that for later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A preference (wrapping the whole thing in div.algorithm vs. marking variables as ignorable in Bikeshed)?
<div algorithm>
To <dfn export>get the underlying value</dfn> of an attribute |attr| given a value |target|,
return the result of performing the actions listed in the description of |attr| that occur on getting,
or those listed in the description of the inherited attribute,
if |attr| is declared to inherit its getter, on |target| if |target| is not null.
</div>
or:
To <dfn export>get the underlying value</dfn> of an attribute <var ignore>attr</var>given a value <var ignore>target</var>,
return the result of performing the actions listed in the description of <var ignore>attr</var> that occur on getting,
or those listed in the description of the inherited attribute,
if <var ignore>attr</var> is declared to inherit its getter,
on <var ignore>target</var> if <var ignore>target</var> is not null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Algorithm seems good.
1. [=stack/Push=] |I| onto |stack|. | ||
1. Return |stack|. | ||
</div> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole section would, of course, benefit from an example. I think it should be short and contrived but illustrate the algorithm. Something like:
interface A : B {
attribute x;
};
interface B {
attribute y;
};
interface C {
attribute z;
};
interface D {
attribute w;
};
A implements C;
B implements D;
and then explain what keys the map ends up containing, and in what order.
Would need your input on #323 (comment) (surfacing it as GH's helpful UI hid it somewhere). Still need to address removing some of the extra JSON examples. Otherwise, I think I addressed everything in your latest feedback. |
index.bs
Outdated
<div class=example> | ||
|
||
The following [=IDL fragment=] defines a number of [=interfaces=] | ||
which are [=inherited interface|inherited=] or [=consequential interfaces=] of `A`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backtick <code>
ification doesn't seem to have worked for some reason
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get burnt by that everytime. :(
index.bs
Outdated
G* - A - H* | ||
</pre> | ||
|
||
Interfaces markes with an asterixk ("*") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"asterisk"
index.bs
Outdated
</pre> | ||
|
||
Calling the <code>toJSON</code> method of an object | ||
implementing interface `A` defined above |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<code>
instead of backticks
index.bs
Outdated
"s": "...", | ||
"x": "...", | ||
"y": "..." | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, so it would be easier to check this if the lowercase property names matched the uppercase interface names.
So, this order is a bit surprising to me. I would the following interface order:
C, B, F, E, I, D, A, G, H
My reasoning is:
- First go to the most distance ancestor on the JavaScript prototype chain (C).
- Then go to the next-most-distance, B
- Then go B's mixins
- But D has inheritance, so go up its "chain", ending up with F; then E plus its I mixin; then finally D
- Now we can do A, and then its two mixins, G and H.
This all might be moot once we simplify interfaces... but I think it'd be good to get right, if possible. We could add a warning that we anticipate such complex inherited/consequential interface chains not being possible in the future, which would make all this less ridiculous. Probably linking to the mixin issue I'm about to open.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we mostly agree, except you consider mixins happen after interface definition rather than before.
interface A {
attribute DOMString a;
};
interface B {
attribute DOMString b;
};
A implements B;
Do we get {a,b}
or {b,a}
?
I don't think the spec is clear about that, tbh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, not clear as-is. I think it should definitely be {a,b} though, as the "main" stuff takes priority, and stuff added by other specs would come later, not first.
Rely on get the underlying value dfn.
It feels sort of icky that the whole default operation is ES-only, especially since the boundary between the IDL part and the ES binding is now so clear. It seems other extended attributes are similarly not ES-specific (e.g. [PutForwards]). Should we not worry about this until WASM is a thing? |
I was going make a few more tweaks, but it appears "enable edits from collaborators" is not enabled, so here's a diff: diff --git a/index.bs b/index.bs
index 3757492..2fd5d64 100644
--- a/index.bs
+++ b/index.bs
@@ -2026,7 +2026,7 @@ Additionaly, in the ECMAScript language binding,
the <code>toJSON</code> operation can take a [{{Default}}] [=extended attribute=],
in which case the [=default toJSON operation=] is exposed instead.
-<div class="example">
+<div class="example" id="tojson-example">
The following [=IDL fragment=] defines an interface <code class="idl">Transaction</code>
that has a <code>toJSON</code> method defined in prose:
@@ -2055,18 +2055,18 @@ in which case the [=default toJSON operation=] is exposed instead.
<div algorithm="example tojson">
- To invoke the <code>toJSON</code> operation of the <code class="idl">Transaction</code> interface
- on |target|, run the following steps:
+ To invoke the <code>toJSON()</code> operation of the <code class="idl">Transaction</code>
+ interface, run the following steps:
1. Let |json| be a new <code class="idl">TransactionJSONValue</code> dictionary.
- 1. [=list/For each=] attribute [=identifier=] |attr| in [=list=] « "from", "to", "amount", "description" »:
+ 1. [=list/For each=] attribute [=identifier=] |attr| in « "from", "to", "amount", "description" »:
1. Let |value| be result of [=get the underlying value|getting the underlying value=]
- of [=attribute=] |attr| of <code class="idl">Transaction</code> on |target|.
+ of the [=attribute=] identified by |attr|, given |target|.
1. Set |json|[|attr|] to |value|.
1. Return |json|.
</div>
- In the ECMAScript language binding, there would exist a <code>toJSON</code> method on
+ In the ECMAScript language binding, there would exist a <code>toJSON()</code> method on
<code class="idl">Transaction</code> objects:
<pre highlight="js">
@@ -7897,7 +7897,7 @@ The [{{Default}}] extended attribute must not
be used on anything other than a [=regular operation=]
for which a [=corresponding default operation=] has been defined.
-<div class="example">
+<div class="example" id="example-tojson-default">
As an example, the [{{Default}}] extended attribute is suitable
for use on the <code>toJSON</code> [=regular operation=] of the
@@ -10355,7 +10355,7 @@ The characteristics of this property are as follows:
[=extended attribute=], then return <emu-val>undefined</emu-val>.
1. Otherwise, [=ECMAScript/throw=] a <emu-val>TypeError</emu-val>.
1. Let |R| be the result of [=get the underlying value|getting the underlying value=]
- of |attribute| on |O|.
+ of |attribute| given |O|.
1. Return the result of [=converted to an ECMAScript value|converting=] |R| to an
ECMAScript value of the type |attribute| is declared as.
@@ -10609,7 +10609,7 @@ The [=return type=] of the [=default toJSON operation=] must be {{object}}.
that is an [=interface member=] of |I|, in order:
1. Let |id| be the [=identifier=] of |attr|.
1. Let |value| be the result of [=get the underlying value|getting the underlying value=]
- of |attr| for |O|.
+ of |attr| given |O|.
1. If |value| is a [=JSON type=], then [=map/set=] |map|[|id|] to |value|.
1. [=list|For each=] [=implements statements=] where the left-hand side [=identifier=] references |I|:
1. Let |rhs| be the [=interface=] represented by the right-hand side [=identifier=].
@@ -10635,7 +10635,7 @@ The [=return type=] of the [=default toJSON operation=] must be {{object}}.
1. Return |stack|.
</div>
-<div class=example>
+<div class=example id=example-tojson-default-inheritance-and-mixins>
The following [=IDL fragment=] defines a number of [=interfaces=]
which are [=inherited interface|inherited=] or [=consequential interfaces=] of <code class="idl">A</code>,
@@ -10719,7 +10719,7 @@ The [=return type=] of the [=default toJSON operation=] must be {{object}}.
}
</pre>
- An object implementing interface `B` would return:
+ An object implementing interface <code class="idl">B</code> would return:
<pre highlight=json>
{
It seems fine to me. All extended attributes, including [PutForwards], are ES-specific; they're in the ECMAScript binding section. I agree there's some confusing bleed-over, e.g. the interfaces section says which extended attributes are applicable to interfaces; maybe that should be consolidated into the extended attribute definitions instead. But yeah, not worth worrying about for now. Still would be good to replace the PaymentRequest example with a made-up one. |
Thanks. Not sure what happened there.
Yes. Absolutely. |
Fixed the last example with something that made more sense. Last nit: there's little consistency with |
It seems OK to me as-is. My take is that:
The remaining question then is when you say something like "the toJSON regular operation" are you using toJSON as an identifier or as a method name. I could go either way. It seems like the current state of this PR is to treat it as an identifier, which works fine for me. One minor diff, but LGTM to merge otherwise. Let's do this!! diff --git a/index.bs b/index.bs
index 24edade..b68344a 100644
--- a/index.bs
+++ b/index.bs
@@ -7922,8 +7922,8 @@ for which a [=corresponding default operation=] has been defined.
In the ECMAScript language binding, there would exist a <code>toJSON()</code> method on
<code class="idl">Animal</code>, <code class="idl">Human</code>,
- and <code class="idl">Dog</code> objects:
-
+ and (via inheritance) <code class="idl">Dog</code> objects:
+
<pre highlight="js">
// Get an instance of Human.
var alice = getHuman();
@@ -7937,7 +7937,7 @@ for which a [=corresponding default operation=] has been defined.
// pet: Dog
// }
alice.toJSON();
-
+
// Evaluates to an object like this (notice how "breed" is absent,
// as the Dog interface doesn't declare a default toJSON operation):
//
@@ -10701,7 +10701,7 @@ The [=return type=] of the [=default toJSON operation=] must be {{object}}.
E implements I;
</pre>
- Calling the <code>toJSON</code> method of an object
+ Calling the <code>toJSON()</code> method of an object
implementing interface <code class="idl">A</code> defined above
would return the following JSON object: |
How did the "Allow edits from maintainers" option get unchecked again?! |
The bug seems to be that it gets unchecked every push :-/. |
I just checked it again, it doesn't even hold. As soon as the xhr/fetch request is done, the input gets unchecked again. Filing an issue with GitHub. |
…ttribute 他 Add bug template URL for Chromium ( whatwg/webidl#374 ) whatwg/webidl@4269cc0 40ac0890 Add frozen array types to the list of JSON types whatwg/webidl@375b588 27d62c6c Editorial: minor language fix whatwg/webidl@d42f606 a840517b Editorial: fix toJSON algorithm example whatwg/webidl@b323f1a e62d9718 Replace serializers by toJSON and [Default] extended attribute ( whatwg/webidl#323 ) whatwg/webidl@a505f33 7f0877b1
@tobie Did implementation bugs ever get filed on the changes here? There were substantive changes to the list of serializable types too (like adding records), right? |
Thank you. I do recall seeing that now, but it didn't mention the changes to the list of serializable types, which affected the priority... :( |
I'm sorry, I didn't think about this when filing it. I'll try to make sure not to forget small fixes done in passing next time I file vendor bugs. |
Fixes #188.
Closes #259.
Fixes #260.
Fixes #262.
Fixes #370.
Closes https://www.w3.org/Bugs/Public/show_bug.cgi?id=27428.
Note: I opted for a generic [Default] extended attribute combined with a default toJSON method instead of
jsonifier;
. This is because it's the only solution I find to be able to get rid of the abstract intermediary JSON formats that was previously defined, by only defining a default toJSON operation for the ES binding.There's probably a few typos and things that could be phrased better, but I really wanted to get some feedback on the overall structure of the patch, so please bear with me while I fix these smaller things. :)
Preview | Diff