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

Abort controller #437

Merged
merged 1 commit into from
Jul 14, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 241 additions & 19 deletions dom.bs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ No Editor: true
!Tests: <a href=https://github.com/w3c/web-platform-tests/tree/master/dom>web-platform-tests dom/</a> (<a href=https://github.com/w3c/web-platform-tests/labels/dom>ongoing work</a>)
!Translation (non-normative): <span title=Japanese><a href=https://triple-underscore.github.io/DOM4-ja.html lang=ja hreflang=ja rel=alternate>日本語</a></span>
Logo: https://resources.whatwg.org/logo-dom.svg
Abstract: DOM defines a platform-neutral model for events and node trees.
Abstract: DOM defines a platform-neutral model for events, aborting activities, and node trees.
Ignored Terms: EmptyString, Array, Document
Boilerplate: omit feedback-header, omit conformance
</pre>
Expand Down Expand Up @@ -557,7 +557,7 @@ algorithm below.
the operation that caused <var>event</var> to be <a>dispatched</a> that it needs to be canceled.

<dt><code><var>event</var> . {{Event/defaultPrevented}}</code>
<dd>Returns true if {{Event/preventDefault()}} was invoked successfully to indicate cancellation,
<dd>Returns true if {{Event/preventDefault()}} was invoked successfully to indicate cancelation,
and false otherwise.

<dt><code><var>event</var> . {{Event/composed}}</code>
Expand Down Expand Up @@ -1413,6 +1413,228 @@ can only be used to influence an ongoing one.



<h2 id=aborting-ongoing-activities>Aborting ongoing activities</h3>

<p>Though promises do not have a built-in aborting mechanism, many APIs using them require abort
semantics. {{AbortController}} is meant to support these requirements by providing an
{{AbortController/abort()}} method that toggles the state of a corresponding {{AbortSignal}} object.
The API which wishes to support aborting can accept an {{AbortSignal}} object, and use its state to
determine how to proceed.

<p>APIs that rely upon {{AbortController}} are encouraged to respond to {{AbortController/abort()}}
by rejecting any unsettled promise with a new {{DOMException}} with [=error name=] "{{AbortError}}".

<div class=example id=aborting-ongoing-activities-example>
<p>A hypothetical <code>doAmazingness({ ... })</code> method could accept an {{AbortSignal}} object
in order to support aborting as follows:

<pre><code class=lang-javascript>
const controller = new AbortController();
const signal = controller.signal;

startSpinner();

doAmazingness({ ..., signal })
.then(result => ...)
.catch(err => {
if (err.name == 'AbortError') return;
showUserErrorMessage();
})
.then(() => stopSpinner());

// &hellip;

controller.abort();</code></pre>

<p><code>doAmazingness</code> could be implemented as follows:

<pre><code class=lang-javascript>
function doAmazingness({signal}) {
return new Promise((resolve, reject) => {
// Begin doing amazingness, and call resolve(result) when done.
// But also, watch for signals:
signal.addEventListener('abort', () => {
// Stop doing amazingness, and:
reject(new DOMException('Aborted', 'AbortError'));
});
});
}
</code></pre>

<p>APIs that require more granular control could extend both {{AbortController}} and
{{AbortSignal}} objects according to their needs.
</div>


<h3 id=interface-abortcontroller>Interface {{AbortController}}</h3>

<pre class="idl">
[Constructor,
Exposed=(Window,Worker)]
interface AbortController {
[SameObject] readonly attribute AbortSignal signal;

void abort();
};</pre>

<dl class=domintro>
<dt><code><var>controller</var> = new <a constructor lt=AbortController()>AbortController</a>()</code>
<dd>Returns a new <var>controller</var> whose {{AbortController/signal}} is set to a newly
created {{AbortSignal}} object.

<dt><code><var>controller</var> . <a attribute for=AbortController>signal</a></code>
<dd>Returns the {{AbortSignal}} object associated with this object.

<dt><code><var>controller</var> . <a method for=AbortController lt=abort()>abort</a>()</code>
<dd>Invoking this method will set this object's {{AbortSignal}}'s [=AbortSignal/aborted flag=] and
signal to any observers that the associated activity is to be aborted.
</dl>

<p>An {{AbortController}} object has an associated <dfn for=AbortController>signal</dfn> (an
{{AbortSignal}} object).

<p>The <dfn constructor for=AbortController><code>AbortController()</code></dfn> constructor, when
invoked, must run these steps:

<ol>
<li><p>Let <var>signal</var> be a new {{AbortSignal}} object.

<li><p>Let <var>controller</var> be a new {{AbortController}} object whose
<a for=AbortController>signal</a> is <var>signal</var>.

<li><p>Return <var>controller</var>.
</ol>

<p>The <dfn attribute for=AbortController><code>signal</code></dfn> attribute's getter must return
<a>context object</a>'s <a for=AbortController>signal</a>.

<p>The <dfn method for=AbortController><code>abort()</code></dfn> method, when invoked, must
<a for=AbortSignal>signal abort</a> on <a>context object</a>'s <a for=AbortController>signal</a>.


<h3 id=interface-AbortSignal>Interface {{AbortSignal}}</h3>

<pre class="idl">
[Exposed=(Window,Worker)]
interface AbortSignal : EventTarget {
readonly attribute boolean aborted;

attribute EventHandler onabort;
};</pre>

<dl class=domintro>
<dt><code><var>signal</var> . <a attribute for=AbortSignal>aborted</a></code>
<dd>Returns true if this {{AbortSignal}}'s {{AbortController}} has signaled to abort, and false
otherwise.
</dl>

<p>An {{AbortSignal}} object has an associated <dfn for=AbortSignal>aborted flag</dfn>. It is unset
unless specified otherwise.

<p>An {{AbortSignal}} object has associated <dfn for=AbortSignal>abort algorithms</dfn>, which is a
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. This isn't exported, so I can't remove my algorithm from it. Maybe we should define "remove an algorithm from an AbortSignal" as well. Then it can stay un-exported.

Copy link
Member

Choose a reason for hiding this comment

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

If removing doesn't have any conditions I'll just export this instead.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it does, but it's kind of nice to say that the internal list of abort algorithms is an implementation detail, and other specs only interact by adding and removing. E.g. this way other specs cannot empty the list of algorithms, or reshuffle them.

Copy link
Member

Choose a reason for hiding this comment

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

In Fullscreen we want the other way. Maybe we should revisit that.

cc @foolip

Copy link
Member

Choose a reason for hiding this comment

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

@annevk, you mean whatwg/fullscreen#79 + whatwg/html#2650, right?

Whether to expose the data structure directly or wrap in a set of abstract operations I think won't have the same answer every time, but I suppose the more different specs that interact with a thing the more I'd lean towards abstract operations.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I guess it's okay for it to be a little different.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FWIW, I agree with abstract operations here. Prevents other specs setting "abort algorithms" etc etc.

<a for=/>set</a> of algorithms which are to be executed when its [=AbortSignal/aborted flag=] is
set. Unless specified otherwise, its value is the empty set.
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 too familiar with the spec language here, but this sounds like it encourages other specs to replace the set, but in reality we only want other things to add/remove item.

Should this say something like "The set is initially empty"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Following the above discussion, I guess other specs can't replace it.


<p>To <dfn export for=AbortSignal>add</dfn> an algorithm <var>algorithm</var> to an {{AbortSignal}}
object <var>signal</var>, run these steps:

<ol>
<li><p>If <var>signal</var>'s <a for=AbortSignal>aborted flag</a> is set, then return.

<li><p><a for=set>Append</a> <var>algorithm</var> to <var>signal</var>'s
<a for=AbortSignal>abort algorithms</a>.
</ol>

<p>To <dfn export for=AbortSignal>remove</dfn> an algorithm <var>algorithm</var> from an
{{AbortSignal}} <var>signal</var>, <a for=set>remove</a> <var>algorithm</var> from
<var>signal</var>'s <a for=AbortSignal>abort algorithms</a>.

<p class="note no-backref">The [=AbortSignal/abort algorithms=] enable APIs with complex
requirements to react in a reasonable way to {{AbortController/abort()}}. For example, a given API's
[=AbortSignal/aborted flag=] might need to be propagated to a cross-thread environment, such as a
service worker.

<p>The <dfn attribute for=AbortSignal>aborted</dfn> attribute's getter must return true if
<a>context object</a>'s [=AbortSignal/aborted flag=] is set, and false otherwise.

<p class=note>Changes to an {{AbortSignal}} object represent the wishes of the corresponding
{{AbortController}} object, but an API observing the {{AbortSignal}} object can chose to ignore
them. For instance, if the operation has already completed.

<p>To <dfn export for=AbortSignal>signal abort</dfn>, given a {{AbortSignal}} object
<var>signal</var>, run these steps:

<ol>
<li><p>If <var>signal</var>'s [=AbortSignal/aborted flag=] is set, then return.

<li><p>Set <var>signal</var>'s [=AbortSignal/aborted flag=].

<li><p><a for=set>For each</a> <var>algorithm</var> in <var>signal</var>'s
[=AbortSignal/abort algorithms=]: run <var>algorithm</var>.
Copy link
Member

@domenic domenic May 15, 2017

Choose a reason for hiding this comment

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

Probably you want to clear the abort algorithms here. It's unobservable, but seems like good practice.

Copy link
Member

Choose a reason for hiding this comment

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

If we do that, we probably shouldn't allow adding new abort algorithms either if the aborted flag is set. Should we have a dedicated add algorithm folks can use?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm yeah, good point. We could do that, or we could go with a different strategy, of noting that abort algorithms will never fire after the aborted flag is set, so even though the spec doesn't clean them up explicitly, implementations probably should. I guess that's not as good though, once I typed it out.


<li><p><a for=set>Empty</a> <var>signal</var>'s <a for=AbortSignal>abort algorithms</a>.

<li><p>[=Fire an event=] named <code event for=AbortSignal>abort</code> at <var>signal</var>.
</ol>


<h3 id=abortcontroller-api-integration>Using {{AbortController}} and {{AbortSignal}} objects in
APIs</h3>

<p>Any web platform API using promises to represent operations that can be aborted must adhere to
the following:

<ul class=brief>
<li>Accept {{AbortSignal}} objects through a <code>signal</code> dictionary member.
<li>Convey that the operation got aborted by rejecting the promise with an "{{AbortError}}"
{{DOMException}}.
<li>Reject immediately if the {{AbortSignal}}'s [=AbortSignal/aborted flag=] is already set,
otherwise:
<li>Use the [=AbortSignal/abort algorithms=] mechanism to observe changes to the {{AbortSignal}}
object and do so in a manner that does not lead to clashes with other observers.
</ul>

<div class=example id=aborting-ongoing-activities-spec-example>
<p>The steps for a promise-returning method <code>doAmazingness(options)</code> could be as
follows:

<ol>
<li><p>Let |p| be [=a new promise=].

<li>
<p>If |options|' <code>signal</code> member is present, then:

<ol>
<li><p>If |options|' <code>signal</code>'s [=AbortSignal/aborted flag=] is set, then [=reject=]
|p| with an "{{AbortError}}" {{DOMException}} and return |p|.

<li>
<p>[=AbortSignal/Add|Add the following abort steps=] to |options|' <code>signal</code>:

<ol>
<li><p>Stop doing amazing things.

<li><p>[=Reject=] |p| with an "{{AbortError}}" {{DOMException}}.
</ol>
</ol>

<li>
<p>Run these steps [=in parallel=]:

<ol>
<li><p>Let |amazingResult| be the result of doing some amazing things.

<li><p>[=Resolve=] |p| with |amazingResult|.
</ol>

<li><p>Return |p|.
</ol>
</div>

<p>APIs not using promises should still adhere to the above as much as possible.



<h2 id=nodes>Nodes</h2>

<h3 id=introduction-to-the-dom>Introduction to "The DOM"</h3>
Expand Down Expand Up @@ -1931,7 +2153,7 @@ before a <var>child</var>, with an optional <i>suppress observers flag</i>, run

<li>If <var>node</var> is a {{DocumentFragment}}
<a>node</a>,
<a>remove</a> its
<a for=/>remove</a> its
<a>children</a> with the
<i>suppress observers flag</i> set.

Expand Down Expand Up @@ -2103,7 +2325,7 @@ within a <var>parent</var>, run these steps:
<ol>
<li><p>Set <var>removedNodes</var> to a list solely containing <var>child</var>.

<li><p><a>Remove</a> <var>child</var> from its <var>parent</var> with the
<li><p><a for=/>Remove</a> <var>child</var> from its <var>parent</var> with the
<i>suppress observers flag</i> set.
</ol>

Expand Down Expand Up @@ -2144,7 +2366,7 @@ To <dfn export for=Node id=concept-node-replace-all>replace all</dfn> with a
<a>node</a>, and a list containing <var>node</var>
otherwise.

<li><a>Remove</a> all
<li><a for=/>Remove</a> all
<var>parent</var>'s <a>children</a>, in
<a>tree order</a>, with the
<i>suppress observers flag</i> set.
Expand All @@ -2169,8 +2391,7 @@ To <dfn export id=concept-node-pre-remove>pre-remove</dfn> a <var>child</var> fr
<li>If <var>child</var>'s <a for=tree>parent</a> is not <var>parent</var>, then <a>throw</a> a
{{NotFoundError}}.

<li><a>Remove</a> <var>child</var>
from <var>parent</var>.
<li><a for=/>Remove</a> <var>child</var> from <var>parent</var>.

<li>Return <var>child</var>.
<!-- technically this is post-remove -->
Expand All @@ -2180,7 +2401,7 @@ To <dfn export id=concept-node-pre-remove>pre-remove</dfn> a <var>child</var> fr
<p><a lt="Other applicable specifications">Specifications</a> may define
<dfn export id=concept-node-remove-ext>removing steps</dfn> for all or some <a>nodes</a>. The
algorithm is passed <var ignore>removedNode</var>, and optionally <var ignore>oldParent</var>, as
indicated in the <a>remove</a> algorithm below.
indicated in the <a for=/>remove</a> algorithm below.

<p>To <dfn export id=concept-node-remove>remove</dfn> a <var>node</var> from a <var>parent</var>,
with an optional <i>suppress observers flag</i>, run these steps:
Expand Down Expand Up @@ -2651,7 +2872,7 @@ steps:
<ol>
<li><p>If <a>context object</a>'s <a for=tree>parent</a> is null, then return.

<li><p><a>Remove</a> the <a>context object</a> from <a>context object</a>'s
<li><p><a for=/>Remove</a> the <a>context object</a> from <a>context object</a>'s
<a for=tree>parent</a>.
</ol>

Expand Down Expand Up @@ -3810,8 +4031,8 @@ steps for each <a>descendant</a> <a>exclusive <code>Text</code> node</a> <var>no
<ol>
<li>Let <var>length</var> be <var>node</var>'s <a for=Node>length</a>.

<li>If <var>length</var> is zero, then <a>remove</a> <var>node</var> and continue with the next
<a>exclusive <code>Text</code> node</a>, if any.
<li>If <var>length</var> is zero, then <a for=/>remove</a> <var>node</var> and continue with the
next <a>exclusive <code>Text</code> node</a>, if any.

<li>Let <var>data</var> be the concatenation of the <a for=CharacterData>data</a> of
<var>node</var>'s <a>contiguous exclusive <code>Text</code> nodes</a> (excluding itself), in
Expand Down Expand Up @@ -3849,8 +4070,8 @@ steps for each <a>descendant</a> <a>exclusive <code>Text</code> node</a> <var>no
<li><p>Set <var>currentNode</var> to its <a for=tree>next sibling</a>.
</ol>

<li><a>Remove</a> <var>node</var>'s <a>contiguous exclusive <code>Text</code> nodes</a> (excluding
itself), in <a>tree order</a>.
<li><a for=/>Remove</a> <var>node</var>'s <a>contiguous exclusive <code>Text</code> nodes</a>
(excluding itself), in <a>tree order</a>.
</ol>

<p class="note">{{Node/normalize()}} does not need to run any
Expand Down Expand Up @@ -4955,8 +5176,8 @@ these steps:
<ol>
<li><p>Let <var>oldDocument</var> be <var>node</var>'s <a for=Node>node document</a>.

<li><p>If <var>node</var>'s <a for=tree>parent</a> is not null, <a>remove</a> <var>node</var> from its
<a for=tree>parent</a>.
<li><p>If <var>node</var>'s <a for=tree>parent</a> is not null, <a for=/>remove</a> <var>node</var>
from its <a for=tree>parent</a>.

<li>
<p>If <var>document</var> is not <var>oldDocument</var>, then:
Expand Down Expand Up @@ -7124,7 +7345,7 @@ might itself be modified as part of the mutation to the
<a>node tree</a> when e.g. part of the content
it represents is mutated.

<p class="note no-backref">See the <a>insert</a> and <a>remove</a> algorithms, the
<p class="note no-backref">See the <a>insert</a> and <a for=/>remove</a> algorithms, the
{{Node/normalize()}} method, and the <a>replace data</a> and <a lt="split a Text node">split</a>
algorithms for the hairy details.

Expand Down Expand Up @@ -7193,7 +7414,7 @@ the <a>boundary point</a>'s
<a>length</a>, inclusive. Algorithms that
modify a <a>tree</a> (in particular the
<a>insert</a>,
<a>remove</a>,
<a for=/>remove</a>,
<a>replace data</a>, and
<a lt="split a Text node">split</a> algorithms) also modify
<a>ranges</a> associated with that
Expand Down Expand Up @@ -7781,7 +8002,7 @@ run these steps:

<li>For each <var>node</var> in <var>nodes to remove</var>,
in <a>tree order</a>,
<a>remove</a> <var>node</var> from
<a for=/>remove</a> <var>node</var> from
its <a for=tree>parent</a>.

<li>If <var>original end node</var> is a {{Text}},
Expand Down Expand Up @@ -8349,7 +8570,7 @@ the result of <a lt="clone the contents of a range">cloning the contents</a> of
<!-- Because we're about to remove node from its parent. -->

<li>If <var>node</var>'s <a for=tree>parent</a> is not
null, <a>remove</a> <var>node</var> from its
null, <a for=/>remove</a> <var>node</var> from its
<a for=tree>parent</a>.

<!-- Browsers disagree on how to handle the case where the range is
Expand Down Expand Up @@ -9772,6 +9993,7 @@ Mounir Lamouri,
Michael™ Smith,
Mike Champion,
Mike Taylor,
Mike West,
Ojan Vafai,
Oliver Nightingale,
Olli Pettay,
Expand Down