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

Handle exceptions when calling callbacks by default #1423

Closed
domenic opened this issue Jul 25, 2024 · 6 comments · Fixed by #1424
Closed

Handle exceptions when calling callbacks by default #1423

domenic opened this issue Jul 25, 2024 · 6 comments · Fixed by #1424

Comments

@domenic
Copy link
Member

domenic commented Jul 25, 2024

What is the issue with the Web IDL Standard?

Splitting from whatwg/html#958 (comment).

After whatwg/html#10404 lands, HTML will have a solid foundation for reporting exceptions.

It turns out that most cases, when spec writers use Web IDL to construct or invoke callback functions, or to call a user object's operation, they want errors to be reported to the global error handler; they don't want them to propagate out. Such reporting is a safer default.

We should add a new optional named parameter rethrowExceptions (default false) to these definitions. When it is false, we introduce the following new behavior: any exceptions thrown by web developer code get caught and routed to HTML's new "report the exception". ("Web developer code" is roughly everything between the "prepare" and "cleanup" steps, including both argument conversion and the actual code invocation.)

Which global do we propagate to? @jeremyroman's research in whatwg/html#10404 settled on the JavaScript function object's realm's global object.


Before merging this change, however, we need to have PRs ready for all call sites of construct/invoke/call a user object's operation, to prepare them for the new default. In most cases this will be deleting some spec code that try/catches the exception and reports it. So, this will be a multi-spec project.

We can use these links to find all callers:

domenic pushed a commit to whatwg/html that referenced this issue Jul 30, 2024
This carries out the first portion of the plan in #958 (comment), by creating a new "report an exception" algorithm that replaces the previous "report an error" and "report the exception".

The new algorithm directly includes the error propagation and fallback behavior, and requires callers to supply the global object to be used, rather than magically inferring it. It no longer takes a script, since in most cases the script was not rigorously determined. Follow-up work on determining the correct muting behavior (which the script argument was used for) is tracked in #10514.

All call sites within HTML are updated. #10516 tracks updating call sites in other specifications. In most cases the global used for HTML is now specified rigorously and matching implementations. #10526 and #10527 track the remaining cases with interop issues.

Closes #958, with the rest of the plan there tracked via the issues mentioned above, whatwg/webidl#1423, and the https://github.com/whatwg/html/labels/topic%3A%20error%20reporting label.
@jeremyroman
Copy link
Contributor

jeremyroman commented Jul 30, 2024

A survey of the current uses of these algorithms indexed by webdex. Note that promise-returning callback types cannot actually throw, because WebIDL turns this into a promise rejection internally.

algorithm spec topic catches and reports rethrows is promise other notes
invoke DOM mutation observers
invoke HTML canvas toBlob should report (doesn't currently)
invoke HTML custom element reactions
invoke HTML navigate event
invoke HTML event handler attribute
invoke HTML timers
invoke HTML microtask queuing
invoke HTML animation frames
invoke HTML worklet registration example probably intended to rethrow but unclear
invoke Notifications request
invoke Streams
invoke Files and Directories unclear, but likely should report (assuming browsers do)
invoke Prioritized Task Scheduling postTask catches and rejects a promise; might be able to change to use a Promise<any> return type instead of any to have WebIDL handle this
invoke Shared Storage select-url result index catches and rejects a promise (in a task)
invoke Shared Storage run() not entirely certain this is true; the callback type is oddly abstract
invoke Video rVFC
invoke Web Smart Card
invoke CSS Layout API intrinsic sizes unclear, but likely should report
invoke CSS Layout API layout callback unclear, but likely should report
invoke CSS Paint API unclear
invoke CSS View Transitions update
invoke Geolocation acquire unclear, but likely should report
invoke Reporting API
invoke Web Locks
invoke WebXR XR animation frame
call a user object's operation DOM handleEvent reports but also sets a special flag for IndexedDB
construct DOM sync create custom element reports but also does other things
construct HTML upgrade custom element reports but also does other things
construct SharedStorage register unclear but should probably rethrow
construct Web Audio instantiate processor special handling to fire processorerror

Excluded from this list are references which do not invoke these algorithms (but mention or modify it).

The "catch and report" cases naturally don't specify the global (except HTML, where this was part of whatwg/html#10404). It's likely that the callback's realm's global is appropriate, though for each a cursory check of existing browser behavior would be nice.

WebIDL also doesn't have many normative references to HTML. This wouldn't be the first, though.

Do you still think this should be a boolean defaulting to reporting, or should this be a required parameter? Or maybe some kind of conditionally required parameter, where it must be specified unless it's a promise-returning callback?

@domenic
Copy link
Member Author

domenic commented Jul 31, 2024

Wow, amazing work; thank you.

canvas toBlob

This needs to catch and report, I think? Right now the error just goes to the top level of the task, which is... undefined behavior?

It's likely that the callback's realm's global is appropriate, though for each a cursory check of existing browser behavior would be nice.

I'd really like us to be able to use the same answer for all cases and not have this be configurable per call site. But yeah, agreed that a check would be good.

Do you still think this should be a boolean defaulting to reporting, or should this be a required parameter? Or maybe some kind of conditionally required parameter, where it must be specified unless it's a promise-returning callback?

I hadn't thought a conditionally-required parameter before. Maybe that's the way to go, if we're changing all the call sites anyway.

Reporting-by-default makes "invoke" and friends require fewer brains to use; if you told me there was a Web IDL algorithm called "invoke" for callbacks I might just call it without thinking to look up if it had extra arguments. But if we can assume that all spec writers look up the signatures of the algorithms they use, then probably it's better. And I think that's a ... reasonable? ... assumption.

In that case I'd change from a boolean to something like "report" vs. "rethrow".

I was wondering if we could do "report-and-rethrow" for the cases that report but also do custom behavior. But I feel like probably in those cases you want to do the custom behavior first, before potentially running author code? (Although at least one place does not do that right now... I wonder if that's a bug.) I don't know; if there were a way to make it easier to ensure that people always send it to the right global for a callback after doing their custom processing, that would be nice. Let me know if you have an yideas.

For how to do "conditionally-required", I'd do something like

To invoke a callback function type value callable with a Web IDL arguments list args, exception behavior exceptionBehavior, and an optional callback this value thisArg, perform the following steps. These steps will either return an IDL value or throw an exception. The exceptionBehavior argument must be supplied if and only if callable's return type is not a promise type.

The other strategy I thought of was declaring it as optional with a default of "promise", and then using an Assert to state that it's not left as the default for non-promise _callable_s. However, this has the disadvantage that someone skimming the algorithm declaration might think it was actually optional.

@jeremyroman
Copy link
Contributor

canvas toBlob

This needs to catch and report, I think? Right now the error just goes to the top level of the task, which is... undefined behavior?

Agreed, it should probably report. Updated the table.

It's likely that the callback's realm's global is appropriate, though for each a cursory check of existing browser behavior would be nice.

I'd really like us to be able to use the same answer for all cases and not have this be configurable per call site. But yeah, agreed that a check would be good.

Agreed, though if that is not what browsers do then we have to get vendors to change it, assuming doing so would be web-compatible. I'm hoping that it's either already doing that everywhere or it's easy to fix.

Do you still think this should be a boolean defaulting to reporting, or should this be a required parameter? Or maybe some kind of conditionally required parameter, where it must be specified unless it's a promise-returning callback?

I hadn't thought a conditionally-required parameter before. Maybe that's the way to go, if we're changing all the call sites anyway.

Reporting-by-default makes "invoke" and friends require fewer brains to use; if you told me there was a Web IDL algorithm called "invoke" for callbacks I might just call it without thinking to look up if it had extra arguments. But if we can assume that all spec writers look up the signatures of the algorithms they use, then probably it's better. And I think that's a ... reasonable? ... assumption.

In that case I'd change from a boolean to something like "report" vs. "rethrow".

I was wondering if we could do "report-and-rethrow" for the cases that report but also do custom behavior. But I feel like probably in those cases you want to do the custom behavior first, before potentially running author code? (Although at least one place does not do that right now... I wonder if that's a bug.) I don't know; if there were a way to make it easier to ensure that people always send it to the right global for a callback after doing their custom processing, that would be nice. Let me know if you have an yideas.

There are few enough sites and they're special enough already that I think complicating WebIDL to handle them is probably not worthwhile. And hopefully the number of such sites doesn't grow too much.

For how to do "conditionally-required", I'd do something like

To invoke a callback function type value callable with a Web IDL arguments list args, exception behavior exceptionBehavior, and an optional callback this value thisArg, perform the following steps. These steps will either return an IDL value or throw an exception. The exceptionBehavior argument must be supplied if and only if callable's return type is not a promise type.

The other strategy I thought of was declaring it as optional with a default of "promise", and then using an Assert to state that it's not left as the default for non-promise _callable_s. However, this has the disadvantage that someone skimming the algorithm declaration might think it was actually optional.

I had those exact same two thoughts and typed both of them in a response before noticing you'd described exactly the same things.

(also, bikeshed: exceptionBehavior vs exceptionHandling?)

@jeremyroman
Copy link
Contributor

jeremyroman commented Jul 31, 2024

Another quick question -- in the report case, what happens if the return type is not undefined? I'm inclined to assert that never happens, but we could instead try to convert undefined to whatever IDL type is expected, which might be better since that's what we do if IsCallable(F) is false, in this same algorithm.

Edit: Converting undefined to a type can itself throw, and that isn't handled above. Fun.

@jeremyroman
Copy link
Contributor

Also, since all call sites of construct and call a user object's operation do something special, I'm leaving them alone for now. Construct, in particular, can't just report and return nothing because callers are definitely going to be expecting a result.

jeremyroman added a commit to jeremyroman/webidl that referenced this issue Jul 31, 2024
This makes it more concise for users to, as is frequently desired for
undefined-returning callbacks especially, immediately catch and report
the exception rather than needing to handle it themselves.

This isn't appropriate for other types of callback functions, where a
result may be expected or the exception needs to be rethrown. Callers
need to explicitly decide which behavior they want, unless the callback
returns a promise type, in which case exceptions are turned into
rejected promises implicitly.

Fixes whatwg#1423.
@domenic domenic closed this as completed in 192be41 Aug 2, 2024
@domenic
Copy link
Member Author

domenic commented Aug 2, 2024

(Agreed on all the decisions you made regarding return types, construct, and call a user object's operation.)

@jeremyroman are you up for turning your table in #1423 (comment) into a checklist, now that we know which cases need mandatory updates? Probably create a new tracking issue in this repo.

JinDX added a commit to JinDX/htmlspecs.com that referenced this issue Aug 4, 2024
…2aff51d86


全面改革异常报告算法

此更改执行了 #958(评论)中计划的第一部分,创建了一个新的“报告异常”算法,取代了之前的“报告错误”和“报告异常”。

新算法直接包括了错误传播和回退行为,并要求调用者提供要使用的全局对象,而不是通过某种方式自动推断。它不再接受脚本参数,因为在大多数情况下,脚本并未被严格确定。后续工作将着重于确定正确的静音行为(脚本参数曾用于此),并在 #10514 中进行跟踪。

HTML 中的所有调用点都已更新。#10516 用于跟踪在其他规范中更新调用点。在大多数情况下,HTML 使用的全局对象现在已被严格指定,并与实现相匹配。#10526 和 #10527 用于跟踪具有互操作性问题的剩余情况。

#958 已完成,剩余计划将通过上述提到的问题、whatwg/webidl#1423 和 [https://github.com/whatwg/html/labels/topic%3A%20error%20reporting](https://github.com/whatwg/html/labels/topic%3A%20error%20reporting) 标签进行跟踪。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

2 participants