Changeset 239644 in webkit


Ignore:
Timestamp:
Jan 4, 2019 4:01:43 PM (5 years ago)
Author:
youenn@apple.com
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:

Location:
trunk
Files:
32 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r239642 r239644  
     12019-01-04  Youenn Fablet  <youenn@apple.com>
     2
     3        [Fetch API] Implement abortable fetch
     4        https://bugs.webkit.org/show_bug.cgi?id=174980
     5        <rdar://problem/46861402>
     6
     7        Reviewed by Chris Dumez.
     8
     9        * TestExpectations: Enable abort tests.
     10
    1112019-01-04  Brent Fulgham  <bfulgham@apple.com>
    212
  • trunk/LayoutTests/TestExpectations

    r239574 r239644  
    212212
    213213# Skip service worker tests that are timing out.
    214 imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https.html [ Skip ]
    215214imported/w3c/web-platform-tests/service-workers/service-worker/performance-timeline.https.html [ Skip ]
    216215imported/w3c/web-platform-tests/service-workers/service-worker/respond-with-body-accessed-response.https.html [ Skip ]
     
    383382webkit.org/b/189910 imported/w3c/web-platform-tests/resource-timing/resource_timing_store_and_clear_during_callback.html [ Pass Failure ]
    384383webkit.org/b/190523 imported/w3c/web-platform-tests/resource-timing/resource_timing_cross_origin_redirect_chain.html [ Pass Failure ]
    385 
    386 # The follow two tests change their output each run
    387 imported/w3c/web-platform-tests/fetch/api/abort/general.any.html [ Skip ]
    388 imported/w3c/web-platform-tests/fetch/api/abort/general.any.worker.html [ Skip ]
    389384
    390385# These tests time out
  • trunk/LayoutTests/imported/w3c/ChangeLog

    r239574 r239644  
     12019-01-04  Youenn Fablet  <youenn@apple.com>
     2
     3        [Fetch API] Implement abortable fetch
     4        https://bugs.webkit.org/show_bug.cgi?id=174980
     5        <rdar://problem/46861402>
     6
     7        Reviewed by Chris Dumez.
     8
     9        Fixed tests to run in WebKit CI.
     10        Also fixed a bug in a test where the fetch response body is not actually empty.
     11
     12        * web-platform-tests/fetch/api/abort/cache.https-expected.txt:
     13        * web-platform-tests/fetch/api/abort/general-serviceworker.https-expected.txt:
     14        * web-platform-tests/fetch/api/abort/general.any-expected.txt:
     15        * web-platform-tests/fetch/api/abort/general.any.js:
     16        * web-platform-tests/fetch/api/abort/general.any.worker-expected.txt:
     17        * web-platform-tests/fetch/api/abort/serviceworker-intercepted.https-expected.txt:
     18        * web-platform-tests/fetch/api/response/response-consume-stream-expected.txt:
     19
    1202019-01-02  Simon Fraser  <simon.fraser@apple.com>
    221
  • trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/cache.https-expected.txt

    r222692 r239644  
    11
    2 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')"
    3 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')"
     2PASS Signals are not stored in the cache API
     3PASS Signals are not stored in the cache API, even if they're already aborted
    44
  • trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https-expected.txt

    r224218 r239644  
    11
    2 Harness Error (TIMEOUT), message = null
    3 
    4 FAIL Aborting rejects with AbortError assert_unreached: Should have rejected: undefined Reached unreachable code
    5 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
     2PASS Aborting rejects with AbortError
     3PASS Aborting rejects with AbortError - no-cors
    64PASS TypeError from request constructor takes priority - RequestInit's window is not null
    75PASS TypeError from request constructor takes priority - Input URL is not valid
     
    2018PASS TypeError from request constructor takes priority - Bad cache init parameter value
    2119PASS TypeError from request constructor takes priority - Bad redirect init parameter value
    22 FAIL Request objects have a signal property assert_true: Signal member is present & truthy expected true got false
    23 FAIL Signal on request object assert_true: Signal member is present & truthy expected true got false
    24 FAIL Signal on request object created from request object assert_unreached: Should have rejected: undefined Reached unreachable code
    25 FAIL Signal on request object created from request object, with signal on second request assert_unreached: Should have rejected: undefined Reached unreachable code
    26 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
    27 FAIL Signal retained after unrelated properties are overridden by fetch assert_unreached: Should have rejected: undefined Reached unreachable code
     20PASS Request objects have a signal property
     21PASS Signal on request object
     22PASS Signal on request object created from request object
     23PASS Signal on request object created from request object, with signal on second request
     24PASS Signal on request object created from request object, with signal on second request overriding another
     25PASS Signal retained after unrelated properties are overridden by fetch
    2826PASS Signal removed by setting to null
    29 FAIL Already aborted signal rejects immediately assert_unreached: Fetch must not resolve Reached unreachable code
     27PASS Already aborted signal rejects immediately
    3028PASS Request is still 'used' if signal is aborted before fetching
    31 FAIL response.arrayBuffer() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    32 FAIL response.blob() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    33 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
    34 FAIL response.json() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    35 FAIL response.text() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    36 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"
    37 FAIL Already aborted signal can be used for many fetches assert_unreached: Should have rejected: undefined Reached unreachable code
    38 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
    39 FAIL Underlying connection is closed when aborting after receiving response promise_test: Unhandled rejection with value: object "Error: Timed out"
    40 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."
    41 TIMEOUT Fetch aborted & connection closed when aborted after calling response.arrayBuffer() Test timed out
    42 NOTRUN Fetch aborted & connection closed when aborted after calling response.blob()
    43 NOTRUN Fetch aborted & connection closed when aborted after calling response.formData()
    44 NOTRUN Fetch aborted & connection closed when aborted after calling response.json()
    45 NOTRUN Fetch aborted & connection closed when aborted after calling response.text()
    46 NOTRUN Stream errors once aborted. Underlying connection closed.
    47 NOTRUN Stream errors once aborted, after reading. Underlying connection closed.
    48 NOTRUN Stream will not error if body is empty. It's closed with an empty queue before it errors.
    49 NOTRUN Readable stream synchronously cancels with AbortError if aborted before reading
    50 FAIL Signal state is cloned undefined is not an object (evaluating 'request.signal.aborted')
    51 FAIL Clone aborts with original controller undefined is not an object (evaluating 'request.signal.addEventListener')
     29PASS response.arrayBuffer() rejects if already aborted
     30PASS response.blob() rejects if already aborted
     31PASS response.formData() rejects if already aborted
     32PASS response.json() rejects if already aborted
     33PASS response.text() rejects if already aborted
     34PASS Already aborted signal does not make request
     35PASS Already aborted signal can be used for many fetches
     36PASS Signal can be used to abort other fetches, even if another fetch succeeded before aborting
     37PASS Underlying connection is closed when aborting after receiving response
     38PASS Underlying connection is closed when aborting after receiving response - no-cors
     39PASS Fetch aborted & connection closed when aborted after calling response.arrayBuffer()
     40PASS Fetch aborted & connection closed when aborted after calling response.blob()
     41FAIL 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
     42PASS Fetch aborted & connection closed when aborted after calling response.json()
     43PASS Fetch aborted & connection closed when aborted after calling response.text()
     44PASS Stream errors once aborted. Underlying connection closed.
     45PASS Stream errors once aborted, after reading. Underlying connection closed.
     46PASS Stream will not error if body is empty. It's closed with an empty queue before it errors.
     47FAIL Readable stream synchronously cancels with AbortError if aborted before reading assert_true: Cancel called sync expected true got false
     48PASS Signal state is cloned
     49PASS Clone aborts with original controller
    5250
  • trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any-expected.txt

    r222692 r239644  
    1 Blocked access to external URL http://www1.localhost:8800/fetch/api/resources/data.json
    2 CONSOLE MESSAGE: line 36: Fetch API cannot load http://www1.localhost:8800/fetch/api/resources/data.json due to access control checks.
    3 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
    4 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.
     1CONSOLE MESSAGE: Unhandled Promise Rejection: AbortError: Request signal is aborted
    52
    6 Harness Error (TIMEOUT), message = null
    7 
    8 FAIL Aborting rejects with AbortError assert_unreached: Should have rejected: undefined Reached unreachable code
    9 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
     3PASS Aborting rejects with AbortError
     4PASS Aborting rejects with AbortError - no-cors
    105PASS TypeError from request constructor takes priority - RequestInit's window is not null
    116PASS TypeError from request constructor takes priority - Input URL is not valid
     
    1611PASS TypeError from request constructor takes priority - RequestInit's method is forbidden
    1712PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and method is not simple
    18 PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and integrity is not empty
    1913PASS TypeError from request constructor takes priority - RequestInit's cache mode is only-if-cached and mode is not same-origin
    2014PASS TypeError from request constructor takes priority - Request with cache mode: only-if-cached and fetch mode cors
     
    2519PASS TypeError from request constructor takes priority - Bad cache init parameter value
    2620PASS TypeError from request constructor takes priority - Bad redirect init parameter value
    27 FAIL Request objects have a signal property assert_true: Signal member is present & truthy expected true got false
    28 FAIL Signal on request object assert_true: Signal member is present & truthy expected true got false
    29 FAIL Signal on request object created from request object assert_unreached: Should have rejected: undefined Reached unreachable code
    30 FAIL Signal on request object created from request object, with signal on second request assert_unreached: Should have rejected: undefined Reached unreachable code
    31 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
    32 FAIL Signal retained after unrelated properties are overridden by fetch assert_unreached: Should have rejected: undefined Reached unreachable code
     21PASS Request objects have a signal property
     22PASS Signal on request object
     23PASS Signal on request object created from request object
     24PASS Signal on request object created from request object, with signal on second request
     25PASS Signal on request object created from request object, with signal on second request overriding another
     26PASS Signal retained after unrelated properties are overridden by fetch
    3327PASS Signal removed by setting to null
    34 FAIL Already aborted signal rejects immediately assert_unreached: Fetch must not resolve Reached unreachable code
     28PASS Already aborted signal rejects immediately
    3529PASS Request is still 'used' if signal is aborted before fetching
    36 FAIL response.arrayBuffer() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    37 FAIL response.blob() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    38 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
    39 FAIL response.json() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    40 FAIL response.text() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    41 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"
    42 FAIL Already aborted signal can be used for many fetches assert_unreached: Should have rejected: undefined Reached unreachable code
    43 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
    44 FAIL Underlying connection is closed when aborting after receiving response promise_test: Unhandled rejection with value: object "Error: Timed out"
    45 FAIL Underlying connection is closed when aborting after receiving response - no-cors promise_test: Unhandled rejection with value: object "TypeError: Type error"
    46 TIMEOUT Fetch aborted & connection closed when aborted after calling response.arrayBuffer() Test timed out
    47 NOTRUN Fetch aborted & connection closed when aborted after calling response.blob()
    48 NOTRUN Fetch aborted & connection closed when aborted after calling response.formData()
    49 NOTRUN Fetch aborted & connection closed when aborted after calling response.json()
    50 NOTRUN Fetch aborted & connection closed when aborted after calling response.text()
    51 NOTRUN Stream errors once aborted. Underlying connection closed.
    52 NOTRUN Stream errors once aborted, after reading. Underlying connection closed.
    53 NOTRUN Stream will not error if body is empty. It's closed with an empty queue before it errors.
    54 NOTRUN Readable stream synchronously cancels with AbortError if aborted before reading
    55 FAIL Signal state is cloned undefined is not an object (evaluating 'request.signal.aborted')
    56 FAIL Clone aborts with original controller undefined is not an object (evaluating 'request.signal.addEventListener')
     30PASS response.arrayBuffer() rejects if already aborted
     31PASS response.blob() rejects if already aborted
     32PASS response.formData() rejects if already aborted
     33PASS response.json() rejects if already aborted
     34PASS response.text() rejects if already aborted
     35PASS Already aborted signal does not make request
     36PASS Already aborted signal can be used for many fetches
     37PASS Signal can be used to abort other fetches, even if another fetch succeeded before aborting
     38PASS Underlying connection is closed when aborting after receiving response
     39PASS Underlying connection is closed when aborting after receiving response - no-cors
     40PASS Fetch aborted & connection closed when aborted after calling response.arrayBuffer()
     41PASS Fetch aborted & connection closed when aborted after calling response.blob()
     42FAIL 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
     43PASS Fetch aborted & connection closed when aborted after calling response.json()
     44PASS Fetch aborted & connection closed when aborted after calling response.text()
     45PASS Stream errors once aborted. Underlying connection closed.
     46PASS Stream errors once aborted, after reading. Underlying connection closed.
     47PASS Stream will not error if body is empty. It's closed with an empty queue before it errors.
     48FAIL Readable stream synchronously cancels with AbortError if aborted before reading assert_true: Cancel called sync expected true got false
     49PASS Signal state is cloned
     50PASS Clone aborts with original controller
    5751
  • trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.js

    r230330 r239644  
    11// META: script=/common/utils.js
     2// META: script=/common/get-host-info.sub.js
    23// META: script=../request/request-error.js
    34
     
    1617}
    1718
     19const hostInfo = get_host_info();
     20const urlHostname = hostInfo.REMOTE_HOST;
     21
    1822promise_test(async t => {
    1923  const controller = new AbortController();
     
    3236
    3337  const url = new URL('../resources/data.json', location);
    34   url.hostname = 'www1.' + url.hostname;
     38  url.hostname = urlHostname;
    3539
    3640  const fetchPromise = fetch(url, {
     
    315319
    316320  const url = new URL(`../resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, location);
    317   url.hostname = 'www1.' + url.hostname;
     321  url.hostname = urlHostname;
    318322
    319323  await fetch(url, {
     
    323327
    324328  const stashTakeURL = new URL(`../resources/stash-take.py?key=${stateKey}`, location);
    325   stashTakeURL.hostname = 'www1.' + stashTakeURL.hostname;
     329  stashTakeURL.hostname = urlHostname;
    326330
    327331  const beforeAbortResult = await fetch(stashTakeURL).then(r => r.json());
     
    441445  const signal = controller.signal;
    442446
    443   const response = await fetch(`../resources/empty.txt`, { signal });
     447  const response = await fetch(`../resources/method.py`, { signal });
    444448
    445449  // Read whole response to ensure close signal has sent.
  • trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/general.any.worker-expected.txt

    r222692 r239644  
    1 Blocked access to external URL http://www1.localhost:8800/fetch/api/resources/data.json
    2 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
    31
    4 Harness Error (TIMEOUT), message = null
    5 
    6 FAIL Aborting rejects with AbortError assert_unreached: Should have rejected: undefined Reached unreachable code
    7 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
     2PASS Aborting rejects with AbortError
     3PASS Aborting rejects with AbortError - no-cors
    84PASS TypeError from request constructor takes priority - RequestInit's window is not null
    95PASS TypeError from request constructor takes priority - Input URL is not valid
     
    1410PASS TypeError from request constructor takes priority - RequestInit's method is forbidden
    1511PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and method is not simple
    16 PASS TypeError from request constructor takes priority - RequestInit's mode is no-cors and integrity is not empty
    1712PASS TypeError from request constructor takes priority - RequestInit's cache mode is only-if-cached and mode is not same-origin
    1813PASS TypeError from request constructor takes priority - Request with cache mode: only-if-cached and fetch mode cors
     
    2318PASS TypeError from request constructor takes priority - Bad cache init parameter value
    2419PASS TypeError from request constructor takes priority - Bad redirect init parameter value
    25 FAIL Request objects have a signal property assert_true: Signal member is present & truthy expected true got false
    26 FAIL Signal on request object assert_true: Signal member is present & truthy expected true got false
    27 FAIL Signal on request object created from request object assert_unreached: Should have rejected: undefined Reached unreachable code
    28 FAIL Signal on request object created from request object, with signal on second request assert_unreached: Should have rejected: undefined Reached unreachable code
    29 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
    30 FAIL Signal retained after unrelated properties are overridden by fetch assert_unreached: Should have rejected: undefined Reached unreachable code
     20PASS Request objects have a signal property
     21PASS Signal on request object
     22PASS Signal on request object created from request object
     23PASS Signal on request object created from request object, with signal on second request
     24PASS Signal on request object created from request object, with signal on second request overriding another
     25PASS Signal retained after unrelated properties are overridden by fetch
    3126PASS Signal removed by setting to null
    32 FAIL Already aborted signal rejects immediately assert_unreached: Fetch must not resolve Reached unreachable code
     27PASS Already aborted signal rejects immediately
    3328PASS Request is still 'used' if signal is aborted before fetching
    34 FAIL response.arrayBuffer() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    35 FAIL response.blob() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    36 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
    37 FAIL response.json() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    38 FAIL response.text() rejects if already aborted assert_unreached: Should have rejected: undefined Reached unreachable code
    39 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"
    40 FAIL Already aborted signal can be used for many fetches assert_unreached: Should have rejected: undefined Reached unreachable code
    41 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
    42 FAIL Underlying connection is closed when aborting after receiving response promise_test: Unhandled rejection with value: object "Error: Timed out"
    43 FAIL Underlying connection is closed when aborting after receiving response - no-cors promise_test: Unhandled rejection with value: object "TypeError: Type error"
    44 TIMEOUT Fetch aborted & connection closed when aborted after calling response.arrayBuffer() Test timed out
    45 NOTRUN Fetch aborted & connection closed when aborted after calling response.blob()
    46 NOTRUN Fetch aborted & connection closed when aborted after calling response.formData()
    47 NOTRUN Fetch aborted & connection closed when aborted after calling response.json()
    48 NOTRUN Fetch aborted & connection closed when aborted after calling response.text()
    49 NOTRUN Stream errors once aborted. Underlying connection closed.
    50 NOTRUN Stream errors once aborted, after reading. Underlying connection closed.
    51 NOTRUN Stream will not error if body is empty. It's closed with an empty queue before it errors.
    52 NOTRUN Readable stream synchronously cancels with AbortError if aborted before reading
    53 FAIL Signal state is cloned undefined is not an object (evaluating 'request.signal.aborted')
    54 FAIL Clone aborts with original controller undefined is not an object (evaluating 'request.signal.addEventListener')
     29PASS response.arrayBuffer() rejects if already aborted
     30PASS response.blob() rejects if already aborted
     31PASS response.formData() rejects if already aborted
     32PASS response.json() rejects if already aborted
     33PASS response.text() rejects if already aborted
     34PASS Already aborted signal does not make request
     35PASS Already aborted signal can be used for many fetches
     36PASS Signal can be used to abort other fetches, even if another fetch succeeded before aborting
     37PASS Underlying connection is closed when aborting after receiving response
     38PASS Underlying connection is closed when aborting after receiving response - no-cors
     39PASS Fetch aborted & connection closed when aborted after calling response.arrayBuffer()
     40PASS Fetch aborted & connection closed when aborted after calling response.blob()
     41FAIL 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
     42PASS Fetch aborted & connection closed when aborted after calling response.json()
     43PASS Fetch aborted & connection closed when aborted after calling response.text()
     44PASS Stream errors once aborted. Underlying connection closed.
     45PASS Stream errors once aborted, after reading. Underlying connection closed.
     46PASS Stream will not error if body is empty. It's closed with an empty queue before it errors.
     47FAIL Readable stream synchronously cancels with AbortError if aborted before reading assert_true: Cancel called sync expected true got false
     48PASS Signal state is cloned
     49PASS Clone aborts with original controller
    5550
  • trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/abort/serviceworker-intercepted.https-expected.txt

    r224947 r239644  
    11
    2 FAIL Already aborted request does not land in service worker assert_unreached: Should have rejected: undefined Reached unreachable code
    3 FAIL response.arrayBuffer() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
    4 FAIL response.blob() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
    5 FAIL response.formData() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
    6 FAIL response.json() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
    7 FAIL response.text() rejects if already aborted promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
    8 FAIL Stream errors once aborted. promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
    92
     3PASS Already aborted request does not land in service worker
     4PASS response.arrayBuffer() rejects if already aborted
     5PASS response.blob() rejects if already aborted
     6PASS response.formData() rejects if already aborted
     7PASS response.json() rejects if already aborted
     8PASS response.text() rejects if already aborted
     9FAIL Stream errors once aborted. assert_unreached: Should have rejected: undefined Reached unreachable code
     10
  • trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/response/response-consume-stream-expected.txt

    r222307 r239644  
    66PASS Read URLSearchParams response's body as readableStream
    77PASS Read array buffer response's body as readableStream
    8 FAIL Read form data response's body as readableStream promise_test: Unhandled rejection with value: object "TypeError: not implemented"
     8FAIL Read form data response's body as readableStream promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
    99PASS Getting an error Response stream
    1010PASS Getting a redirect Response stream
  • trunk/LayoutTests/platform/ios-simulator/TestExpectations

    r238592 r239644  
    6868webkit.org/b/174079 fast/text/variations/skia-postscript-name.html [ ImageOnlyFailure ]
    6969
     70imported/w3c/web-platform-tests/fetch/api/abort/general-serviceworker.https.html [ Pass Failure ]
    7071imported/w3c/web-platform-tests/2dcontext/transformations/canvas_transformations_reset_001.html [ ImageOnlyFailure ]
    7172imported/w3c/web-platform-tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html [ Failure ]
  • trunk/Source/WebCore/ChangeLog

    r239642 r239644  
     12019-01-04  Youenn Fablet  <youenn@apple.com>
     2
     3        [Fetch API] Implement abortable fetch
     4        https://bugs.webkit.org/show_bug.cgi?id=174980
     5        <rdar://problem/46861402>
     6
     7        Reviewed by Chris Dumez.
     8
     9        Add an AbortSignal to FetchRequest.
     10
     11        Add support for AbortSignal algorithm.
     12        The fetch request signal is added an algorithm to abort the fetch.
     13        Update clone algorithm to let signal of the cloned request be following the origin request.
     14
     15        Update ReadableStream error handling to return an exception instead of a string.
     16        This allows passing an AbortError instead of a TypeError as previously done.
     17
     18        Update FetchBodyOwner to store a loading error either as an exception or as a resource error.
     19        The latter is used for passing the error from service worker back to the page.
     20        The former is used to pass it to ReadableStream or body accessors.
     21
     22        Covered by enabled tests.
     23
     24        * Modules/cache/DOMCache.cpp:
     25        (WebCore::DOMCache::put):
     26        * Modules/fetch/FetchBody.cpp:
     27        (WebCore::FetchBody::consumeAsStream):
     28        (WebCore::FetchBody::loadingFailed):
     29        * Modules/fetch/FetchBody.h:
     30        * Modules/fetch/FetchBodyConsumer.cpp:
     31        (WebCore::FetchBodyConsumer::loadingFailed):
     32        * Modules/fetch/FetchBodyConsumer.h:
     33        * Modules/fetch/FetchBodyOwner.cpp:
     34        (WebCore::FetchBodyOwner::arrayBuffer):
     35        (WebCore::FetchBodyOwner::blob):
     36        (WebCore::FetchBodyOwner::cloneBody):
     37        (WebCore::FetchBodyOwner::formData):
     38        (WebCore::FetchBodyOwner::json):
     39        (WebCore::FetchBodyOwner::text):
     40        (WebCore::FetchBodyOwner::loadBlob):
     41        (WebCore::FetchBodyOwner::blobLoadingFailed):
     42        (WebCore::FetchBodyOwner::consumeBodyAsStream):
     43        (WebCore::FetchBodyOwner::setLoadingError):
     44        * Modules/fetch/FetchBodyOwner.h:
     45        (WebCore::FetchBodyOwner::loadingError const):
     46        (WebCore::FetchBodyOwner::loadingException const):
     47        * Modules/fetch/FetchBodySource.cpp:
     48        (WebCore::FetchBodySource::error):
     49        * Modules/fetch/FetchBodySource.h:
     50        * Modules/fetch/FetchRequest.cpp:
     51        (WebCore::FetchRequest::initializeWith):
     52        (WebCore::FetchRequest::clone):
     53        * Modules/fetch/FetchRequest.h:
     54        (WebCore::FetchRequest::FetchRequest):
     55        * Modules/fetch/FetchRequest.idl:
     56        * Modules/fetch/FetchRequestInit.h:
     57        (WebCore::FetchRequestInit::hasMembers const):
     58        * Modules/fetch/FetchRequestInit.idl:
     59        * Modules/fetch/FetchResponse.cpp:
     60        (WebCore::FetchResponse::clone):
     61        (WebCore::FetchResponse::fetch):
     62        (WebCore::FetchResponse::BodyLoader::didFail):
     63        * Modules/fetch/FetchResponse.h:
     64        * bindings/js/ReadableStreamDefaultController.h:
     65        (WebCore::ReadableStreamDefaultController::error):
     66        * dom/AbortSignal.cpp:
     67        (WebCore::AbortSignal::abort):
     68        (WebCore::AbortSignal::follow):
     69        * dom/AbortSignal.h:
     70
    1712019-01-04  Brent Fulgham  <bfulgham@apple.com>
    272
  • trunk/Source/WebCore/Modules/cache/DOMCache.cpp

    r239427 r239644  
    322322    auto request = requestOrException.releaseReturnValue();
    323323
    324     if (response->loadingError()) {
    325         promise.reject(Exception { TypeError, response->loadingError()->localizedDescription() });
     324    if (auto exception = response->loadingException()) {
     325        promise.reject(*exception);
    326326        return;
    327327    }
  • trunk/Source/WebCore/Modules/fetch/FetchBody.cpp

    r239535 r239644  
    187187        m_data = nullptr;
    188188    } else if (isFormData())
    189         source.error("not implemented"_s);
     189        source.error(Exception { NotSupportedError, "Not implemented"_s });
    190190    else if (m_consumer.hasData())
    191191        closeStream = source.enqueue(m_consumer.takeAsArrayBuffer());
     
    225225}
    226226
    227 void FetchBody::loadingFailed()
    228 {
    229     m_consumer.loadingFailed();
     227void FetchBody::loadingFailed(const Exception& exception)
     228{
     229    m_consumer.loadingFailed(exception);
    230230}
    231231
  • trunk/Source/WebCore/Modules/fetch/FetchBody.h

    r239427 r239644  
    6161    WEBCORE_EXPORT static Optional<FetchBody> fromFormData(FormData&);
    6262
    63     void loadingFailed();
     63    void loadingFailed(const Exception&);
    6464    void loadingSucceeded();
    6565
  • trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.cpp

    r233122 r239644  
    213213}
    214214
    215 void FetchBodyConsumer::loadingFailed()
     215void FetchBodyConsumer::loadingFailed(const Exception& exception)
    216216{
    217217    m_isLoading = false;
    218218    if (m_consumePromise) {
    219         m_consumePromise->reject();
     219        m_consumePromise->reject(exception);
    220220        m_consumePromise = nullptr;
    221221    }
    222222    if (m_source) {
    223         m_source->error("Loading failed"_s);
     223        m_source->error(exception);
    224224        m_source = nullptr;
    225225    }
  • trunk/Source/WebCore/Modules/fetch/FetchBodyConsumer.h

    r224956 r239644  
    6767    void resolveWithData(Ref<DeferredPromise>&&, const unsigned char*, unsigned);
    6868
    69     void loadingFailed();
     69    void loadingFailed(const Exception&);
    7070    void loadingSucceeded();
    7171
  • trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.cpp

    r239427 r239644  
    100100void FetchBodyOwner::arrayBuffer(Ref<DeferredPromise>&& promise)
    101101{
     102    if (auto exception = loadingException()) {
     103        promise->reject(*exception);
     104        return;
     105    }
     106
    102107    if (isBodyNullOrOpaque()) {
    103108        fulfillPromiseWithArrayBuffer(WTFMove(promise), nullptr, 0);
     
    114119void FetchBodyOwner::blob(Ref<DeferredPromise>&& promise)
    115120{
     121    if (auto exception = loadingException()) {
     122        promise->reject(*exception);
     123        return;
     124    }
     125
    116126    if (isBodyNullOrOpaque()) {
    117127        promise->resolve<IDLInterface<Blob>>(Blob::create(Vector<uint8_t> { }, Blob::normalizedContentType(extractMIMETypeFromMediaType(m_contentType))));
     
    128138void FetchBodyOwner::cloneBody(FetchBodyOwner& owner)
    129139{
     140    m_loadingError = owner.m_loadingError;
     141
    130142    m_contentType = owner.m_contentType;
    131143    if (owner.isBodyNull())
     
    162174void FetchBodyOwner::formData(Ref<DeferredPromise>&& promise)
    163175{
     176    if (auto exception = loadingException()) {
     177        promise->reject(*exception);
     178        return;
     179    }
     180
    164181    if (isBodyNullOrOpaque()) {
    165182        promise->reject();
     
    176193void FetchBodyOwner::json(Ref<DeferredPromise>&& promise)
    177194{
     195    if (auto exception = loadingException()) {
     196        promise->reject(*exception);
     197        return;
     198    }
     199
    178200    if (isBodyNullOrOpaque()) {
    179201        promise->reject(SyntaxError);
     
    190212void FetchBodyOwner::text(Ref<DeferredPromise>&& promise)
    191213{
     214    if (auto exception = loadingException()) {
     215        promise->reject(*exception);
     216        return;
     217    }
     218
    192219    if (isBodyNullOrOpaque()) {
    193220        promise->resolve<IDLDOMString>({ });
     
    209236
    210237    if (!scriptExecutionContext()) {
    211         m_body->loadingFailed();
     238        m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
    212239        return;
    213240    }
     
    218245    m_blobLoader->loader->start(*scriptExecutionContext(), blob);
    219246    if (!m_blobLoader->loader->isStarted()) {
    220         m_body->loadingFailed();
     247        m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
    221248        m_blobLoader = WTF::nullopt;
    222249        return;
     
    252279    if (m_readableStreamSource) {
    253280        if (!m_readableStreamSource->isCancelling())
    254             m_readableStreamSource->error("Blob loading failed"_s);
     281            m_readableStreamSource->error(Exception { TypeError, "Blob loading failed"_s});
    255282        m_readableStreamSource = nullptr;
    256283    } else
    257284#endif
    258         m_body->loadingFailed();
     285        m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
    259286
    260287    finishBlobLoading();
     
    319346    ASSERT(m_readableStreamSource);
    320347
    321     if (m_loadingError) {
    322         auto errorMessage = m_loadingError->localizedDescription();
    323         m_readableStreamSource->error(errorMessage.isEmpty() ? "Loading failed"_s : errorMessage);
     348    if (auto exception = loadingException()) {
     349        m_readableStreamSource->error(*exception);
    324350        return;
    325351    }
     
    330356}
    331357
     358ResourceError FetchBodyOwner::loadingError() const
     359{
     360    return WTF::switchOn(m_loadingError, [](const ResourceError& error) {
     361        return ResourceError { error };
     362    }, [](const Exception& exception) {
     363        return ResourceError { errorDomainWebKitInternal, 0, { }, exception.message() };
     364    }, [](auto&&) {
     365        return ResourceError { };
     366    });
     367}
     368
     369Optional<Exception> FetchBodyOwner::loadingException() const
     370{
     371    return WTF::switchOn(m_loadingError, [](const ResourceError& error) {
     372        return Exception { TypeError, error.localizedDescription().isEmpty() ? "Loading failed"_s : error.localizedDescription() };
     373    }, [](const Exception& exception) {
     374        return Exception { exception };
     375    }, [](auto&&) -> Optional<Exception> {
     376        return WTF::nullopt;
     377    });
     378}
     379
     380bool FetchBodyOwner::hasLoadingError() const
     381{
     382    return WTF::switchOn(m_loadingError, [](const ResourceError&) {
     383        return true;
     384    }, [](const Exception&) {
     385        return true;
     386    }, [](auto&&) {
     387        return false;
     388    });
     389}
     390
     391void FetchBodyOwner::setLoadingError(Exception&& exception)
     392{
     393    if (hasLoadingError())
     394        return;
     395
     396    m_loadingError = WTFMove(exception);
     397}
     398
     399void FetchBodyOwner::setLoadingError(ResourceError&& error)
     400{
     401    if (hasLoadingError())
     402        return;
     403
     404    m_loadingError = WTFMove(error);
     405}
     406
    332407} // namespace WebCore
  • trunk/Source/WebCore/Modules/fetch/FetchBodyOwner.h

    r239427 r239644  
    6767#endif
    6868
     69    bool hasLoadingError() const;
     70    ResourceError loadingError() const;
     71    Optional<Exception> loadingException() const;
     72
    6973protected:
    7074    const FetchBody& body() const { return *m_body; }
     
    8892    void setBodyAsOpaque() { m_isBodyOpaque = true; }
    8993    bool isBodyOpaque() const { return m_isBodyOpaque; }
     94
     95    void setLoadingError(Exception&&);
     96    void setLoadingError(ResourceError&&);
    9097
    9198private:
     
    117124#endif
    118125    Ref<FetchHeaders> m_headers;
    119     Optional<ResourceError> m_loadingError;
    120126
    121127private:
    122128    Optional<BlobLoader> m_blobLoader;
    123129    bool m_isBodyOpaque { false };
     130
     131    Variant<std::nullptr_t, Exception, ResourceError> m_loadingError;
    124132};
    125133
  • trunk/Source/WebCore/Modules/fetch/FetchBodySource.cpp

    r234045 r239644  
    8989}
    9090
    91 void FetchBodySource::error(const String& value)
     91void FetchBodySource::error(const Exception& value)
    9292{
    9393    controller().error(value);
  • trunk/Source/WebCore/Modules/fetch/FetchBodySource.h

    r234045 r239644  
    4545    bool enqueue(RefPtr<JSC::ArrayBuffer>&& chunk) { return controller().enqueue(WTFMove(chunk)); }
    4646    void close();
    47     void error(const String&);
     47    void error(const Exception&);
    4848
    4949    bool isCancelling() const { return m_isCancelling; }
  • trunk/Source/WebCore/Modules/fetch/FetchRequest.cpp

    r239427 r239644  
    160160        return optionsResult.releaseException();
    161161
     162    if (init.signal && init.signal.value())
     163        m_signal->follow(*init.signal.value());
     164
    162165    if (init.headers) {
    163166        auto fillResult = m_headers->fill(*init.headers);
     
    188191    if (optionsResult.hasException())
    189192        return optionsResult.releaseException();
     193
     194    if (init.signal) {
     195        if (init.signal.value())
     196            m_signal->follow(*init.signal.value());
     197    } else
     198        m_signal->follow(input.m_signal);
    190199
    191200    if (init.headers) {
     
    294303    auto clone = adoptRef(*new FetchRequest(context, WTF::nullopt, FetchHeaders::create(m_headers.get()), ResourceRequest { m_request }, FetchOptions { m_options}, String { m_referrer }));
    295304    clone->cloneBody(*this);
     305    clone->m_signal->follow(m_signal);
    296306    return WTFMove(clone);
    297307}
  • trunk/Source/WebCore/Modules/fetch/FetchRequest.h

    r239427 r239644  
    2929#pragma once
    3030
     31#include "AbortSignal.h"
    3132#include "ExceptionOr.h"
    3233#include "FetchBodyOwner.h"
     
    6970    Redirect redirect() const { return m_options.redirect; }
    7071    bool keepalive() const { return m_options.keepAlive; };
     72    AbortSignal& signal() { return m_signal.get(); }
    7173
    7274    const String& integrity() const { return m_options.integrity; }
     
    9799    String m_referrer;
    98100    mutable String m_requestURL;
     101    Ref<AbortSignal> m_signal;
    99102};
    100103
     
    104107    , m_options(WTFMove(options))
    105108    , m_referrer(WTFMove(referrer))
     109    , m_signal(AbortSignal::create(context))
    106110{
    107111    updateContentType();
  • trunk/Source/WebCore/Modules/fetch/FetchRequest.idl

    r227079 r239644  
    5656    readonly attribute DOMString integrity;
    5757    [EnabledAtRuntime=FetchAPIKeepAlive] readonly attribute boolean keepalive;
     58    readonly attribute AbortSignal signal;
    5859
    5960    [CallWith=ScriptExecutionContext, MayThrowException, NewObject] FetchRequest clone();
  • trunk/Source/WebCore/Modules/fetch/FetchRequestInit.h

    r239427 r239644  
    2626#pragma once
    2727
     28#include "AbortSignal.h"
    2829#include "FetchBody.h"
    2930#include "FetchHeaders.h"
     
    4748    String integrity;
    4849    Optional<bool> keepalive;
     50    Optional<AbortSignal*> signal;
    4951    JSC::JSValue window;
    5052
    51     bool hasMembers() const { return !method.isEmpty() || headers || body || !referrer.isEmpty() || referrerPolicy || mode || credentials || cache || redirect || !integrity.isEmpty() || keepalive || !window.isUndefined(); }
     53    bool hasMembers() const { return !method.isEmpty() || headers || body || !referrer.isEmpty() || referrerPolicy || mode || credentials || cache || redirect || !integrity.isEmpty() || keepalive || !window.isUndefined() || signal; }
    5254};
    5355
  • trunk/Source/WebCore/Modules/fetch/FetchRequestInit.idl

    r221201 r239644  
    4040    DOMString integrity;
    4141    boolean keepalive;
     42    AbortSignal? signal;
    4243    any window; // can only be set to null
    4344};
  • trunk/Source/WebCore/Modules/fetch/FetchResponse.cpp

    r239427 r239644  
    180180
    181181    auto clone = FetchResponse::create(context, WTF::nullopt, headers().guard(), ResourceResponse { m_internalResponse });
    182     clone->m_loadingError = m_loadingError;
    183182    clone->cloneBody(*this);
    184183    clone->m_opaqueLoadIdentifier = m_opaqueLoadIdentifier;
     
    187186}
    188187
     188void FetchResponse::addAbortSteps(Ref<AbortSignal>&& signal)
     189{
     190    m_abortSignal = WTFMove(signal);
     191    m_abortSignal->addAlgorithm([this, weakThis = makeWeakPtr(this)] {
     192        // FIXME: Cancel request body if it is a stream.
     193        if (!weakThis)
     194            return;
     195
     196        m_abortSignal = nullptr;
     197
     198        setLoadingError(Exception { AbortError, "Fetch is aborted"_s });
     199
     200        if (m_bodyLoader) {
     201            if (auto callback = m_bodyLoader->takeNotificationCallback())
     202                callback(Exception { AbortError, "Fetch is aborted"_s });
     203        }
     204
     205        if (m_readableStreamSource) {
     206            if (!m_readableStreamSource->isCancelling())
     207                m_readableStreamSource->error(*loadingException());
     208            m_readableStreamSource = nullptr;
     209        }
     210        if (m_body)
     211            m_body->loadingFailed(*loadingException());
     212
     213        if (m_bodyLoader) {
     214            m_bodyLoader->stop();
     215            m_bodyLoader = WTF::nullopt;
     216        }
     217    });
     218}
     219
    189220void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& request, NotificationCallback&& responseCallback)
    190221{
     222    if (request.signal().aborted()) {
     223        responseCallback(Exception { AbortError, "Request signal is aborted"_s });
     224        // FIXME: Cancel request body if it is a stream.
     225        return;
     226    }
     227
    191228    if (request.hasReadableStreamBody()) {
    192         if (responseCallback)
    193             responseCallback(Exception { NotSupportedError, "ReadableStream uploading is not supported" });
     229        responseCallback(Exception { NotSupportedError, "ReadableStream uploading is not supported"_s });
    194230        return;
    195231    }
     
    197233
    198234    response->body().consumer().setAsLoading();
     235
     236    response->addAbortSteps(request.signal());
    199237
    200238    response->m_bodyLoader.emplace(response.get(), WTFMove(responseCallback));
     
    246284    ASSERT(m_response.hasPendingActivity());
    247285
    248     m_response.m_loadingError = error;
     286    m_response.setLoadingError(ResourceError { error });
    249287
    250288    if (auto responseCallback = WTFMove(m_responseCallback))
     
    257295    if (m_response.m_readableStreamSource) {
    258296        if (!m_response.m_readableStreamSource->isCancelling())
    259             m_response.m_readableStreamSource->error(makeString("Loading failed: "_s, error.localizedDescription()));
     297            m_response.m_readableStreamSource->error(*m_response.loadingException());
    260298        m_response.m_readableStreamSource = nullptr;
    261299    }
  • trunk/Source/WebCore/Modules/fetch/FetchResponse.h

    r239427 r239644  
    3434#include "ResourceResponse.h"
    3535#include <JavaScriptCore/TypedArrays.h>
     36#include <wtf/WeakPtr.h>
    3637
    3738namespace JSC {
     
    4243namespace WebCore {
    4344
     45class AbortSignal;
    4446class FetchRequest;
    4547struct ReadableStreamChunk;
    4648class ReadableStreamSource;
    4749
    48 class FetchResponse final : public FetchBodyOwner {
     50class FetchResponse final : public FetchBodyOwner, public CanMakeWeakPtr<FetchResponse> {
    4951public:
    5052    using Type = ResourceResponse::Type;
     
    108110    void initializeOpaqueLoadIdentifierForTesting() { m_opaqueLoadIdentifier = 1; }
    109111
    110     const Optional<ResourceError>& loadingError() const { return m_loadingError; }
    111 
    112112    const HTTPHeaderMap& internalResponseHeaders() const { return m_internalResponse.httpHeaderFields(); }
    113113
     
    125125#endif
    126126
     127    void addAbortSteps(Ref<AbortSignal>&&);
     128
    127129    class BodyLoader final : public FetchLoaderClient {
    128130    public:
     
    138140        RefPtr<SharedBuffer> startStreaming();
    139141#endif
     142        NotificationCallback takeNotificationCallback() { return WTFMove(m_responseCallback); }
    140143
    141144    private:
     
    159162    uint64_t m_bodySizeWithPadding { 0 };
    160163    uint64_t m_opaqueLoadIdentifier { 0 };
     164    RefPtr<AbortSignal> m_abortSignal;
    161165};
    162166
  • trunk/Source/WebCore/bindings/js/ReadableStreamDefaultController.h

    r237009 r239644  
    5050    bool enqueue(RefPtr<JSC::ArrayBuffer>&&);
    5151
    52     template<class ResolveResultType>
    53     void error(const ResolveResultType&);
     52    void error(const Exception&);
    5453
    5554    void close() { invoke(*globalObject().globalExec(), jsController(), "close", JSC::jsUndefined()); }
     
    105104}
    106105
    107 template<>
    108 inline void ReadableStreamDefaultController::error<String>(const String& errorMessage)
     106inline void ReadableStreamDefaultController::error(const Exception& exception)
    109107{
    110108    JSC::ExecState& state = globalExec();
    111109    JSC::JSLockHolder locker(&state);
    112     error(state, JSC::createTypeError(&state, errorMessage));
     110    error(state, createDOMException(&state, exception.code(), exception.message()));
    113111}
    114112
  • trunk/Source/WebCore/dom/AbortSignal.cpp

    r234995 r239644  
    3838}
    3939
    40 
    4140AbortSignal::AbortSignal(ScriptExecutionContext& context)
    4241    : ContextDestructionObserver(&context)
     
    5352    // 2. Set signal’s aborted flag.
    5453    m_aborted = true;
    55    
    56     // 3. For each algorithm in signal's abort algorithms: run algorithm.
    57     // 4. Empty signal's abort algorithms.
    58     // FIXME: Add support for 'abort algorithms' - https://dom.spec.whatwg.org/#abortsignal-abort-algorithms
     54
     55    auto protectedThis = makeRef(*this);
     56    auto algorithms = WTFMove(m_algorithms);
     57    for (auto& algorithm : algorithms)
     58        algorithm();
    5959
    6060    // 5. Fire an event named abort at signal.
     
    6262}
    6363
     64// https://dom.spec.whatwg.org/#abortsignal-follow
     65void AbortSignal::follow(AbortSignal& signal)
     66{
     67    if (aborted())
     68        return;
     69
     70    if (signal.aborted()) {
     71        abort();
     72        return;
     73    }
     74
     75    signal.addAlgorithm([weakThis = makeWeakPtr(this)] {
     76        if (!weakThis)
     77            return;
     78        weakThis->abort();
     79    });
    6480}
     81
     82}
  • trunk/Source/WebCore/dom/AbortSignal.h

    r222692 r239644  
    2828#include "ContextDestructionObserver.h"
    2929#include "EventTarget.h"
     30#include <wtf/Function.h>
    3031#include <wtf/Ref.h>
    3132#include <wtf/RefCounted.h>
     33#include <wtf/WeakPtr.h>
    3234
    3335namespace WebCore {
     
    3537class ScriptExecutionContext;
    3638
    37 class AbortSignal final : public RefCounted<AbortSignal>, public EventTargetWithInlineData, private ContextDestructionObserver {
     39class AbortSignal final : public RefCounted<AbortSignal>, public EventTargetWithInlineData, public CanMakeWeakPtr<AbortSignal>, private ContextDestructionObserver {
    3840public:
    3941    static Ref<AbortSignal> create(ScriptExecutionContext&);
     
    4648    using RefCounted::deref;
    4749
     50    using Algorithm = WTF::Function<void()>;
     51    void addAlgorithm(Algorithm&& algorithm) { m_algorithms.append(WTFMove(algorithm)); }
     52
     53    void follow(AbortSignal&);
     54
    4855private:
    4956    explicit AbortSignal(ScriptExecutionContext&);
     
    5562   
    5663    bool m_aborted { false };
     64    Vector<Algorithm> m_algorithms;
    5765};
    5866
  • trunk/Source/WebCore/workers/service/context/ServiceWorkerFetch.cpp

    r239427 r239644  
    5454    client->didReceiveResponse(response->resourceResponse());
    5555
    56     if (response->loadingError()) {
    57         client->didFail(*response->loadingError());
     56    auto loadingError = response->loadingError();
     57    if (!loadingError.isNull()) {
     58        client->didFail(loadingError);
    5859        return;
    5960    }
Note: See TracChangeset for help on using the changeset viewer.