Skip to content

Commit

Permalink
Allow DOMException subclasses to be used as exceptions
Browse files Browse the repository at this point in the history
Also be clearer about how exception names are meant to be used, at least for now (pending discussion in #1219).

See discussion in #1168 (comment). Closes #1223.

This also includes some updates to all exception creation and throwing, such as reflecting how messages are actually passed along in practice, or using Web IDL infrastructure to create DOMException instances instead of Construct()ing them.
  • Loading branch information
domenic committed Oct 20, 2022
1 parent 7e9e8e4 commit 3145082
Showing 1 changed file with 228 additions and 89 deletions.
317 changes: 228 additions & 89 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -5069,96 +5069,125 @@ No [=extended attributes=] are applicable to dictionaries.

<h3 id="idl-exceptions">Exceptions</h3>

An <dfn id="dfn-exception" export>exception</dfn> is a type of object that
represents an error and which can be thrown or treated as a first
class value by implementations. Web IDL does not allow exceptions
to be defined, but instead has a number of pre-defined exceptions
that specifications can reference and throw in their definition of
operations, attributes, and so on. Exceptions have an
<dfn id="dfn-exception-error-name" for="exception" export>error name</dfn>,
a {{DOMString}},
which is the type of error the exception represents, and a
<dfn id="dfn-exception-message" for="exception" export>message</dfn>, which is an optional,
user agent-defined value that provides human readable details of the error.

There are two kinds of exceptions available to be thrown from specifications.
The first is a <dfn id="dfn-simple-exception" export>simple exception</dfn>, which
is identified by one of the following types:
An <dfn id="dfn-exception" export>exception</dfn> is a type of object that represents an error and
which can be thrown or treated as a first class value by implementations. Web IDL has a number of
pre-defined exceptions that specifications can reference and throw in their definition of
operations, attributes, and so on. Custom exception types can also be defined, as [=interfaces=]
that [=interface/inherit=] from {{DOMException}}.

In addition to their type, which is either one of the [=simple exceptions=], {{DOMException}}, or a
derived interface of {{DOMException}}, all exceptions have a
<dfn id="dfn-exception-message" for="exception" export>message</dfn>, which is an
[=implementation-defined=] [=string=] that provides human readable details of the error.
{{DOMException}} instances also have an
<dfn id="dfn-exception-error-name" for="exception" export>error name</dfn> [=string=], which
provides a general category for the exception. {{DOMException}} derived interfaces will
have other properties, as defined in their specifications.

A <dfn id="dfn-simple-exception" export>simple exception</dfn> is identified by one of the
following types:

* <dfn exception>EvalError</dfn>
* <dfn exception>RangeError</dfn>
* <dfn exception>ReferenceError</dfn>
* <dfn exception>TypeError</dfn>
* <dfn exception>URIError</dfn>

These correspond to all of the ECMAScript [=ECMAScript/error objects=]
(apart from {{ECMAScript/SyntaxError}} and {{ECMAScript/Error}},
which are deliberately omitted as they are reserved for use
by the ECMAScript parser and by authors, respectively).
The meaning of each [=simple exception=] matches
its corresponding error object in the
ECMAScript specification.

The second kind of exception is a {{DOMException}},
which is an exception that encapsulates a name and an optional integer code,
for compatibility with historically defined exceptions in the DOM.

For [=simple exceptions=], the [=error name=] is the type of the exception.
For a {{DOMException}}, the [=error name=] must be one of the names
listed in the [=error names table=] below.
The table also indicates the {{DOMException}}'s integer code for that error name,
if it has one.

Note: As {{DOMException}} is an [=interface type=], it can be used as a type in IDL.
This allows for example an [=operation=]
to be declared to have a {{DOMException}}
[=return type=].

[=Simple exceptions=] can be <dfn id="dfn-create-exception" for="exception" export>created</dfn>
by providing their [=error name=].
A {{DOMException}} can be [=created=]
by providing its [=error name=] followed by {{DOMException}}.
Exceptions can also be <dfn id="dfn-throw" for="exception" export lt="throw">thrown</dfn>, by providing the
same details required to [=created|create=] one.
These correspond to all of the ECMAScript [=ECMAScript/error objects=] (apart from
{{ECMAScript/SyntaxError}} and {{ECMAScript/Error}}, which are deliberately omitted as they are
reserved for use by the ECMAScript parser and by authors, respectively). The meaning of each
[=simple exception=] matches its corresponding error object in the ECMAScript specification.

The resulting behavior from creating and throwing an exception is language binding-specific.
The second kind of exception is a {{DOMException}}, which provides further
programmatically-introspectable detail on the error that occurred by giving an [=exception/error
name=]. Such [=exception/error names=] are drawn from the [=error names table=] below.

<p class=note>As {{DOMException}} is an [=interface type=], it can be used as a type in IDL. This
allows for example an [=operation=] to be declared to have a {{DOMException}} [=return type=]. This
is generally a bad pattern, however, as exceptions are meant to be thrown and not returned.

Note: See [[#es-creating-throwing-exceptions]] for details on what creating and throwing an exception
entails in the ECMAScript language binding.
The final kind of exception is a derived interface of {{DOMException}}. These are more complicated,
and thus described in the dedicated section [[#idl-DOMException-derived-interfaces]].

<div class="example" id="example-56ad390c">
[=Simple exceptions=] can be
<dfn id="dfn-create-exception" for="exception" export lt="create">created</dfn> by providing their
type name. A {{DOMException}} can be [=exception/created=] by providing its [=error name=] followed
by {{DOMException}}. Exceptions can also be
<dfn id="dfn-throw" for="exception" export lt="throw">thrown</dfn>, by providing the same details
required to [=exception/create=] one. In both cases, the caller may provide additional information
about what the exception indicates, which is useful when constructing the exception's
[=exception/message=].

Here is are some examples of wording to use to create and throw exceptions.
To throw a new [=simple exception=] named {{TypeError}}:
<div class="example" id="example-exception-creation-and-throwing">
<p>Here is are some examples of wording to use to create and throw exceptions.
To throw a new [=simple exception=] whose type is {{TypeError}}:

<blockquote>
[=exception/Throw=] a {{TypeError}}.
<p>[=exception/Throw=] a {{TypeError}}.
</blockquote>

To throw a new {{DOMException}} with [=error name=] "{{NotAllowedError!!exception}}":
<p>To throw a new {{DOMException}} with [=error name=] "{{NotAllowedError}}":

<blockquote>
[=exception/Throw=] a "{{NotAllowedError!!exception}}" {{DOMException}}.
<p>[=exception/Throw=] an "{{NotAllowedError}}" {{DOMException}}.
</blockquote>

To create a new {{DOMException}} with [=error name=] "{{SyntaxError!!exception}}":
<p>To create a new {{DOMException}} with [=error name=] "{{SyntaxError}}":

<blockquote>
Let <var ignore>object</var> be a newly [=created=] "{{SyntaxError!!exception}}" {{DOMException}}.
<p>Let <var ignore>object</var> be a newly [=exception/created=] "{{SyntaxError}}"
{{DOMException}}.
</blockquote>

<p>To [=reject=] a promise with a new {{DOMException}} with [=error name=] "{{OperationError}}":

<blockquote>
[=Reject=] <var ignore>p</var> with an "{{OperationError}}" {{DOMException}}.
</blockquote>
</div>

<div class=example id=example-exception-throwing-message-hint>
<p>An example of including additional information used to construct the [=exception/message=]
would be:

<h4 id="idl-DOMException-error-names">Error names</h4>
<blockquote>
[=exception/Throw=] a "{{SyntaxError}}" {{DOMException}} indicating that the given value
had disallowed trailing spaces.
</blockquote>

The <dfn id="dfn-error-names-table" export>error names table</dfn> below lists all the allowed error names
for {{DOMException}}, a description, and legacy code values.
<p>Such additional context is most helpful to implementers when it is not immediately obvious
why the exception is being thrown, e.g., because there are many different steps in the algorithm
which throw a "{{SyntaxError}}" {{DOMException}}. In contrast, if your specification throws a
"{{NotAllowedError}}" {{DOMException}} immediately after checking if the user has provided
permission to use a given feature, it's fairly obvious what sort of [=exception/message=] the
implementation should construct, and so specifying it is not necessary.
</div>

<p class="warning">
The {{DOMException}} names marked as deprecated are kept for legacy purposes but their usage is discouraged.
</p>
The resulting behavior from creating and throwing an exception is language binding-specific.

<p class=note>See [[#es-creating-throwing-exceptions]] for details on what creating and throwing an
exception entails in the ECMAScript language binding.


<h4 id="idl-DOMException-error-names">Base {{DOMException}} error names</h4>

The <dfn id="dfn-error-names-table" export>error names table</dfn> below lists all the allowed error
names for instances of the base {{DOMException}} interface, along with a description of what such
error names mean, and legacy numeric error code values.

<p class="note">Interfaces [=interface/inheriting=] from {{DOMException}}, in the manner described
in [[#idl-DOMException-derived-interfaces]], will have their own names, not listed in this table.

Note: If an error name is not listed here, please file a bug as indicated at the top of this specification and it will be addressed shortly. Thanks!
When [=exception/creating=] or [=exception/throwing=] a {{DOMException}}, specifications must use
one of these names. If a specification author believes none of these names are a good fit for their
case, they must
<a href="https://github.com/heycam/webidl/issues/new?title=DOMException%20name%20proposal">file an issue</a>
to discuss adding a new name to the shared namespace, so that the community can coordinate such
efforts. Note that adding new use-case-specific names is only important if you believe web
developers will discriminate multiple error conditions arising from a single API.

<p class="warning">The {{DOMException}} names marked as deprecated are kept for legacy purposes, but
their usage is discouraged.

Note: Don't confuse the "{{SyntaxError!!exception}}" {{DOMException}} defined here
with ECMAScript's {{ECMAScript/SyntaxError}}.
Expand Down Expand Up @@ -5342,6 +5371,94 @@ over just using {{SyntaxError!!exception}} to refer to the {{DOMException}}. [[D
</tbody>
</table>

<h4 id="idl-DOMException-derived-interfaces">{{DOMException}} derived interfaces</h4>

When an exception needs to carry additional programmatically-introspectable information, beyond just
its [=error name=], specification authors can create an [=interface=] which [=interface/inherits=]
from {{DOMException}}. Such interfaces need to follow certain rules, in order to have a predictable
shape for developers. Specifically:

* The [=identifier=] of the [=interface=] must end with <code>Error</code>, and must not be any
of the names in the [=error names table=].
* The [=interface=] must have a [=constructor operation=] which sets the instance's
[=DOMException/name=] to the interface's [=identifier=].
* Their [=constructor operation=] must take as its first parameter an [=optional=] {{DOMString}}
named |message| defaulting to the empty string, and must set the instance's
[=DOMException/message=] to |message|.
* Their [=constructor operation=] should take as its second parameter a [=dictionary=] containing
the additional information that needs to be exposed.
* They should have [=read only=] [=attributes=], whose names are the same as the members of the
constructor dictionary, which return the values accepted by the constructor operation.
* They should be [=serializable objects=], whose [=serialization steps=] and
[=deserialization steps=] preserve the additional information.

<p class=note>These requirements mean that the inherited {{DOMException/code}} property of these
interfaces will always return 0.

<div class=example id=example-domexception-derived-interface>
The definition for a {{DOMException}} derived interface which carries along an additional
"hardware error code", for interfacing with a hypothetical Widget hardware device, could look
something like this:

<pre highlight=webidl>
[Exposed=Window]
interface WidgetError : DOMException {
constructor(optional DOMString message = "", WidgetErrorOptions options);

readonly attribute unsigned long long hardwareErrorCode;
};

dictionary WidgetErrorOptions {
required [EnforceRange] unsigned long long hardwareErrorCode;
};
</pre>

Every <code>WidgetError</code> instance has a <dfn for="WidgetError">hardware error code</dfn>,
a number.

<div algorithm="WidgetError constructor">
The <b><code>new WidgetError(|message|, |options|)</code></b> constructor steps are:

1. Set [=this=]'s [=DOMException/name=] to "<code>WidgetError</code>".
1. Set [=this=]'s [=DOMException/message=] to |message|.
1. Set [=this=]'s [=WidgetError/hardware error code=] to
|options|["<code>hardwareErrorCode</code>"].
</div>

<div algorithm="WigetError hardwareErrorCode">
The <b><code>hardwareErrorCode</code></b> getter steps are to return [=this=]'s
[=WidgetError/hardware error code=].
</div>

<code>WidgetError</code> objects are [=serializable objects=].

<div algorithm="WidgetError serialization steps">
Their [=serialization steps=], given |value| and |serialized|, are:

1. Run the {{DOMException}} [=serialization steps=] given |value| and |serialized|.
1. Set |serialized|.\[[HardwareErrorCode]] to |value|'s [=WidgetError/hardware error code=].
</div>

<div algorithm="WidgetError deserialization steps">
Their [=deserialization steps=], given |value| and |serialized|, are:

1. Run the {{DOMException}} [=deserialization steps=] given |value| and |serialized|.
1. Set |value|'s [=WidgetError/hardware error code=] to |serialized|.\[[HardwareErrorCode]].
</div>
</div>

To [=exception/create=] or [=exception/throw=] a {{DOMException}} derived interface, supply its
[=interface=] [=identifier=] as well as the additional information needed to construct it.

<div class=example id=example-domexception-derived-throwing>
<p>To throw an instance of the <code>WidgetError</code> exemplified
<a href=#example-domexception-derived-interface>above</a>:

<blockquote>
<p>[=exception/Throw=] a <code>WidgetError</code> whose [=WidgetError/hardware error code=]
is 42.
</blockquote>
</div>

<h3 id="idl-enums">Enumerations</h3>

Expand Down Expand Up @@ -14367,39 +14484,61 @@ A {{DOMException}} is represented by a
<h4 id="es-creating-throwing-exceptions">Creating and throwing exceptions</h4>


<div algorithm="to create a simple exception or DOMException">
<div algorithm="to create a simple exception">
To create a [=simple exception=] of type |T|:

To create a [=simple exception=] or {{DOMException}} |E|, with a string giving the
[=error name=] |N| for the {{DOMException}} case and optionally a string giving a user
agent-defined message |M|:
1. Let |message| be an [=implementation-defined=] message appropriate for the exceptional
situation. The calling specification may contain information to to help implementations
construct this message.

1. If |M| was not specified, let |M| be <emu-val>undefined</emu-val>.
1. Let |args| be a list of ECMAScript values determined based on the type of |E|:
<dl class="switch">
: |E| is {{DOMException}}
:: |args| is «|M|, |N|».
: |E| is a [=simple exception=]
:: |args| is «|M|».
</dl>
1. Let |X| be an object determined based on the type of |E|:
<dl class="switch">
: |E| is {{DOMException}}
:: |X| is the {{DOMException}} [=interface object=]
from the [=current realm=].
: |E| is a [=simple exception=]
:: |X| is the [=constructor=] for the corresponding ECMAScript error
from the [=current realm=].
</dl>
1. Return [=!=] <a abstract-op>Construct</a>(|X|, |args|).
Implementations need to be cautious not to leak sensitive or secured information when
constructing this message, e.g., by including the [=Document/URL=] of a cross-origin frame,
or information which could identify the user.

1. Let |args| be « |message| ».

1. Let |constructor| be [=current realm=].\[[Intrinsics]].[[%|T|%]].

1. Return [=!=] [$Construct$](|constructor|, |args|).
</div>

<div algorithm="throw an exception">
<div algorithm="to create a DOMException">
To create a {{DOMException}} given [=error name=] |name|:

1. Let |ex| be a [=new=] {{DOMException}} created in the [=current realm=].

1. Set |ex|'s [=DOMException/name=] to |name|.

1. Set |ex|'s [=DOMException/message=] to an [=implementation-defined=] message appropriate for
the exceptional situation. The calling specification may contain information to to help
implementations construct this message.

1. Return |ex|.
</div>

To [=exception/throw=] a [=simple exception=] or {{DOMException}}, with a string giving the
[=error name=] for the {{DOMException}} case and optionally a string giving a user
agent-defined message:
<div algorithm="to create a DOMException derived interface">
To create a {{DOMException}} derived interface given the [=interface=] [=identifier=] |type|
and additional initialization instructions:

1. Let |ex| be a [=new=] instance of the [=interface=] identified by |type|, created in the
[=current realm=].

1. Set |ex|'s [=DOMException/name=] to |type|.

1. Set |ex|'s [=DOMException/message=] to an [=implementation-defined=] message appropriate for
the exceptional situation. The calling specification may contain information to to help
implementations construct this message.

1. Initialize |ex|'s additionally as described by the caller.

1. Return |ex|.
</div>

<div algorithm="throw an exception">
To [=exception/throw=] an [=exception=]:

1. Let |O| be the result of [=created|creating an exception=] with the same arguments.
1. Let |O| be the result of [=exception/create|creating an exception=] with the same
arguments.
1. Throw |O|.
</div>

Expand Down

0 comments on commit 3145082

Please sign in to comment.