Title: [239644] trunk
Revision
239644
Author
[email protected]
Date
2019-01-04 16:01:43 -0800 (Fri, 04 Jan 2019)

Log Message

[Fetch API] Implement abortable fetch
https://bugs.webkit.org/show_bug.cgi?id=174980
<rdar://problem/46861402>

Reviewed by Chris Dumez.

LayoutTests/imported/w3c:

Fixed tests to run in WebKit CI.
Also fixed a bug in a test where the fetch response body is not actually empty.

* web-platform-tests/fetch/api/abort/cache.https-expected.txt:
* web-platform-tests/fetch/api/abort/general-serviceworker.https-expected.txt:
* web-platform-tests/fetch/api/abort/general.any-expected.txt:
* web-platform-tests/fetch/api/abort/general.any.js:
* web-platform-tests/fetch/api/abort/general.any.worker-expected.txt:
* web-platform-tests/fetch/api/abort/serviceworker-intercepted.https-expected.txt:
* web-platform-tests/fetch/api/response/response-consume-stream-expected.txt:

Source/WebCore:

Add an AbortSignal to FetchRequest.

Add support for AbortSignal algorithm.
The fetch request signal is added an algorithm to abort the fetch.
Update clone algorithm to let signal of the cloned request be following the origin request.

Update ReadableStream error handling to return an exception instead of a string.
This allows passing an AbortError instead of a TypeError as previously done.

Update FetchBodyOwner to store a loading error either as an exception or as a resource error.
The latter is used for passing the error from service worker back to the page.
The former is used to pass it to ReadableStream or body accessors.

Covered by enabled tests.

* Modules/cache/DOMCache.cpp:
(WebCore::DOMCache::put):
* Modules/fetch/FetchBody.cpp:
(WebCore::FetchBody::consumeAsStream):
(WebCore::FetchBody::loadingFailed):
* Modules/fetch/FetchBody.h:
* Modules/fetch/FetchBodyConsumer.cpp:
(WebCore::FetchBodyConsumer::loadingFailed):
* Modules/fetch/FetchBodyConsumer.h:
* Modules/fetch/FetchBodyOwner.cpp:
(WebCore::FetchBodyOwner::arrayBuffer):
(WebCore::FetchBodyOwner::blob):
(WebCore::FetchBodyOwner::cloneBody):
(WebCore::FetchBodyOwner::formData):
(WebCore::FetchBodyOwner::json):
(WebCore::FetchBodyOwner::text):
(WebCore::FetchBodyOwner::loadBlob):
(WebCore::FetchBodyOwner::blobLoadingFailed):
(WebCore::FetchBodyOwner::consumeBodyAsStream):
(WebCore::FetchBodyOwner::setLoadingError):
* Modules/fetch/FetchBodyOwner.h:
(WebCore::FetchBodyOwner::loadingError const):
(WebCore::FetchBodyOwner::loadingException const):
* Modules/fetch/FetchBodySource.cpp:
(WebCore::FetchBodySource::error):
* Modules/fetch/FetchBodySource.h:
* Modules/fetch/FetchRequest.cpp:
(WebCore::FetchRequest::initializeWith):
(WebCore::FetchRequest::clone):
* Modules/fetch/FetchRequest.h:
(WebCore::FetchRequest::FetchRequest):
* Modules/fetch/FetchRequest.idl:
* Modules/fetch/FetchRequestInit.h:
(WebCore::FetchRequestInit::hasMembers const):
* Modules/fetch/FetchRequestInit.idl:
* Modules/fetch/FetchResponse.cpp:
(WebCore::FetchResponse::clone):
(WebCore::FetchResponse::fetch):
(WebCore::FetchResponse::BodyLoader::didFail):
* Modules/fetch/FetchResponse.h:
* bindings/js/ReadableStreamDefaultController.h:
(WebCore::ReadableStreamDefaultController::error):
* dom/AbortSignal.cpp:
(WebCore::AbortSignal::abort):
(WebCore::AbortSignal::follow):
* dom/AbortSignal.h:

LayoutTests:

* TestExpectations: Enable abort tests.

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (239643 => 239644)


--- trunk/LayoutTests/ChangeLog	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/ChangeLog	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,3 +1,13 @@
+2019-01-04  Youenn Fablet  <[email protected]>
+
+        [Fetch API] Implement abortable fetch
+        https://bugs.webkit.org/show_bug.cgi?id=174980
+        <rdar://problem/46861402>
+
+        Reviewed by Chris Dumez.
+
+        * TestExpectations: Enable abort tests.
+
 2019-01-04  Brent Fulgham  <[email protected]>
 
         Parsed protocol of _javascript_ URLs with embedded newlines and carriage returns do not match parsed protocol in Chrome and Firefox

Modified: trunk/LayoutTests/TestExpectations (239643 => 239644)


--- trunk/LayoutTests/TestExpectations	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/TestExpectations	2019-01-05 00:01:43 UTC (rev 239644)
@@ -211,7 +211,6 @@
 imported/w3c/web-platform-tests/cors/remote-origin.htm [ Skip ]
 
 # Skip service worker tests that are timing out.
-imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https.html [ Skip ]
 imported/w3c/web-platform-tests/service-workers/service-worker/performance-timeline.https.html [ Skip ]
 imported/w3c/web-platform-tests/service-workers/service-worker/respond-with-body-accessed-response.https.html [ Skip ]
 imported/w3c/web-platform-tests/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html [ Skip ]
@@ -383,10 +382,6 @@
 webkit.org/b/189910 imported/w3c/web-platform-tests/resource-timing/resource_timing_store_and_clear_during_callback.html [ Pass Failure ]
 webkit.org/b/190523 imported/w3c/web-platform-tests/resource-timing/resource_timing_cross_origin_redirect_chain.html [ Pass Failure ]
 
-# The follow two tests change their output each run
-imported/w3c/web-platform-tests/fetch/api/abort/general.any.html [ Skip ]
-imported/w3c/web-platform-tests/fetch/api/abort/general.any.worker.html [ Skip ]
-
 # These tests time out
 imported/w3c/web-platform-tests/fetch/api/request/destination/fetch-destination-no-load-event.https.html [ Skip ]
 imported/w3c/web-platform-tests/fetch/api/request/destination/fetch-destination.https.html [ Skip ]

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,3 +1,22 @@
+2019-01-04  Youenn Fablet  <[email protected]>
+
+        [Fetch API] Implement abortable fetch
+        https://bugs.webkit.org/show_bug.cgi?id=174980
+        <rdar://problem/46861402>
+
+        Reviewed by Chris Dumez.
+
+        Fixed tests to run in WebKit CI.
+        Also fixed a bug in a test where the fetch response body is not actually empty.
+
+        * web-platform-tests/fetch/api/abort/cache.https-expected.txt:
+        * web-platform-tests/fetch/api/abort/general-serviceworker.https-expected.txt:
+        * web-platform-tests/fetch/api/abort/general.any-expected.txt:
+        * web-platform-tests/fetch/api/abort/general.any.js:
+        * web-platform-tests/fetch/api/abort/general.any.worker-expected.txt:
+        * web-platform-tests/fetch/api/abort/serviceworker-intercepted.https-expected.txt:
+        * web-platform-tests/fetch/api/response/response-consume-stream-expected.txt:
+
 2019-01-02  Simon Fraser  <[email protected]>
 
         Support css-color-4 rgb functions

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/cache.https-expected.txt (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/cache.https-expected.txt	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/cache.https-expected.txt	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,4 +1,4 @@
 
-FAIL Signals are not stored in the cache API promise_test: Unhandled rejection with value: object "TypeError: undefined is not an object (evaluating 'cachedRequest.signal.aborted')"
-FAIL Signals are not stored in the cache API, even if they're already aborted promise_test: Unhandled rejection with value: object "TypeError: undefined is not an object (evaluating 'cachedRequest.signal.aborted')"
+PASS Signals are not stored in the cache API 
+PASS Signals are not stored in the cache API, even if they're already aborted 
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https-expected.txt (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https-expected.txt	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https-expected.txt	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,8 +1,6 @@
 
-Harness Error (TIMEOUT), message = null
-
-FAIL Aborting rejects with AbortError assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Aborting rejects with AbortError - no-cors assert_throws: function "function () { throw e }" threw object "TypeError: A server with the specified hostname could not be found." that is not a DOMException AbortError: property "code" is equal to undefined, expected 20
+PASS Aborting rejects with AbortError 
+PASS Aborting rejects with AbortError - no-cors 
 PASS TypeError from request constructor takes priority - RequestInit's window is not null 
 PASS TypeError from request constructor takes priority - Input URL is not valid 
 PASS TypeError from request constructor takes priority - Input URL has credentials 
@@ -19,34 +17,34 @@
 PASS TypeError from request constructor takes priority - Bad credentials init parameter value 
 PASS TypeError from request constructor takes priority - Bad cache init parameter value 
 PASS TypeError from request constructor takes priority - Bad redirect init parameter value 
-FAIL Request objects have a signal property assert_true: Signal member is present & truthy expected true got false
-FAIL Signal on request object assert_true: Signal member is present & truthy expected true got false
-FAIL Signal on request object created from request object assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal on request object created from request object, with signal on second request assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal on request object created from request object, with signal on second request overriding another assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal retained after unrelated properties are overridden by fetch assert_unreached: Should have rejected: undefined Reached unreachable code
+PASS Request objects have a signal property 
+PASS Signal on request object 
+PASS Signal on request object created from request object 
+PASS Signal on request object created from request object, with signal on second request 
+PASS Signal on request object created from request object, with signal on second request overriding another 
+PASS Signal retained after unrelated properties are overridden by fetch 
 PASS Signal removed by setting to null 
-FAIL Already aborted signal rejects immediately assert_unreached: Fetch must not resolve Reached unreachable code
+PASS Already aborted signal rejects immediately 
 PASS Request is still 'used' if signal is aborted before fetching 
-FAIL response.arrayBuffer() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.blob() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.formData() rejects if already aborted assert_throws: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
-FAIL response.json() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.text() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Already aborted signal does not make request assert_equals: Request hasn't been made to the server expected (object) null but got (string) "open"
-FAIL Already aborted signal can be used for many fetches assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal can be used to abort other fetches, even if another fetch succeeded before aborting assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Underlying connection is closed when aborting after receiving response promise_test: Unhandled rejection with value: object "Error: Timed out"
-FAIL Underlying connection is closed when aborting after receiving response - no-cors promise_test: Unhandled rejection with value: object "TypeError: A server with the specified hostname could not be found."
-TIMEOUT Fetch aborted & connection closed when aborted after calling response.arrayBuffer() Test timed out
-NOTRUN Fetch aborted & connection closed when aborted after calling response.blob() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.formData() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.json() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.text() 
-NOTRUN Stream errors once aborted. Underlying connection closed. 
-NOTRUN Stream errors once aborted, after reading. Underlying connection closed. 
-NOTRUN Stream will not error if body is empty. It's closed with an empty queue before it errors. 
-NOTRUN Readable stream synchronously cancels with AbortError if aborted before reading 
-FAIL Signal state is cloned undefined is not an object (evaluating 'request.signal.aborted')
-FAIL Clone aborts with original controller undefined is not an object (evaluating 'request.signal.addEventListener')
+PASS response.arrayBuffer() rejects if already aborted 
+PASS response.blob() rejects if already aborted 
+PASS response.formData() rejects if already aborted 
+PASS response.json() rejects if already aborted 
+PASS response.text() rejects if already aborted 
+PASS Already aborted signal does not make request 
+PASS Already aborted signal can be used for many fetches 
+PASS Signal can be used to abort other fetches, even if another fetch succeeded before aborting 
+PASS Underlying connection is closed when aborting after receiving response 
+PASS Underlying connection is closed when aborting after receiving response - no-cors 
+PASS Fetch aborted & connection closed when aborted after calling response.arrayBuffer() 
+PASS Fetch aborted & connection closed when aborted after calling response.blob() 
+FAIL Fetch aborted & connection closed when aborted after calling response.formData() assert_throws: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
+PASS Fetch aborted & connection closed when aborted after calling response.json() 
+PASS Fetch aborted & connection closed when aborted after calling response.text() 
+PASS Stream errors once aborted. Underlying connection closed. 
+PASS Stream errors once aborted, after reading. Underlying connection closed. 
+PASS Stream will not error if body is empty. It's closed with an empty queue before it errors. 
+FAIL Readable stream synchronously cancels with AbortError if aborted before reading assert_true: Cancel called sync expected true got false
+PASS Signal state is cloned 
+PASS Clone aborts with original controller 
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any-expected.txt (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any-expected.txt	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any-expected.txt	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,12 +1,7 @@
-Blocked access to external URL http://www1.localhost:8800/fetch/api/resources/data.json
-CONSOLE MESSAGE: line 36: Fetch API cannot load http://www1.localhost:8800/fetch/api/resources/data.json due to access control checks.
-Blocked access to external URL http://www1.localhost:8800/fetch/api/resources/infinite-slow-response.py?stateKey=28d5c068-417e-4c81-a0cd-9b8c22aed3c1&abortKey=ef9a1b5a-7afd-4734-b145-f033788c0e6b
-CONSOLE MESSAGE: line 318: Fetch API cannot load http://www1.localhost:8800/fetch/api/resources/infinite-slow-response.py?stateKey=28d5c068-417e-4c81-a0cd-9b8c22aed3c1&abortKey=ef9a1b5a-7afd-4734-b145-f033788c0e6b due to access control checks.
+CONSOLE MESSAGE: Unhandled Promise Rejection: AbortError: Request signal is aborted
 
-Harness Error (TIMEOUT), message = null
-
-FAIL Aborting rejects with AbortError assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Aborting rejects with AbortError - no-cors assert_throws: function "function () { throw e }" threw object "TypeError: Type error" that is not a DOMException AbortError: property "code" is equal to undefined, expected 20
+PASS Aborting rejects with AbortError 
+PASS Aborting rejects with AbortError - no-cors 
 PASS TypeError from request constructor takes priority - RequestInit's window is not null 
 PASS TypeError from request constructor takes priority - Input URL is not valid 
 PASS TypeError from request constructor takes priority - Input URL has credentials 
@@ -15,7 +10,6 @@
 PASS TypeError from request constructor takes priority - RequestInit's method is invalid 
 PASS TypeError from request constructor takes priority - RequestInit's method is forbidden 
 PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and method is not simple 
-PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and integrity is not empty 
 PASS TypeError from request constructor takes priority - RequestInit's cache mode is only-if-cached and mode is not same-origin 
 PASS TypeError from request constructor takes priority - Request with cache mode: only-if-cached and fetch mode cors 
 PASS TypeError from request constructor takes priority - Request with cache mode: only-if-cached and fetch mode no-cors 
@@ -24,34 +18,34 @@
 PASS TypeError from request constructor takes priority - Bad credentials init parameter value 
 PASS TypeError from request constructor takes priority - Bad cache init parameter value 
 PASS TypeError from request constructor takes priority - Bad redirect init parameter value 
-FAIL Request objects have a signal property assert_true: Signal member is present & truthy expected true got false
-FAIL Signal on request object assert_true: Signal member is present & truthy expected true got false
-FAIL Signal on request object created from request object assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal on request object created from request object, with signal on second request assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal on request object created from request object, with signal on second request overriding another assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal retained after unrelated properties are overridden by fetch assert_unreached: Should have rejected: undefined Reached unreachable code
+PASS Request objects have a signal property 
+PASS Signal on request object 
+PASS Signal on request object created from request object 
+PASS Signal on request object created from request object, with signal on second request 
+PASS Signal on request object created from request object, with signal on second request overriding another 
+PASS Signal retained after unrelated properties are overridden by fetch 
 PASS Signal removed by setting to null 
-FAIL Already aborted signal rejects immediately assert_unreached: Fetch must not resolve Reached unreachable code
+PASS Already aborted signal rejects immediately 
 PASS Request is still 'used' if signal is aborted before fetching 
-FAIL response.arrayBuffer() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.blob() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.formData() rejects if already aborted assert_throws: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
-FAIL response.json() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.text() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Already aborted signal does not make request assert_equals: Request hasn't been made to the server expected (object) null but got (string) "open"
-FAIL Already aborted signal can be used for many fetches assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal can be used to abort other fetches, even if another fetch succeeded before aborting assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Underlying connection is closed when aborting after receiving response promise_test: Unhandled rejection with value: object "Error: Timed out"
-FAIL Underlying connection is closed when aborting after receiving response - no-cors promise_test: Unhandled rejection with value: object "TypeError: Type error"
-TIMEOUT Fetch aborted & connection closed when aborted after calling response.arrayBuffer() Test timed out
-NOTRUN Fetch aborted & connection closed when aborted after calling response.blob() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.formData() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.json() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.text() 
-NOTRUN Stream errors once aborted. Underlying connection closed. 
-NOTRUN Stream errors once aborted, after reading. Underlying connection closed. 
-NOTRUN Stream will not error if body is empty. It's closed with an empty queue before it errors. 
-NOTRUN Readable stream synchronously cancels with AbortError if aborted before reading 
-FAIL Signal state is cloned undefined is not an object (evaluating 'request.signal.aborted')
-FAIL Clone aborts with original controller undefined is not an object (evaluating 'request.signal.addEventListener')
+PASS response.arrayBuffer() rejects if already aborted 
+PASS response.blob() rejects if already aborted 
+PASS response.formData() rejects if already aborted 
+PASS response.json() rejects if already aborted 
+PASS response.text() rejects if already aborted 
+PASS Already aborted signal does not make request 
+PASS Already aborted signal can be used for many fetches 
+PASS Signal can be used to abort other fetches, even if another fetch succeeded before aborting 
+PASS Underlying connection is closed when aborting after receiving response 
+PASS Underlying connection is closed when aborting after receiving response - no-cors 
+PASS Fetch aborted & connection closed when aborted after calling response.arrayBuffer() 
+PASS Fetch aborted & connection closed when aborted after calling response.blob() 
+FAIL Fetch aborted & connection closed when aborted after calling response.formData() assert_throws: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
+PASS Fetch aborted & connection closed when aborted after calling response.json() 
+PASS Fetch aborted & connection closed when aborted after calling response.text() 
+PASS Stream errors once aborted. Underlying connection closed. 
+PASS Stream errors once aborted, after reading. Underlying connection closed. 
+PASS Stream will not error if body is empty. It's closed with an empty queue before it errors. 
+FAIL Readable stream synchronously cancels with AbortError if aborted before reading assert_true: Cancel called sync expected true got false
+PASS Signal state is cloned 
+PASS Clone aborts with original controller 
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.js (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.js	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.js	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,4 +1,5 @@
 // META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
 // META: script=../request/request-error.js
 
 const BODY_METHODS = ['arrayBuffer', 'blob', 'formData', 'json', 'text'];
@@ -15,6 +16,9 @@
   );
 }
 
+const hostInfo = get_host_info();
+const urlHostname = hostInfo.REMOTE_HOST;
+
 promise_test(async t => {
   const controller = new AbortController();
   const signal = controller.signal;
@@ -31,7 +35,7 @@
   controller.abort();
 
   const url = "" URL('../resources/data.json', location);
-  url.hostname = 'www1.' + url.hostname;
+  url.hostname = urlHostname;
 
   const fetchPromise = fetch(url, {
     signal,
@@ -314,7 +318,7 @@
   requestAbortKeys.push(abortKey);
 
   const url = "" URL(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, location);
-  url.hostname = 'www1.' + url.hostname;
+  url.hostname = urlHostname;
 
   await fetch(url, {
     signal,
@@ -322,7 +326,7 @@
   });
 
   const stashTakeURL = new URL(`../resources/stash-take.py?key=${stateKey}`, location);
-  stashTakeURL.hostname = 'www1.' + stashTakeURL.hostname;
+  stashTakeURL.hostname = urlHostname;
 
   const beforeAbortResult = await fetch(stashTakeURL).then(r => r.json());
   assert_equals(beforeAbortResult, "open", "Connection is open");
@@ -440,7 +444,7 @@
   const controller = new AbortController();
   const signal = controller.signal;
 
-  const response = await fetch(`../resources/empty.txt`, { signal });
+  const response = await fetch(`../resources/method.py`, { signal });
 
   // Read whole response to ensure close signal has sent.
   await response.clone().text();

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.worker-expected.txt (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.worker-expected.txt	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.worker-expected.txt	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,10 +1,6 @@
-Blocked access to external URL http://www1.localhost:8800/fetch/api/resources/data.json
-Blocked access to external URL http://www1.localhost:8800/fetch/api/resources/infinite-slow-response.py?stateKey=7471d98e-1bdb-4254-a315-9489c98b8f59&abortKey=4c631f17-2786-4b07-84f4-0a5760d28f1e
 
-Harness Error (TIMEOUT), message = null
-
-FAIL Aborting rejects with AbortError assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Aborting rejects with AbortError - no-cors assert_throws: function "function () { throw e }" threw object "TypeError: Type error" that is not a DOMException AbortError: property "code" is equal to undefined, expected 20
+PASS Aborting rejects with AbortError 
+PASS Aborting rejects with AbortError - no-cors 
 PASS TypeError from request constructor takes priority - RequestInit's window is not null 
 PASS TypeError from request constructor takes priority - Input URL is not valid 
 PASS TypeError from request constructor takes priority - Input URL has credentials 
@@ -13,7 +9,6 @@
 PASS TypeError from request constructor takes priority - RequestInit's method is invalid 
 PASS TypeError from request constructor takes priority - RequestInit's method is forbidden 
 PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and method is not simple 
-PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and integrity is not empty 
 PASS TypeError from request constructor takes priority - RequestInit's cache mode is only-if-cached and mode is not same-origin 
 PASS TypeError from request constructor takes priority - Request with cache mode: only-if-cached and fetch mode cors 
 PASS TypeError from request constructor takes priority - Request with cache mode: only-if-cached and fetch mode no-cors 
@@ -22,34 +17,34 @@
 PASS TypeError from request constructor takes priority - Bad credentials init parameter value 
 PASS TypeError from request constructor takes priority - Bad cache init parameter value 
 PASS TypeError from request constructor takes priority - Bad redirect init parameter value 
-FAIL Request objects have a signal property assert_true: Signal member is present & truthy expected true got false
-FAIL Signal on request object assert_true: Signal member is present & truthy expected true got false
-FAIL Signal on request object created from request object assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal on request object created from request object, with signal on second request assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal on request object created from request object, with signal on second request overriding another assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal retained after unrelated properties are overridden by fetch assert_unreached: Should have rejected: undefined Reached unreachable code
+PASS Request objects have a signal property 
+PASS Signal on request object 
+PASS Signal on request object created from request object 
+PASS Signal on request object created from request object, with signal on second request 
+PASS Signal on request object created from request object, with signal on second request overriding another 
+PASS Signal retained after unrelated properties are overridden by fetch 
 PASS Signal removed by setting to null 
-FAIL Already aborted signal rejects immediately assert_unreached: Fetch must not resolve Reached unreachable code
+PASS Already aborted signal rejects immediately 
 PASS Request is still 'used' if signal is aborted before fetching 
-FAIL response.arrayBuffer() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.blob() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.formData() rejects if already aborted assert_throws: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
-FAIL response.json() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.text() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Already aborted signal does not make request assert_equals: Request hasn't been made to the server expected (object) null but got (string) "open"
-FAIL Already aborted signal can be used for many fetches assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Signal can be used to abort other fetches, even if another fetch succeeded before aborting assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL Underlying connection is closed when aborting after receiving response promise_test: Unhandled rejection with value: object "Error: Timed out"
-FAIL Underlying connection is closed when aborting after receiving response - no-cors promise_test: Unhandled rejection with value: object "TypeError: Type error"
-TIMEOUT Fetch aborted & connection closed when aborted after calling response.arrayBuffer() Test timed out
-NOTRUN Fetch aborted & connection closed when aborted after calling response.blob() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.formData() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.json() 
-NOTRUN Fetch aborted & connection closed when aborted after calling response.text() 
-NOTRUN Stream errors once aborted. Underlying connection closed. 
-NOTRUN Stream errors once aborted, after reading. Underlying connection closed. 
-NOTRUN Stream will not error if body is empty. It's closed with an empty queue before it errors. 
-NOTRUN Readable stream synchronously cancels with AbortError if aborted before reading 
-FAIL Signal state is cloned undefined is not an object (evaluating 'request.signal.aborted')
-FAIL Clone aborts with original controller undefined is not an object (evaluating 'request.signal.addEventListener')
+PASS response.arrayBuffer() rejects if already aborted 
+PASS response.blob() rejects if already aborted 
+PASS response.formData() rejects if already aborted 
+PASS response.json() rejects if already aborted 
+PASS response.text() rejects if already aborted 
+PASS Already aborted signal does not make request 
+PASS Already aborted signal can be used for many fetches 
+PASS Signal can be used to abort other fetches, even if another fetch succeeded before aborting 
+PASS Underlying connection is closed when aborting after receiving response 
+PASS Underlying connection is closed when aborting after receiving response - no-cors 
+PASS Fetch aborted & connection closed when aborted after calling response.arrayBuffer() 
+PASS Fetch aborted & connection closed when aborted after calling response.blob() 
+FAIL Fetch aborted & connection closed when aborted after calling response.formData() assert_throws: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
+PASS Fetch aborted & connection closed when aborted after calling response.json() 
+PASS Fetch aborted & connection closed when aborted after calling response.text() 
+PASS Stream errors once aborted. Underlying connection closed. 
+PASS Stream errors once aborted, after reading. Underlying connection closed. 
+PASS Stream will not error if body is empty. It's closed with an empty queue before it errors. 
+FAIL Readable stream synchronously cancels with AbortError if aborted before reading assert_true: Cancel called sync expected true got false
+PASS Signal state is cloned 
+PASS Clone aborts with original controller 
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/serviceworker-intercepted.https-expected.txt (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/serviceworker-intercepted.https-expected.txt	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/serviceworker-intercepted.https-expected.txt	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,9 +1,10 @@
 
-FAIL Already aborted request does not land in service worker assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL response.arrayBuffer() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
-FAIL response.blob() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
-FAIL response.formData() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
-FAIL response.json() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
-FAIL response.text() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
-FAIL Stream errors once aborted. promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
 
+PASS Already aborted request does not land in service worker 
+PASS response.arrayBuffer() rejects if already aborted 
+PASS response.blob() rejects if already aborted 
+PASS response.formData() rejects if already aborted 
+PASS response.json() rejects if already aborted 
+PASS response.text() rejects if already aborted 
+FAIL Stream errors once aborted. assert_unreached: Should have rejected: undefined Reached unreachable code
+

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/response/response-consume-stream-expected.txt (239643 => 239644)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/response/response-consume-stream-expected.txt	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/response/response-consume-stream-expected.txt	2019-01-05 00:01:43 UTC (rev 239644)
@@ -5,7 +5,7 @@
 PASS Read text response's body as readableStream 
 PASS Read URLSearchParams response's body as readableStream 
 PASS Read array buffer response's body as readableStream 
-FAIL Read form data response's body as readableStream promise_test: Unhandled rejection with value: object "TypeError: not implemented"
+FAIL Read form data response's body as readableStream promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
 PASS Getting an error Response stream 
 PASS Getting a redirect Response stream 
 

Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (239643 => 239644)


--- trunk/LayoutTests/platform/ios-simulator/TestExpectations	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/LayoutTests/platform/ios-simulator/TestExpectations	2019-01-05 00:01:43 UTC (rev 239644)
@@ -67,6 +67,7 @@
 # This test requires Skia, which isn't available on iOS.
 webkit.org/b/174079 fast/text/variations/skia-postscript-name.html [ ImageOnlyFailure ]
 
+imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https.html [ Pass Failure ]
 imported/w3c/web-platform-tests/2dcontext/transformations/canvas_transformations_reset_001.html [ ImageOnlyFailure ]
 imported/w3c/web-platform-tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html [ Failure ]
 imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-offer.html [ Failure ]

Modified: trunk/Source/WebCore/ChangeLog (239643 => 239644)


--- trunk/Source/WebCore/ChangeLog	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/ChangeLog	2019-01-05 00:01:43 UTC (rev 239644)
@@ -1,3 +1,73 @@
+2019-01-04  Youenn Fablet  <[email protected]>
+
+        [Fetch API] Implement abortable fetch
+        https://bugs.webkit.org/show_bug.cgi?id=174980
+        <rdar://problem/46861402>
+
+        Reviewed by Chris Dumez.
+
+        Add an AbortSignal to FetchRequest.
+
+        Add support for AbortSignal algorithm.
+        The fetch request signal is added an algorithm to abort the fetch.
+        Update clone algorithm to let signal of the cloned request be following the origin request.
+
+        Update ReadableStream error handling to return an exception instead of a string.
+        This allows passing an AbortError instead of a TypeError as previously done.
+
+        Update FetchBodyOwner to store a loading error either as an exception or as a resource error.
+        The latter is used for passing the error from service worker back to the page.
+        The former is used to pass it to ReadableStream or body accessors.
+
+        Covered by enabled tests.
+
+        * Modules/cache/DOMCache.cpp:
+        (WebCore::DOMCache::put):
+        * Modules/fetch/FetchBody.cpp:
+        (WebCore::FetchBody::consumeAsStream):
+        (WebCore::FetchBody::loadingFailed):
+        * Modules/fetch/FetchBody.h:
+        * Modules/fetch/FetchBodyConsumer.cpp:
+        (WebCore::FetchBodyConsumer::loadingFailed):
+        * Modules/fetch/FetchBodyConsumer.h:
+        * Modules/fetch/FetchBodyOwner.cpp:
+        (WebCore::FetchBodyOwner::arrayBuffer):
+        (WebCore::FetchBodyOwner::blob):
+        (WebCore::FetchBodyOwner::cloneBody):
+        (WebCore::FetchBodyOwner::formData):
+        (WebCore::FetchBodyOwner::json):
+        (WebCore::FetchBodyOwner::text):
+        (WebCore::FetchBodyOwner::loadBlob):
+        (WebCore::FetchBodyOwner::blobLoadingFailed):
+        (WebCore::FetchBodyOwner::consumeBodyAsStream):
+        (WebCore::FetchBodyOwner::setLoadingError):
+        * Modules/fetch/FetchBodyOwner.h:
+        (WebCore::FetchBodyOwner::loadingError const):
+        (WebCore::FetchBodyOwner::loadingException const):
+        * Modules/fetch/FetchBodySource.cpp:
+        (WebCore::FetchBodySource::error):
+        * Modules/fetch/FetchBodySource.h:
+        * Modules/fetch/FetchRequest.cpp:
+        (WebCore::FetchRequest::initializeWith):
+        (WebCore::FetchRequest::clone):
+        * Modules/fetch/FetchRequest.h:
+        (WebCore::FetchRequest::FetchRequest):
+        * Modules/fetch/FetchRequest.idl:
+        * Modules/fetch/FetchRequestInit.h:
+        (WebCore::FetchRequestInit::hasMembers const):
+        * Modules/fetch/FetchRequestInit.idl:
+        * Modules/fetch/FetchResponse.cpp:
+        (WebCore::FetchResponse::clone):
+        (WebCore::FetchResponse::fetch):
+        (WebCore::FetchResponse::BodyLoader::didFail):
+        * Modules/fetch/FetchResponse.h:
+        * bindings/js/ReadableStreamDefaultController.h:
+        (WebCore::ReadableStreamDefaultController::error):
+        * dom/AbortSignal.cpp:
+        (WebCore::AbortSignal::abort):
+        (WebCore::AbortSignal::follow):
+        * dom/AbortSignal.h:
+
 2019-01-04  Brent Fulgham  <[email protected]>
 
         Parsed protocol of _javascript_ URLs with embedded newlines and carriage returns do not match parsed protocol in Chrome and Firefox

Modified: trunk/Source/WebCore/Modules/cache/DOMCache.cpp (239643 => 239644)


--- trunk/Source/WebCore/Modules/cache/DOMCache.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/cache/DOMCache.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -321,8 +321,8 @@
     }
     auto request = requestOrException.releaseReturnValue();
 
-    if (response->loadingError()) {
-        promise.reject(Exception { TypeError, response->loadingError()->localizedDescription() });
+    if (auto exception = response->loadingException()) {
+        promise.reject(*exception);
         return;
     }
 

Modified: trunk/Source/WebCore/Modules/fetch/FetchBody.cpp (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBody.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBody.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -186,7 +186,7 @@
         owner.loadBlob(blobBody(), nullptr);
         m_data = nullptr;
     } else if (isFormData())
-        source.error("not implemented"_s);
+        source.error(Exception { NotSupportedError, "Not implemented"_s });
     else if (m_consumer.hasData())
         closeStream = source.enqueue(m_consumer.takeAsArrayBuffer());
     else
@@ -224,9 +224,9 @@
     m_data = nullptr;
 }
 
-void FetchBody::loadingFailed()
+void FetchBody::loadingFailed(const Exception& exception)
 {
-    m_consumer.loadingFailed();
+    m_consumer.loadingFailed(exception);
 }
 
 void FetchBody::loadingSucceeded()

Modified: trunk/Source/WebCore/Modules/fetch/FetchBody.h (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBody.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBody.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -60,7 +60,7 @@
 
     WEBCORE_EXPORT static Optional<FetchBody> fromFormData(FormData&);
 
-    void loadingFailed();
+    void loadingFailed(const Exception&);
     void loadingSucceeded();
 
     RefPtr<FormData> bodyAsFormData(ScriptExecutionContext&) const;

Modified: trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -212,15 +212,15 @@
     }
 }
 
-void FetchBodyConsumer::loadingFailed()
+void FetchBodyConsumer::loadingFailed(const Exception& exception)
 {
     m_isLoading = false;
     if (m_consumePromise) {
-        m_consumePromise->reject();
+        m_consumePromise->reject(exception);
         m_consumePromise = nullptr;
     }
     if (m_source) {
-        m_source->error("Loading failed"_s);
+        m_source->error(exception);
         m_source = nullptr;
     }
 }

Modified: trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.h (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -66,7 +66,7 @@
     void resolve(Ref<DeferredPromise>&&, ReadableStream*);
     void resolveWithData(Ref<DeferredPromise>&&, const unsigned char*, unsigned);
 
-    void loadingFailed();
+    void loadingFailed(const Exception&);
     void loadingSucceeded();
 
     void setConsumePromise(Ref<DeferredPromise>&&);

Modified: trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.cpp (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -99,6 +99,11 @@
 
 void FetchBodyOwner::arrayBuffer(Ref<DeferredPromise>&& promise)
 {
+    if (auto exception = loadingException()) {
+        promise->reject(*exception);
+        return;
+    }
+
     if (isBodyNullOrOpaque()) {
         fulfillPromiseWithArrayBuffer(WTFMove(promise), nullptr, 0);
         return;
@@ -113,6 +118,11 @@
 
 void FetchBodyOwner::blob(Ref<DeferredPromise>&& promise)
 {
+    if (auto exception = loadingException()) {
+        promise->reject(*exception);
+        return;
+    }
+
     if (isBodyNullOrOpaque()) {
         promise->resolve<IDLInterface<Blob>>(Blob::create(Vector<uint8_t> { }, Blob::normalizedContentType(extractMIMETypeFromMediaType(m_contentType))));
         return;
@@ -127,6 +137,8 @@
 
 void FetchBodyOwner::cloneBody(FetchBodyOwner& owner)
 {
+    m_loadingError = owner.m_loadingError;
+
     m_contentType = owner.m_contentType;
     if (owner.isBodyNull())
         return;
@@ -161,6 +173,11 @@
 
 void FetchBodyOwner::formData(Ref<DeferredPromise>&& promise)
 {
+    if (auto exception = loadingException()) {
+        promise->reject(*exception);
+        return;
+    }
+
     if (isBodyNullOrOpaque()) {
         promise->reject();
         return;
@@ -175,6 +192,11 @@
 
 void FetchBodyOwner::json(Ref<DeferredPromise>&& promise)
 {
+    if (auto exception = loadingException()) {
+        promise->reject(*exception);
+        return;
+    }
+
     if (isBodyNullOrOpaque()) {
         promise->reject(SyntaxError);
         return;
@@ -189,6 +211,11 @@
 
 void FetchBodyOwner::text(Ref<DeferredPromise>&& promise)
 {
+    if (auto exception = loadingException()) {
+        promise->reject(*exception);
+        return;
+    }
+
     if (isBodyNullOrOpaque()) {
         promise->resolve<IDLDOMString>({ });
         return;
@@ -208,7 +235,7 @@
     ASSERT(!isBodyNull());
 
     if (!scriptExecutionContext()) {
-        m_body->loadingFailed();
+        m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
         return;
     }
 
@@ -217,7 +244,7 @@
 
     m_blobLoader->loader->start(*scriptExecutionContext(), blob);
     if (!m_blobLoader->loader->isStarted()) {
-        m_body->loadingFailed();
+        m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
         m_blobLoader = WTF::nullopt;
         return;
     }
@@ -251,11 +278,11 @@
 #if ENABLE(STREAMS_API)
     if (m_readableStreamSource) {
         if (!m_readableStreamSource->isCancelling())
-            m_readableStreamSource->error("Blob loading failed"_s);
+            m_readableStreamSource->error(Exception { TypeError, "Blob loading failed"_s});
         m_readableStreamSource = nullptr;
     } else
 #endif
-        m_body->loadingFailed();
+        m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
 
     finishBlobLoading();
 }
@@ -318,9 +345,8 @@
 {
     ASSERT(m_readableStreamSource);
 
-    if (m_loadingError) {
-        auto errorMessage = m_loadingError->localizedDescription();
-        m_readableStreamSource->error(errorMessage.isEmpty() ? "Loading failed"_s : errorMessage);
+    if (auto exception = loadingException()) {
+        m_readableStreamSource->error(*exception);
         return;
     }
 
@@ -329,4 +355,53 @@
         m_readableStreamSource = nullptr;
 }
 
+ResourceError FetchBodyOwner::loadingError() const
+{
+    return WTF::switchOn(m_loadingError, [](const ResourceError& error) {
+        return ResourceError { error };
+    }, [](const Exception& exception) {
+        return ResourceError { errorDomainWebKitInternal, 0, { }, exception.message() };
+    }, [](auto&&) {
+        return ResourceError { };
+    });
+}
+
+Optional<Exception> FetchBodyOwner::loadingException() const
+{
+    return WTF::switchOn(m_loadingError, [](const ResourceError& error) {
+        return Exception { TypeError, error.localizedDescription().isEmpty() ? "Loading failed"_s : error.localizedDescription() };
+    }, [](const Exception& exception) {
+        return Exception { exception };
+    }, [](auto&&) -> Optional<Exception> {
+        return WTF::nullopt;
+    });
+}
+
+bool FetchBodyOwner::hasLoadingError() const
+{
+    return WTF::switchOn(m_loadingError, [](const ResourceError&) {
+        return true;
+    }, [](const Exception&) {
+        return true;
+    }, [](auto&&) {
+        return false;
+    });
+}
+
+void FetchBodyOwner::setLoadingError(Exception&& exception)
+{
+    if (hasLoadingError())
+        return;
+
+    m_loadingError = WTFMove(exception);
+}
+
+void FetchBodyOwner::setLoadingError(ResourceError&& error)
+{
+    if (hasLoadingError())
+        return;
+
+    m_loadingError = WTFMove(error);
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.h (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -66,6 +66,10 @@
     virtual void cancel() { }
 #endif
 
+    bool hasLoadingError() const;
+    ResourceError loadingError() const;
+    Optional<Exception> loadingException() const;
+
 protected:
     const FetchBody& body() const { return *m_body; }
     FetchBody& body() { return *m_body; }
@@ -88,6 +92,9 @@
     void setBodyAsOpaque() { m_isBodyOpaque = true; }
     bool isBodyOpaque() const { return m_isBodyOpaque; }
 
+    void setLoadingError(Exception&&);
+    void setLoadingError(ResourceError&&);
+
 private:
     // Blob loading routines
     void blobChunk(const char*, size_t);
@@ -116,11 +123,12 @@
     RefPtr<FetchBodySource> m_readableStreamSource;
 #endif
     Ref<FetchHeaders> m_headers;
-    Optional<ResourceError> m_loadingError;
 
 private:
     Optional<BlobLoader> m_blobLoader;
     bool m_isBodyOpaque { false };
+
+    Variant<std::nullptr_t, Exception, ResourceError> m_loadingError;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/Modules/fetch/FetchBodySource.cpp (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBodySource.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBodySource.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -88,7 +88,7 @@
     m_bodyOwner = nullptr;
 }
 
-void FetchBodySource::error(const String& value)
+void FetchBodySource::error(const Exception& value)
 {
     controller().error(value);
     clean();

Modified: trunk/Source/WebCore/Modules/fetch/FetchBodySource.h (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchBodySource.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchBodySource.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -44,7 +44,7 @@
 
     bool enqueue(RefPtr<JSC::ArrayBuffer>&& chunk) { return controller().enqueue(WTFMove(chunk)); }
     void close();
-    void error(const String&);
+    void error(const Exception&);
 
     bool isCancelling() const { return m_isCancelling; }
 

Modified: trunk/Source/WebCore/Modules/fetch/FetchRequest.cpp (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchRequest.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchRequest.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -159,6 +159,9 @@
     if (optionsResult.hasException())
         return optionsResult.releaseException();
 
+    if (init.signal && init.signal.value())
+        m_signal->follow(*init.signal.value());
+
     if (init.headers) {
         auto fillResult = m_headers->fill(*init.headers);
         if (fillResult.hasException())
@@ -188,6 +191,12 @@
     if (optionsResult.hasException())
         return optionsResult.releaseException();
 
+    if (init.signal) {
+        if (init.signal.value())
+            m_signal->follow(*init.signal.value());
+    } else
+        m_signal->follow(input.m_signal);
+
     if (init.headers) {
         auto fillResult = m_headers->fill(*init.headers);
         if (fillResult.hasException())
@@ -293,6 +302,7 @@
 
     auto clone = adoptRef(*new FetchRequest(context, WTF::nullopt, FetchHeaders::create(m_headers.get()), ResourceRequest { m_request }, FetchOptions { m_options}, String { m_referrer }));
     clone->cloneBody(*this);
+    clone->m_signal->follow(m_signal);
     return WTFMove(clone);
 }
 

Modified: trunk/Source/WebCore/Modules/fetch/FetchRequest.h (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchRequest.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchRequest.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -28,6 +28,7 @@
 
 #pragma once
 
+#include "AbortSignal.h"
 #include "ExceptionOr.h"
 #include "FetchBodyOwner.h"
 #include "FetchOptions.h"
@@ -68,6 +69,7 @@
     Cache cache() const { return m_options.cache; }
     Redirect redirect() const { return m_options.redirect; }
     bool keepalive() const { return m_options.keepAlive; };
+    AbortSignal& signal() { return m_signal.get(); }
 
     const String& integrity() const { return m_options.integrity; }
 
@@ -96,6 +98,7 @@
     FetchOptions m_options;
     String m_referrer;
     mutable String m_requestURL;
+    Ref<AbortSignal> m_signal;
 };
 
 inline FetchRequest::FetchRequest(ScriptExecutionContext& context, Optional<FetchBody>&& body, Ref<FetchHeaders>&& headers, ResourceRequest&& request, FetchOptions&& options, String&& referrer)
@@ -103,6 +106,7 @@
     , m_request(WTFMove(request))
     , m_options(WTFMove(options))
     , m_referrer(WTFMove(referrer))
+    , m_signal(AbortSignal::create(context))
 {
     updateContentType();
 }

Modified: trunk/Source/WebCore/Modules/fetch/FetchRequest.idl (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchRequest.idl	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchRequest.idl	2019-01-05 00:01:43 UTC (rev 239644)
@@ -55,6 +55,7 @@
     readonly attribute FetchRequestRedirect redirect;
     readonly attribute DOMString integrity;
     [EnabledAtRuntime=FetchAPIKeepAlive] readonly attribute boolean keepalive;
+    readonly attribute AbortSignal signal;
 
     [CallWith=ScriptExecutionContext, MayThrowException, NewObject] FetchRequest clone();
 };

Modified: trunk/Source/WebCore/Modules/fetch/FetchRequestInit.h (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchRequestInit.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchRequestInit.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "AbortSignal.h"
 #include "FetchBody.h"
 #include "FetchHeaders.h"
 #include "FetchOptions.h"
@@ -46,9 +47,10 @@
     Optional<FetchOptions::Redirect> redirect;
     String integrity;
     Optional<bool> keepalive;
+    Optional<AbortSignal*> signal;
     JSC::JSValue window;
 
-    bool hasMembers() const { return !method.isEmpty() || headers || body || !referrer.isEmpty() || referrerPolicy || mode || credentials || cache || redirect || !integrity.isEmpty() || keepalive || !window.isUndefined(); }
+    bool hasMembers() const { return !method.isEmpty() || headers || body || !referrer.isEmpty() || referrerPolicy || mode || credentials || cache || redirect || !integrity.isEmpty() || keepalive || !window.isUndefined() || signal; }
 };
 
 }

Modified: trunk/Source/WebCore/Modules/fetch/FetchRequestInit.idl (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchRequestInit.idl	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchRequestInit.idl	2019-01-05 00:01:43 UTC (rev 239644)
@@ -39,5 +39,6 @@
     FetchRequestRedirect redirect;
     DOMString integrity;
     boolean keepalive;
+    AbortSignal? signal;
     any window; // can only be set to null
 };

Modified: trunk/Source/WebCore/Modules/fetch/FetchResponse.cpp (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchResponse.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchResponse.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -179,7 +179,6 @@
         m_internalResponse.setHTTPHeaderFields(HTTPHeaderMap { headers().internalHeaders() });
 
     auto clone = FetchResponse::create(context, WTF::nullopt, headers().guard(), ResourceResponse { m_internalResponse });
-    clone->m_loadingError = m_loadingError;
     clone->cloneBody(*this);
     clone->m_opaqueLoadIdentifier = m_opaqueLoadIdentifier;
     clone->m_bodySizeWithPadding = m_bodySizeWithPadding;
@@ -186,11 +185,48 @@
     return WTFMove(clone);
 }
 
+void FetchResponse::addAbortSteps(Ref<AbortSignal>&& signal)
+{
+    m_abortSignal = WTFMove(signal);
+    m_abortSignal->addAlgorithm([this, weakThis = makeWeakPtr(this)] {
+        // FIXME: Cancel request body if it is a stream.
+        if (!weakThis)
+            return;
+
+        m_abortSignal = nullptr;
+
+        setLoadingError(Exception { AbortError, "Fetch is aborted"_s });
+
+        if (m_bodyLoader) {
+            if (auto callback = m_bodyLoader->takeNotificationCallback())
+                callback(Exception { AbortError, "Fetch is aborted"_s });
+        }
+
+        if (m_readableStreamSource) {
+            if (!m_readableStreamSource->isCancelling())
+                m_readableStreamSource->error(*loadingException());
+            m_readableStreamSource = nullptr;
+        }
+        if (m_body)
+            m_body->loadingFailed(*loadingException());
+
+        if (m_bodyLoader) {
+            m_bodyLoader->stop();
+            m_bodyLoader = WTF::nullopt;
+        }
+    });
+}
+
 void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& request, NotificationCallback&& responseCallback)
 {
+    if (request.signal().aborted()) {
+        responseCallback(Exception { AbortError, "Request signal is aborted"_s });
+        // FIXME: Cancel request body if it is a stream.
+        return;
+    }
+
     if (request.hasReadableStreamBody()) {
-        if (responseCallback)
-            responseCallback(Exception { NotSupportedError, "ReadableStream uploading is not supported" });
+        responseCallback(Exception { NotSupportedError, "ReadableStream uploading is not supported"_s });
         return;
     }
     auto response = adoptRef(*new FetchResponse(context, FetchBody { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { }));
@@ -197,6 +233,8 @@
 
     response->body().consumer().setAsLoading();
 
+    response->addAbortSteps(request.signal());
+
     response->m_bodyLoader.emplace(response.get(), WTFMove(responseCallback));
     if (!response->m_bodyLoader->start(context, request))
         response->m_bodyLoader = WTF::nullopt;
@@ -245,7 +283,7 @@
 {
     ASSERT(m_response.hasPendingActivity());
 
-    m_response.m_loadingError = error;
+    m_response.setLoadingError(ResourceError { error });
 
     if (auto responseCallback = WTFMove(m_responseCallback))
         responseCallback(Exception { TypeError, error.localizedDescription() });
@@ -256,7 +294,7 @@
 #if ENABLE(STREAMS_API)
     if (m_response.m_readableStreamSource) {
         if (!m_response.m_readableStreamSource->isCancelling())
-            m_response.m_readableStreamSource->error(makeString("Loading failed: "_s, error.localizedDescription()));
+            m_response.m_readableStreamSource->error(*m_response.loadingException());
         m_response.m_readableStreamSource = nullptr;
     }
 #endif

Modified: trunk/Source/WebCore/Modules/fetch/FetchResponse.h (239643 => 239644)


--- trunk/Source/WebCore/Modules/fetch/FetchResponse.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/Modules/fetch/FetchResponse.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -33,6 +33,7 @@
 #include "ReadableStreamSink.h"
 #include "ResourceResponse.h"
 #include <_javascript_Core/TypedArrays.h>
+#include <wtf/WeakPtr.h>
 
 namespace JSC {
 class ExecState;
@@ -41,11 +42,12 @@
 
 namespace WebCore {
 
+class AbortSignal;
 class FetchRequest;
 struct ReadableStreamChunk;
 class ReadableStreamSource;
 
-class FetchResponse final : public FetchBodyOwner {
+class FetchResponse final : public FetchBodyOwner, public CanMakeWeakPtr<FetchResponse> {
 public:
     using Type = ResourceResponse::Type;
 
@@ -107,8 +109,6 @@
 
     void initializeOpaqueLoadIdentifierForTesting() { m_opaqueLoadIdentifier = 1; }
 
-    const Optional<ResourceError>& loadingError() const { return m_loadingError; }
-
     const HTTPHeaderMap& internalResponseHeaders() const { return m_internalResponse.httpHeaderFields(); }
 
 private:
@@ -124,6 +124,8 @@
     void closeStream();
 #endif
 
+    void addAbortSteps(Ref<AbortSignal>&&);
+
     class BodyLoader final : public FetchLoaderClient {
     public:
         BodyLoader(FetchResponse&, NotificationCallback&&);
@@ -137,6 +139,7 @@
 #if ENABLE(STREAMS_API)
         RefPtr<SharedBuffer> startStreaming();
 #endif
+        NotificationCallback takeNotificationCallback() { return WTFMove(m_responseCallback); }
 
     private:
         // FetchLoaderClient API
@@ -158,6 +161,7 @@
     // Opaque responses will padd their body size when used with Cache API.
     uint64_t m_bodySizeWithPadding { 0 };
     uint64_t m_opaqueLoadIdentifier { 0 };
+    RefPtr<AbortSignal> m_abortSignal;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/bindings/js/ReadableStreamDefaultController.h (239643 => 239644)


--- trunk/Source/WebCore/bindings/js/ReadableStreamDefaultController.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/bindings/js/ReadableStreamDefaultController.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -49,8 +49,7 @@
 
     bool enqueue(RefPtr<JSC::ArrayBuffer>&&);
 
-    template<class ResolveResultType>
-    void error(const ResolveResultType&);
+    void error(const Exception&);
 
     void close() { invoke(*globalObject().globalExec(), jsController(), "close", JSC::jsUndefined()); }
 
@@ -104,12 +103,11 @@
     return true;
 }
 
-template<>
-inline void ReadableStreamDefaultController::error<String>(const String& errorMessage)
+inline void ReadableStreamDefaultController::error(const Exception& exception)
 {
     JSC::ExecState& state = globalExec();
     JSC::JSLockHolder locker(&state);
-    error(state, JSC::createTypeError(&state, errorMessage));
+    error(state, createDOMException(&state, exception.code(), exception.message()));
 }
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/dom/AbortSignal.cpp (239643 => 239644)


--- trunk/Source/WebCore/dom/AbortSignal.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/dom/AbortSignal.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -37,7 +37,6 @@
     return adoptRef(*new AbortSignal(context));
 }
 
-
 AbortSignal::AbortSignal(ScriptExecutionContext& context)
     : ContextDestructionObserver(&context)
 {
@@ -52,13 +51,32 @@
     
     // 2. Set signal’s aborted flag.
     m_aborted = true;
-    
-    // 3. For each algorithm in signal's abort algorithms: run algorithm.
-    // 4. Empty signal's abort algorithms.
-    // FIXME: Add support for 'abort algorithms' - https://dom.spec.whatwg.org/#abortsignal-abort-algorithms
 
+    auto protectedThis = makeRef(*this);
+    auto algorithms = WTFMove(m_algorithms);
+    for (auto& algorithm : algorithms)
+        algorithm();
+
     // 5. Fire an event named abort at signal.
     dispatchEvent(Event::create(eventNames().abortEvent, Event::CanBubble::No, Event::IsCancelable::No));
 }
 
+// https://dom.spec.whatwg.org/#abortsignal-follow
+void AbortSignal::follow(AbortSignal& signal)
+{
+    if (aborted())
+        return;
+
+    if (signal.aborted()) {
+        abort();
+        return;
+    }
+
+    signal.addAlgorithm([weakThis = makeWeakPtr(this)] {
+        if (!weakThis)
+            return;
+        weakThis->abort();
+    });
 }
+
+}

Modified: trunk/Source/WebCore/dom/AbortSignal.h (239643 => 239644)


--- trunk/Source/WebCore/dom/AbortSignal.h	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/dom/AbortSignal.h	2019-01-05 00:01:43 UTC (rev 239644)
@@ -27,14 +27,16 @@
 
 #include "ContextDestructionObserver.h"
 #include "EventTarget.h"
+#include <wtf/Function.h>
 #include <wtf/Ref.h>
 #include <wtf/RefCounted.h>
+#include <wtf/WeakPtr.h>
 
 namespace WebCore {
 
 class ScriptExecutionContext;
 
-class AbortSignal final : public RefCounted<AbortSignal>, public EventTargetWithInlineData, private ContextDestructionObserver {
+class AbortSignal final : public RefCounted<AbortSignal>, public EventTargetWithInlineData, public CanMakeWeakPtr<AbortSignal>, private ContextDestructionObserver {
 public:
     static Ref<AbortSignal> create(ScriptExecutionContext&);
 
@@ -45,6 +47,11 @@
     using RefCounted::ref;
     using RefCounted::deref;
 
+    using Algorithm = WTF::Function<void()>;
+    void addAlgorithm(Algorithm&& algorithm) { m_algorithms.append(WTFMove(algorithm)); }
+
+    void follow(AbortSignal&);
+
 private:
     explicit AbortSignal(ScriptExecutionContext&);
 
@@ -54,6 +61,7 @@
     void derefEventTarget() final { deref(); }
     
     bool m_aborted { false };
+    Vector<Algorithm> m_algorithms;
 };
 
 }

Modified: trunk/Source/WebCore/workers/service/context/ServiceWorkerFetch.cpp (239643 => 239644)


--- trunk/Source/WebCore/workers/service/context/ServiceWorkerFetch.cpp	2019-01-04 23:53:22 UTC (rev 239643)
+++ trunk/Source/WebCore/workers/service/context/ServiceWorkerFetch.cpp	2019-01-05 00:01:43 UTC (rev 239644)
@@ -53,8 +53,9 @@
 
     client->didReceiveResponse(response->resourceResponse());
 
-    if (response->loadingError()) {
-        client->didFail(*response->loadingError());
+    auto loadingError = response->loadingError();
+    if (!loadingError.isNull()) {
+        client->didFail(loadingError);
         return;
     }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to