Implement quota limitation for keepalive Fetch requests
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Aug 2017 19:35:11 +0000 (19:35 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Aug 2017 19:35:11 +0000 (19:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175482

Reviewed by Sam Weinig and Youenn Fablet.

LayoutTests/imported/w3c:

* web-platform-tests/fetch/api/basic/scheme-about.any-expected.txt:
* web-platform-tests/fetch/api/basic/scheme-about.any.worker-expected.txt:
* web-platform-tests/fetch/api/cors/cors-multiple-origins-expected.txt:
* web-platform-tests/fetch/api/cors/cors-multiple-origins-worker-expected.txt:
* web-platform-tests/fetch/api/cors/cors-preflight-star.any-expected.txt:
* web-platform-tests/fetch/api/cors/cors-preflight-star.any.worker-expected.txt:
* web-platform-tests/fetch/http-cache/cc-request-expected.txt:
Rebaseline tests now that we provide a more helpful error message when rejecting
the fetch() promise.

* web-platform-tests/fetch/api/request/request-keepalive-quota-expected.txt:
Rebaseline test for keepalive Fetch requests quota which is now passing.

Source/WebCore:

Implement quota limitation for keepalive Fetch requests as per:
- https://fetch.spec.whatwg.org/#http-network-or-cache-fetch (Step 9)

This partly works for Beacon as well, meaning that no Beacon with a body
over 64Kb can be sent. However, we don't keep track about wether or not
beacon loads are inflight or not.

Also update CachedResourceLoader::requestResource() so that the caller
can get a ResourceError when it returns null. This is useful for both
Fetch and Beacon to return better error messages.

Test: http/wpt/beacon/beacon-quota.html

* CMakeLists.txt:
* Modules/beacon/NavigatorBeacon.cpp:
(WebCore::NavigatorBeacon::sendBeacon):
* Modules/fetch/FetchBodyOwner.cpp:
(WebCore::FetchBodyOwner::BlobLoader::didReceiveResponse):
(WebCore::FetchBodyOwner::BlobLoader::didFail):
* Modules/fetch/FetchBodyOwner.h:
* Modules/fetch/FetchLoader.cpp:
(WebCore::FetchLoader::start):
(WebCore::FetchLoader::didFail):
* Modules/fetch/FetchLoaderClient.h:
* Modules/fetch/FetchResponse.cpp:
(WebCore::FetchResponse::BodyLoader::didFail):
* Modules/fetch/FetchResponse.h:
* WebCore.xcodeproj/project.pbxproj:
* loader/DocumentThreadableLoader.cpp:
(WebCore::DocumentThreadableLoader::loadRequest):
* loader/cache/CachedResource.cpp:
(WebCore::CachedResource::load):
* loader/cache/CachedResource.h:
(WebCore::CachedResource::isMainOrMediaOrIconOrRawResource const):
* loader/cache/CachedResourceLoader.cpp:
(WebCore::createResource):
(WebCore::CachedResourceLoader::requestImage):
(WebCore::CachedResourceLoader::requestFont):
(WebCore::CachedResourceLoader::requestTextTrack):
(WebCore::CachedResourceLoader::requestCSSStyleSheet):
(WebCore::CachedResourceLoader::requestUserCSSStyleSheet):
(WebCore::CachedResourceLoader::requestScript):
(WebCore::CachedResourceLoader::requestXSLStyleSheet):
(WebCore::CachedResourceLoader::requestSVGDocument):
(WebCore::CachedResourceLoader::requestLinkResource):
(WebCore::CachedResourceLoader::requestMedia):
(WebCore::CachedResourceLoader::requestIcon):
(WebCore::CachedResourceLoader::requestRawResource):
(WebCore::CachedResourceLoader::requestBeaconResource):
(WebCore::CachedResourceLoader::requestMainResource):
(WebCore::CachedResourceLoader::requestResource):
(WebCore::CachedResourceLoader::preload):
* loader/cache/CachedResourceLoader.h:
* loader/cache/KeepaliveRequestTracker.cpp: Added.
(WebCore::KeepaliveRequestTracker::~KeepaliveRequestTracker):
(WebCore::KeepaliveRequestTracker::canLoadRequest):
(WebCore::KeepaliveRequestTracker::registerRequest):
(WebCore::KeepaliveRequestTracker::responseReceived):
(WebCore::KeepaliveRequestTracker::notifyFinished):
(WebCore::KeepaliveRequestTracker::unregisterRequest):
* loader/cache/KeepaliveRequestTracker.h: Added.
* platform/network/FormData.cpp:
(WebCore::FormDataElement::lengthInBytes const):
(WebCore::FormData::lengthInBytes const):
* platform/network/FormData.h:

LayoutTests:

* fast/xmlhttprequest/xmlhttprequest-nonexistent-file-expected.txt:
* http/tests/contentextensions/async-xhr-onerror-expected.txt:
* http/tests/security/contentSecurityPolicy/block-all-mixed-content/insecure-xhr-asynchronous-in-iframe-expected.txt:
* http/tests/security/contentSecurityPolicy/block-all-mixed-content/insecure-xhr-asynchronous-in-main-frame-expected.txt:
* http/tests/security/mixedContent/insecure-xhr-in-main-frame-expected.txt:
Rebaseline a few tests now that we provide a more helpful error message when an XHR is failing synchronously.

* http/wpt/beacon/beacon-quota-expected.txt: Added.
* http/wpt/beacon/beacon-quota.html: Added.
Add layout test coverage for keepalive Fetch requests quota in the context of sendBeacon().
The second part of the test is still failing because WebCore does not currently know when
ping loads (e.g. beacon loads) complete. This will be addressed via Bug 175443.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220751 268f45cc-cd09-0410-ab3c-d52691b4dbfc

42 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/xmlhttprequest/xmlhttprequest-nonexistent-file-expected.txt
LayoutTests/http/tests/contentextensions/async-xhr-onerror-expected.txt
LayoutTests/http/tests/security/contentSecurityPolicy/block-all-mixed-content/insecure-xhr-asynchronous-in-iframe-expected.txt
LayoutTests/http/tests/security/contentSecurityPolicy/block-all-mixed-content/insecure-xhr-asynchronous-in-main-frame-expected.txt
LayoutTests/http/tests/security/mixedContent/insecure-xhr-in-main-frame-expected.txt
LayoutTests/http/wpt/beacon/beacon-quota-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/beacon/beacon-quota.html [new file with mode: 0644]
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/fetch/api/basic/scheme-about.any-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/basic/scheme-about.any.worker-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-multiple-origins-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-multiple-origins-worker-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-star.any-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-star.any.worker-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/redirect/redirect-count-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/redirect/redirect-count-worker-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/request/request-keepalive-quota-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/http-cache/cc-request-expected.txt
LayoutTests/inspector/worker/resources-in-worker-expected.txt
LayoutTests/platform/mac-wk1/imported/w3c/web-platform-tests/fetch/api/request/request-cache-only-if-cached-expected.txt
LayoutTests/platform/mac-wk1/imported/w3c/web-platform-tests/fetch/http-cache/cc-request-expected.txt
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/Modules/beacon/NavigatorBeacon.cpp
Source/WebCore/Modules/fetch/FetchBodyOwner.cpp
Source/WebCore/Modules/fetch/FetchBodyOwner.h
Source/WebCore/Modules/fetch/FetchLoader.cpp
Source/WebCore/Modules/fetch/FetchLoaderClient.h
Source/WebCore/Modules/fetch/FetchResponse.cpp
Source/WebCore/Modules/fetch/FetchResponse.h
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/loader/DocumentThreadableLoader.cpp
Source/WebCore/loader/cache/CachedRawResource.cpp
Source/WebCore/loader/cache/CachedResource.cpp
Source/WebCore/loader/cache/CachedResource.h
Source/WebCore/loader/cache/CachedResourceLoader.cpp
Source/WebCore/loader/cache/CachedResourceLoader.h
Source/WebCore/loader/cache/KeepaliveRequestTracker.cpp [new file with mode: 0644]
Source/WebCore/loader/cache/KeepaliveRequestTracker.h [new file with mode: 0644]
Source/WebCore/platform/network/FormData.cpp
Source/WebCore/platform/network/FormData.h

index dd00686..d3a782a 100644 (file)
@@ -1,3 +1,23 @@
+2017-08-15  Chris Dumez  <cdumez@apple.com>
+
+        Implement quota limitation for keepalive Fetch requests
+        https://bugs.webkit.org/show_bug.cgi?id=175482
+
+        Reviewed by Sam Weinig and Youenn Fablet.
+
+        * fast/xmlhttprequest/xmlhttprequest-nonexistent-file-expected.txt:
+        * http/tests/contentextensions/async-xhr-onerror-expected.txt:
+        * http/tests/security/contentSecurityPolicy/block-all-mixed-content/insecure-xhr-asynchronous-in-iframe-expected.txt:
+        * http/tests/security/contentSecurityPolicy/block-all-mixed-content/insecure-xhr-asynchronous-in-main-frame-expected.txt:
+        * http/tests/security/mixedContent/insecure-xhr-in-main-frame-expected.txt:
+        Rebaseline a few tests now that we provide a more helpful error message when an XHR is failing synchronously.
+
+        * http/wpt/beacon/beacon-quota-expected.txt: Added.
+        * http/wpt/beacon/beacon-quota.html: Added.
+        Add layout test coverage for keepalive Fetch requests quota in the context of sendBeacon().
+        The second part of the test is still failing because WebCore does not currently know when
+        ping loads (e.g. beacon loads) complete. This will be addressed via Bug 175443.
+
 2017-08-15  Darin Adler  <darin@apple.com>
 
         REGRESSION(r220052): http/tests/appcache/deferred-events-delete-while-raising-timer.html is crashing.
index f9eee90..ec7ecfb 100644 (file)
@@ -1,5 +1,5 @@
 CONSOLE MESSAGE: line 64: Not allowed to load local resource: nonexistent.html
-CONSOLE MESSAGE: line 64: XMLHttpRequest cannot load nonexistent.html due to access control checks.
+CONSOLE MESSAGE: line 64: XMLHttpRequest cannot load nonexistent.html. Not allowed to request resource
 
 Bug 22475: REGRESSION: Async XMLHttpRequest never finishes on nonexistent files anymore
 
index 3f2aa09..d40b123 100644 (file)
@@ -1,7 +1,7 @@
 CONSOLE MESSAGE: line 30: Content blocker prevented frame displaying http://127.0.0.1:8000/contentextensions/async-xhr-onerror.html from loading a resource from http://127.0.0.1:8000/contentextensions/resources/url-blocking-test.js
-CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/contentextensions/resources/url-blocking-test.js due to access control checks.
+CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/contentextensions/resources/url-blocking-test.js. Resource blocked by content blocker
 CONSOLE MESSAGE: line 30: Content blocker prevented frame displaying http://127.0.0.1:8000/contentextensions/async-xhr-onerror.html from loading a resource from http://127.0.0.1:8000/contentextensions/resources/url-blocking-test.js
-CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/contentextensions/resources/url-blocking-test.js due to access control checks.
+CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/contentextensions/resources/url-blocking-test.js. Resource blocked by content blocker
 Asynchronous onreadystatechange status: 0, readyState:1, responseText: 
 Finished runTest. Waiting for callbacks
 Asynchronous onreadystatechange status: 0, readyState:1, responseText: 
index 5d3fbb7..89516e3 100644 (file)
@@ -2,7 +2,7 @@ frame "<!--framePath //<!--frame0-->-->" - didStartProvisionalLoadForFrame
 main frame - didFinishDocumentLoadForFrame
 frame "<!--framePath //<!--frame0-->-->" - didCommitLoadForFrame
 CONSOLE MESSAGE: Blocked mixed content http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow-star.cgi because 'block-all-mixed-content' appears in the Content Security Policy.
-CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow-star.cgi due to access control checks.
+CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow-star.cgi. Not allowed to request resource
 frame "<!--framePath //<!--frame0-->-->" - didFinishDocumentLoadForFrame
 frame "<!--framePath //<!--frame0-->-->" - didHandleOnloadEventsForFrame
 main frame - didHandleOnloadEventsForFrame
index c8e65e2..3f6476f 100644 (file)
@@ -4,7 +4,7 @@ main frame - didHandleOnloadEventsForFrame
 main frame - didFinishLoadForFrame
 main frame - didCommitLoadForFrame
 CONSOLE MESSAGE: Blocked mixed content http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow-star.cgi because 'block-all-mixed-content' appears in the Content Security Policy.
-CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow-star.cgi due to access control checks.
+CONSOLE MESSAGE: line 30: XMLHttpRequest cannot load http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow-star.cgi. Not allowed to request resource
 main frame - didFinishDocumentLoadForFrame
 main frame - didHandleOnloadEventsForFrame
 main frame - didFinishLoadForFrame
index 7fcfdd1..c42e555 100644 (file)
@@ -1,4 +1,4 @@
 CONSOLE MESSAGE: line 28: [blocked] The page at https://127.0.0.1:8443/security/mixedContent/resources/insecure-xhr-in-main-frame-window.html was not allowed to display insecure content from http://127.0.0.1:8000/.
 
-CONSOLE MESSAGE: line 28: XMLHttpRequest cannot load http://127.0.0.1:8000/ due to access control checks.
+CONSOLE MESSAGE: line 28: XMLHttpRequest cannot load http://127.0.0.1:8000/. Not allowed to request resource
 This test opens a HTTPS window that loads insecure data via XHR. We should trigger a mixed content callback because the main frame in the window is HTTPS but now has insecure data.
diff --git a/LayoutTests/http/wpt/beacon/beacon-quota-expected.txt b/LayoutTests/http/wpt/beacon/beacon-quota-expected.txt
new file mode 100644 (file)
index 0000000..b78c982
--- /dev/null
@@ -0,0 +1,5 @@
+CONSOLE MESSAGE: line 14: Reached maximum amount of queued data of 64Kb for keepalive requests
+
+PASS Beacon with a body above the Quota Limit should fail. 
+FAIL Multiple Beacons Quota Limit assert_false: Second beacon should not be sent because we reached the quota expected false got true
+
diff --git a/LayoutTests/http/wpt/beacon/beacon-quota.html b/LayoutTests/http/wpt/beacon/beacon-quota.html
new file mode 100644 (file)
index 0000000..18e58f8
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    // We should expect 64KiB of rolling quota for any type of keep-alive request sent.
+    var expectedQuota = 65536;
+
+    function createPayload(payloadSize)
+    {
+        return new Blob(["*".repeat(payloadSize)]);
+    }
+
+    test(function() {
+        assert_false(navigator.sendBeacon("/", createPayload(expectedQuota + 1)));
+    }, "Beacon with a body above the Quota Limit should fail.");
+
+    test(function() {
+        assert_true(navigator.sendBeacon("/", createPayload(expectedQuota)), "Beacon with a body at the Quota Limit should succeed.");
+        assert_false(navigator.sendBeacon("/", createPayload(1)), "Second beacon should not be sent because we reached the quota");
+    }, "Multiple Beacons Quota Limit");
+</script>
index cac61eb..9319bb3 100644 (file)
@@ -1,3 +1,23 @@
+2017-08-15  Chris Dumez  <cdumez@apple.com>
+
+        Implement quota limitation for keepalive Fetch requests
+        https://bugs.webkit.org/show_bug.cgi?id=175482
+
+        Reviewed by Sam Weinig and Youenn Fablet.
+
+        * web-platform-tests/fetch/api/basic/scheme-about.any-expected.txt:
+        * web-platform-tests/fetch/api/basic/scheme-about.any.worker-expected.txt:
+        * web-platform-tests/fetch/api/cors/cors-multiple-origins-expected.txt:
+        * web-platform-tests/fetch/api/cors/cors-multiple-origins-worker-expected.txt:
+        * web-platform-tests/fetch/api/cors/cors-preflight-star.any-expected.txt:
+        * web-platform-tests/fetch/api/cors/cors-preflight-star.any.worker-expected.txt:
+        * web-platform-tests/fetch/http-cache/cc-request-expected.txt:
+        Rebaseline tests now that we provide a more helpful error message when rejecting
+        the fetch() promise.
+
+        * web-platform-tests/fetch/api/request/request-keepalive-quota-expected.txt:
+        Rebaseline test for keepalive Fetch requests quota which is now passing.
+
 2017-08-14  Chris Dumez  <cdumez@apple.com>
 
         Import eventsource Web-Platform-Tests
index b608de7..c12cfa7 100644 (file)
@@ -5,9 +5,9 @@ CONSOLE MESSAGE: line 27: Fetch API cannot load about:invalid.com. Cross origin
 CONSOLE MESSAGE: line 27: Fetch API cannot load about:config. Cross origin requests are only supported for HTTP.
 CONSOLE MESSAGE: line 27: Fetch API cannot load about:unicorn. Cross origin requests are only supported for HTTP.
 
-FAIL Fetching about:blank (GET) is OK promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetching about:blank (PUT) is OK promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetching about:blank (POST) is OK promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Fetching about:blank (GET) is OK promise_test: Unhandled rejection with value: object "TypeError: Cross origin requests are only supported for HTTP."
+FAIL Fetching about:blank (PUT) is OK promise_test: Unhandled rejection with value: object "TypeError: Preflight response is not successful"
+FAIL Fetching about:blank (POST) is OK promise_test: Unhandled rejection with value: object "TypeError: Cross origin requests are only supported for HTTP."
 PASS Fetching about:invalid.com is KO 
 PASS Fetching about:config is KO 
 PASS Fetching about:unicorn is KO 
index 9399260..6304a83 100644 (file)
@@ -1,7 +1,7 @@
 
-FAIL Fetching about:blank (GET) is OK promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetching about:blank (PUT) is OK promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetching about:blank (POST) is OK promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Fetching about:blank (GET) is OK promise_test: Unhandled rejection with value: object "TypeError: Cross origin requests are only supported for HTTP."
+FAIL Fetching about:blank (PUT) is OK promise_test: Unhandled rejection with value: object "TypeError: Preflight response is not successful"
+FAIL Fetching about:blank (POST) is OK promise_test: Unhandled rejection with value: object "TypeError: Cross origin requests are only supported for HTTP."
 PASS Fetching about:invalid.com is KO 
 PASS Fetching about:config is KO 
 PASS Fetching about:unicorn is KO 
index 49f3ce5..8da7aa6 100644 (file)
@@ -11,10 +11,10 @@ CONSOLE MESSAGE: Fetch API cannot load http://localhost:8801/fetch/api/resources
 CONSOLE MESSAGE: Access-Control-Allow-Origin cannot contain more than one origin.
 CONSOLE MESSAGE: Fetch API cannot load http://localhost:8801/fetch/api/resources/preflight.py?origin=%2C%20http%3A%2F%2Fexample.com%2C%20https%3A%2F%2Fexample2.com. Access-Control-Allow-Origin cannot contain more than one origin.
 
-FAIL 3 origins allowed, match the 3rd (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match the 3rd ("*") promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match twice (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match twice ("*") promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match twice ("*" and http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL 3 origins allowed, match the 3rd (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match the 3rd ("*") promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match twice (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match twice ("*") promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match twice ("*" and http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
 PASS 3 origins allowed, no match 
 
index 909cc92..81037f7 100644 (file)
@@ -5,10 +5,10 @@ CONSOLE MESSAGE: Access-Control-Allow-Origin cannot contain more than one origin
 CONSOLE MESSAGE: Access-Control-Allow-Origin cannot contain more than one origin.
 CONSOLE MESSAGE: Access-Control-Allow-Origin cannot contain more than one origin.
 
-FAIL 3 origins allowed, match the 3rd (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match the 3rd ("*") promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match twice (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match twice ("*") promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL 3 origins allowed, match twice ("*" and http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL 3 origins allowed, match the 3rd (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match the 3rd ("*") promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match twice (http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match twice ("*") promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
+FAIL 3 origins allowed, match twice ("*" and http://localhost:8800) promise_test: Unhandled rejection with value: object "TypeError: Access-Control-Allow-Origin cannot contain more than one origin."
 PASS 3 origins allowed, no match 
 
index 07a66db..918ef8d 100644 (file)
@@ -7,8 +7,8 @@ CONSOLE MESSAGE: Fetch API cannot load http://127.0.0.1:8800/fetch/api/resources
 CONSOLE MESSAGE: Fetch API cannot load http://127.0.0.1:8800/fetch/api/resources/preflight.py?origin=http://localhost:8800&credentials&allow_methods=*&allow_headers=*&. Request header field X-Test is not allowed by Access-Control-Allow-Headers.
 
 PASS CORS that succeeds with credentials: false; method: GET (allowed: get); header: X-Test,1 (allowed: x-test) 
-FAIL CORS that succeeds with credentials: false; method: SUPER (allowed: *); header: X-Test,1 (allowed: x-test) promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL CORS that succeeds with credentials: false; method: OK (allowed: *); header: X-Test,1 (allowed: *) promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL CORS that succeeds with credentials: false; method: SUPER (allowed: *); header: X-Test,1 (allowed: x-test) promise_test: Unhandled rejection with value: object "TypeError: Method SUPER is not allowed by Access-Control-Allow-Methods."
+FAIL CORS that succeeds with credentials: false; method: OK (allowed: *); header: X-Test,1 (allowed: *) promise_test: Unhandled rejection with value: object "TypeError: Method OK is not allowed by Access-Control-Allow-Methods."
 PASS CORS that fails with credentials: true; method: OK (allowed: *); header: X-Test,1 (allowed: *) 
 PASS CORS that fails with credentials: true; method: PUT (allowed: *); header: undefined (allowed: ) 
 PASS CORS that fails with credentials: true; method: PUT (allowed: put); header: undefined (allowed: *) 
index ae4cf83..82cf622 100644 (file)
@@ -1,7 +1,7 @@
 
 PASS CORS that succeeds with credentials: false; method: GET (allowed: get); header: X-Test,1 (allowed: x-test) 
-FAIL CORS that succeeds with credentials: false; method: SUPER (allowed: *); header: X-Test,1 (allowed: x-test) promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL CORS that succeeds with credentials: false; method: OK (allowed: *); header: X-Test,1 (allowed: *) promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL CORS that succeeds with credentials: false; method: SUPER (allowed: *); header: X-Test,1 (allowed: x-test) promise_test: Unhandled rejection with value: object "TypeError: Method SUPER is not allowed by Access-Control-Allow-Methods."
+FAIL CORS that succeeds with credentials: false; method: OK (allowed: *); header: X-Test,1 (allowed: *) promise_test: Unhandled rejection with value: object "TypeError: Method OK is not allowed by Access-Control-Allow-Methods."
 PASS CORS that fails with credentials: true; method: OK (allowed: *); header: X-Test,1 (allowed: *) 
 PASS CORS that fails with credentials: true; method: PUT (allowed: *); header: undefined (allowed: ) 
 PASS CORS that fails with credentials: true; method: PUT (allowed: put); header: undefined (allowed: *) 
index 1edddce..8a3ce19 100644 (file)
@@ -1,12 +1,12 @@
 
-FAIL Redirect 301 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 301 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 301 21 times 
-FAIL Redirect 302 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 302 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 302 21 times 
-FAIL Redirect 303 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 303 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 303 21 times 
-FAIL Redirect 307 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 307 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 307 21 times 
-FAIL Redirect 308 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 308 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 308 21 times 
 
index 1edddce..8a3ce19 100644 (file)
@@ -1,12 +1,12 @@
 
-FAIL Redirect 301 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 301 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 301 21 times 
-FAIL Redirect 302 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 302 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 302 21 times 
-FAIL Redirect 303 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 303 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 303 21 times 
-FAIL Redirect 307 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 307 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 307 21 times 
-FAIL Redirect 308 20 times promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL Redirect 308 20 times promise_test: Unhandled rejection with value: object "TypeError: too many HTTP redirects"
 PASS Redirect 308 21 times 
 
index 6b52eb7..f5e5efd 100644 (file)
@@ -1,8 +1,14 @@
+CONSOLE MESSAGE: line 44: Fetch API cannot load http://localhost:8800/fetch/api/resources/trickle.py?count=1&ms=0. Reached maximum amount of queued data of 64Kb for keepalive requests
+CONSOLE MESSAGE: line 71: Fetch API cannot load http://localhost:8800/fetch/api/resources/trickle.py?count=1&ms=0. Reached maximum amount of queued data of 64Kb for keepalive requests
+CONSOLE MESSAGE: Unhandled Promise Rejection: TypeError: Reached maximum amount of queued data of 64Kb for keepalive requests
+CONSOLE MESSAGE: line 88: Fetch API cannot load http://localhost:8800/fetch/api/resources/trickle.py?count=1&ms=0. Reached maximum amount of queued data of 64Kb for keepalive requests
+
+Harness Error (FAIL), message = Reached maximum amount of queued data of 64Kb for keepalive requests
 
 PASS A Keep-Alive fetch() with a small body should succeed. 
 PASS A Keep-Alive fetch() with a body at the Quota Limit should succeed. 
-FAIL A Keep-Alive fetch() with a body over the Quota Limit should reject. assert_unreached: Should have rejected: undefined Reached unreachable code
+PASS A Keep-Alive fetch() with a body over the Quota Limit should reject. 
 PASS A Keep-Alive fetch() should return it's allocated Quota upon promise resolution. 
 PASS A Keep-Alive fetch() should return only it's allocated Quota upon promise resolution. 
-FAIL A Keep-Alive fetch() should not be allowed if the Quota is used up. assert_unreached: Should have rejected: undefined Reached unreachable code
+PASS A Keep-Alive fetch() should not be allowed if the Quota is used up. 
 
index d5f23f9..9c1ea17 100644 (file)
@@ -10,5 +10,5 @@ PASS HTTP cache doesn't reuse fresh response when request contains Cache-Control
 PASS HTTP cache validates fresh response with Last-Modified when request contains Cache-Control: no-cache. 
 PASS HTTP cache validates fresh response with ETag when request contains Cache-Control: no-cache. 
 FAIL HTTP cache doesn't reuse fresh response when request contains Cache-Control: no-store. assert_equals: Response used expected 2 but got 1
-FAIL HTTP cache generates 504 status code when nothing is in cache and request contains Cache-Control: only-if-cached. promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL HTTP cache generates 504 status code when nothing is in cache and request contains Cache-Control: only-if-cached. promise_test: Unhandled rejection with value: object "TypeError: can’t load from network"
 
index d143763..d4207d1 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: Type error
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: The requested URL was not found on this server.
 Test for Resources in a Worker.
 
 
index 86f646b..56ae0db 100644 (file)
@@ -5,10 +5,10 @@ PASS RequestCache "only-if-cached" mode checks the cache for previously cached c
 PASS RequestCache "only-if-cached" mode checks the cache for previously cached content and avoids revalidation for fresh responses with Last-Modified and fresh response 
 PASS RequestCache "only-if-cached" mode checks the cache for previously cached content and does not go to the network if a cached response is not found with Etag and fresh response 
 PASS RequestCache "only-if-cached" mode checks the cache for previously cached content and does not go to the network if a cached response is not found with Last-Modified and fresh response 
-FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Etag and fresh response promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Last-Modified and fresh response promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Etag and stale response promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Last-Modified and stale response promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Etag and fresh response promise_test: Unhandled rejection with value: object "TypeError: resource unavailable"
+FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Last-Modified and fresh response promise_test: Unhandled rejection with value: object "TypeError: resource unavailable"
+FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Etag and stale response promise_test: Unhandled rejection with value: object "TypeError: resource unavailable"
+FAIL RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content with Last-Modified and stale response promise_test: Unhandled rejection with value: object "TypeError: resource unavailable"
 PASS RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects with Etag and fresh response 
 PASS RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects with Last-Modified and fresh response 
 PASS RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects with Etag and stale response 
index 74534ee..6db5c3d 100644 (file)
@@ -10,5 +10,5 @@ PASS HTTP cache doesn't reuse fresh response when request contains Cache-Control
 PASS HTTP cache validates fresh response with Last-Modified when request contains Cache-Control: no-cache. 
 PASS HTTP cache validates fresh response with ETag when request contains Cache-Control: no-cache. 
 PASS HTTP cache doesn't reuse fresh response when request contains Cache-Control: no-store. 
-FAIL HTTP cache generates 504 status code when nothing is in cache and request contains Cache-Control: only-if-cached. promise_test: Unhandled rejection with value: object "TypeError: Type error"
+FAIL HTTP cache generates 504 status code when nothing is in cache and request contains Cache-Control: only-if-cached. promise_test: Unhandled rejection with value: object "TypeError: can’t load from network"
 
index 1a4e0b7..cf4a2d0 100644 (file)
@@ -2070,6 +2070,7 @@ set(WebCore_SOURCES
     loader/cache/CachedSVGFont.cpp
     loader/cache/CachedScript.cpp
     loader/cache/CachedXSLStyleSheet.cpp
+    loader/cache/KeepaliveRequestTracker.cpp
     loader/cache/MemoryCache.cpp
 
     loader/icon/IconLoader.cpp
index 8623704..2cb62af 100644 (file)
@@ -1,3 +1,76 @@
+2017-08-15  Chris Dumez  <cdumez@apple.com>
+
+        Implement quota limitation for keepalive Fetch requests
+        https://bugs.webkit.org/show_bug.cgi?id=175482
+
+        Reviewed by Sam Weinig and Youenn Fablet.
+
+        Implement quota limitation for keepalive Fetch requests as per:
+        - https://fetch.spec.whatwg.org/#http-network-or-cache-fetch (Step 9)
+
+        This partly works for Beacon as well, meaning that no Beacon with a body
+        over 64Kb can be sent. However, we don't keep track about wether or not
+        beacon loads are inflight or not.
+
+        Also update CachedResourceLoader::requestResource() so that the caller
+        can get a ResourceError when it returns null. This is useful for both
+        Fetch and Beacon to return better error messages.
+
+        Test: http/wpt/beacon/beacon-quota.html
+
+        * CMakeLists.txt:
+        * Modules/beacon/NavigatorBeacon.cpp:
+        (WebCore::NavigatorBeacon::sendBeacon):
+        * Modules/fetch/FetchBodyOwner.cpp:
+        (WebCore::FetchBodyOwner::BlobLoader::didReceiveResponse):
+        (WebCore::FetchBodyOwner::BlobLoader::didFail):
+        * Modules/fetch/FetchBodyOwner.h:
+        * Modules/fetch/FetchLoader.cpp:
+        (WebCore::FetchLoader::start):
+        (WebCore::FetchLoader::didFail):
+        * Modules/fetch/FetchLoaderClient.h:
+        * Modules/fetch/FetchResponse.cpp:
+        (WebCore::FetchResponse::BodyLoader::didFail):
+        * Modules/fetch/FetchResponse.h:
+        * WebCore.xcodeproj/project.pbxproj:
+        * loader/DocumentThreadableLoader.cpp:
+        (WebCore::DocumentThreadableLoader::loadRequest):
+        * loader/cache/CachedResource.cpp:
+        (WebCore::CachedResource::load):
+        * loader/cache/CachedResource.h:
+        (WebCore::CachedResource::isMainOrMediaOrIconOrRawResource const):
+        * loader/cache/CachedResourceLoader.cpp:
+        (WebCore::createResource):
+        (WebCore::CachedResourceLoader::requestImage):
+        (WebCore::CachedResourceLoader::requestFont):
+        (WebCore::CachedResourceLoader::requestTextTrack):
+        (WebCore::CachedResourceLoader::requestCSSStyleSheet):
+        (WebCore::CachedResourceLoader::requestUserCSSStyleSheet):
+        (WebCore::CachedResourceLoader::requestScript):
+        (WebCore::CachedResourceLoader::requestXSLStyleSheet):
+        (WebCore::CachedResourceLoader::requestSVGDocument):
+        (WebCore::CachedResourceLoader::requestLinkResource):
+        (WebCore::CachedResourceLoader::requestMedia):
+        (WebCore::CachedResourceLoader::requestIcon):
+        (WebCore::CachedResourceLoader::requestRawResource):
+        (WebCore::CachedResourceLoader::requestBeaconResource):
+        (WebCore::CachedResourceLoader::requestMainResource):
+        (WebCore::CachedResourceLoader::requestResource):
+        (WebCore::CachedResourceLoader::preload):
+        * loader/cache/CachedResourceLoader.h:
+        * loader/cache/KeepaliveRequestTracker.cpp: Added.
+        (WebCore::KeepaliveRequestTracker::~KeepaliveRequestTracker):
+        (WebCore::KeepaliveRequestTracker::canLoadRequest):
+        (WebCore::KeepaliveRequestTracker::registerRequest):
+        (WebCore::KeepaliveRequestTracker::responseReceived):
+        (WebCore::KeepaliveRequestTracker::notifyFinished):
+        (WebCore::KeepaliveRequestTracker::unregisterRequest):
+        * loader/cache/KeepaliveRequestTracker.h: Added.
+        * platform/network/FormData.cpp:
+        (WebCore::FormDataElement::lengthInBytes const):
+        (WebCore::FormData::lengthInBytes const):
+        * platform/network/FormData.h:
+
 2017-08-15  Darin Adler  <darin@apple.com>
 
         REGRESSION(r220052): http/tests/appcache/deferred-events-delete-while-raising-timer.html is crashing.
index 2c1991b..8b2f5a7 100644 (file)
@@ -73,7 +73,12 @@ ExceptionOr<bool> NavigatorBeacon::sendBeacon(Navigator&, Document& document, co
                 options.mode = FetchOptions::Mode::NoCors;
         }
     }
-    document.cachedResourceLoader().requestBeaconResource({ WTFMove(request), options });
+    ResourceError error;
+    if (!document.cachedResourceLoader().requestBeaconResource({ WTFMove(request), options }, &error)) {
+        if (!error.isNull())
+            document.addConsoleMessage(MessageSource::Network, MessageLevel::Error, error.localizedDescription());
+        return false;
+    }
     return true;
 }
 
index 37d30f8..6bc1d9a 100644 (file)
@@ -32,6 +32,7 @@
 #include "FetchLoader.h"
 #include "HTTPParsers.h"
 #include "JSBlob.h"
+#include "ResourceError.h"
 #include "ResourceResponse.h"
 
 namespace WebCore {
@@ -277,10 +278,10 @@ FetchBodyOwner::BlobLoader::BlobLoader(FetchBodyOwner& owner)
 void FetchBodyOwner::BlobLoader::didReceiveResponse(const ResourceResponse& response)
 {
     if (response.httpStatusCode() != 200)
-        didFail();
+        didFail({ });
 }
 
-void FetchBodyOwner::BlobLoader::didFail()
+void FetchBodyOwner::BlobLoader::didFail(const ResourceError&)
 {
     // didFail might be called within FetchLoader::start call.
     if (loader->isStarted())
index c3213e9..5b83d26 100644 (file)
@@ -88,7 +88,7 @@ private:
         // FetchLoaderClient API
         void didReceiveResponse(const ResourceResponse&) final;
         void didReceiveData(const char* data, size_t size) final { owner.blobChunk(data, size); }
-        void didFail() final;
+        void didFail(const ResourceError&) final;
         void didSucceed() final { owner.blobLoadingSucceeded(); }
 
         FetchBodyOwner& owner;
index e3a51f1..4c2bf83 100644 (file)
@@ -35,6 +35,7 @@
 #include "FetchBody.h"
 #include "FetchLoaderClient.h"
 #include "FetchRequest.h"
+#include "ResourceError.h"
 #include "ResourceRequest.h"
 #include "ScriptExecutionContext.h"
 #include "SecurityOrigin.h"
@@ -48,7 +49,7 @@ void FetchLoader::start(ScriptExecutionContext& context, const Blob& blob)
 {
     auto urlForReading = BlobURL::createPublicURL(context.securityOrigin());
     if (urlForReading.isEmpty()) {
-        m_client.didFail();
+        m_client.didFail({ errorDomainWebKitInternal, 0, URL(), ASCIILiteral("Could not create URL for Blob") });
         return;
     }
 
@@ -88,7 +89,7 @@ void FetchLoader::start(ScriptExecutionContext& context, const FetchRequest& req
     contentSecurityPolicy.upgradeInsecureRequestIfNeeded(fetchRequest, ContentSecurityPolicy::InsecureRequestType::Load);
 
     if (!context.shouldBypassMainWorldContentSecurityPolicy() && !contentSecurityPolicy.allowConnectToSource(fetchRequest.url())) {
-        m_client.didFail();
+        m_client.didFail({ errorDomainWebKitInternal, 0, fetchRequest.url(), ASCIILiteral("Not allowed by ContentSecurityPolicy"), ResourceError::Type::AccessControl });
         return;
     }
 
@@ -144,9 +145,9 @@ void FetchLoader::didFinishLoading(unsigned long)
     m_client.didSucceed();
 }
 
-void FetchLoader::didFail(const ResourceError&)
+void FetchLoader::didFail(const ResourceError& error)
 {
-    m_client.didFail();
+    m_client.didFail(error);
 }
 
 } // namespace WebCore
index 195f1ef..cbff9bd 100644 (file)
@@ -32,6 +32,7 @@
 
 namespace WebCore {
 
+class ResourceError;
 class ResourceResponse;
 
 class FetchLoaderClient {
@@ -43,7 +44,7 @@ public:
     virtual void didReceiveData(const char*, size_t) { }
 
     virtual void didSucceed() = 0;
-    virtual void didFail() = 0;
+    virtual void didFail(const ResourceError&) = 0;
 };
 
 } // namespace WebCore
index 68adb55..22532c4 100644 (file)
@@ -33,6 +33,7 @@
 #include "HTTPParsers.h"
 #include "JSBlob.h"
 #include "JSFetchResponse.h"
+#include "ResourceError.h"
 #include "ScriptExecutionContext.h"
 
 namespace WebCore {
@@ -138,11 +139,11 @@ void FetchResponse::BodyLoader::didSucceed()
     }
 }
 
-void FetchResponse::BodyLoader::didFail()
+void FetchResponse::BodyLoader::didFail(const ResourceError& error)
 {
     ASSERT(m_response.hasPendingActivity());
     if (m_promise)
-        std::exchange(m_promise, std::nullopt)->reject(TypeError);
+        std::exchange(m_promise, std::nullopt)->reject(Exception { TypeError, String(error.localizedDescription()) });
 
 #if ENABLE(STREAMS_API)
     if (m_response.m_readableStreamSource) {
index f9b02fb..540e67b 100644 (file)
@@ -118,7 +118,7 @@ private:
     private:
         // FetchLoaderClient API
         void didSucceed() final;
-        void didFail() final;
+        void didFail(const ResourceError&) final;
         void didReceiveResponse(const ResourceResponse&) final;
         void didReceiveData(const char*, size_t) final;
 
index b7324af..25eb95c 100644 (file)
                839AAFED1A0C0C8D00605F99 /* HTMLWBRElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 839AAFEB1A0C0C8D00605F99 /* HTMLWBRElement.h */; };
                83A4A9F91CE7FD8100709B00 /* JSXMLDocumentCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83A4A9F81CE7FD7E00709B00 /* JSXMLDocumentCustom.cpp */; };
                83B2D1751B8BCD6A00A02E47 /* NativeNodeFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E959E11B8BC22B004D9385 /* NativeNodeFilter.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               83B74EF51F3E0BF200996BC7 /* KeepaliveRequestTracker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83B74EF21F3E0BD700996BC7 /* KeepaliveRequestTracker.cpp */; };
+               83B74EF61F3E0BF200996BC7 /* KeepaliveRequestTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B74EF31F3E0BD700996BC7 /* KeepaliveRequestTracker.h */; settings = {ATTRIBUTES = (Private, ); }; };
                83B9687B19F8AB83004EF7AF /* StyleBuilderConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B9687919F8AB83004EF7AF /* StyleBuilderConverter.h */; };
                83BB5C881D5D6F45005A71F4 /* AllDescendantsCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BB5C871D5D6F3A005A71F4 /* AllDescendantsCollection.h */; };
                83C05A5A1A686212007E5DEA /* StylePropertyShorthandFunctions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83C05A581A686212007E5DEA /* StylePropertyShorthandFunctions.cpp */; };
                839AAFEA1A0C0C8D00605F99 /* HTMLWBRElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTMLWBRElement.cpp; sourceTree = "<group>"; };
                839AAFEB1A0C0C8D00605F99 /* HTMLWBRElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLWBRElement.h; sourceTree = "<group>"; };
                83A4A9F81CE7FD7E00709B00 /* JSXMLDocumentCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSXMLDocumentCustom.cpp; sourceTree = "<group>"; };
+               83B74EF21F3E0BD700996BC7 /* KeepaliveRequestTracker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeepaliveRequestTracker.cpp; sourceTree = "<group>"; };
+               83B74EF31F3E0BD700996BC7 /* KeepaliveRequestTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeepaliveRequestTracker.h; sourceTree = "<group>"; };
                83B9687919F8AB83004EF7AF /* StyleBuilderConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StyleBuilderConverter.h; sourceTree = "<group>"; };
                83BB5C871D5D6F3A005A71F4 /* AllDescendantsCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AllDescendantsCollection.h; sourceTree = "<group>"; };
                83C05A581A686212007E5DEA /* StylePropertyShorthandFunctions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StylePropertyShorthandFunctions.cpp; sourceTree = "<group>"; };
                                BCB16C0E0979C3BD00467741 /* CachedXSLStyleSheet.cpp */,
                                BCB16C0F0979C3BD00467741 /* CachedXSLStyleSheet.h */,
                                F587864902DE3A9A01EA4122 /* CachePolicy.h */,
+                               83B74EF21F3E0BD700996BC7 /* KeepaliveRequestTracker.cpp */,
+                               83B74EF31F3E0BD700996BC7 /* KeepaliveRequestTracker.h */,
                                BCB16BFE0979C3BD00467741 /* MemoryCache.cpp */,
                                BCB16BFF0979C3BD00467741 /* MemoryCache.h */,
                        );
                                1A762C780A074F2600989F5B /* JSXPathNSResolver.h in Headers */,
                                1A762C7A0A074F2600989F5B /* JSXPathResult.h in Headers */,
                                BCEFE1EB0DCA5F6400739219 /* JSXSLTProcessor.h in Headers */,
+                               83B74EF61F3E0BF200996BC7 /* KeepaliveRequestTracker.h in Headers */,
                                85031B440A44EFC700F992E0 /* KeyboardEvent.h in Headers */,
                                1AE00D59182DAC8D00087DD7 /* KeyedCoding.h in Headers */,
                                517A63C51B74318F00E7DCDC /* KeyedDecoderCF.h in Headers */,
                                1A762C790A074F2600989F5B /* JSXPathResult.cpp in Sources */,
                                A1C7FAA2133A5D3500D6732D /* JSXPathResultCustom.cpp in Sources */,
                                BCEFE1EA0DCA5F6400739219 /* JSXSLTProcessor.cpp in Sources */,
+                               83B74EF51F3E0BF200996BC7 /* KeepaliveRequestTracker.cpp in Sources */,
                                85031B430A44EFC700F992E0 /* KeyboardEvent.cpp in Sources */,
                                517A63C31B74318700E7DCDC /* KeyedDecoderCF.cpp in Sources */,
                                517A63C41B74318B00E7DCDC /* KeyedEncoderCF.cpp in Sources */,
index 7ee60be..548b163 100644 (file)
@@ -451,12 +451,16 @@ void DocumentThreadableLoader::loadRequest(ResourceRequest&& request, SecurityCh
 
         // We create an URL here as the request will be moved in requestRawResource
         URL requestUrl = newRequest.resourceRequest().url();
-        m_resource = m_document.cachedResourceLoader().requestRawResource(WTFMove(newRequest));
+        ResourceError error;
+        m_resource = m_document.cachedResourceLoader().requestRawResource(WTFMove(newRequest), &error);
         if (m_resource)
             m_resource->addClient(*this);
         else {
-            // FIXME: Since we receive a synchronous error, this is probably due to some AccessControl checks. We should try to retrieve the actual error.
-            logErrorAndFail(ResourceError(String(), 0, requestUrl, String(), ResourceError::Type::AccessControl));
+            if (error.isNull()) {
+                // FIXME: Since we receive a synchronous error, this is probably due to some AccessControl checks. We should try to retrieve the actual error.
+                logErrorAndFail(ResourceError(String(), 0, requestUrl, String(), ResourceError::Type::AccessControl));
+            } else
+                logErrorAndFail(error);
         }
         return;
     }
index 29b8d87..d2d316d 100644 (file)
@@ -93,7 +93,7 @@ void CachedRawResource::finishLoading(SharedBuffer* data)
     }
 
 #if USE(QUICK_LOOK)
-    m_allowEncodedDataReplacement = !m_loader->isQuickLookResource();
+    m_allowEncodedDataReplacement = m_loader && !m_loader->isQuickLookResource();
 #endif
 
     CachedResource::finishLoading(data);
index e27e813..e5eec25 100644 (file)
@@ -260,14 +260,23 @@ void CachedResource::load(CachedResourceLoader& cachedResourceLoader)
         m_fragmentIdentifierForRequest = String();
     }
 
-    // FIXME: We should not special-case Beacon here.
-    if (m_options.keepAlive && type() == CachedResource::Beacon) {
+    if (m_options.keepAlive) {
+        if (!cachedResourceLoader.keepaliveRequestTracker().tryRegisterRequest(*this)) {
+            setResourceError({ errorDomainWebKitInternal, 0, request.url(), ASCIILiteral("Reached maximum amount of queued data of 64Kb for keepalive requests") });
+            failBeforeStarting();
+            return;
+        }
+        // FIXME: We should not special-case Beacon here.
+        if (type() == CachedResource::Beacon) {
         ASSERT(m_origin);
-        // Beacon is not exposed to workers so it is safe to rely on the document here.
-        auto* document = cachedResourceLoader.document();
-        auto* contentSecurityPolicy = document && !document->shouldBypassMainWorldContentSecurityPolicy() ? document->contentSecurityPolicy() : nullptr;
-        platformStrategies()->loaderStrategy()->createPingHandle(frame.loader().networkingContext(), request, *m_origin, contentSecurityPolicy, m_options);
-        return;
+            // Beacon is not exposed to workers so it is safe to rely on the document here.
+            auto* document = cachedResourceLoader.document();
+            auto* contentSecurityPolicy = document && !document->shouldBypassMainWorldContentSecurityPolicy() ? document->contentSecurityPolicy() : nullptr;
+            platformStrategies()->loaderStrategy()->createPingHandle(frame.loader().networkingContext(), request, *m_origin, contentSecurityPolicy, m_options);
+            // FIXME: We currently do not get notified when ping loads finish so we treat them as finishing right away.
+            finishLoading(nullptr);
+            return;
+        }
     }
 
     m_loader = platformStrategies()->loaderStrategy()->loadResource(frame, *this, request, m_options);
index 6acbc9a..6996bba 100644 (file)
@@ -162,7 +162,7 @@ public:
 
     bool isImage() const { return type() == ImageResource; }
     // FIXME: CachedRawResource could be a main resource, an audio/video resource, or a raw XHR/icon resource.
-    bool isMainOrMediaOrIconOrRawResource() const { return type() == MainResource || type() == MediaResource || type() == Icon || type() == RawResource; }
+    bool isMainOrMediaOrIconOrRawResource() const { return type() == MainResource || type() == MediaResource || type() == Icon || type() == RawResource || type() == Beacon; }
 
     // Whether this request should impact request counting and delay window.onload.
     bool ignoreForRequestCount() const
index 94ec7a2..beaa91f 100644 (file)
@@ -103,13 +103,12 @@ static CachedResource* createResource(CachedResource::Type type, CachedResourceR
 #endif
     case CachedResource::FontResource:
         return new CachedFont(WTFMove(request), sessionID);
+    case CachedResource::Beacon:
     case CachedResource::MediaResource:
     case CachedResource::RawResource:
     case CachedResource::Icon:
     case CachedResource::MainResource:
         return new CachedRawResource(WTFMove(request), type, sessionID);
-    case CachedResource::Beacon:
-        return new CachedResource(WTFMove(request), CachedResource::Beacon, sessionID);
 #if ENABLE(XSLT)
     case CachedResource::XSLStyleSheet:
         return new CachedXSLStyleSheet(WTFMove(request), sessionID);
@@ -182,7 +181,7 @@ SessionID CachedResourceLoader::sessionID() const
     return sessionID;
 }
 
-CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest&& request)
+CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest&& request, ResourceError* error)
 {
     if (Frame* frame = this->frame()) {
         if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) {
@@ -196,33 +195,33 @@ CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResou
     }
 
     auto defer = clientDefersImage(request.resourceRequest().url()) ? DeferOption::DeferredByClient : DeferOption::NoDefer;
-    return downcast<CachedImage>(requestResource(CachedResource::ImageResource, WTFMove(request), ForPreload::No, defer).get());
+    return downcast<CachedImage>(requestResource(CachedResource::ImageResource, WTFMove(request), error, ForPreload::No, defer).get());
 }
 
-CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG)
+CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG, ResourceError* error)
 {
 #if ENABLE(SVG_FONTS)
     if (isSVG)
-        return downcast<CachedSVGFont>(requestResource(CachedResource::SVGFontResource, WTFMove(request)).get());
+        return downcast<CachedSVGFont>(requestResource(CachedResource::SVGFontResource, WTFMove(request), error).get());
 #else
     UNUSED_PARAM(isSVG);
 #endif
-    return downcast<CachedFont>(requestResource(CachedResource::FontResource, WTFMove(request)).get());
+    return downcast<CachedFont>(requestResource(CachedResource::FontResource, WTFMove(request), error).get());
 }
 
 #if ENABLE(VIDEO_TRACK)
-CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest&& request)
+CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedTextTrack>(requestResource(CachedResource::TextTrackResource, WTFMove(request)).get());
+    return downcast<CachedTextTrack>(requestResource(CachedResource::TextTrackResource, WTFMove(request), error).get());
 }
 #endif
 
-CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest&& request)
+CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedCSSStyleSheet>(requestResource(CachedResource::CSSStyleSheet, WTFMove(request)).get());
+    return downcast<CachedCSSStyleSheet>(requestResource(CachedResource::CSSStyleSheet, WTFMove(request), error).get());
 }
 
-CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest&& request)
+CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest&& request, ResourceError*)
 {
     ASSERT(document());
     request.setDomainForCachePartition(*document());
@@ -248,55 +247,55 @@ CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSSt
     return userSheet;
 }
 
-CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest&& request)
+CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedScript>(requestResource(CachedResource::Script, WTFMove(request)).get());
+    return downcast<CachedScript>(requestResource(CachedResource::Script, WTFMove(request), error).get());
 }
 
 #if ENABLE(XSLT)
-CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest&& request)
+CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedXSLStyleSheet>(requestResource(CachedResource::XSLStyleSheet, WTFMove(request)).get());
+    return downcast<CachedXSLStyleSheet>(requestResource(CachedResource::XSLStyleSheet, WTFMove(request), error).get());
 }
 #endif
 
-CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest&& request)
+CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedSVGDocument>(requestResource(CachedResource::SVGDocumentResource, WTFMove(request)).get());
+    return downcast<CachedSVGDocument>(requestResource(CachedResource::SVGDocumentResource, WTFMove(request), error).get());
 }
 
 #if ENABLE(LINK_PREFETCH)
-CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest&& request)
+CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest&& request, ResourceError* error)
 {
     ASSERT(frame());
     ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource);
-    return requestResource(type, WTFMove(request));
+    return requestResource(type, WTFMove(request), error);
 }
 #endif
 
-CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMedia(CachedResourceRequest&& request)
+CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMedia(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedRawResource>(requestResource(CachedResource::MediaResource, WTFMove(request)).get());
+    return downcast<CachedRawResource>(requestResource(CachedResource::MediaResource, WTFMove(request), error).get());
 }
 
-CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestIcon(CachedResourceRequest&& request)
+CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestIcon(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedRawResource>(requestResource(CachedResource::Icon, WTFMove(request)).get());
+    return downcast<CachedRawResource>(requestResource(CachedResource::Icon, WTFMove(request), error).get());
 }
 
-CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest&& request)
+CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedRawResource>(requestResource(CachedResource::RawResource, WTFMove(request)).get());
+    return downcast<CachedRawResource>(requestResource(CachedResource::RawResource, WTFMove(request), error).get());
 }
 
-CachedResourceHandle<CachedResource> CachedResourceLoader::requestBeaconResource(CachedResourceRequest&& request)
+CachedResourceHandle<CachedResource> CachedResourceLoader::requestBeaconResource(CachedResourceRequest&& request, ResourceError* error)
 {
-    return requestResource(CachedResource::Beacon, WTFMove(request)).get();
+    return requestResource(CachedResource::Beacon, WTFMove(request), error).get();
 }
 
-CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest&& request)
+CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest&& request, ResourceError* error)
 {
-    return downcast<CachedRawResource>(requestResource(CachedResource::MainResource, WTFMove(request)).get());
+    return downcast<CachedRawResource>(requestResource(CachedResource::MainResource, WTFMove(request), error).get());
 }
 
 static MixedContentChecker::ContentType contentTypeFromResourceType(CachedResource::Type type)
@@ -689,7 +688,7 @@ void CachedResourceLoader::updateHTTPRequestHeaders(CachedResource::Type type, C
     request.updateAccordingCacheMode();
 }
 
-CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, DeferOption defer)
+CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ResourceError* error, ForPreload forPreload, DeferOption defer)
 {
     if (Document* document = this->document())
         request.upgradeInsecureRequestIfNeeded(*document);
@@ -700,6 +699,8 @@ CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(Cache
 
     if (!url.isValid()) {
         RELEASE_LOG_IF_ALLOWED("requestResource: URL is invalid (frame = %p)", frame());
+        if (error)
+            *error = { errorDomainWebKitInternal, 0, url, ASCIILiteral("URL is invalid") };
         return nullptr;
     }
 
@@ -708,6 +709,8 @@ CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(Cache
     // We are passing url as well as request, as request url may contain a fragment identifier.
     if (!canRequest(type, url, request, forPreload)) {
         RELEASE_LOG_IF_ALLOWED("requestResource: Not allowed to request resource (frame = %p)", frame());
+        if (error)
+            *error = { errorDomainWebKitInternal, 0, url, ASCIILiteral("Not allowed to request resource"), ResourceError::Type::AccessControl };
         return nullptr;
     }
 
@@ -725,6 +728,8 @@ CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(Cache
                 resource->setResourceError(ResourceError(ContentExtensions::WebKitContentBlockerDomain, 0, resourceRequest.url(), WEB_UI_STRING("The URL was blocked by a content blocker", "WebKitErrorBlockedByContentBlocker description")));
                 return resource;
             }
+            if (error)
+                *error = { errorDomainWebKitInternal, 0, url, ASCIILiteral("Resource blocked by content blocker"), ResourceError::Type::AccessControl };
             return nullptr;
         }
         if (blockedStatus.madeHTTPS
@@ -829,6 +834,11 @@ CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(Cache
         if (resource->errorOccurred()) {
             if (resource->allowsCaching() && resource->inCache())
                 memoryCache.remove(*resource);
+            if (error) {
+                auto resourceError = resource->resourceError();
+                // Synchronous cancellations are likely due to access control.
+                *error = !resourceError.isNull() && !resourceError.isCancellation() ? resourceError : ResourceError(String(), 0, url, String(), ResourceError::Type::AccessControl);
+            }
             return nullptr;
         }
     }
@@ -1245,7 +1255,7 @@ CachedResourceHandle<CachedResource> CachedResourceLoader::preload(CachedResourc
     if (request.charset().isEmpty() && (type == CachedResource::Script || type == CachedResource::CSSStyleSheet))
         request.setCharset(m_document->charset());
 
-    CachedResourceHandle<CachedResource> resource = requestResource(type, WTFMove(request), ForPreload::Yes);
+    CachedResourceHandle<CachedResource> resource = requestResource(type, WTFMove(request), nullptr, ForPreload::Yes);
     if (resource && (!m_preloads || !m_preloads->contains(resource.get()))) {
         // Fonts need special treatment since just creating the resource doesn't trigger a load.
         if (type == CachedResource::FontResource)
index 71a5b88..38a9c09 100644 (file)
@@ -30,6 +30,7 @@
 #include "CachedResourceHandle.h"
 #include "CachedResourceRequest.h"
 #include "ContentSecurityPolicy.h"
+#include "KeepaliveRequestTracker.h"
 #include "ResourceTimingInformation.h"
 #include "Timer.h"
 #include <wtf/HashMap.h>
@@ -71,25 +72,25 @@ public:
     static Ref<CachedResourceLoader> create(DocumentLoader* documentLoader) { return adoptRef(*new CachedResourceLoader(documentLoader)); }
     ~CachedResourceLoader();
 
-    CachedResourceHandle<CachedImage> requestImage(CachedResourceRequest&&);
-    CachedResourceHandle<CachedCSSStyleSheet> requestCSSStyleSheet(CachedResourceRequest&&);
-    CachedResourceHandle<CachedCSSStyleSheet> requestUserCSSStyleSheet(CachedResourceRequest&&);
-    CachedResourceHandle<CachedScript> requestScript(CachedResourceRequest&&);
-    CachedResourceHandle<CachedFont> requestFont(CachedResourceRequest&&, bool isSVG);
-    CachedResourceHandle<CachedRawResource> requestMedia(CachedResourceRequest&&);
-    CachedResourceHandle<CachedRawResource> requestIcon(CachedResourceRequest&&);
-    CachedResourceHandle<CachedResource> requestBeaconResource(CachedResourceRequest&&);
-    CachedResourceHandle<CachedRawResource> requestRawResource(CachedResourceRequest&&);
-    CachedResourceHandle<CachedRawResource> requestMainResource(CachedResourceRequest&&);
-    CachedResourceHandle<CachedSVGDocument> requestSVGDocument(CachedResourceRequest&&);
+    CachedResourceHandle<CachedImage> requestImage(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedCSSStyleSheet> requestCSSStyleSheet(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedCSSStyleSheet> requestUserCSSStyleSheet(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedScript> requestScript(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedFont> requestFont(CachedResourceRequest&&, bool isSVG, ResourceError* = nullptr);
+    CachedResourceHandle<CachedRawResource> requestMedia(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedRawResource> requestIcon(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedResource> requestBeaconResource(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedRawResource> requestRawResource(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedRawResource> requestMainResource(CachedResourceRequest&&, ResourceError* = nullptr);
+    CachedResourceHandle<CachedSVGDocument> requestSVGDocument(CachedResourceRequest&&, ResourceError* = nullptr);
 #if ENABLE(XSLT)
-    CachedResourceHandle<CachedXSLStyleSheet> requestXSLStyleSheet(CachedResourceRequest&&);
+    CachedResourceHandle<CachedXSLStyleSheet> requestXSLStyleSheet(CachedResourceRequest&&, ResourceError* = nullptr);
 #endif
 #if ENABLE(LINK_PREFETCH)
-    CachedResourceHandle<CachedResource> requestLinkResource(CachedResource::Type, CachedResourceRequest&&);
+    CachedResourceHandle<CachedResource> requestLinkResource(CachedResource::Type, CachedResourceRequest&&, ResourceError* = nullptr);
 #endif
 #if ENABLE(VIDEO_TRACK)
-    CachedResourceHandle<CachedTextTrack> requestTextTrack(CachedResourceRequest&&);
+    CachedResourceHandle<CachedTextTrack> requestTextTrack(CachedResourceRequest&&, ResourceError* = nullptr);
 #endif
 
     // Logs an access denied message to the console for the specified URL.
@@ -146,13 +147,15 @@ public:
 
     bool isAlwaysOnLoggingAllowed() const;
 
+    KeepaliveRequestTracker& keepaliveRequestTracker() { return m_keepaliveRequestTracker; }
+
 private:
     explicit CachedResourceLoader(DocumentLoader*);
 
     enum class ForPreload { Yes, No };
     enum class DeferOption { NoDefer, DeferredByClient };
 
-    CachedResourceHandle<CachedResource> requestResource(CachedResource::Type, CachedResourceRequest&&, ForPreload = ForPreload::No, DeferOption = DeferOption::NoDefer);
+    CachedResourceHandle<CachedResource> requestResource(CachedResource::Type, CachedResourceRequest&&, ResourceError* = nullptr, ForPreload = ForPreload::No, DeferOption = DeferOption::NoDefer);
     CachedResourceHandle<CachedResource> revalidateResource(CachedResourceRequest&&, CachedResource&);
     CachedResourceHandle<CachedResource> loadResource(CachedResource::Type, CachedResourceRequest&&);
 
@@ -193,6 +196,7 @@ private:
     Timer m_garbageCollectDocumentResourcesTimer;
 
     ResourceTimingInformation m_resourceTimingInfo;
+    KeepaliveRequestTracker m_keepaliveRequestTracker;
 
     // 29 bits left
     bool m_autoLoadImages : 1;
diff --git a/Source/WebCore/loader/cache/KeepaliveRequestTracker.cpp b/Source/WebCore/loader/cache/KeepaliveRequestTracker.cpp
new file mode 100644 (file)
index 0000000..55beb7e
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "KeepaliveRequestTracker.h"
+
+namespace WebCore {
+
+const uint64_t maxInflightKeepaliveBytes { 65536 }; // 64 kibibytes as per Fetch specification.
+
+KeepaliveRequestTracker::~KeepaliveRequestTracker()
+{
+    auto inflightRequests = WTFMove(m_inflightKeepaliveRequests);
+    for (auto& resource : inflightRequests)
+        resource->removeClient(*this);
+}
+
+bool KeepaliveRequestTracker::tryRegisterRequest(CachedResource& resource)
+{
+    ASSERT(resource.options().keepAlive);
+    auto* body = resource.resourceRequest().httpBody();
+    if (!body)
+        return true;
+
+    uint64_t bodySize = body->lengthInBytes();
+    if (m_inflightKeepaliveBytes + bodySize > maxInflightKeepaliveBytes)
+        return false;
+
+    registerRequest(resource);
+    return true;
+}
+
+void KeepaliveRequestTracker::registerRequest(CachedResource& resource)
+{
+    ASSERT(resource.options().keepAlive);
+    auto* body = resource.resourceRequest().httpBody();
+    if (!body)
+        return;
+    ASSERT(!m_inflightKeepaliveRequests.contains(&resource));
+    m_inflightKeepaliveRequests.append(&resource);
+    m_inflightKeepaliveBytes += body->lengthInBytes();
+    ASSERT(m_inflightKeepaliveBytes <= maxInflightKeepaliveBytes);
+
+    resource.addClient(*this);
+}
+
+void KeepaliveRequestTracker::responseReceived(CachedResource& resource, const ResourceResponse&)
+{
+    // Per Fetch specification, allocated quota should be returned before the promise is resolved,
+    // which is when the response is received.
+    unregisterRequest(resource);
+}
+
+void KeepaliveRequestTracker::notifyFinished(CachedResource& resource)
+{
+    unregisterRequest(resource);
+}
+
+void KeepaliveRequestTracker::unregisterRequest(CachedResource& resource)
+{
+    ASSERT(resource.options().keepAlive);
+    resource.removeClient(*this);
+    bool wasRemoved = m_inflightKeepaliveRequests.removeFirst(&resource);
+    ASSERT_UNUSED(wasRemoved, wasRemoved);
+    m_inflightKeepaliveBytes -= resource.resourceRequest().httpBody()->lengthInBytes();
+    ASSERT(m_inflightKeepaliveBytes <= maxInflightKeepaliveBytes);
+}
+
+}
diff --git a/Source/WebCore/loader/cache/KeepaliveRequestTracker.h b/Source/WebCore/loader/cache/KeepaliveRequestTracker.h
new file mode 100644 (file)
index 0000000..47da4ed
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CachedRawResourceClient.h"
+#include "CachedResource.h"
+#include "CachedResourceHandle.h"
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class KeepaliveRequestTracker final : public CachedRawResourceClient {
+public:
+    ~KeepaliveRequestTracker();
+    bool tryRegisterRequest(CachedResource&);
+
+    // CachedRawResourceClient.
+    void responseReceived(CachedResource&, const ResourceResponse&) final;
+    void notifyFinished(CachedResource&) final;
+
+private:
+    void registerRequest(CachedResource&);
+    void unregisterRequest(CachedResource&);
+
+    Vector<CachedResourceHandle<CachedResource>> m_inflightKeepaliveRequests;
+    uint64_t m_inflightKeepaliveBytes { 0 };
+};
+
+}
index 564a053..4d1801f 100644 (file)
@@ -33,6 +33,7 @@
 #include "FormDataList.h"
 #include "Page.h"
 #include "TextEncoding.h"
+#include "ThreadableBlobRegistry.h"
 
 namespace WebCore {
 
@@ -124,6 +125,26 @@ Ref<FormData> FormData::isolatedCopy() const
     return formData;
 }
 
+uint64_t FormDataElement::lengthInBytes() const
+{
+    switch (m_type) {
+    case Type::Data:
+        return m_data.size();
+    case Type::EncodedFile: {
+        if (m_fileLength != BlobDataItem::toEndOfFile)
+            return m_fileLength;
+        long long fileSize;
+        if (getFileSize(m_shouldGenerateFile ? m_generatedFilename : m_filename, fileSize))
+            return fileSize;
+        return 0;
+    }
+    case Type::EncodedBlob:
+        return blobRegistry().blobSize(m_url);
+    }
+    ASSERT_NOT_REACHED();
+    return 0;
+}
+
 FormDataElement FormDataElement::isolatedCopy() const
 {
     switch (m_type) {
@@ -146,16 +167,19 @@ void FormData::appendData(const void* data, size_t size)
 void FormData::appendFile(const String& filename, bool shouldGenerateFile)
 {
     m_elements.append(FormDataElement(filename, 0, BlobDataItem::toEndOfFile, invalidFileTime(), shouldGenerateFile));
+    m_lengthInBytes = std::nullopt;
 }
 
 void FormData::appendFileRange(const String& filename, long long start, long long length, double expectedModificationTime, bool shouldGenerateFile)
 {
     m_elements.append(FormDataElement(filename, start, length, expectedModificationTime, shouldGenerateFile));
+    m_lengthInBytes = std::nullopt;
 }
 
 void FormData::appendBlob(const URL& blobURL)
 {
     m_elements.append(FormDataElement(blobURL));
+    m_lengthInBytes = std::nullopt;
 }
 
 void FormData::appendKeyValuePairItems(const FormDataList& list, const TextEncoding& encoding, bool isMultiPartForm, Document* document, EncodingType encodingType)
@@ -244,6 +268,7 @@ void FormData::appendKeyValuePairItems(const FormDataList& list, const TextEncod
 
 char* FormData::expandDataStore(size_t size)
 {
+    m_lengthInBytes = std::nullopt;
     if (m_elements.isEmpty() || m_elements.last().m_type != FormDataElement::Type::Data)
         m_elements.append(FormDataElement());
     FormDataElement& e = m_elements.last();
@@ -386,4 +411,15 @@ void FormData::removeGeneratedFilesIfNeeded()
     }
 }
 
+uint64_t FormData::lengthInBytes() const
+{
+    if (!m_lengthInBytes) {
+        uint64_t length = 0;
+        for (auto& element : m_elements)
+            length += element.lengthInBytes();
+        m_lengthInBytes = length;
+    }
+    return *m_lengthInBytes;
+}
+
 } // namespace WebCore
index 7e44dca..bcb8813 100644 (file)
@@ -69,6 +69,8 @@ public:
     {
     }
 
+    uint64_t lengthInBytes() const;
+
     FormDataElement isolatedCopy() const;
 
     template<typename Encoder>
@@ -254,6 +256,8 @@ public:
         return FormURLEncoded;
     }
 
+    uint64_t lengthInBytes() const;
+
 private:
     FormData();
     FormData(const FormData&);
@@ -269,6 +273,7 @@ private:
     bool m_alwaysStream { false };
     Vector<char> m_boundary;
     bool m_containsPasswordData { false };
+    mutable std::optional<uint64_t> m_lengthInBytes;
 };
 
 inline bool operator==(const FormData& a, const FormData& b)