Skip to content
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

Create a 'report an exception' algorithm per #958 #10404

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

jeremyroman
Copy link
Collaborator

@jeremyroman jeremyroman commented Jun 7, 2024

This algorithm directly includes the error propagation and fallback behavior, and requires callers to supply the global scope to be used, rather than magically inferring it.

Call sites within HTML are replaced, but there is more work yet to be done.

  • At least two implementers are interested (and none opposed):
  • Tests are written and can be reviewed and commented upon at:
    • These changes are not intended to modify existing behavior.
  • Implementation bugs are filed:
    • These changes are not intended to modify existing behavior.
  • MDN issue is filed: n/a
  • The top of this comment includes a clear commit message to use.

(See WHATWG Working Mode: Changes for more details.)

The following call sites are changed (unchecked ones merit some extra attention):

  • report validity steps: If the element is not being rendered, an unspecified error may be reported. No browser actually seems to fire error events in this case, though Safari does log to the developer console.
  • invoke custom element reactions: The mysterious "associated script" trick is carried forward here, but we should clarify which global and script this in fact is.
  • run a classic script: The script and its settings object (thus global) are readily available.
  • run a module script: Ditto (though the script plumbing may only be necessary for classic scripts, since muting is the only use).
  • reportError(): The global it's called on is used, contrary to that not being plumbed in previously but aligned with what one would expect.
  • register an import map: The errors here are always synthesized in parse an import map string, when there's typically no running script. A type="importmap" script can be synchronously added by other script ought to be reported to that global but is still reported to the relevant global of the script element and its document, which is the same one the register algorithm is being run for.
  • HostEnqueueFinalizationRegistryCleanupJob: finalizationRegistry.[[CleanupCallback]].[[Callback]].[[Realm]] doesn't seem to be used in either Chrome or Firefox (contrary to how timers work), and this can differ from finalizationRegistry.[[Realm]] if the function passed was created in a different realm than the registry itself.
  • HostEnqueuePromiseJob: If result can be an abrupt completion when realm is null (which seems to be possible in an obscure case), what global should learn of this?
  • event handler attributes: If it fails to parse, there's no JavaScript exception sitting around, so a JS SyntaxError is synthesized.
  • timers: I'm pretty confident (with experimentation) that browsers use the global scope for the realm the callback functions come from; in the case of passing strings to setTimeout and setInterval, it's always going to be the global scope that it was called on, since that's where it's evaluated.
  • microtask queuing: Similar to timers.
  • attaching a declarative shadow root: Not confident. It seems like this is the global object associated with the element we're parsing, but this might be distinguishable in cases where another realm is responsible for causing synchronous parsing at a distance in some way, and the previous logic would have used the "associated script" magic to determine the realm.

I also need to:

  • add links for the previously exported anchor names (or whatever decision Anne and Domenic come to)

/custom-elements.html ( diff )
/form-control-infrastructure.html ( diff )
/imagebitmap-and-animations.html ( diff )
/infrastructure.html ( diff )
/nav-history-apis.html ( diff )
/parsing.html ( diff )
/timers-and-user-prompts.html ( diff )
/webappapis.html ( diff )
/workers.html ( diff )

This algorithm directly includes the error propagation and fallback
behavior, and requires callers to supply the global scope to be used,
rather than magically inferring it.

Call sites within HTML are replaced, but there is more work yet to be
done.
@jeremyroman jeremyroman marked this pull request as ready for review June 14, 2024 21:07
@jeremyroman
Copy link
Collaborator Author

@domenic @annevk When you have a chance, would you mind an early review to make sure this aligns with what you had in mind as the first step of #958? There's probably a couple things that deserve a little extra attention before this is totally mergeable (see checkmarks above and class="XXX" added in this PR, some of which might be resolvable before merge), but want to make sure this is at least mostly correct.

Copy link
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Back from vacation! Thanks so much for tackling this.

I got halfway through the review. Then I had an epiphany. (Maybe some parts of the review are still useful.)

A lot of complexity and XXX boxes here and coming from passing the script around, and trying to determine which script to use. But Looking at the Chromium code, muting is not really done by passing a script around. It bottoms out in IsSharedCrossOrigin(), which is a method of v8::Message. In other words, it is derived from the JS engine's representation of the thrown exception, in the same way that we are already hand-waving about for line number, column number, and URL.

Given that we have no interop on muting, and are pretty sure the current spec doesn't match our ideal (per long discussions in #958 and a few other issues like #3149) why are we trying to preserve the framework of it being derived from the script?

My suggestion is that we change the spec hand-wave on the muting. We can have it mention that the user agent will probably want to use, for classic scripts, the "muted errors" boolean. (And that for module scripts, there is no need to mute errors.) But we don't need to find a way to thread through the "right" script.

We can then change the algorithm to only take an exception and a global.

source Outdated
<span>report an exception</span> with an <span>implementation-defined</span> value for
<var>element</var>'s <span>relevant global object</span> and the <span>running
script</span>.</p> <p class="XXX">Is this actually reported to that global in browsers? Or
does it care about what realm/script called this.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good catch, this seems worth removing IMO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

should be added.</p>

<p>When the user agent is required to <dfn>report an exception</dfn> <var>exception</var>, which
may be a JavaScript value or an <span>implementation-defined</span> value, for a particular
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing an implementation-defined value here that is somehow not a JavaScript object seems bad. I'll review the call sites where that escape hatch is used to see if we can get rid of it...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cases are:

  1. non-parseable event handler strings (we just check whether they can be parsed, without actually throwing a real exception)
  2. multiple constraint validation failures (above, which is quite hand-wavy generally and I haven't yet tried to rig up a test page to see what browsers do in practice)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove multiple constraint validation failures, per https://software.hixie.ch/utilities/js/live-dom-viewer/?saved=12866 and https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/forms/listed_element.cc;l=557;drc=7b232da0f22e8cdf555d43c52b6491baeb87f729

For non-parseable event handler strings, I think based on http://software.hixie.ch/utilities/js/live-dom-viewer/?saved=12864 what happens is that people invoke the parser and get an actual SyntaxError object back. So I'd tweak it to something like

Attempt to parse body as a FunctionBody. If parsing fails or detects an early error, let error be a SyntaxError object representing that error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, slight correction on the constraint validation failures -- it's not that there are multiple, it's that the input isn't rendered. It seems that Chrome and Firefox do not log to the developer console in this case, but Safari does (though I haven't managed to get Safari to actually fire an error event in this case, so it might not be going through the full reporting steps). It says: "An invalid form control with name='' is not focusable." (because these steps are used to display validity to the user, which of course isn't possible.

https://software.hixie.ch/utilities/js/live-dom-viewer/?saved=12883

Still, not having managed to find a case where an error event is fired for constraint validation failure, we can probably remove that. I've just removed it altogether (since UAs can generally report whatever developer console messages they find appropriate), but we could amend it to discuss reporting an error to the developer console only if you'd like to keep it in some form.

may be a JavaScript value or an <span>implementation-defined</span> value, for a particular
<span>global object</span> <var>global</var>, optional <span
data-x="concept-script">script</span> <var>script</var>, and optional boolean
<var>exposeError</var> (default true), it must run these steps:</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Booleans should default to false, per https://infra.spec.whatwg.org/#algorithm-params .

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


<p class="note">Returning true in an event handler cancels the event per <span>the event handler
processing algorithm</span>.</p>
<p class="note">The actual <code data-x="">Error</code> will not be available in the owner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p class="note">The actual <code data-x="">Error</code> will not be available in the owner
<p class="note">The actual <var>exception</var> value will not be available in the owner

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

source Outdated

<p w-nodev>The <span>task source</span> for the task mentioned above is the <span>DOM manipulation
task source</span>.</p>
<span w-dev>will</span> <span data-x="report an exception">report</span> it for the worker's
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can now become "will" for both editions, since the report algorithm takes care of the normative requirements.

Similarly, the last sentence is probably redundant with the normative requirements.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, done. With those changes, this entire h4 section is infomative.

@@ -105462,6 +105475,9 @@ new PaymentRequest(&hellip;); // Allowed to use
object">the environment settings object's global object</dfn>.</p></li>
</ul>

<p>A <span>global object</span> has an <dfn for="global object">in error reporting mode</dfn>
boolean, which is initially false.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the past we've avoided adding too many things to all global objects, instead putting them on #environment-settings-object. (Like "outstanding rejected promises weak set".) I'm not sure that was a great decision though, so maybe what you've done here is reasonable too...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It felt more natural to have it here because you'd suggested the algorithm take the global object, not the environment settings object. But I don't feel strongly at all, so happy to do whatever makes editors happy.

source Outdated
<li><p><span>Report an exception</span> given by <var>evaluationStatus</var>.[[Value]] for
<var>script</var>'s <span data-x="concept-script-settings-object">settings object</span>'s
<span data-x="concept-settings-object-global">global object</span> and <var>script</var>.
</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No whitespace after .

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the output of https://domenic.github.io/rewrapper/, and I'm not sure how to avoid it. Should <var>script</var>. all wrap to the next line? Not wrapping at all puts this over the character limit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should <var>script</var>. all wrap to the next line?

Yep, that's the idea. If you collapse away the whitespace all into one line, that's what the rewrapper outputs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

source Outdated
<li><p><span>Report an exception</span> given by <var>evaluationStatus</var>.[[Value]] for
<var>script</var>'s <span data-x="concept-script-settings-object">settings object</span>'s
<span data-x="concept-settings-object-global">global object</span> and <var>script</var>.
</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that whenever you do have a script, you'll almost certainly want to use its global object. Maybe we should make the global object optional, and assert that either the global object or the script are provided?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether that's true, mostly because I'm not entirely sure where these script objects are being summoned from.

may be a JavaScript value or an <span>implementation-defined</span> value, for a particular
<span>global object</span> <var>global</var>, optional <span
data-x="concept-script">script</span> <var>script</var>, and optional boolean
<var>exposeError</var> (default true), it must run these steps:</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use named parameter style for exposeError (and maybe the other two, especially if we go the route of making them both optional). You can see some examples of how to do that in HTML markup by searching for the following fragment:

  <p>To <dfn>fire a traverse <code data-x="event-navigate">navigate</code> event</dfn> at a
  <code>Navigation</code> <var>navigation</var> given a <span>session history entry</span> <dfn
  data-x="fire-navigate-traverse-destinationSHE"><var>destinationSHE</var></dfn> and an optional
  <span>user navigation involvement</span> <dfn
  data-x="fire-navigate-traverse-userInvolvement"><var>userInvolvement</var></dfn> (default "<code
  data-x="uni-none">none</code>"):</p>

and its call sites

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done for exposeError (now redactError).

source Outdated
<span>associated realm</span>'s <span data-x="concept-realm-global">global object</span>.</p>

<p w-nodev class="note">This global object not necessarily the same as <span>this</span>, if
<var>callback</var> comes from another <span>realm</span>.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you determine which choice of global to use here? If you're sure you're right, then can we have a tracking issue to write web platform tests to confirm? If you're not sure, we might want to leave this as an XXX box.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lovely test page https://excited-jealous-saturn.glitch.me/ (and a few more pages under that glitch for other cases, like finalization registries and import maps).

Copy link
Collaborator Author

@jeremyroman jeremyroman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

responded to most comments; will address editorial comments later

@@ -93696,7 +93709,7 @@ const p2 = navigation.navigate(url2).finished;</code></pre>
data-x="dom-ErrorEvent-lineno">lineno</code>, and <code
data-x="dom-ErrorEvent-colno">colno</code> initialized to <span class="XXX">appropriate values
that can be extracted from <var>error</var> and the current JavaScript stack in the same
underspecified way that the <span>report the exception</span> algorithm does</span>.</p>
underspecified way that the <span>report an exception</span> algorithm does</span>.</p>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, though as long as it's a total handwave is it helpful to do so? I'm kinda loathe to try to figure out what the inputs to the handwave are, which making an algorithm, even one that just says "implementation-defined 🤷", would seem to require.

@@ -105462,6 +105475,9 @@ new PaymentRequest(&hellip;); // Allowed to use
object">the environment settings object's global object</dfn>.</p></li>
</ul>

<p>A <span>global object</span> has an <dfn for="global object">in error reporting mode</dfn>
boolean, which is initially false.</p>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It felt more natural to have it here because you'd suggested the algorithm take the global object, not the environment settings object. But I don't feel strongly at all, so happy to do whatever makes editors happy.

source Outdated
<li><p><span>Report an exception</span> given by <var>evaluationStatus</var>.[[Value]] for
<var>script</var>'s <span data-x="concept-script-settings-object">settings object</span>'s
<span data-x="concept-settings-object-global">global object</span> and <var>script</var>.
</p></li>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the output of https://domenic.github.io/rewrapper/, and I'm not sure how to avoid it. Should <var>script</var>. all wrap to the next line? Not wrapping at all puts this over the character limit.

exception">report</span> it for the associated <span
data-x="concept-script">script</span>'s <span
data-x="concept-script-settings-object">settings object</span>'s <span
data-x="concept-settings-object-global">global object</span> and the associated <span
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was my instinct too, but I couldn't convince myself this was always equivalent (depending on what "the associated script", a term not defined, is). For example, the reaction callback could be from another realm, and that realm's global object might be what's used.

I checked several other sites to see what's used in practice, but I had not gotten to this one yet. I now have in Chrome and Firefox at least (Safari I'd have to go home or otherwise find a Mac, I think) -- they both seem to report, in practice, in the callback's realm, for callback reactions at least (see this test page), which is not element's relevant global object.

For upgrade reactions (test page), Chrome and Firefox diverge (and again, haven't checked Safari):

  • if an exception is thrown during the ctor, Chrome fires the ctor's global's onerror but Firefox runs it on the script that we entered (the root, in the test page)
  • the same is true of exceptions the spec requires the UA to throw (e.g., for returning an object other than the element being constructed from the ctor)

In no case is it necessarily element's relevant global object, at least as currently implemented in those browsers. Of course, probably nobody relies on this extreme fringe case in the field.

source Outdated
<li><p><span>Report an exception</span> given by <var>evaluationStatus</var>.[[Value]] for
<var>script</var>'s <span data-x="concept-script-settings-object">settings object</span>'s
<span data-x="concept-settings-object-global">global object</span> and <var>script</var>.
</p></li>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether that's true, mostly because I'm not entirely sure where these script objects are being summoned from.

should be added.</p>

<p>When the user agent is required to <dfn>report an exception</dfn> <var>exception</var>, which
may be a JavaScript value or an <span>implementation-defined</span> value, for a particular
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cases are:

  1. non-parseable event handler strings (we just check whether they can be parsed, without actually throwing a real exception)
  2. multiple constraint validation failures (above, which is quite hand-wavy generally and I haven't yet tried to rig up a test page to see what browsers do in practice)

source Outdated
<span>associated realm</span>'s <span data-x="concept-realm-global">global object</span>.</p>

<p w-nodev class="note">This global object not necessarily the same as <span>this</span>, if
<var>callback</var> comes from another <span>realm</span>.</p>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lovely test page https://excited-jealous-saturn.glitch.me/ (and a few more pages under that glitch for other cases, like finalization registries and import maps).

Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this. Great to see some progress on this issue. I wanted to note that "report the exception" has many callers that we cannot break: https://dontcallmedom.github.io/webdex/r.html#report%20the%20exception%40%40html%25%25dfn

Continuing to export the term is probably the bare minimum we need to do. Follow-up issues/downstream PRs might also be needed?

source Outdated
Comment on lines 111663 to 111664
for <var>settings object</var>'s <span data-x="concept-settings-object-global">global object
</span> and the appropriate <span data-x="concept-script">script</span>. The value reported
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

</span> cannot have preceding whitespace.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

<span>report the exception</span>.</p></li>
<span data-x="report an exception">report</span> it for <var>handler</var>'s
corresponding JavaScript object's <span>associated realm</span>'s
<span data-x="concept-realm-global">global object</span>.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I would expect <span on the previous line.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@jeremyroman
Copy link
Collaborator Author

Thank you for working on this. Great to see some progress on this issue. I wanted to note that "report the exception" has many callers that we cannot break: https://dontcallmedom.github.io/webdex/r.html#report%20the%20exception%40%40html%25%25dfn

Continuing to export the term is probably the bare minimum we need to do. Follow-up issues/downstream PRs might also be needed?

I agree. Tracking followup PRs in the original issue makes sense to me once this PR is in a shape you are happy with. I had the task of doing that as an XXX in the original PR because I wasn't sure what the right wattsi syntax is. I'm still not sure I got it right, but <span id="..."></span> is at least used in one other place. I guess the alternative would be to have explicit visible <dfn>s for both of them in a paragraph noting that other specs should migrate.

@annevk
Copy link
Member

annevk commented Jul 8, 2024

Maintaining incoming ID references is no longer enough now HTML participates in Bikeshed's cross-referencing system. Once you drop a <dfn export> you break downstream that relies on it.

@domenic
Copy link
Member

domenic commented Jul 8, 2024

My plan was to break all of those so that they would notice and update to the new algorithm, which will have new inputs and possibly be used quite differently (e.g. if we do the automatic handling of Web IDL callbacks). That feels a bit better than having them call the new algorithm with the wrong arguments silently...

@annevk
Copy link
Member

annevk commented Jul 8, 2024

Hmm. It's usually pretty frustrating when a build is broken, especially when fixing it is tricky, but also just because it might block landing other PRs. (Although I understand that outside the WHATWG folks typically don't have fatal builds, that's more of a problem worth fixing than something we should rely on I think.)

@jeremyroman
Copy link
Collaborator Author

The alternative might be to expose "stub" dfns, rewrite the uses indexed by webdex, and then convert them into mere anchors?

@annevk
Copy link
Member

annevk commented Jul 9, 2024

Right yeah, I'm not opposed to that at all. I'd just like to avoid build time errors for people trying to fix something else in their specification unrelated to this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants