Add initial support for navigator.sendBeacon
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 2 Aug 2017 04:44:23 +0000 (04:44 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 2 Aug 2017 04:44:23 +0000 (04:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175007
<rdar://problem/33547728>

Reviewed by Sam Weinig.

LayoutTests/imported/w3c:

Import more beacon web-platform-tests and rebaseline the one we had
already imported now that navigator.sendBeacon is exposed.

* resources/import-expectations.json:
* resources/resource-files.json:
* web-platform-tests/beacon/beacon-basic-blob-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-blob.html: Added.
* web-platform-tests/beacon/beacon-basic-blobMax-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-blobMax.html: Added.
* web-platform-tests/beacon/beacon-basic-buffersource-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-buffersource.html: Added.
* web-platform-tests/beacon/beacon-basic-buffersourceMax-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-buffersourceMax.html: Added.
* web-platform-tests/beacon/beacon-basic-formdata-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-formdata.html: Added.
* web-platform-tests/beacon/beacon-basic-formdataMax-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-formdataMax.html: Added.
* web-platform-tests/beacon/beacon-basic-string-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-string.html: Added.
* web-platform-tests/beacon/beacon-basic-stringMax-expected.txt: Added.
* web-platform-tests/beacon/beacon-basic-stringMax.html: Added.
* web-platform-tests/beacon/beacon-common.js: Added.
(allTests.forEach):
(CreateArrayBufferFromPayload):
(CreateEmptyFormDataPayload):
(CreateFormDataFromPayload):
(initSession.return.add):
(initSession):
(runTests.):
(runTests):
(continueAfterSendingBeacon):
(waitForResults.):
(waitForResults):
(runSendInIframeAndNavigateTests.self.buildId):
(runSendInIframeAndNavigateTests.window.onmessage):
(runSendInIframeAndNavigateTests.self.sendFunc):
(runSendInIframeAndNavigateTests.iframe.onload):
* web-platform-tests/beacon/beacon-cors.window.js: Added.
(false.forEach.self.buildId):
(false.forEach.self.buildBaseUrl):
(false.forEach.self.buildTargetUrl):
(false.forEach):
* web-platform-tests/beacon/beacon-error.window.js: Added.
(test):
* web-platform-tests/beacon/beacon-redirect.window.js: Added.
(308.forEach.self.buildId):
(308.forEach.self.buildTargetUrl):
(308.forEach):
* web-platform-tests/beacon/fetch-keepalive-navigate.iFrame.html: Added.
* web-platform-tests/beacon/headers/header-content-type-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-no-referrer-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-no-referrer-when-downgrade.https-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-origin-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-origin-when-cross-origin-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-same-origin-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-strict-origin-when-cross-origin.https-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-strict-origin.https-expected.txt:
* web-platform-tests/beacon/headers/header-referrer-unsafe-url.https-expected.txt:
* web-platform-tests/beacon/resources/beacon.py: Added.
(build_stash_key):
(main):
(main.wrap_key):
* web-platform-tests/beacon/resources/w3c-import.log:
* web-platform-tests/beacon/w3c-import.log: Added.
* web-platform-tests/url/failure-expected.txt:

Source/WebCore:

Add initial support for navigator.sendBeacon behind an experimental
feature runtime flag. The specification is available at:
- https://w3c.github.io/beacon/

The current implementation supports sending beacons with all types of
payloads except for ReadableStream. Some functionality is incomplete
and will be taken care of in follow-up patches:
- Support for CORS preflight for the cases where it is required. We currently
  return false and do not send the beacon in such cases.
- Better support for redirects.
- Use a more power-friendly network priority for beacon requests.

Tests: http/tests/blink/sendbeacon/*
       http/tests/security/mixedContent/beacon/insecure-beacon-in-iframe.html
       http/wpt/beacon/*
       imported/blink/fast/beacon/*
       imported/w3c/web-platform-tests/beacon/*

* CMakeLists.txt:
* DerivedSources.make:
* Modules/beacon/NavigatorBeacon.cpp: Added.
(WebCore::NavigatorBeacon::sendBeacon):
* Modules/beacon/NavigatorBeacon.h: Added.
* Modules/beacon/NavigatorBeacon.idl: Added.
* WebCore.xcodeproj/project.pbxproj:
* loader/PingLoader.cpp:
(WebCore::PingLoader::sendBeacon):
* loader/PingLoader.h:

Source/WebKit:

Add experimental feature flag for the Beacon API, disabled by default.

* Shared/WebPreferencesDefinitions.h:
* WebProcess/InjectedBundle/InjectedBundle.cpp:
(WebKit::InjectedBundle::overrideBoolPreferenceForTestRunner):
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::updatePreferences):

Source/WebKitLegacy/mac:

Add setting to toggle support for the Beacon API (it is disabled by default).

* WebView/WebPreferenceKeysPrivate.h:
* WebView/WebPreferences.mm:
(+[WebPreferences initialize]):
(-[WebPreferences beaconAPIEnabled]):
(-[WebPreferences setBeaconAPIEnabled:]):
* WebView/WebPreferencesPrivate.h:
* WebView/WebView.mm:
(-[WebView _preferencesChanged:]):

Tools:

Enable the Beacon API at runtime in the context of layout tests since the
feature is currently disabled by default.

* DumpRenderTree/mac/DumpRenderTree.mm:
(enableExperimentalFeatures):
* WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
(WTR::InjectedBundle::beginTesting):
* WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::TestRunner::setModernMediaControlsEnabled):
(WTR::TestRunner::setBeaconAPIEnabled):
* WebKitTestRunner/InjectedBundle/TestRunner.h:

LayoutTests:

* fast/dom/navigator-detached-no-crash-expected.txt:
Rebaseline test now that sendBeacon is exposed on navigator.

* http/tests/blink/sendbeacon/beacon-cookie-expected.txt: Added.
* http/tests/blink/sendbeacon/beacon-cookie.html: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin-expected.txt: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob-expected.txt: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob.html: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin-redirect-expected.txt: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin-redirect.html: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin.html: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin.https-expected.txt: Added.
* http/tests/blink/sendbeacon/beacon-cross-origin.https.html: Added.
* http/tests/blink/sendbeacon/beacon-detached-no-crash-expected.txt: Added.
* http/tests/blink/sendbeacon/beacon-detached-no-crash.html: Added.
* http/tests/blink/sendbeacon/beacon-same-origin-expected.txt: Added.
* http/tests/blink/sendbeacon/beacon-same-origin.html: Added.
* http/tests/blink/sendbeacon/connect-src-beacon-allowed-expected.txt: Added.
* http/tests/blink/sendbeacon/connect-src-beacon-allowed.html: Added.
* http/tests/blink/sendbeacon/resources/check-beacon.php: Added.
* http/tests/blink/sendbeacon/resources/save-beacon.php: Added.
Import more beacon test coverage from Blink.

* http/wpt/beacon/connect-src-beacon-blocked.sub-expected.txt: Added.
* http/wpt/beacon/connect-src-beacon-blocked.sub.html: Added.
Improve test coverage for sendBeacon and CSP.

* http/wpt/beacon/headers/header-content-type-same-origin-expected.txt: Added.
* http/wpt/beacon/headers/header-content-type-same-origin.html: Added.
Improve test coverage for sendBeacon with various types of payload. The test is done
using same origin as we do not currently support sending some of those payloads cross
origin yet.

* imported/blink/fast/beacon/beacon-basic-expected.txt: Added.
* imported/blink/fast/beacon/beacon-basic.html: Added.
Import basic Beacon test coverage from Blink.

* resources/window-postmessage-open-close.html: Added.
* tests-options.json:

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

92 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/navigator-detached-no-crash-expected.txt
LayoutTests/http/tests/blink/sendbeacon/beacon-cookie-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cookie.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.https-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.https.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-detached-no-crash-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-detached-no-crash.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-same-origin-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/beacon-same-origin.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/connect-src-beacon-allowed-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/connect-src-beacon-allowed.html [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/resources/check-beacon.php [new file with mode: 0644]
LayoutTests/http/tests/blink/sendbeacon/resources/save-beacon.php [new file with mode: 0644]
LayoutTests/http/wpt/beacon/connect-src-beacon-blocked.sub-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/beacon/connect-src-beacon-blocked.sub.html [new file with mode: 0644]
LayoutTests/http/wpt/beacon/headers/header-content-type-same-origin-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/beacon/headers/header-content-type-same-origin.html [new file with mode: 0644]
LayoutTests/imported/blink/fast/beacon/beacon-basic-expected.txt [new file with mode: 0644]
LayoutTests/imported/blink/fast/beacon/beacon-basic.html [new file with mode: 0644]
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/resources/import-expectations.json
LayoutTests/imported/w3c/resources/resource-files.json
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blob-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blob.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdata-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdata.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-string-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-string.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.window.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.window.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/fetch-keepalive-navigate.iFrame.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-content-type-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-no-referrer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-no-referrer-when-downgrade.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-origin-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-origin-when-cross-origin-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-same-origin-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-strict-origin-when-cross-origin.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-strict-origin.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-unsafe-url.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/beacon/resources/w3c-import.log
LayoutTests/imported/w3c/web-platform-tests/beacon/w3c-import.log [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/url/failure-expected.txt
LayoutTests/resources/window-postmessage-open-close.html [new file with mode: 0644]
LayoutTests/tests-options.json
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/DerivedSources.make
Source/WebCore/Modules/beacon/NavigatorBeacon.cpp [new file with mode: 0644]
Source/WebCore/Modules/beacon/NavigatorBeacon.h [new file with mode: 0644]
Source/WebCore/Modules/beacon/NavigatorBeacon.idl [new file with mode: 0644]
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
Source/WebCore/bindings/scripts/test/JS/JSTestGenerateIsReachable.cpp
Source/WebCore/bindings/scripts/test/JS/JSTestNode.cpp
Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp
Source/WebCore/loader/PingLoader.cpp
Source/WebCore/loader/PingLoader.h
Source/WebCore/page/Settings.in
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferencesDefinitions.h
Source/WebKit/UIProcess/API/C/WKPreferences.cpp
Source/WebKit/UIProcess/API/C/WKPreferencesRefPrivate.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebView/WebPreferenceKeysPrivate.h
Source/WebKitLegacy/mac/WebView/WebPreferences.mm
Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h
Source/WebKitLegacy/mac/WebView/WebView.mm
Tools/ChangeLog
Tools/DumpRenderTree/mac/DumpRenderTree.mm
Tools/WebKitTestRunner/TestController.cpp

index 771c4ec..a279e4e 100644 (file)
@@ -1,3 +1,51 @@
+2017-08-01  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for navigator.sendBeacon
+        https://bugs.webkit.org/show_bug.cgi?id=175007
+        <rdar://problem/33547728>
+
+        Reviewed by Sam Weinig.
+
+        * fast/dom/navigator-detached-no-crash-expected.txt:
+        Rebaseline test now that sendBeacon is exposed on navigator.
+
+        * http/tests/blink/sendbeacon/beacon-cookie-expected.txt: Added.
+        * http/tests/blink/sendbeacon/beacon-cookie.html: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin-expected.txt: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob-expected.txt: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob.html: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin-redirect-expected.txt: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin-redirect.html: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin.html: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin.https-expected.txt: Added.
+        * http/tests/blink/sendbeacon/beacon-cross-origin.https.html: Added.
+        * http/tests/blink/sendbeacon/beacon-detached-no-crash-expected.txt: Added.
+        * http/tests/blink/sendbeacon/beacon-detached-no-crash.html: Added.
+        * http/tests/blink/sendbeacon/beacon-same-origin-expected.txt: Added.
+        * http/tests/blink/sendbeacon/beacon-same-origin.html: Added.
+        * http/tests/blink/sendbeacon/connect-src-beacon-allowed-expected.txt: Added.
+        * http/tests/blink/sendbeacon/connect-src-beacon-allowed.html: Added.
+        * http/tests/blink/sendbeacon/resources/check-beacon.php: Added.
+        * http/tests/blink/sendbeacon/resources/save-beacon.php: Added.
+        Import more beacon test coverage from Blink.
+
+        * http/wpt/beacon/connect-src-beacon-blocked.sub-expected.txt: Added.
+        * http/wpt/beacon/connect-src-beacon-blocked.sub.html: Added.
+        Improve test coverage for sendBeacon and CSP.
+
+        * http/wpt/beacon/headers/header-content-type-same-origin-expected.txt: Added.
+        * http/wpt/beacon/headers/header-content-type-same-origin.html: Added.
+        Improve test coverage for sendBeacon with various types of payload. The test is done
+        using same origin as we do not currently support sending some of those payloads cross
+        origin yet.
+
+        * imported/blink/fast/beacon/beacon-basic-expected.txt: Added.
+        * imported/blink/fast/beacon/beacon-basic.html: Added.
+        Import basic Beacon test coverage from Blink.
+
+        * resources/window-postmessage-open-close.html: Added.
+        * tests-options.json:
+
 2017-08-01  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: simplify WebInspector with WI
index b3b252f..811bbf7 100644 (file)
@@ -14,6 +14,7 @@ navigator.platform is OK
 navigator.plugins is OK
 navigator.product is OK
 navigator.productSub is OK
+navigator.sendBeacon() threw err TypeError: Not enough arguments
 navigator.userAgent is OK
 navigator.vendor is OK
 navigator.vendorSub is OK
@@ -32,6 +33,7 @@ navigator.platform is OK
 navigator.plugins is OK
 navigator.product is OK
 navigator.productSub is OK
+navigator.sendBeacon() threw err TypeError: Not enough arguments
 navigator.userAgent is OK
 navigator.vendor is OK
 navigator.vendorSub is OK
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cookie-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/beacon-cookie-expected.txt
new file mode 100644 (file)
index 0000000..b35b457
--- /dev/null
@@ -0,0 +1,19 @@
+Checking transmission of Beacons involving cookies.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS navigator.sendBeacon("resources/save-beacon.php?name=cookie", "Blip"); is true
+PASS Beacon sent successfully
+PASS Content-Type: text/plain;charset=UTF-8
+PASS Cookie: hello=world
+PASS Origin: null
+PASS Referer: http://127.0.0.1:8000/blink/sendbeacon/beacon-cookie.html
+PASS Request-Method: POST
+PASS Length: 4
+PASS Body: Blip
+PASS 
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cookie.html b/LayoutTests/http/tests/blink/sendbeacon/beacon-cookie.html
new file mode 100644 (file)
index 0000000..7144f53
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description("Checking transmission of Beacons involving cookies.");
+
+window.jsTestIsAsync = true;
+
+function test() {
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+        //testRunner.dumpPingLoaderCallbacks();
+    }
+    try {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", "../../cookies/resources/setCookies.cgi", false);
+        xhr.setRequestHeader("SET-COOKIE", "hello=world;path=/");
+        xhr.send(null);
+        if (xhr.status != 200) {
+            testFailed("cookie not set");
+            finishJSTest();
+        }
+    } catch (e) {
+        testFailed("cookie not set");
+        finishJSTest();
+    }
+
+    shouldBeTrue('navigator.sendBeacon("resources/save-beacon.php?name=cookie", "Blip");');
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", "resources/check-beacon.php?name=cookie");
+    xhr.onload = function () {
+        var lines = xhr.responseText.split("\n");
+        for (var i in lines)
+            testPassed(lines[i]);
+        finishJSTest();
+    };
+    xhr.onerror = function () {
+        testFailed("Unable to fetch beacon status");
+        finishJSTest();
+    };
+
+    xhr.send();
+}
+</script>
+</head>
+<body onload="test();">
+</body>
+</html>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-expected.txt
new file mode 100644 (file)
index 0000000..774e1a6
--- /dev/null
@@ -0,0 +1,18 @@
+Testing navigator.sendBeacon() cross-origin.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS navigator.sendBeacon("http://localhost:8000/blink/sendbeacon/resources/save-beacon.php?name=cross-origin", "CrossOrigin"); is true
+PASS Beacon sent successfully
+PASS Content-Type: text/plain;charset=UTF-8
+PASS Origin: null
+PASS Referer: http://127.0.0.1:8000/blink/sendbeacon/beacon-cross-origin.html
+PASS Request-Method: POST
+PASS Length: 11
+PASS Body: CrossOrigin
+PASS 
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob-expected.txt
new file mode 100644 (file)
index 0000000..0230ec3
--- /dev/null
@@ -0,0 +1,10 @@
+Verifying navigator.sendBeacon(Blob) non-CORS cross-origin redirect handling.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS navigator.sendBeacon("http://127.0.0.1:8080/navigation/resources/redirection-response.php?status=302&simple=true&target=/non-existent.php", blob); is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob.html b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-blob.html
new file mode 100644 (file)
index 0000000..e376623
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description("Verifying navigator.sendBeacon(Blob) non-CORS cross-origin redirect handling.");
+
+if (window.testRunner) {
+  //testRunner.dumpPingLoaderCallbacks();
+}
+
+const blob = new Blob(["Cross", "Origin"], {type: "text/plain;from-beacon=true"});
+// The "simple" parameter is just for differentiating the URLs.
+shouldBeTrue('navigator.sendBeacon("http://127.0.0.1:8080/navigation/resources/redirection-response.php?status=302&simple=true&target=/non-existent.php", blob);');
+</script>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect-expected.txt
new file mode 100644 (file)
index 0000000..161fd82
--- /dev/null
@@ -0,0 +1,10 @@
+Verifying that navigator.sendBeacon() to non-CORS cross-origin redirect fails.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS navigator.sendBeacon("http://127.0.0.1:8080/navigation/resources/redirection-response.php?status=302&target=/non-existent.php", "CrossOrigin"); is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect.html b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin-redirect.html
new file mode 100644 (file)
index 0000000..6fb1777
--- /dev/null
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description("Verifying that navigator.sendBeacon() to non-CORS cross-origin redirect fails.");
+
+window.jsTestIsAsync = true;
+
+function test() {
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        //testRunner.dumpPingLoaderCallbacks();
+    }
+
+    shouldBeTrue('navigator.sendBeacon("http://127.0.0.1:8080/navigation/resources/redirection-response.php?status=302&target=/non-existent.php", "CrossOrigin");');
+    // Wait a while for the redirect response handling to happen before finishing up.
+    setTimeout(finishJSTest, 200);
+}
+</script>
+</head>
+<body onload="test();">
+</body>
+</html>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.html b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.html
new file mode 100644 (file)
index 0000000..38de7ab
--- /dev/null
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description("Testing navigator.sendBeacon() cross-origin.");
+
+window.jsTestIsAsync = true;
+
+function test() {
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+        //testRunner.dumpPingLoaderCallbacks();
+    }
+
+    shouldBeTrue('navigator.sendBeacon("http://localhost:8000/blink/sendbeacon/resources/save-beacon.php?name=cross-origin", "CrossOrigin");');
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", "resources/check-beacon.php?name=cross-origin");
+    xhr.onload = function () {
+        var lines = xhr.responseText.split("\n");
+        for (var i in lines)
+            testPassed(lines[i]);
+        finishJSTest();
+    };
+    xhr.onerror = function () {
+        testFailed("Unable to fetch beacon status");
+        finishJSTest();
+    };
+    xhr.send();
+}
+</script>
+</head>
+<body onload="test();">
+</body>
+</html>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.https-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.https-expected.txt
new file mode 100644 (file)
index 0000000..dcf06b4
--- /dev/null
@@ -0,0 +1,11 @@
+Verify navigator.sendBeacon() mixed content checking.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+FAIL navigator.sendBeacon("http://example.test:8000/blink/sendbeacon/resources/save-beacon.php?name=cross-origin", "CrossOrigin"); should be false. Was true.
+PASS successfullyParsed is true
+Some tests failed.
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.https.html b/LayoutTests/http/tests/blink/sendbeacon/beacon-cross-origin.https.html
new file mode 100644 (file)
index 0000000..0c57f4f
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description("Verify navigator.sendBeacon() mixed content checking.");
+
+window.jsTestIsAsync = true;
+
+function test() {
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+
+    shouldBeFalse('navigator.sendBeacon("http://example.test:8000/blink/sendbeacon/resources/save-beacon.php?name=cross-origin", "CrossOrigin");');
+    finishJSTest();
+}
+</script>
+</head>
+<body onload="test();">
+</body>
+</html>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-detached-no-crash-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/beacon-detached-no-crash-expected.txt
new file mode 100644 (file)
index 0000000..806092a
--- /dev/null
@@ -0,0 +1,12 @@
+main frame - has 1 onunload handler(s)
+Accessing Navigator sendBeacon methods on a closed window.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS otherNavigator.sendBeacon() threw exception TypeError: Not enough arguments.
+PASS otherNavigator.sendBeacon('resources/blank.txt') is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-detached-no-crash.html b/LayoutTests/http/tests/blink/sendbeacon/beacon-detached-no-crash.html
new file mode 100644 (file)
index 0000000..34b4828
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description("Accessing Navigator sendBeacon methods on a closed window.");
+
+window.jsTestIsAsync = true;
+
+var w;
+function processMessage(event) {
+    if (event.data == "opened") {
+        otherNavigator = w.navigator;
+        w.close();
+    } else if (event.data == "closed") {
+        shouldThrowErrorName("otherNavigator.sendBeacon()", "TypeError");
+        shouldBeFalse("otherNavigator.sendBeacon('resources/blank.txt')");
+        finishJSTest();
+    }
+}
+
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.setCanOpenWindows();
+    testRunner.waitUntilDone();
+}
+w = window.open('/js-test-resources/window-postmessage-open-close.html');
+window.addEventListener("message", processMessage, false);
+</script>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-same-origin-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/beacon-same-origin-expected.txt
new file mode 100644 (file)
index 0000000..b2e1e80
--- /dev/null
@@ -0,0 +1,59 @@
+Testing navigator.sendBeacon() within same origin.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Sending beacon with type: [object String]
+PASS navigator.sendBeacon("resources/save-beacon.php?name=same-origin", payload); is true
+PASS Beacon sent successfully
+PASS Content-Type: text/plain;charset=UTF-8
+PASS Origin: null
+PASS Referer: http://127.0.0.1:8000/blink/sendbeacon/beacon-same-origin.html
+PASS Request-Method: POST
+PASS Length: 10
+PASS Body: SameOrigin
+PASS 
+Sending beacon with type: [object Uint32Array]
+PASS navigator.sendBeacon("resources/save-beacon.php?name=same-origin", payload); is true
+PASS Beacon sent successfully
+PASS Content-Type: application/octet-stream
+PASS Origin: null
+PASS Referer: http://127.0.0.1:8000/blink/sendbeacon/beacon-same-origin.html
+PASS Request-Method: POST
+PASS Length: 40
+PASS Body: QAAAAEEAAABCAAAAQwAAAEQAAABFAAAARgAAAEcAAABIAAAASQAAAA==
+PASS 
+Sending beacon with type: [object Blob]
+PASS navigator.sendBeacon("resources/save-beacon.php?name=same-origin", payload); is true
+PASS Beacon sent successfully
+PASS Content-Type: text/plain;from-beacon=true
+PASS Origin: null
+PASS Referer: http://127.0.0.1:8000/blink/sendbeacon/beacon-same-origin.html
+PASS Request-Method: POST
+PASS Length: 11
+PASS Body: hello world
+PASS 
+Sending beacon with type: [object FormData]
+PASS navigator.sendBeacon("resources/save-beacon.php?name=same-origin", payload); is true
+PASS Beacon sent successfully
+PASS Content-Type: multipart/form-data;
+PASS Origin: null
+PASS Referer: http://127.0.0.1:8000/blink/sendbeacon/beacon-same-origin.html
+PASS Request-Method: POST
+PASS Length: 9
+PASS Body: key=value
+PASS 
+Sending beacon with type: [object URLSearchParams]
+PASS navigator.sendBeacon("resources/save-beacon.php?name=same-origin", payload); is true
+PASS Beacon sent successfully
+PASS Content-Type: application/x-www-form-urlencoded;charset=UTF-8
+PASS Origin: null
+PASS Referer: http://127.0.0.1:8000/blink/sendbeacon/beacon-same-origin.html
+PASS Request-Method: POST
+PASS Length: 7
+PASS Body: YT1iJmM9ZA==
+PASS 
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/beacon-same-origin.html b/LayoutTests/http/tests/blink/sendbeacon/beacon-same-origin.html
new file mode 100644 (file)
index 0000000..dec061e
--- /dev/null
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description("Testing navigator.sendBeacon() within same origin.");
+
+window.jsTestIsAsync = true;
+
+var binary_array = new Uint32Array(10);
+for (var i = 0; i < binary_array.length; i++) {
+    binary_array[i] = 64 + i;
+}
+
+var blob = new Blob(["hello", " ", "world"], {type: "text/plain;from-beacon=true"});
+var form = new FormData();
+form.append("key", "value");
+var searchParams = new URLSearchParams("a=b&c=d");
+
+var tests = [
+    "SameOrigin",
+    binary_array,
+    blob,
+    form,
+    searchParams];
+
+var payload;
+function testOne() {
+    payload = tests.shift();
+    if (!payload) {
+        finishJSTest();
+        return;
+    }
+    debug("Sending beacon with type: " + Object.prototype.toString.call(payload));
+    shouldBeTrue('navigator.sendBeacon("resources/save-beacon.php?name=same-origin", payload);');
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", "resources/check-beacon.php?name=same-origin");
+    xhr.onload = function () {
+        var lines = xhr.responseText.split("\n");
+        for (var i in lines)
+            testPassed(lines[i]);
+        testOne();
+    };
+    xhr.onerror = function () {
+        testFailed("Unable to fetch beacon status");
+        testOne();
+    };
+    xhr.send();
+}
+
+function test() {
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+        //testRunner.dumpPingLoaderCallbacks();
+    }
+    testOne();
+}
+</script>
+</head>
+<body onload="test();">
+</body>
+</html>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/connect-src-beacon-allowed-expected.txt b/LayoutTests/http/tests/blink/sendbeacon/connect-src-beacon-allowed-expected.txt
new file mode 100644 (file)
index 0000000..0533f45
--- /dev/null
@@ -0,0 +1,2 @@
+Pass
+
diff --git a/LayoutTests/http/tests/blink/sendbeacon/connect-src-beacon-allowed.html b/LayoutTests/http/tests/blink/sendbeacon/connect-src-beacon-allowed.html
new file mode 100644 (file)
index 0000000..832f48b
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="connect-src http://127.0.0.1:8000">
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+</head>
+<body>
+<pre id="console"></pre>
+<script>
+function log(msg)
+{
+    document.getElementById("console").appendChild(document.createTextNode(msg + "\n"));
+}
+
+try {
+    var es = navigator.sendBeacon("http://127.0.0.1:8000/security/contentSecurityPolicy/resources/echo-report.php");
+    log("Pass");
+} catch(e) {
+    log("Fail");
+}
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/resources/check-beacon.php b/LayoutTests/http/tests/blink/sendbeacon/resources/check-beacon.php
new file mode 100644 (file)
index 0000000..5bd3f6b
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+require_once '../../../resources/portabilityLayer.php';
+
+$beaconFilename = sys_get_temp_dir() . "/beacon" . (isset($_REQUEST['name']) ? $_REQUEST['name'] : "") . ".txt";
+
+$max_attempts = 700;
+$retries = isset($_REQUEST['retries']) ? (int)$_REQUEST['retries'] : $max_attempts;
+while (!file_exists($beaconFilename) && $retries != 0) {
+    usleep(10000);
+    # file_exists() caches results, we want to invalidate the cache.
+    clearstatcache();
+    $retries--;
+}
+
+header('Content-Type: text/plain');
+header('Access-Control-Allow-Origin: *');
+if (file_exists($beaconFilename)) {
+    $beaconFile = false;
+    if (is_readable($beaconFilename)) {
+        $beaconFile = fopen($beaconFilename, 'r');
+    }
+    if ($beaconFile) {
+        echo "Beacon sent successfully\n";
+        while ($line = fgets($beaconFile)) {
+            $trimmed = trim($line);
+            if ($trimmed != "")
+                echo "$trimmed\n";
+        }
+        fclose($beaconFile);
+        unlink($beaconFilename);
+    } else {
+        echo "Beacon status not readable\n";
+    }
+} else {
+    echo "Beacon not sent\n";
+}
+?>
diff --git a/LayoutTests/http/tests/blink/sendbeacon/resources/save-beacon.php b/LayoutTests/http/tests/blink/sendbeacon/resources/save-beacon.php
new file mode 100644 (file)
index 0000000..ec79651
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+require_once '../../../resources/portabilityLayer.php';
+
+function prettify($name) {
+   return str_replace(' ', '-', ucwords(str_replace('_', ' ', str_replace('http_', '', strtolower($name)))));
+}
+
+$beaconFilename = sys_get_temp_dir() . "/beacon" . (isset($_REQUEST['name']) ? $_REQUEST['name'] : "") . ".txt";
+$beaconFile = fopen($beaconFilename . ".tmp", 'w');
+$httpHeaders = $_SERVER;
+ksort($httpHeaders, SORT_STRING);
+$contentType = "";
+foreach ($httpHeaders as $name => $value) {
+    if ($name === "CONTENT_TYPE" || $name === "HTTP_REFERER" || $name === "REQUEST_METHOD" || $name === "HTTP_COOKIE" || $name === "HTTP_ORIGIN") {
+        if ($name === "CONTENT_TYPE") {
+            $contentType = $value;
+            $value = preg_replace('/boundary=.*$/', '', $value);
+        }
+        $headerName = prettify($name);
+        fwrite($beaconFile, "$headerName: $value\n");
+    }
+}
+$postdata = file_get_contents("php://input");
+if (strlen($postdata) == 0)
+   $postdata = http_build_query($_POST);
+
+fwrite($beaconFile, "Length: " . strlen($postdata) . "\n");
+if (strpos($contentType, "application/") !== false) {
+    $postdata = base64_encode($postdata);
+}
+
+fwrite($beaconFile, "Body: $postdata\n");
+fclose($beaconFile);
+rename($beaconFilename . ".tmp", $beaconFilename);
+
+if (!array_key_exists('dontclearcookies', $_GET)) {
+  foreach ($_COOKIE as $name => $value)
+      setcookie($name, "deleted", time() - 60, "/");
+}
+?>
diff --git a/LayoutTests/http/wpt/beacon/connect-src-beacon-blocked.sub-expected.txt b/LayoutTests/http/wpt/beacon/connect-src-beacon-blocked.sub-expected.txt
new file mode 100644 (file)
index 0000000..2b73ca9
--- /dev/null
@@ -0,0 +1,5 @@
+CONSOLE MESSAGE: Refused to connect to http://www.localhost:8800/common/text-plain.txt because it does not appear in the connect-src directive of the Content Security Policy.
+
+PASS sendBeacon should not throw. 
+PASS redirect case 
+
diff --git a/LayoutTests/http/wpt/beacon/connect-src-beacon-blocked.sub.html b/LayoutTests/http/wpt/beacon/connect-src-beacon-blocked.sub.html
new file mode 100644 (file)
index 0000000..c7f7e38
--- /dev/null
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="connect-src 'self'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    async_test(t => {
+      document.addEventListener("securitypolicyviolation", t.step_func_done(e => {
+        if (e.blockedURI != "http://{{domains[www]}}:{{ports[http][0]}}/common/text-plain.txt")
+            return;
+
+        assert_equals(e.violatedDirective, "connect-src");
+      }));
+
+      assert_true(navigator.sendBeacon("http://{{domains[www]}}:{{ports[http][0]}}/common/text-plain.txt"));
+    }, "sendBeacon should not throw.");
+
+    async_test(t => {
+      document.addEventListener("securitypolicyviolation", t.step_func_done(e => {
+        if (e.blockedURI != "http://{{domains[www]}}:{{ports[http][0]}}/common/text-plain.txt")
+            return;
+
+        assert_equals(e.violatedDirective, "connect-src");
+      }));
+
+      assert_true(navigator.sendBeacon("common/redirect-opt-in.py?status=307&location=http://{{domains[www]}}:{{ports[http][0]}}/common/text-plain.txt"));
+    }, "redirect case");
+</script>
diff --git a/LayoutTests/http/wpt/beacon/headers/header-content-type-same-origin-expected.txt b/LayoutTests/http/wpt/beacon/headers/header-content-type-same-origin-expected.txt
new file mode 100644 (file)
index 0000000..60ee6e5
--- /dev/null
@@ -0,0 +1,8 @@
+
+PASS Test content-type header for a body string 
+PASS Test content-type header for a body ArrayBufferView 
+PASS Test content-type header for a body ArrayBuffer 
+PASS Test content-type header for a body Blob 
+PASS Test content-type header for a body FormData 
+PASS Test content-type header for a body URLSearchParams 
+
diff --git a/LayoutTests/http/wpt/beacon/headers/header-content-type-same-origin.html b/LayoutTests/http/wpt/beacon/headers/header-content-type-same-origin.html
new file mode 100644 (file)
index 0000000..e24c2e7
--- /dev/null
@@ -0,0 +1,87 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>SendBeacon Content-Type header</title>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+  </head>
+  <body>
+    <script src="/common/utils.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script>
+var RESOURCES_DIR = "/beacon/resources/";
+
+function pollResult(test, id) {
+  var checkUrl = RESOURCES_DIR + "content-type.py?cmd=get&id=" + id;
+
+  return new Promise(resolve => {
+    step_timeout(test.step_func(() => {
+      fetch(checkUrl).then(response => {
+        response.text().then(body => {
+          resolve(body);
+        });
+      });
+    }), 1000);
+  });
+}
+
+function testContentTypeHeader(what, contentType, title) {
+  var testBase = RESOURCES_DIR;
+  var id = self.token();
+  var testUrl = testBase + "content-type.py?cmd=put&id=" + id;
+
+  promise_test(function(test) {
+    assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
+    return pollResult(test, id) .then(result => {
+      assert_true(result.startsWith(contentType), "Correct content-type header result");
+    });
+  }, "Test content-type header for a body " + title);
+}
+
+function stringToArrayBufferView(input) {
+  var buffer = new ArrayBuffer(input.length * 2);
+  var view = new Uint16Array(buffer);
+
+  // dumbly copy over the bytes
+  for (var i = 0, len = input.length; i < len; i++) {
+    view[i] = input.charCodeAt(i);
+  }
+  return view;
+}
+
+function stringToArrayBuffer(input) {
+  var buffer = new ArrayBuffer(input.length * 2);
+  var view = new Uint16Array(buffer);
+
+  // dumbly copy over the bytes
+  for (var i = 0, len = input.length; i < len; i++) {
+    view[i] = input.charCodeAt(i);
+  }
+  return buffer;
+}
+
+function stringToBlob(input) {
+  return new Blob([input], {type: "text/plain"});
+}
+
+function stringToFormData(input) {
+  var formdata = new FormData();
+  formdata.append(input, new Blob(['hi']));
+  return formdata;
+}
+
+function stringToURLSearchParams(input)
+{
+  return new URLSearchParams(input);
+}
+
+testContentTypeHeader("hi!", "text/plain;charset=UTF-8", "string");
+testContentTypeHeader(stringToArrayBufferView("123"), "application/octet-stream", "ArrayBufferView"); // Specification says no content-type but browsers seem to use "application/octet-stream".
+testContentTypeHeader(stringToArrayBuffer("123"), "application/octet-stream", "ArrayBuffer"); // Specification says no content-type but browsers seem to use "application/octet-stream".
+testContentTypeHeader(stringToBlob("123"), "text/plain", "Blob");
+testContentTypeHeader(stringToFormData("qwerty"), "multipart/form-data", "FormData");
+testContentTypeHeader(stringToURLSearchParams("key1=value1&key2=value2"), "application/x-www-form-urlencoded;charset=UTF-8", "URLSearchParams");
+    </script>
+  </body>
+</html>
diff --git a/LayoutTests/imported/blink/fast/beacon/beacon-basic-expected.txt b/LayoutTests/imported/blink/fast/beacon/beacon-basic-expected.txt
new file mode 100644 (file)
index 0000000..c6ccb49
--- /dev/null
@@ -0,0 +1,15 @@
+Exercising the Beacon API
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS Object.getPrototypeOf(navigator).hasOwnProperty('sendBeacon') is true
+PASS typeof navigator.sendBeacon is "function"
+PASS navigator.sendBeacon() threw exception TypeError: Not enough arguments.
+PASS navigator.sendBeacon('http://foo:-80/') threw exception TypeError: This URL is invalid.
+PASS navigator.sendBeacon('javascript:alert(1);') threw exception TypeError: Beacons can only be sent over HTTP(S).
+PASS navigator.sendBeacon('http://foo:-80/', new Uint8Array(new SharedArrayBuffer(10))) threw exception TypeError: This URL is invalid.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/imported/blink/fast/beacon/beacon-basic.html b/LayoutTests/imported/blink/fast/beacon/beacon-basic.html
new file mode 100644 (file)
index 0000000..ef26971
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<script src="../../../../resources/js-test.js"></script>
+<script>
+description("Exercising the Beacon API");
+
+shouldBeTrue("Object.getPrototypeOf(navigator).hasOwnProperty('sendBeacon')");
+shouldBeEqualToString("typeof navigator.sendBeacon", "function");
+shouldThrow("navigator.sendBeacon()");
+shouldThrow("navigator.sendBeacon('http://foo:-80/')");
+shouldThrow("navigator.sendBeacon('javascript:alert(1);')");
+
+if (window.SharedArrayBuffer) {
+  shouldThrow("navigator.sendBeacon('http://foo:-80/', new Uint8Array(new SharedArrayBuffer(10)))");
+}
+
+</script>
index 29eceb9..8f96b07 100644 (file)
@@ -1,3 +1,77 @@
+2017-08-01  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for navigator.sendBeacon
+        https://bugs.webkit.org/show_bug.cgi?id=175007
+        <rdar://problem/33547728>
+
+        Reviewed by Sam Weinig.
+
+        Import more beacon web-platform-tests and rebaseline the one we had
+        already imported now that navigator.sendBeacon is exposed.
+
+        * resources/import-expectations.json:
+        * resources/resource-files.json:
+        * web-platform-tests/beacon/beacon-basic-blob-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-blob.html: Added.
+        * web-platform-tests/beacon/beacon-basic-blobMax-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-blobMax.html: Added.
+        * web-platform-tests/beacon/beacon-basic-buffersource-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-buffersource.html: Added.
+        * web-platform-tests/beacon/beacon-basic-buffersourceMax-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-buffersourceMax.html: Added.
+        * web-platform-tests/beacon/beacon-basic-formdata-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-formdata.html: Added.
+        * web-platform-tests/beacon/beacon-basic-formdataMax-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-formdataMax.html: Added.
+        * web-platform-tests/beacon/beacon-basic-string-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-string.html: Added.
+        * web-platform-tests/beacon/beacon-basic-stringMax-expected.txt: Added.
+        * web-platform-tests/beacon/beacon-basic-stringMax.html: Added.
+        * web-platform-tests/beacon/beacon-common.js: Added.
+        (allTests.forEach):
+        (CreateArrayBufferFromPayload):
+        (CreateEmptyFormDataPayload):
+        (CreateFormDataFromPayload):
+        (initSession.return.add):
+        (initSession):
+        (runTests.):
+        (runTests):
+        (continueAfterSendingBeacon):
+        (waitForResults.):
+        (waitForResults):
+        (runSendInIframeAndNavigateTests.self.buildId):
+        (runSendInIframeAndNavigateTests.window.onmessage):
+        (runSendInIframeAndNavigateTests.self.sendFunc):
+        (runSendInIframeAndNavigateTests.iframe.onload):
+        * web-platform-tests/beacon/beacon-cors.window.js: Added.
+        (false.forEach.self.buildId):
+        (false.forEach.self.buildBaseUrl):
+        (false.forEach.self.buildTargetUrl):
+        (false.forEach):
+        * web-platform-tests/beacon/beacon-error.window.js: Added.
+        (test):
+        * web-platform-tests/beacon/beacon-redirect.window.js: Added.
+        (308.forEach.self.buildId):
+        (308.forEach.self.buildTargetUrl):
+        (308.forEach):
+        * web-platform-tests/beacon/fetch-keepalive-navigate.iFrame.html: Added.
+        * web-platform-tests/beacon/headers/header-content-type-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-no-referrer-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-no-referrer-when-downgrade.https-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-origin-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-origin-when-cross-origin-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-same-origin-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-strict-origin-when-cross-origin.https-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-strict-origin.https-expected.txt:
+        * web-platform-tests/beacon/headers/header-referrer-unsafe-url.https-expected.txt:
+        * web-platform-tests/beacon/resources/beacon.py: Added.
+        (build_stash_key):
+        (main):
+        (main.wrap_key):
+        * web-platform-tests/beacon/resources/w3c-import.log:
+        * web-platform-tests/beacon/w3c-import.log: Added.
+        * web-platform-tests/url/failure-expected.txt:
+
 2017-07-30  Sam Weinig  <sam@webkit.org>
 
         [WebIDL] Remove JS builtin bindings for FetchRequest, DOMWindowFetch and WorkerGlobalScopeFetch
index e60a895..3ac1f72 100644 (file)
@@ -40,6 +40,7 @@
     "web-platform-tests/assumptions": "skip", 
     "web-platform-tests/auxclick": "skip", 
     "web-platform-tests/battery-status": "skip", 
+    "web-platform-tests/beacon": "import", 
     "web-platform-tests/bluetooth": "skip", 
     "web-platform-tests/browser-payment-api": "skip", 
     "web-platform-tests/clear-site-data": "skip", 
index 931c985..7d570a0 100644 (file)
@@ -23,6 +23,7 @@
         "web-platform-tests/FileAPI/support/url-origin.html",
         "web-platform-tests/XMLHttpRequest/xmlhttprequest-sync-block-defer-scripts-subframe.html",
         "web-platform-tests/XMLHttpRequest/xmlhttprequest-sync-not-hang-scriptloader-subframe.html",
+        "web-platform-tests/beacon/fetch-keepalive-navigate.iFrame.html",
         "web-platform-tests/css/css-grid-1/grid-items/ref-filled-green-100px-square-image.html",
         "web-platform-tests/css/css-grid-1/test-plan/index.html",
         "web-platform-tests/css/css-shapes-1/test-plan/index.html",
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blob-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blob-expected.txt
new file mode 100644 (file)
index 0000000..2e73a8b
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: EmptyBlob 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallBlob 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: MediumBlob 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: LargeBlob 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blob.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blob.html
new file mode 100644 (file)
index 0000000..b35c276
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic Blob Test</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(blobTests);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax-expected.txt
new file mode 100644 (file)
index 0000000..cd47637
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: MaxBlob 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax.html
new file mode 100644 (file)
index 0000000..705a4eb
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic Blob Test - MaxSize</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(blobMaxTest);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource-expected.txt
new file mode 100644 (file)
index 0000000..e555962
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: EmptyBufferSource 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallBufferSource 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: MediumBufferSource 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: LargeBufferSource 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource.html
new file mode 100644 (file)
index 0000000..69d7f5d
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic BufferSource Test</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(bufferSourceTests);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax-expected.txt
new file mode 100644 (file)
index 0000000..9b4ad2d
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: MaxBufferSource 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax.html
new file mode 100644 (file)
index 0000000..75251bc
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic BufferSource Test - MaxSize</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(bufferSourceMaxTest);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdata-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdata-expected.txt
new file mode 100644 (file)
index 0000000..bddbb49
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: EmptyFormData 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallFormData 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: MediumFormData 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: LargeFormData 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdata.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdata.html
new file mode 100644 (file)
index 0000000..3f7703b
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic FormData Test</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(formDataTests);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax-expected.txt
new file mode 100644 (file)
index 0000000..c8d074c
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: LargeFormData 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax.html
new file mode 100644 (file)
index 0000000..27cb378
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic FormData Test</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(formDataMaxTest);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-string-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-string-expected.txt
new file mode 100644 (file)
index 0000000..d19f887
--- /dev/null
@@ -0,0 +1,8 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: NoData 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: NullData 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: UndefinedData 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallString 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: MediumString 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: LargeString 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-string.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-string.html
new file mode 100644 (file)
index 0000000..c8571e5
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic String Test</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(stringTests);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax-expected.txt
new file mode 100644 (file)
index 0000000..4bd6c67
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: MaxString 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax.html
new file mode 100644 (file)
index 0000000..8e1de94
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon Basic String Test - MaxSize</title>
+    <meta name="timeout" content="long">
+    <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <script src="/common/utils.js"></script>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+        runTests(stringMaxTest);
+    </script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.js
new file mode 100644 (file)
index 0000000..ce3d331
--- /dev/null
@@ -0,0 +1,353 @@
+"use strict";
+
+// Different sizes of payloads to test.
+var smallPayloadSize = 10;
+var mediumPayloadSize = 10000;
+var largePayloadSize = 50000;
+var maxPayloadSize = 65536; // The maximum payload size allowed for a beacon request.
+
+// String payloads of various sizes sent by sendbeacon. The format of the payloads is a string:
+//     <numberOfCharacters>:<numberOfCharacters *'s>
+//     ex. "10:**********"
+var smallPayload = smallPayloadSize + ":" + Array(smallPayloadSize).fill('*').join("");
+var mediumPayload = mediumPayloadSize + ":" + Array(mediumPayloadSize).fill('*').join("");
+var largePayload = largePayloadSize + ":" + Array(largePayloadSize).fill('*').join("");
+// Subtract 6 from maxPayloadSize because 65536 is 5 digits, plus 1 more for the ':'
+var maxPayload = (maxPayloadSize - 6) + ":" + Array(maxPayloadSize - 6).fill('*').join("")
+
+// Test case definitions.
+//      id: String containing the unique name of the test case.
+//      data: Payload object to send through sendbeacon.
+var noDataTest = { id: "NoData" };
+var nullDataTest = { id: "NullData", data: null };
+var undefinedDataTest = { id: "UndefinedData", data: undefined };
+var smallStringTest = { id: "SmallString", data: smallPayload };
+var mediumStringTest = { id: "MediumString", data: mediumPayload };
+var largeStringTest = { id: "LargeString", data: largePayload };
+var maxStringTest = { id: "MaxString", data: maxPayload };
+var emptyBlobTest = { id: "EmptyBlob", data: new Blob() };
+var smallBlobTest = { id: "SmallBlob", data: new Blob([smallPayload]) };
+var mediumBlobTest = { id: "MediumBlob", data: new Blob([mediumPayload]) };
+var largeBlobTest = { id: "LargeBlob", data: new Blob([largePayload]) };
+var maxBlobTest = { id: "MaxBlob", data: new Blob([maxPayload]) };
+var emptyBufferSourceTest = { id: "EmptyBufferSource", data: new Uint8Array() };
+var smallBufferSourceTest = { id: "SmallBufferSource", data: CreateArrayBufferFromPayload(smallPayload) };
+var mediumBufferSourceTest = { id: "MediumBufferSource", data: CreateArrayBufferFromPayload(mediumPayload) };
+var largeBufferSourceTest = { id: "LargeBufferSource", data: CreateArrayBufferFromPayload(largePayload) };
+var maxBufferSourceTest = { id: "MaxBufferSource", data: CreateArrayBufferFromPayload(maxPayload) };
+var emptyFormDataTest = { id: "EmptyFormData", data: CreateEmptyFormDataPayload() };
+var smallFormDataTest = { id: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) };
+var mediumFormDataTest = { id: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) };
+var largeFormDataTest = { id: "LargeFormData", data: CreateFormDataFromPayload(largePayload) };
+// We don't test maxFormData because the extra multipart separators make it difficult to
+// calculate a maxPayload.
+
+// Test case suites.
+// Due to quota limits we split the max payload tests into their own bucket.
+var stringTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, mediumStringTest, largeStringTest];
+var stringMaxTest = [maxStringTest];
+var blobTests = [emptyBlobTest, smallBlobTest, mediumBlobTest, largeBlobTest];
+var blobMaxTest = [maxBlobTest];
+var bufferSourceTests = [emptyBufferSourceTest, smallBufferSourceTest, mediumBufferSourceTest, largeBufferSourceTest];
+var bufferSourceMaxTest = [maxBufferSourceTest];
+var formDataTests = [emptyFormDataTest, smallFormDataTest, mediumFormDataTest, largeFormDataTest];
+var formDataMaxTest = [largeFormDataTest];
+var allTests = [].concat(stringTests, stringMaxTest, blobTests, blobMaxTest, bufferSourceTests, bufferSourceMaxTest, formDataTests, formDataMaxTest);
+
+// This special cross section of test cases is meant to provide a slimmer but reasonably-
+// representative set of tests for parameterization across variables (e.g. redirect codes,
+// cors modes, etc.)
+var sampleTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, smallBlobTest, smallBufferSourceTest, smallFormDataTest];
+
+// Build a test lookup table, which is useful when instructing a web worker or an iframe
+// to run a test, so that we don't have to marshal the entire test case across a process boundary.
+var testLookup = {};
+allTests.forEach(function(testCase) {
+    testLookup[testCase.id] = testCase;
+});
+
+// Helper function to create an ArrayBuffer representation of a string.
+function CreateArrayBufferFromPayload(payload) {
+    var length = payload.length;
+    var buffer = new Uint8Array(length);
+
+    for (var i = 0; i < length; i++) {
+        buffer[i] = payload.charCodeAt(i);
+    }
+
+    return buffer;
+}
+
+// Helper function to create an empty FormData object.
+function CreateEmptyFormDataPayload() {
+    // http://osgvsowi/8344051 - DOM: Workers: Add FormData support to Web Workers
+    if (self.document === undefined) {
+        return null;
+    }
+
+    return new FormData();
+}
+
+// Helper function to create a FormData representation of a string.
+function CreateFormDataFromPayload(payload) {
+    // http://osgvsowi/8344051 - DOM: Workers: Add FormData support to Web Workers
+    if (self.document === undefined) {
+        return null;
+    }
+
+    var formData = new FormData();
+    formData.append("payload", payload);
+    return formData;
+}
+
+// Initializes a session with a client-generated SID.
+// A "session" is a run of one or more tests. It is used to batch several beacon
+// tests in a way that isolates the server-side session state and makes it easy
+// to poll the results of the tests in one request.
+//     testCases: The array of test cases participating in the session.
+function initSession(testCases) {
+    return {
+        // Provides a unique session identifier to prevent mixing server-side data
+        // with other sessions.
+        id: self.token(),
+        // Dictionary of test name to live testCase object.
+        testCaseLookup: {},
+        // Array of testCase objects for iteration.
+        testCases: [],
+        // Tracks the total number of tests in the session.
+        totalCount: testCases.length,
+        // Tracks the number of tests for which we have sent the beacon.
+        // When it reaches totalCount, we will start polling for results.
+        sentCount: 0,
+        // Tracks the number of tests for which we have verified the results.
+        // When it reaches sentCount, we will stop polling for results.
+        doneCount: 0,
+        // Helper to add a testCase to the session.
+        add: function add(testCase) {
+            this.testCases.push(testCase);
+            this.testCaseLookup[testCase.id] = testCase;
+        }
+    };
+}
+
+// Schedules async_test's for each of the test cases, treating them as a single session,
+// and wires up the continueAfterSendingBeacon() and waitForResults() calls.
+// The method looks for several "extension" functions in the global scope:
+//   - self.buildId: if present, can change the display name of a test.
+//   - self.buildBaseUrl: if present, can change the base URL of a beacon target URL (this
+//     is the scheme, hostname, and port).
+//   - self.buildTargetUrl: if present, can modify a beacon target URL (for example wrap it).
+// Parameters:
+//     testCases: An array of test cases.
+function runTests(testCases) {
+    var session = initSession(testCases);
+
+    testCases.forEach(function(testCase, testIndex) {
+        // Make a copy of the test case as we'll be storing some metadata on it,
+        // such as which session it belongs to.
+        var testCaseCopy = Object.assign({ session: session }, testCase);
+
+        // Extension point: generate the test id.
+        var testId = testCase.id;
+        if (self.buildId) {
+            testId = self.buildId(testId);
+        }
+        testCaseCopy.origId = testCaseCopy.id;
+        testCaseCopy.id = testId;
+        testCaseCopy.index = testIndex;
+
+        session.add(testCaseCopy);
+
+        // Schedule the sendbeacon in an async test.
+        async_test(function(test) {
+            // Save the testharness.js 'test' object, so that we only have one object
+            // to pass around.
+            testCaseCopy.test = test;
+
+            // Extension point: generate the beacon URL.
+            var baseUrl = "http://{{host}}:{{ports[http][0]}}";
+            if (self.buildBaseUrl) {
+                baseUrl = self.buildBaseUrl(baseUrl);
+            }
+            var targetUrl = `${baseUrl}/beacon/resources/beacon.py?cmd=store&sid=${session.id}&tid=${testId}&tidx=${testIndex}`;
+            if (self.buildTargetUrl) {
+                targetUrl = self.buildTargetUrl(targetUrl);
+            }
+            // Attach the URL to the test object for debugging purposes.
+            testCaseCopy.url = targetUrl;
+
+            // Extension point: send the beacon immediately, or defer.
+            var sendFunc = test.step_func(function sendImmediately(testCase) {
+                var sendResult = sendData(testCase);
+                continueAfterSendingBeacon(sendResult, testCase);
+            });
+            if (self.sendFunc) {
+                sendFunc = test.step_func(self.sendFunc);
+            }
+            sendFunc(testCaseCopy);
+        }, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCaseCopy.id}`);
+    });
+}
+
+// Sends the beacon for a single test. This step is factored into its own function so that
+// it can be called from a web worker. It does not check for results.
+// Note: do not assert from this method, as when called from a worker, we won't have the
+// full testharness.js test context. Instead return 'false', and the main scope will fail
+// the test.
+// Returns the result of the 'sendbeacon()' function call, true or false.
+function sendData(testCase) {
+    var sent = false;
+    if (testCase.data) {
+        sent = self.navigator.sendBeacon(testCase.url, testCase.data);
+    } else {
+        sent = self.navigator.sendBeacon(testCase.url)
+    }
+    return sent;
+}
+
+// Continues a single test after the beacon has been sent for that test.
+// Will trigger waitForResults() for the session if this is the last test
+// in the session to send its beacon.
+// Assumption: will be called on the test's step_func so that assert's do
+// not have to be wrapped.
+function continueAfterSendingBeacon(sendResult, testCase) {
+    var session = testCase.session;
+
+    // Recaclulate the sent vs. total counts.
+    if (sendResult) {
+        session.sentCount++;
+    } else {
+        session.totalCount--;
+    }
+
+    // If this was the last test in the session to send its beacon, start polling for results.
+    // Note that we start polling even if just one test in the session sends successfully,
+    // so that if any of the others fail, we still get results from the tests that did send.
+    if (session.sentCount == session.totalCount) {
+        // Exit the current test's execution context in order to run the poll
+        // loop from the harness context.
+        step_timeout(waitForResults.bind(this, session), 0);
+    }
+
+    // Now fail this test if the beacon did not send. It will be excluded from the poll
+    // loop because of the calculation adjustment above.
+    assert_true(sendResult, "'sendbeacon' function call must succeed");
+}
+
+// Kicks off an asynchronous monitor to poll the server for test results. As we
+// verify that the server has received and validated a beacon, we will complete
+// its testharness test.
+function waitForResults(session) {
+    // Poll for status until all of the results come in.
+    fetch(`resources/beacon.py?cmd=stat&sid=${session.id}&tidx_min=0&tidx_max=${session.totalCount-1}`).then(
+        function(response) {
+            // Parse as text(), not json(), so that we can log the raw response if
+            // it's invalid.
+            response.text().then(function(rawResponse) {
+                // Check that we got a response we expect and know how to handle.
+                var results;
+                var failure;
+                try {
+                    results = JSON.parse(rawResponse);
+
+                    if (results.length === undefined) {
+                        failure = `bad validation response schema: rawResponse='${rawResponse}'`;
+                    }
+                } catch (e) {
+                    failure = `bad validation response: rawResponse='${rawResponse}', got parse error '${e}'`;
+                }
+
+                if (failure) {
+                    // At this point we can't deterministically get results for all of the
+                    // tests in the session, so fail the entire session.
+                    failSession(session, failure);
+                    return;
+                }
+
+                // The 'stat' call will return an array of zero or more results
+                // of sendbeacon() calls that the server has received and validated.
+                results.forEach(function(result) {
+                    var testCase = session.testCaseLookup[result.id];
+
+                    // While stash.take on the server is supposed to honor read-once, since we're
+                    // polling so frequently it is possible that we will receive the same test result
+                    // more than once.
+                    if (!testCase.done) {
+                        testCase.done = true;
+                        session.doneCount++;
+                    }
+
+                    // Validate that the sendbeacon() was actually sent to the server.
+                    var test = testCase.test;
+                    test.step(function() {
+                        // null JSON values parse as null, not undefined
+                        assert_equals(result.error, null, "'sendbeacon' data must not fail validation");
+                    });
+
+                    test.done();
+                });
+
+                // Continue polling until all of the results come in.
+                if (session.doneCount < session.sentCount) {
+                    // testharness.js frowns upon the use of explicit timeouts, but there is no way
+                    // around the need to poll for these tests, and there is no use spamming the server
+                    // with requestAnimationFrame() just to avoid the use of step_timeout.
+                    step_timeout(waitForResults.bind(this, session), 100);
+                }
+            }).catch(function(error) {
+                failSession(session, `unexpected error reading response, error='${error}'`);
+            });
+        }
+    );
+}
+
+// Fails all of the tests in the session, meant to be called when an infrastructural
+// issue prevents us from deterministically completing the individual tests.
+function failSession(session, reason) {
+    session.testCases.forEach(function(testCase) {
+        var test = testCase.test;
+        test.unreached_func(reason)();
+    });
+}
+
+// Creates an iframe on the document's body and runs the sample tests from the iframe.
+// The iframe is navigated immediately after it sends the data, and the window verifies
+// that the data is still successfully sent.
+//    funcName: "beacon" to send the data via navigator.sendBeacon(),
+//              "fetch" to send the data via fetch() with the keepalive flag.
+function runSendInIframeAndNavigateTests(funcName) {
+    var iframe = document.createElement("iframe");
+    iframe.id = "iframe";
+    iframe.onload = function() {
+        var tests = Array();
+
+        // Implement the self.buildId extension to identify the parameterized
+        // test in the report.
+        self.buildId = function(baseId) {
+            return `${baseId}-${funcName}-NAVIGATE`;
+        };
+
+        window.onmessage = function(e) {
+            // The iframe will execute sendData() for us and return the result.
+            var testCase = tests[e.data];
+            continueAfterSendingBeacon(true /* sendResult */, testCase);
+        };
+
+        // Implement the self.sendFunc extension to send the beacon indirectly,
+        // from an iFrame that we can then navigate.
+        self.sendFunc = function(testCase) {
+            var iframeWindow = document.getElementById("iframe").contentWindow;
+            // We run into problems passing the testCase over the document boundary,
+            // because of structured cloning constraints. Instead we'll send over the
+            // test case id, and the iFrame can load the static test case by including
+            // beacon-common.js.
+            tests[testCase.origId] = testCase;
+            iframeWindow.postMessage([testCase.origId, testCase.url, funcName], "*");
+        };
+
+        runTests(sampleTests);
+    };
+
+    document.body.appendChild(iframe);
+    iframe.src = "fetch-keepalive-navigate.iFrame.html";
+}
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.window.js
new file mode 100644 (file)
index 0000000..317a7bd
--- /dev/null
@@ -0,0 +1,41 @@
+// META: script=/common/utils.js
+// META: script=beacon-common.js?pipe=sub
+
+"use strict";
+
+// Execute each sample test with a cross-origin URL. If allowCors is 'true'
+// the beacon handler will return CORS headers. This test ensures that the
+// sendBeacon() succeeds in either case.
+[true, false].forEach(function(allowCors) {
+    // Implement the self.buildId extension to identify the parameterized
+    // test in the report.
+    self.buildId = function(baseId) {
+        return `${baseId}-${allowCors ? "CORS-ALLOW" : "CORS-FORBID"}`;
+    };
+
+    // Implement the self.buildBaseUrl and self.buildTargetUrl extensions
+    // to change the target URL to use a cross-origin domain name.
+    self.buildBaseUrl = function(baseUrl) {
+        return "http://{{domains[www]}}:{{ports[http][0]}}";
+    };
+    // Implement the self.buildTargetUrl extension to append a directive
+    // to the handler, that it should return CORS headers, if 'allowCors'
+    // is true.
+    self.buildTargetUrl = function(targetUrl) {
+        // Note that 'allowCors=true' is not necessary for the sendBeacon() to reach
+        // the server. Beacons use the HTTP POST method, which is a CORS-safelisted
+        // method, and thus they do not trigger preflight. If the server does not
+        // respond with Access-Control-Allow-Origin and Access-Control-Allow-Credentials
+        // headers, an error will be printed to the console, but the request will
+        // already have reached the server. Since beacons are fire-and-forget, the
+        // error will not affect any client script, either -- not even the return
+        // value of the sendBeacon() call, because the underlying fetch is asynchronous.
+        // The "Beacon CORS" tests are merely testing that sendBeacon() to a cross-
+        // origin URL *will* work regardless.
+        return allowCors ? `${targetUrl}&origin=http://{{host}}:{{ports[http][0]}}&credentials=true` : targetUrl;
+    }
+
+    runTests(sampleTests);
+});
+
+done();
\ No newline at end of file
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.window.js
new file mode 100644 (file)
index 0000000..be34900
--- /dev/null
@@ -0,0 +1,39 @@
+// META: script=/common/utils.js
+// META: script=beacon-common.js?pipe=sub
+
+"use strict";
+
+test(function() {
+    // Payload that should cause sendBeacon to return false because it exceeds the maximum payload size.
+    var exceedPayload = Array(maxPayloadSize + 1).fill('z').join("");
+
+    var success = navigator.sendBeacon("http://doesnotmatter", exceedPayload);
+    assert_false(success, "calling 'navigator.sendBeacon()' with payload size exceeding the maximum size must fail");
+}, "Verify calling 'navigator.sendBeacon()' with a large payload returns 'false'.");
+
+test(function() {
+    var invalidUrl = "http://invalid:url";
+    assert_throws(new TypeError(), function() { navigator.sendBeacon(invalidUrl, smallPayload); },
+        `calling 'navigator.sendBeacon()' with an invalid URL '${invalidUrl}' must throw a TypeError`);
+}, "Verify calling 'navigator.sendBeacon()' with an invalid URL throws an exception.");
+
+test(function() {
+    var invalidUrl = "nothttp://invalid.url";
+    assert_throws(new TypeError(), function() { navigator.sendBeacon(invalidUrl, smallPayload); },
+         `calling 'navigator.sendBeacon()' with a non-http(s) URL '${invalidUrl}' must throw a TypeError`);
+}, "Verify calling 'navigator.sendBeacon()' with a URL that is not a http(s) scheme throws an exception.");
+
+// We'll validate that we can send one beacon that uses our entire Quota and then fail to send one that is just one char.
+test(function () {
+    var destinationURL = "/fetch/api/resources/trickle.py?count=1&ms=1000";
+
+    var firstSuccess = navigator.sendBeacon(destinationURL, maxPayload);
+    assert_true(firstSuccess, "calling 'navigator.sendBeacon()' with our max payload size should succeed.");
+
+    // Now we'll send just one character.
+    var secondSuccess = navigator.sendBeacon(destinationURL, "1");
+    assert_false(secondSuccess, "calling 'navigator.sendBeacon()' with just one char should fail while our Quota is used up.");
+
+}, "Verify calling 'navigator.sendBeacon()' with a small payload fails while Quota is completely utilized.");
+
+done();
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js
new file mode 100644 (file)
index 0000000..0e263dd
--- /dev/null
@@ -0,0 +1,25 @@
+// META: script=/common/utils.js
+// META: script=beacon-common.js?pipe=sub
+
+"use strict";
+
+// Execute each sample test per redirect status code.
+// Note that status codes 307 and 308 are the only codes that will maintain POST data
+// through a redirect.
+[307, 308].forEach(function(status) {
+    // Implement the self.buildId extension to identify the parameterized
+    // test in the report.
+    self.buildId = function(baseId) {
+        return `${baseId}-${status}`;
+    };
+
+    // Implement the self.buildTargetUrl extension to inject a redirect to
+    // the sendBeacon target.
+    self.buildTargetUrl = function(targetUrl) {
+        return `/common/redirect.py?status=${status}&location=${encodeURIComponent(targetUrl)}`;
+    };
+
+    runTests(sampleTests);
+});
+
+done();
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/fetch-keepalive-navigate.iFrame.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/fetch-keepalive-navigate.iFrame.html
new file mode 100644 (file)
index 0000000..c4e63c8
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>W3C Beacon As Fetch (Fetch KeepAlive) Navigate Test</title>
+</head>
+<body>
+    <script src="beacon-common.js?pipe=sub"></script>
+    <script>
+        "use strict";
+
+        // An array should be passed through postMessage to this iFrame, where
+        //     [0] contains a test case id as defined in beacon-common.js.
+        //     [1] is the URL for the keep alive fetch() or sendBeacon().
+        //     [2] string indicating the function to call - "fetch" to call fetch() or "beacon" to call sendBeacon().
+        // The testcase id is returned back to the window through postMesage.
+        var tests = 0;
+        window.onmessage = function(e) {
+            var testCaseId = e.data[0];
+            var url = e.data[1];
+            var func = e.data[2];
+            tests++;
+
+            // Reconstruct enough of the test case to send the keep alive fetch (data and url).
+            var testCase = testLookup[testCaseId];
+            testCase.url = url;
+
+            if (func === "beacon") {
+                // sendData calls sendBeacon
+                sendData(testCase);
+            }
+            else if (func === "fetch") {
+                // Send the Fetch.
+                fetch(testCase.url, { "keepalive": true, "method": "POST", "body": testCase.data });
+            }
+            else {
+                throw new Error(func + " is an invalid function");
+            }
+
+            // Let the main page continue the test if we don't immediately throw an exception.
+            parent.postMessage(testCaseId, "*");
+
+            // Now navigate ourselves.
+            if (tests == sampleTests.Length) {
+                window.location = "http://microsoft.com";
+            }
+        }
+    </script>
+</body>
+</html>
index ff4e702..ed48fe4 100644 (file)
@@ -1,5 +1,6 @@
+CONSOLE MESSAGE: line 35: This requests requires a CORS preflight but this is not supported yet.
 
-FAIL Test content-type header for a body string navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl, what)', 'navigator.sendBeacon' is undefined)
-FAIL Test content-type header for a body ArrayBuffer navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl, what)', 'navigator.sendBeacon' is undefined)
-FAIL Test content-type header for a body FormData navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl, what)', 'navigator.sendBeacon' is undefined)
+PASS Test content-type header for a body string 
+FAIL Test content-type header for a body ArrayBuffer assert_true: SendBeacon Succeeded expected true got false
+PASS Test content-type header for a body FormData 
 
index 6f60a77..492a80e 100644 (file)
@@ -1,3 +1,3 @@
 
-FAIL Test referer header /beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+PASS Test referer header /beacon/resources/ 
 
index 61a249c..8cf681c 100644 (file)
@@ -1,4 +1,4 @@
 
-FAIL Test referer header https://localhost:9443/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
-FAIL Test referer header http://localhost:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+PASS Test referer header https://localhost:9443/beacon/resources/ 
+PASS Test referer header http://localhost:8800/beacon/resources/ 
 
index 01ee364..663b2b1 100644 (file)
@@ -1,3 +1,3 @@
 
-FAIL Test referer header http://127.0.0.1:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+PASS Test referer header http://127.0.0.1:8800/beacon/resources/ 
 
index eddf4a2..e746c07 100644 (file)
@@ -1,5 +1,5 @@
 CONSOLE MESSAGE: line 8: Failed to set referrer policy: The value 'origin-when-cross-origin' is not one of 'no-referrer', 'origin', 'no-referrer-when-downgrade', or 'unsafe-url'. Defaulting to 'no-referrer'.
 
-FAIL Test referer header http://localhost:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
-FAIL Test referer header http://127.0.0.1:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+FAIL Test referer header http://localhost:8800/beacon/resources/ assert_equals: Correct referrer header result expected "http://localhost:8800/beacon/headers/header-referrer-origin-when-cross-origin.html" but got ""
+FAIL Test referer header http://127.0.0.1:8800/beacon/resources/ assert_equals: Correct referrer header result expected "http://localhost:8800/" but got ""
 
index 185b6df..c5a7061 100644 (file)
@@ -1,5 +1,5 @@
 CONSOLE MESSAGE: line 8: Failed to set referrer policy: The value 'same-origin' is not one of 'no-referrer', 'origin', 'no-referrer-when-downgrade', or 'unsafe-url'. Defaulting to 'no-referrer'.
 
-FAIL Test referer header /beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
-FAIL Test referer header http://127.0.0.1:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+FAIL Test referer header /beacon/resources/ assert_equals: Correct referrer header result expected "http://localhost:8800/beacon/headers/header-referrer-same-origin.html" but got ""
+PASS Test referer header http://127.0.0.1:8800/beacon/resources/ 
 
index f043aba..ba0bb45 100644 (file)
@@ -1,5 +1,5 @@
 CONSOLE MESSAGE: line 8: Failed to set referrer policy: The value 'strict-origin' is not one of 'no-referrer', 'origin', 'no-referrer-when-downgrade', or 'unsafe-url'. Defaulting to 'no-referrer'.
 
-FAIL Test referer header https://localhost:9443/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
-FAIL Test referer header http://localhost:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+FAIL Test referer header https://localhost:9443/beacon/resources/ assert_equals: Correct referrer header result expected "https://localhost:9443/" but got ""
+PASS Test referer header http://localhost:8800/beacon/resources/ 
 
index f043aba..ba0bb45 100644 (file)
@@ -1,5 +1,5 @@
 CONSOLE MESSAGE: line 8: Failed to set referrer policy: The value 'strict-origin' is not one of 'no-referrer', 'origin', 'no-referrer-when-downgrade', or 'unsafe-url'. Defaulting to 'no-referrer'.
 
-FAIL Test referer header https://localhost:9443/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
-FAIL Test referer header http://localhost:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+FAIL Test referer header https://localhost:9443/beacon/resources/ assert_equals: Correct referrer header result expected "https://localhost:9443/" but got ""
+PASS Test referer header http://localhost:8800/beacon/resources/ 
 
index a7eff6f..0b41797 100644 (file)
@@ -1,3 +1,3 @@
 
-FAIL Test referer header http://localhost:8800/beacon/resources/ navigator.sendBeacon is not a function. (In 'navigator.sendBeacon(testUrl)', 'navigator.sendBeacon' is undefined)
+FAIL Test referer header http://localhost:8800/beacon/resources/ assert_equals: Correct referrer header result expected "https://localhost:9443/beacon/headers/header-referrer-unsafe-url.https.html" but got ""
 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py b/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py
new file mode 100644 (file)
index 0000000..afa522a
--- /dev/null
@@ -0,0 +1,112 @@
+import json
+
+def build_stash_key(session_id, test_num):
+    return "%s_%s" % (session_id, test_num)
+
+def main(request, response):
+    """Helper handler for Beacon tests.
+
+    It handles two forms of requests:
+
+    STORE:
+        A URL with a query string of the form 'cmd=store&sid=<token>&tidx=<test_index>&tid=<test_name>'.
+
+        Stores the receipt of a sendBeacon() request along with its validation result, returning HTTP 200 OK.
+
+        Parameters:
+            tidx - the integer index of the test.
+            tid - a friendly identifier or name for the test, used when returning results.
+
+    STAT:
+        A URL with a query string of the form 'cmd=stat&sid=<token>&tidx_min=<min_test_index>&tidx_max=<max_test_index>'.
+
+        Retrieves the results of test with indices [min_test_index, max_test_index] and returns them as
+        a JSON array and HTTP 200 OK status code. Due to the eventual read-once nature of the stash, results for a given test
+        are only guaranteed to be returned once, though they may be returned multiple times.
+
+        Parameters:
+            tidx_min - the lower-bounding integer test index.
+            tidx_max - the upper-bounding integer test index.
+
+        Example response body:
+            [{"id": "Test1", error: null}, {"id": "Test2", error: "some validation details"}]
+
+    Common parameters:
+        cmd - the command, 'store' or 'stat'.
+        sid - session id used to provide isolation to a test run comprising multiple sendBeacon()
+              tests.
+    """
+
+    session_id = request.GET.first("sid");
+    command = request.GET.first("cmd").lower();
+
+    # Workaround to circumvent the limitation that cache keys
+    # can only be UUID's.
+    def wrap_key(key, path):
+        return (str(path), str(key))
+    request.server.stash._wrap_key = wrap_key
+
+    # Append CORS headers if needed.
+    if "origin" in request.GET:
+        response.headers.set("Access-Control-Allow-Origin", request.GET.first("origin"))
+    if "credentials" in request.GET:
+        response.headers.set("Access-Control-Allow-Credentials", request.GET.first("credentials"))
+
+    # Handle the 'store' and 'stat' commands.
+    if command == "store":
+        # The test id is just used to make the results more human-readable.
+        test_id = request.GET.first("tid")
+        # The test index is used to build a predictable stash key, together
+        # with the unique session id, in order to retrieve a range of results
+        # later knowing the index range.
+        test_idx = request.GET.first("tidx")
+
+        test_data_key = build_stash_key(session_id, test_idx)
+        test_data = { "id": test_id, "error": None }
+
+        payload = ""
+        if "Content-Type" in request.headers and \
+           "form-data" in request.headers["Content-Type"]:
+            if "payload" in request.POST:
+                # The payload was sent as a FormData.
+                payload = request.POST.first("payload")
+            else:
+                # A FormData was sent with an empty payload.
+                pass
+        else:
+            # The payload was sent as either a string, Blob, or BufferSource.
+            payload = request.body
+
+        payload_parts = filter(None, payload.split(":"))
+        if len(payload_parts) > 0:
+            payload_size = int(payload_parts[0])
+
+            # Confirm the payload size sent matches with the number of characters sent.
+            if payload_size != len(payload_parts[1]):
+                test_data["error"] = "expected %d characters but got %d" % (payload_size, len(payload_parts[1]))
+            else:
+                # Confirm the payload contains the correct characters.
+                for i in range(0, payload_size):
+                    if payload_parts[1][i] != "*":
+                        test_data["error"] = "expected '*' at index %d but got '%s''" % (i, payload_parts[1][i])
+                        break
+
+        # Store the result in the stash so that it can be retrieved
+        # later with a 'stat' command.
+        request.server.stash.put(test_data_key, test_data)
+    elif command == "stat":
+        test_idx_min = int(request.GET.first("tidx_min"))
+        test_idx_max = int(request.GET.first("tidx_max"))
+
+        # For each result that has come in, append it to the response.
+        results = []
+        for test_idx in range(test_idx_min, test_idx_max+1): # +1 because end is exclusive
+            test_data_key = build_stash_key(session_id, test_idx)
+            test_data = request.server.stash.take(test_data_key)
+            if test_data:
+                results.append(test_data)
+
+        response.headers.set("Content-Type", "text/plain")
+        response.content = json.dumps(results)
+    else:
+        response.status = 400 # BadRequest
\ No newline at end of file
index 459603f..07880d6 100644 (file)
@@ -14,5 +14,6 @@ Property values requiring vendor prefixes:
 None
 ------------------------------------------------------------------------
 List of files:
+/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py
 /LayoutTests/imported/w3c/web-platform-tests/beacon/resources/content-type.py
 /LayoutTests/imported/w3c/web-platform-tests/beacon/resources/inspect-header.py
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/beacon/w3c-import.log
new file mode 100644 (file)
index 0000000..a92bbdc
--- /dev/null
@@ -0,0 +1,30 @@
+The tests in this directory were imported from the W3C repository.
+Do NOT modify these tests directly in WebKit.
+Instead, create a pull request on the WPT github:
+       https://github.com/w3c/web-platform-tests
+
+Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
+
+Do NOT modify or remove this file.
+
+------------------------------------------------------------------------
+Properties requiring vendor prefixes:
+None
+Property values requiring vendor prefixes:
+None
+------------------------------------------------------------------------
+List of files:
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blob.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdata.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-string.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.js
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.window.js
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.window.js
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-navigate.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js
+/LayoutTests/imported/w3c/web-platform-tests/beacon/fetch-keepalive-navigate.iFrame.html
index e17013e..bdc52c2 100644 (file)
@@ -106,7 +106,7 @@ FAIL Location's href: http://foo:-80/ should throw assert_throws: function "() =
 FAIL window.open(): http://foo:-80/ should throw assert_throws: function "() => self.open(test.input).close()" threw object "TypeError: null is not an object (evaluating 'self.open(test.input).close')" that is not a DOMException SyntaxError: property "code" is equal to undefined, expected 12
 PASS URL's href: http:/:@/www.example.com should throw 
 FAIL XHR: http:/:@/www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http:/:@/www.example.com should throw 
+FAIL sendBeacon(): http:/:@/www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http:/:@/www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http:/:@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http://user@/www.example.com should throw 
@@ -116,12 +116,12 @@ FAIL Location's href: http://user@/www.example.com should throw assert_throws: f
 FAIL window.open(): http://user@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" threw object "TypeError: null is not an object (evaluating 'self.open(test.input).close')" that is not a DOMException SyntaxError: property "code" is equal to undefined, expected 12
 PASS URL's href: http:@/www.example.com should throw 
 FAIL XHR: http:@/www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http:@/www.example.com should throw 
+FAIL sendBeacon(): http:@/www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http:@/www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http:@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http:/@/www.example.com should throw 
 FAIL XHR: http:/@/www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http:/@/www.example.com should throw 
+FAIL sendBeacon(): http:/@/www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http:/@/www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http:/@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http://@/www.example.com should throw 
@@ -136,12 +136,12 @@ FAIL Location's href: https:@/www.example.com should throw assert_throws: functi
 FAIL window.open(): https:@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" threw object "TypeError: null is not an object (evaluating 'self.open(test.input).close')" that is not a DOMException SyntaxError: property "code" is equal to undefined, expected 12
 PASS URL's href: http:a:b@/www.example.com should throw 
 FAIL XHR: http:a:b@/www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http:a:b@/www.example.com should throw 
+FAIL sendBeacon(): http:a:b@/www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http:a:b@/www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http:a:b@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http:/a:b@/www.example.com should throw 
 FAIL XHR: http:/a:b@/www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http:/a:b@/www.example.com should throw 
+FAIL sendBeacon(): http:/a:b@/www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http:/a:b@/www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http:/a:b@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http://a:b@/www.example.com should throw 
@@ -151,17 +151,17 @@ FAIL Location's href: http://a:b@/www.example.com should throw assert_throws: fu
 FAIL window.open(): http://a:b@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" threw object "TypeError: null is not an object (evaluating 'self.open(test.input).close')" that is not a DOMException SyntaxError: property "code" is equal to undefined, expected 12
 PASS URL's href: http::@/www.example.com should throw 
 FAIL XHR: http::@/www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http::@/www.example.com should throw 
+FAIL sendBeacon(): http::@/www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http::@/www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http::@/www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http:@:www.example.com should throw 
 FAIL XHR: http:@:www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http:@:www.example.com should throw 
+FAIL sendBeacon(): http:@:www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http:@:www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http:@:www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http:/@:www.example.com should throw 
 FAIL XHR: http:/@:www.example.com should throw assert_throws: function "() => client.open("GET", test.input)" did not throw
-PASS sendBeacon(): http:/@:www.example.com should throw 
+FAIL sendBeacon(): http:/@:www.example.com should throw assert_throws: function "() => self.navigator.sendBeacon(test.input)" did not throw
 FAIL Location's href: http:/@:www.example.com should throw assert_throws: function "() => self[0].location = test.input" did not throw
 FAIL window.open(): http:/@:www.example.com should throw assert_throws: function "() => self.open(test.input).close()" did not throw
 PASS URL's href: http://@:www.example.com should throw 
diff --git a/LayoutTests/resources/window-postmessage-open-close.html b/LayoutTests/resources/window-postmessage-open-close.html
new file mode 100644 (file)
index 0000000..3adb8ce
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<!-- Used when testing access of a closed/detached window's properties -->
+<html>
+<body onunload="opener.postMessage('closed', '*');">
+<script>
+opener.postMessage("opened", "*");
+</script>
+</body>
+</html>
index 360ee40..053bedf 100644 (file)
     "imported/w3c/web-platform-tests/XMLHttpRequest/xmlhttprequest-timeout-worker-twice.html": [
         "slow"
     ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-blob.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-blobMax.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-buffersource.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-buffersourceMax.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-formdata.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-formdataMax.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-string.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-basic-stringMax.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/beacon/beacon-navigate.html": [
+        "slow"
+    ],
     "imported/w3c/web-platform-tests/cors/status-async.htm": [
         "slow"
     ],
index 37a2aa7..74af435 100644 (file)
@@ -15,6 +15,7 @@ set(WebCore_INCLUDE_DIRECTORIES
     "${CMAKE_BINARY_DIR}"
     "${WEBCORE_DIR}"
     "${WEBCORE_DIR}/Modules/airplay"
+    "${WEBCORE_DIR}/Modules/beacon"
     "${WEBCORE_DIR}/Modules/applepay"
     "${WEBCORE_DIR}/Modules/credentials"
     "${WEBCORE_DIR}/Modules/encryptedmedia"
@@ -175,6 +176,8 @@ set(WebCore_IDL_INCLUDES
 set(WebCore_NON_SVG_IDL_FILES
     Modules/airplay/WebKitPlaybackTargetAvailabilityEvent.idl
 
+    Modules/beacon/NavigatorBeacon.idl
+
     Modules/credentials/BasicCredential.idl
     Modules/credentials/CredentialCreationOptions.idl
     Modules/credentials/CredentialData.idl
@@ -870,6 +873,8 @@ endif ()
 set(WebCore_SOURCES
     Modules/airplay/WebKitPlaybackTargetAvailabilityEvent.cpp
 
+    Modules/beacon/NavigatorBeacon.cpp
+
     Modules/credentials/BasicCredential.cpp
     Modules/credentials/CredentialsContainer.cpp
     Modules/credentials/FederatedCredential.cpp
index 179901e..c257cf4 100644 (file)
@@ -1,3 +1,40 @@
+2017-08-01  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for navigator.sendBeacon
+        https://bugs.webkit.org/show_bug.cgi?id=175007
+        <rdar://problem/33547728>
+
+        Reviewed by Sam Weinig.
+
+        Add initial support for navigator.sendBeacon behind an experimental
+        feature runtime flag. The specification is available at:
+        - https://w3c.github.io/beacon/
+
+        The current implementation supports sending beacons with all types of
+        payloads except for ReadableStream. Some functionality is incomplete
+        and will be taken care of in follow-up patches:
+        - Support for CORS preflight for the cases where it is required. We currently
+          return false and do not send the beacon in such cases.
+        - Better support for redirects.
+        - Use a more power-friendly network priority for beacon requests.
+
+        Tests: http/tests/blink/sendbeacon/*
+               http/tests/security/mixedContent/beacon/insecure-beacon-in-iframe.html
+               http/wpt/beacon/*
+               imported/blink/fast/beacon/*
+               imported/w3c/web-platform-tests/beacon/*
+
+        * CMakeLists.txt:
+        * DerivedSources.make:
+        * Modules/beacon/NavigatorBeacon.cpp: Added.
+        (WebCore::NavigatorBeacon::sendBeacon):
+        * Modules/beacon/NavigatorBeacon.h: Added.
+        * Modules/beacon/NavigatorBeacon.idl: Added.
+        * WebCore.xcodeproj/project.pbxproj:
+        * loader/PingLoader.cpp:
+        (WebCore::PingLoader::sendBeacon):
+        * loader/PingLoader.h:
+
 2017-08-01  Filip Pizlo  <fpizlo@apple.com>
 
         Bmalloc and GC should put auxiliaries (butterflies, typed array backing stores) in a gigacage (separate multi-GB VM region)
index c9516b5..326705a 100644 (file)
@@ -30,6 +30,7 @@ VPATH = \
     $(WebCore) \
     $(WebCore)/Modules/airplay \
     $(WebCore)/Modules/applepay \
+    $(WebCore)/Modules/beacon \
     $(WebCore)/Modules/credentials \
     $(WebCore)/Modules/encryptedmedia \
     $(WebCore)/Modules/encryptedmedia/legacy \
@@ -97,6 +98,7 @@ JS_BINDING_IDLS = \
     $(WebCore)/Modules/applepay/ApplePayShippingMethodSelectedEvent.idl \
        $(WebCore)/Modules/applepay/ApplePayShippingMethodUpdate.idl \
     $(WebCore)/Modules/applepay/ApplePayValidateMerchantEvent.idl \
+    $(WebCore)/Modules/beacon/NavigatorBeacon.idl \
     $(WebCore)/Modules/credentials/BasicCredential.idl \
     $(WebCore)/Modules/credentials/CredentialCreationOptions.idl \
     $(WebCore)/Modules/credentials/CredentialData.idl \
diff --git a/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp b/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp
new file mode 100644 (file)
index 0000000..29f7b7c
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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 "NavigatorBeacon.h"
+
+#include "Document.h"
+#include "Navigator.h"
+#include "URL.h"
+
+namespace WebCore {
+
+ExceptionOr<bool> NavigatorBeacon::sendBeacon(Navigator&, Document& document, const String& url, std::optional<BodyInit>&& data)
+{
+    URL parsedUrl = document.completeURL(url);
+
+    // Set parsedUrl to the result of the URL parser steps with url and base. If the algorithm returns an error, or if
+    // parsedUrl's scheme is not "http" or "https", throw a " TypeError" exception and terminate these steps.
+    if (!parsedUrl.isValid())
+        return Exception { TypeError, ASCIILiteral("This URL is invalid") };
+    if (!parsedUrl.protocolIsInHTTPFamily())
+        return Exception { TypeError, ASCIILiteral("Beacons can only be sent over HTTP(S)") };
+
+    auto* frame = document.frame();
+    if (!frame)
+        return false;
+
+    return PingLoader::sendBeacon(*frame, document, parsedUrl, WTFMove(data));
+}
+
+}
+
diff --git a/Source/WebCore/Modules/beacon/NavigatorBeacon.h b/Source/WebCore/Modules/beacon/NavigatorBeacon.h
new file mode 100644 (file)
index 0000000..d9cb3e5
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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 "ExceptionOr.h"
+#include "PingLoader.h"
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class Document;
+class Navigator;
+
+class NavigatorBeacon {
+public:
+    static ExceptionOr<bool> sendBeacon(Navigator&, Document&, const String& url, std::optional<BodyInit>&&);
+};
+
+}
diff --git a/Source/WebCore/Modules/beacon/NavigatorBeacon.idl b/Source/WebCore/Modules/beacon/NavigatorBeacon.idl
new file mode 100644 (file)
index 0000000..3050761
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+// FIXME: Should support ReadableStream too.
+typedef (Blob or BufferSource or DOMFormData or URLSearchParams or USVString) BodyInit;
+
+[
+    EnabledBySetting=BeaconAPI,
+] partial interface Navigator {
+    [CallWith=Document, MayThrowException] boolean sendBeacon(USVString url, optional BodyInit? data = null);
+};
index 6bbbaa2..135c6d3 100644 (file)
                830A36BD1DAC5FAD006D7D09 /* JSMouseEventInit.h in Headers */ = {isa = PBXBuildFile; fileRef = 830A36BB1DAC5FA7006D7D09 /* JSMouseEventInit.h */; };
                83120C701C56F3F6001CB112 /* HTMLDataElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 831D1F291C56ECA000F5F6C0 /* HTMLDataElement.cpp */; };
                83120C711C56F3FB001CB112 /* HTMLDataElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 834B86A71C56E83A00F3F0E3 /* HTMLDataElement.h */; };
+               8321507D1F27EA180095B136 /* NavigatorBeacon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8321507A1F27EA150095B136 /* NavigatorBeacon.cpp */; };
+               8321507E1F27EA1B0095B136 /* NavigatorBeacon.h in Headers */ = {isa = PBXBuildFile; fileRef = 8321507B1F27EA150095B136 /* NavigatorBeacon.h */; };
                832B843419D8E55100B26055 /* SVGAnimateElementBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 832B843319D8E55100B26055 /* SVGAnimateElementBase.h */; };
                832B843619D8E57400B26055 /* SVGAnimateElementBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832B843519D8E57400B26055 /* SVGAnimateElementBase.cpp */; };
                83407FC11E8D9C1700E048D3 /* VisibilityChangeClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 83407FC01E8D9C1200E048D3 /* VisibilityChangeClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
                830A36BA1DAC5FA7006D7D09 /* JSMouseEventInit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSMouseEventInit.cpp; sourceTree = "<group>"; };
                830A36BB1DAC5FA7006D7D09 /* JSMouseEventInit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMouseEventInit.h; sourceTree = "<group>"; };
                831D1F291C56ECA000F5F6C0 /* HTMLDataElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTMLDataElement.cpp; sourceTree = "<group>"; };
+               8321507A1F27EA150095B136 /* NavigatorBeacon.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NavigatorBeacon.cpp; sourceTree = "<group>"; };
+               8321507B1F27EA150095B136 /* NavigatorBeacon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NavigatorBeacon.h; sourceTree = "<group>"; };
+               8321507C1F27EA150095B136 /* NavigatorBeacon.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = NavigatorBeacon.idl; sourceTree = "<group>"; };
                8329A4171EC25B2B008ED4BE /* DocumentAndElementEventHandlers.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DocumentAndElementEventHandlers.idl; sourceTree = "<group>"; };
                8329DCC21C7A6AE300730B33 /* HTMLHyperlinkElementUtils.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = HTMLHyperlinkElementUtils.idl; sourceTree = "<group>"; };
                832B843319D8E55100B26055 /* SVGAnimateElementBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVGAnimateElementBase.h; sourceTree = "<group>"; };
                        path = cf;
                        sourceTree = "<group>";
                };
+               832150791F27E96B0095B136 /* beacon */ = {
+                       isa = PBXGroup;
+                       children = (
+                               8321507A1F27EA150095B136 /* NavigatorBeacon.cpp */,
+                               8321507B1F27EA150095B136 /* NavigatorBeacon.h */,
+                               8321507C1F27EA150095B136 /* NavigatorBeacon.idl */,
+                       );
+                       path = beacon;
+                       sourceTree = "<group>";
+               };
                89878576122CA1DA003AABDA /* FileAPI */ = {
                        isa = PBXGroup;
                        children = (
                971145FE14EF006E00674FD9 /* Modules */ = {
                        isa = PBXGroup;
                        children = (
+                               832150791F27E96B0095B136 /* beacon */,
                                CE26169D187E6554007955F3 /* airplay */,
                                1A58E8611D19D37300C0EA73 /* applepay */,
                                57C7A6881E56946D00C67D71 /* credentials */,
                                FD31608012B026F700C1A359 /* AudioDSPKernel.h in Headers */,
                                FD31608212B026F700C1A359 /* AudioDSPKernelProcessor.h in Headers */,
                                FD31608312B026F700C1A359 /* AudioFileReader.h in Headers */,
+                               8321507E1F27EA1B0095B136 /* NavigatorBeacon.h in Headers */,
                                CD5596921475B678001D0BD0 /* AudioFileReaderIOS.h in Headers */,
                                FD3160BF12B0272A00C1A359 /* AudioFileReaderMac.h in Headers */,
                                CD2F4A2418D89F700063746D /* AudioHardwareListener.h in Headers */,
                                07277E5217D018CC0015534D /* JSMediaStreamTrack.cpp in Sources */,
                                415CDAF71E6CE0DE004F11EE /* JSMediaStreamTrackCustom.cpp in Sources */,
                                07277E5417D018CC0015534D /* JSMediaStreamTrackEvent.cpp in Sources */,
+                               8321507D1F27EA180095B136 /* NavigatorBeacon.cpp in Sources */,
                                932CC0D41DFFD667004C0F9F /* JSMediaTrackConstraints.cpp in Sources */,
                                0787C4691BFBDF6F006DCD7F /* JSMediaTrackSupportedConstraints.cpp in Sources */,
                                E107400D0E77BDC00033AF24 /* JSMessageChannel.cpp in Sources */,
index c29a47c..9448cd5 100644 (file)
@@ -4086,7 +4086,7 @@ sub GenerateImplementation
             push(@implContent, "    if (!${runtimeEnableConditionalString}) {\n");
             push(@implContent, "        auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>(\"$name\"), strlen(\"$name\"));\n");
             push(@implContent, "        VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);\n");
-            push(@implContent, "        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);\n");
+            push(@implContent, "        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);\n");
             push(@implContent, "    }\n");
             push(@implContent, "#endif\n") if $conditionalString;
         }
@@ -4130,7 +4130,7 @@ sub GenerateImplementation
                 push(@implContent, "    putDirect(vm, vm.propertyNames->iteratorSymbol, getDirect(vm, vm.propertyNames->builtinNames().entriesPublicName()), DontEnum);\n");
             } else {
                 AddToImplIncludes("<runtime/ArrayPrototype.h>");
-                push(@implContent, "    putDirect(vm, vm.propertyNames->iteratorSymbol, globalObject()->arrayPrototype()->getDirect(vm, vm.propertyNames->builtinNames().valuesPrivateName()), DontEnum);\n");
+                push(@implContent, "    putDirect(vm, vm.propertyNames->iteratorSymbol, this->globalObject()->arrayPrototype()->getDirect(vm, vm.propertyNames->builtinNames().valuesPrivateName()), DontEnum);\n");
             }
         }
         push(@implContent, "    addValueIterableMethods(*globalObject(), *this);\n") if $interface->iterable and !IsKeyValueIterableInterface($interface);
index a3770e5..c808448 100644 (file)
@@ -102,7 +102,7 @@ void JSTestGenerateIsReachablePrototype::finishCreation(VM& vm)
     if (!jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("aSecretAttribute"), strlen("aSecretAttribute"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
 }
 
index d29c6b2..27fbcf1 100644 (file)
@@ -147,39 +147,39 @@ void JSTestNodePrototype::finishCreation(VM& vm)
     if (!jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("calculateSecretResult"), strlen("calculateSecretResult"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     if (!jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("getSecretBoolean"), strlen("getSecretBoolean"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
 #if ENABLE(TEST_FEATURE)
     if (!(jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext() && RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled())) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("testFeatureGetSecretBoolean"), strlen("testFeatureGetSecretBoolean"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
 #endif
     if (!RuntimeEnabledFeatures::sharedFeatures().domIteratorEnabled()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("entries"), strlen("entries"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     if (!RuntimeEnabledFeatures::sharedFeatures().domIteratorEnabled()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("keys"), strlen("keys"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     if (!RuntimeEnabledFeatures::sharedFeatures().domIteratorEnabled()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("values"), strlen("values"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     if (!RuntimeEnabledFeatures::sharedFeatures().domIteratorEnabled()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("forEach"), strlen("forEach"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     putDirect(vm, vm.propertyNames->iteratorSymbol, getDirect(vm, vm.propertyNames->builtinNames().entriesPublicName()), DontEnum);
 }
index 18b69ab..64e7e35 100644 (file)
@@ -1882,41 +1882,41 @@ void JSTestObjPrototype::finishCreation(VM& vm, JSDOMGlobalObject& globalObject)
     if (!RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("enabledAtRuntimeOperation"), strlen("enabledAtRuntimeOperation"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
 #endif
     if (!(worldForDOMObject(this).someWorld() && RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled())) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("enabledInSpecificWorldWhenRuntimeFeatureEnabled"), strlen("enabledInSpecificWorldWhenRuntimeFeatureEnabled"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     if (!worldForDOMObject(this).someWorld()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("worldSpecificMethod"), strlen("worldSpecificMethod"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     if (!jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("calculateSecretResult"), strlen("calculateSecretResult"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
     if (!jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext()) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("getSecretBoolean"), strlen("getSecretBoolean"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
 #if ENABLE(TEST_FEATURE)
     if (!(jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext() && RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled())) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("testFeatureGetSecretBoolean"), strlen("testFeatureGetSecretBoolean"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
 #endif
 #if ENABLE(TEST_FEATURE)
     if (!(RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled() && RuntimeEnabledFeatures::sharedFeatures().testFeature1Enabled())) {
         auto propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("enabledAtRuntimeAttribute"), strlen("enabledAtRuntimeAttribute"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
-        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+        JSObject::deleteProperty(this, this->globalObject()->globalExec(), propertyName);
     }
 #endif
     auto* context = globalObject.scriptExecutionContext();
@@ -1937,7 +1937,7 @@ void JSTestObjPrototype::finishCreation(VM& vm, JSDOMGlobalObject& globalObject)
 #endif
     putDirect(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().privateMethodPrivateName(), JSFunction::create(vm, globalObject(), 0, String(), jsTestObjPrototypeFunctionPrivateMethod), ReadOnly | DontEnum);
     putDirect(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().publicAndPrivateMethodPrivateName(), JSFunction::create(vm, globalObject(), 0, String(), jsTestObjPrototypeFunctionPublicAndPrivateMethod), ReadOnly | DontEnum);
-    putDirect(vm, vm.propertyNames->iteratorSymbol, globalObject()->arrayPrototype()->getDirect(vm, vm.propertyNames->builtinNames().valuesPrivateName()), DontEnum);
+    putDirect(vm, vm.propertyNames->iteratorSymbol, this->globalObject()->arrayPrototype()->getDirect(vm, vm.propertyNames->builtinNames().valuesPrivateName()), DontEnum);
     addValueIterableMethods(*globalObject(), *this);
     JSObject& unscopables = *constructEmptyObject(globalObject()->globalExec(), globalObject()->nullPrototypeObjectStructure());
     unscopables.putDirect(vm, Identifier::fromString(&vm, "voidMethod"), jsBoolean(true));
index d5fff79..3954efc 100644 (file)
 #include "config.h"
 #include "PingLoader.h"
 
+#include "Blob.h"
 #include "ContentSecurityPolicy.h"
+#include "DOMFormData.h"
 #include "Document.h"
 #include "FormData.h"
 #include "Frame.h"
 #include "FrameLoader.h"
 #include "FrameLoaderClient.h"
 #include "HTTPHeaderNames.h"
+#include "HTTPHeaderValues.h"
+#include "HTTPParsers.h"
 #include "InspectorInstrumentation.h"
 #include "LoaderStrategy.h"
 #include "Page.h"
+#include "ParsedContentType.h"
 #include "PlatformStrategies.h"
 #include "ProgressTracker.h"
 #include "ResourceHandle.h"
 #include "ResourceResponse.h"
 #include "SecurityOrigin.h"
 #include "SecurityPolicy.h"
+#include "URLSearchParams.h"
 #include "UserContentController.h"
+#include <runtime/ArrayBuffer.h>
+#include <runtime/ArrayBufferView.h>
+#include <runtime/JSCInlines.h>
 #include <wtf/text/CString.h>
 
 namespace WebCore {
@@ -181,6 +190,89 @@ void PingLoader::sendViolationReport(Frame& frame, const URL& reportURL, Ref<For
     startPingLoad(frame, request, ShouldFollowRedirects::No);
 }
 
+bool PingLoader::sendBeacon(Frame& frame, Document& document, const URL& url, std::optional<BodyInit>&& data)
+{
+    ResourceRequest request(url);
+    if (processContentExtensionRulesForLoad(frame, request, ResourceType::Raw))
+        return false;
+
+    auto& contentSecurityPolicy = *document.contentSecurityPolicy();
+    if (!document.shouldBypassMainWorldContentSecurityPolicy() && !contentSecurityPolicy.allowConnectToSource(url)) {
+        // We simulate a network error so we return true here. This is consistent with Blink.
+        return true;
+    }
+
+    bool noCors = true;
+    request.setHTTPMethod(ASCIILiteral("POST"));
+    // FIXME: We should restrict the size of payloads.
+    if (data) {
+        String mimeType;
+        WTF::switchOn(data.value(),
+            [&] (RefPtr<Blob>& blob) {
+                auto& blobType = blob->type();
+                if (!blobType.isEmpty() && isValidContentType(blobType))
+                    mimeType = blobType;
+                else
+                    mimeType = ASCIILiteral("application/octet-stream");
+                auto formData = FormData::create();
+                formData->appendBlob(blob->url());
+                request.setHTTPBody(WTFMove(formData));
+            },
+            [&] (RefPtr<JSC::ArrayBuffer>& buffer) {
+                mimeType = ASCIILiteral("application/octet-stream");
+                request.setHTTPBody(FormData::create(buffer->data(), buffer->byteLength()));
+            },
+            [&] (RefPtr<JSC::ArrayBufferView>& buffer) {
+                mimeType = ASCIILiteral("application/octet-stream");
+                request.setHTTPBody(FormData::create(buffer->baseAddress(), buffer->byteLength()));
+            },
+            [&] (RefPtr<DOMFormData>& domFormData) {
+                auto formData = FormData::createMultiPart(*domFormData, domFormData->encoding(), &document);
+                formData->generateFiles(&document);
+                mimeType = makeString("multipart/form-data; boundary=", formData->boundary().data());
+                request.setHTTPBody(WTFMove(formData));
+            },
+            [&] (RefPtr<URLSearchParams>& searchParams) {
+                mimeType = HTTPHeaderValues::formURLEncodedContentType();
+                request.setHTTPBody(FormData::create(searchParams->toString().utf8()));
+            },
+            [&] (String& string) {
+                mimeType = HTTPHeaderValues::textPlainContentType();
+                request.setHTTPBody(FormData::create(string.utf8()));
+            }
+        );
+        noCors = false;
+        if (!mimeType.isEmpty()) {
+            // If mimeType value is a CORS-safelisted request-header value for the Content-Type header, set corsMode to "no-cors".
+            if (isCrossOriginSafeRequestHeader(HTTPHeaderName::ContentType, mimeType))
+                noCors = true;
+            request.setHTTPContentType(mimeType);
+        }
+    }
+    request.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0");
+    frame.loader().addExtraFieldsToSubresourceRequest(request);
+
+    auto& sourceOrigin = document.securityOrigin();
+    bool isCrossOriginRequest = !sourceOrigin.canRequest(url);
+
+    // FIXME: We are supposed to do a preflight in this case but this is not supported yet.
+    if (isCrossOriginRequest && !noCors) {
+        document.addConsoleMessage(MessageSource::Security, MessageLevel::Error, ASCIILiteral("This requests requires a CORS preflight but this is not supported yet."));
+        return false;
+    }
+
+    FrameLoader::addHTTPOriginIfNeeded(request, sourceOrigin.toString());
+    if (!SecurityPolicy::shouldHideReferrer(url, frame.loader().outgoingReferrer())) {
+        String referrer = SecurityPolicy::generateReferrerHeader(document.referrerPolicy(), url, frame.loader().outgoingReferrer());
+        if (!referrer.isEmpty())
+            request.setHTTPReferrer(referrer);
+    }
+
+    request.setAllowCookies(true); // Credentials mode: include.
+    startPingLoad(frame, request, ShouldFollowRedirects::Yes);
+    return true;
+}
+
 void PingLoader::startPingLoad(Frame& frame, ResourceRequest& request, ShouldFollowRedirects shouldFollowRedirects)
 {
     unsigned long identifier = frame.page()->progress().createUniqueIdentifier();
index b29e8da..6ab5ca3 100644 (file)
 
 #pragma once
 
+#include <wtf/Forward.h>
 #include <wtf/Ref.h>
+#include <wtf/Variant.h>
+
+namespace JSC {
+
+class ArrayBuffer;
+class ArrayBufferView;
+
+}
 
 namespace WebCore {
 
+class Blob;
+class DOMFormData;
+class Document;
 class FormData;
 class Frame;
 class URL;
+class URLSearchParams;
 class ResourceRequest;
 
 enum class ViolationReportType {
@@ -46,9 +59,12 @@ enum class ViolationReportType {
     XSSAuditor,
 };
 
+using BodyInit = Variant<RefPtr<Blob>, RefPtr<JSC::ArrayBufferView>, RefPtr<JSC::ArrayBuffer>, RefPtr<DOMFormData>, RefPtr<URLSearchParams>, String>;
+
 class PingLoader {
 public:
     static void loadImage(Frame&, const URL&);
+    static bool sendBeacon(Frame&, Document&, const URL&, std::optional<BodyInit>&&);
     static void sendPing(Frame&, const URL& pingURL, const URL& destinationURL);
     static void sendViolationReport(Frame&, const URL& reportURL, Ref<FormData>&& report, ViolationReportType);
 
index 6ea25fb..d1a8fe3 100644 (file)
@@ -294,6 +294,8 @@ langAttributeAwareFormControlUIEnabled initial=false
 
 subresourceIntegrityEnabled initial=true
 
+beaconAPIEnabled initial=false
+
 constantPropertiesEnabled initial=false
 
 viewportFitEnabled initial=false
index 32c6981..4827697 100644 (file)
@@ -1,3 +1,19 @@
+2017-08-01  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for navigator.sendBeacon
+        https://bugs.webkit.org/show_bug.cgi?id=175007
+        <rdar://problem/33547728>
+
+        Reviewed by Sam Weinig.
+
+        Add experimental feature flag for the Beacon API, disabled by default.
+
+        * Shared/WebPreferencesDefinitions.h:
+        * WebProcess/InjectedBundle/InjectedBundle.cpp:
+        (WebKit::InjectedBundle::overrideBoolPreferenceForTestRunner):
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::updatePreferences):
+
 2017-08-01  Filip Pizlo  <fpizlo@apple.com>
 
         Bmalloc and GC should put auxiliaries (butterflies, typed array backing stores) in a gigacage (separate multi-GB VM region)
index f58ef35..72658e8 100644 (file)
 //   wider testing).
 
 #define FOR_EACH_WEBKIT_EXPERIMENTAL_FEATURE_PREFERENCE(macro) \
+    macro(BeaconAPIEnabled, beaconAPIEnabled, Bool, bool, false, "Beacon API", "Beacon API prototype") \
     macro(ConstantPropertiesEnabled, constantPropertiesEnabled, Bool, bool, true, "Constant Properties", "Enable CSS constant() properties") \
     macro(DisplayContentsEnabled, displayContentsEnabled, Bool, bool, false, "CSS display: contents", "Enable CSS display: contents support") \
     macro(SpringTimingFunctionEnabled, springTimingFunctionEnabled, Bool, bool, DEFAULT_EXPERIMENTAL_FEATURES_ENABLED, "CSS Spring Animations", "CSS Spring Animation prototype") \
index 4e94365..1a4687b 100644 (file)
@@ -824,6 +824,16 @@ bool WKPreferencesGetInlineMediaPlaybackRequiresPlaysInlineAttribute(WKPreferenc
     return toImpl(preferencesRef)->inlineMediaPlaybackRequiresPlaysInlineAttribute();
 }
 
+void WKPreferencesSetBeaconAPIEnabled(WKPreferencesRef preferencesRef, bool flag)
+{
+    toImpl(preferencesRef)->setBeaconAPIEnabled(flag);
+}
+
+bool WKPreferencesGetBeaconAPIEnabled(WKPreferencesRef preferencesRef)
+{
+    return toImpl(preferencesRef)->beaconAPIEnabled();
+}
+
 void WKPreferencesSetMediaControlsScaleWithPageZoom(WKPreferencesRef preferencesRef, bool flag)
 {
     toImpl(preferencesRef)->setMediaControlsScaleWithPageZoom(flag);
index 31e5bf6..b913c71 100644 (file)
@@ -203,6 +203,10 @@ WK_EXPORT bool WKPreferencesGetMediaPlaybackAllowsInline(WKPreferencesRef prefer
 WK_EXPORT void WKPreferencesSetInlineMediaPlaybackRequiresPlaysInlineAttribute(WKPreferencesRef preferencesRef, bool flag);
 WK_EXPORT bool WKPreferencesGetInlineMediaPlaybackRequiresPlaysInlineAttribute(WKPreferencesRef preferencesRef);
 
+// Defaults to false.
+WK_EXPORT void WKPreferencesSetBeaconAPIEnabled(WKPreferencesRef, bool flag);
+WK_EXPORT bool WKPreferencesGetBeaconAPIEnabled(WKPreferencesRef);
+
 // Defaults to false on iOS, true elsewhere.
 WK_EXPORT void WKPreferencesSetMediaControlsScaleWithPageZoom(WKPreferencesRef preferencesRef, bool flag);
 WK_EXPORT bool WKPreferencesGetMediaControlsScaleWithPageZoom(WKPreferencesRef preferencesRef);
index e13d2b9..b6352db 100644 (file)
@@ -3360,6 +3360,7 @@ void WebPage::updatePreferences(const WebPreferencesStore& store)
     }
 
     settings.setSubresourceIntegrityEnabled(store.getBoolValueForKey(WebPreferencesKey::subresourceIntegrityEnabledKey()));
+    settings.setBeaconAPIEnabled(store.getBoolValueForKey(WebPreferencesKey::beaconAPIEnabledKey()));
 
     platformPreferencesDidChange(store);
 
index 63a88ee..b15d24e 100644 (file)
@@ -1,3 +1,22 @@
+2017-08-01  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for navigator.sendBeacon
+        https://bugs.webkit.org/show_bug.cgi?id=175007
+        <rdar://problem/33547728>
+
+        Reviewed by Sam Weinig.
+
+        Add setting to toggle support for the Beacon API (it is disabled by default).
+
+        * WebView/WebPreferenceKeysPrivate.h:
+        * WebView/WebPreferences.mm:
+        (+[WebPreferences initialize]):
+        (-[WebPreferences beaconAPIEnabled]):
+        (-[WebPreferences setBeaconAPIEnabled:]):
+        * WebView/WebPreferencesPrivate.h:
+        * WebView/WebView.mm:
+        (-[WebView _preferencesChanged:]):
+
 2017-07-28  Jeremy Jones  <jeremyj@apple.com>
 
         Remove Web prefix from WebVideoFullscreen and WebPlaybackSession classes.
index 5d31417..571e2b2 100644 (file)
 #define WebKitSimpleLineLayoutDebugBordersEnabledPreferenceKey @"WebKitSimpleLineLayoutDebugBordersEnabled"
 #define WebKitShowRepaintCounterPreferenceKey @"WebKitShowRepaintCounter"
 #define WebKitWebAudioEnabledPreferenceKey @"WebKitWebAudioEnabled"
+#define WebKitBeaconAPIEnabledPreferenceKey @"WebKitBeaconAPIEnabled"
 #define WebKitWebGLEnabledPreferenceKey @"WebKitWebGLEnabled"
 #define WebKitWebGL2EnabledPreferenceKey @"WebKitWebGL2Enabled"
 #define WebKitWebGPUEnabledPreferenceKey @"WebKitWebGPUEnabled"
index cff7043..91fbed6 100644 (file)
@@ -625,6 +625,7 @@ public:
         [NSNumber numberWithBool:YES], WebKitShadowDOMEnabledPreferenceKey,
         [NSNumber numberWithBool:YES], WebKitCustomElementsEnabledPreferenceKey,
         [NSNumber numberWithBool:YES], WebKitModernMediaControlsEnabledPreferenceKey,
+        [NSNumber numberWithBool:NO], WebKitBeaconAPIEnabledPreferenceKey,
 #if ENABLE(WEBGL2)
         [NSNumber numberWithBool:NO], WebKitWebGL2EnabledPreferenceKey,
 #endif
@@ -2070,6 +2071,16 @@ static NSString *classIBCreatorID = nil;
     [self _setBoolValue:enabled forKey:WebKitWebGL2EnabledPreferenceKey];
 }
 
+- (BOOL)beaconAPIEnabled
+{
+    return [self _boolValueForKey:WebKitBeaconAPIEnabledPreferenceKey];
+}
+
+- (void)setBeaconAPIEnabled:(BOOL)enabled
+{
+    [self _setBoolValue:enabled forKey:WebKitBeaconAPIEnabledPreferenceKey];
+}
+
 - (BOOL)forceSoftwareWebGLRendering
 {
     return [self _boolValueForKey:WebKitForceSoftwareWebGLRenderingPreferenceKey];
index 86275af..9def45a 100644 (file)
@@ -266,6 +266,9 @@ extern NSString *WebPreferencesCacheModelChangedInternalNotification;
 - (BOOL)webGL2Enabled;
 - (void)setWebGL2Enabled:(BOOL)enabled;
 
+- (BOOL)beaconAPIEnabled;
+- (void)setBeaconAPIEnabled:(BOOL)enabled;
+
 - (BOOL)forceSoftwareWebGLRendering;
 - (void)setForceSoftwareWebGLRendering:(BOOL)forced;
 
index feacc65..80b2523 100644 (file)
@@ -2982,6 +2982,8 @@ static bool needsSelfRetainWhileLoadingQuirk()
     settings.setViewportFitEnabled([preferences viewportFitEnabled]);
     settings.setConstantPropertiesEnabled([preferences constantPropertiesEnabled]);
 
+    settings.setBeaconAPIEnabled([preferences beaconAPIEnabled]);
+
 #if ENABLE(GAMEPAD)
     RuntimeEnabledFeatures::sharedFeatures().setGamepadsEnabled([preferences gamepadsEnabled]);
 #endif
index 59c7e9e..80f60db 100644 (file)
@@ -1,3 +1,23 @@
+2017-08-01  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for navigator.sendBeacon
+        https://bugs.webkit.org/show_bug.cgi?id=175007
+        <rdar://problem/33547728>
+
+        Reviewed by Sam Weinig.
+
+        Enable the Beacon API at runtime in the context of layout tests since the
+        feature is currently disabled by default.
+
+        * DumpRenderTree/mac/DumpRenderTree.mm:
+        (enableExperimentalFeatures):
+        * WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
+        (WTR::InjectedBundle::beginTesting):
+        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
+        (WTR::TestRunner::setModernMediaControlsEnabled):
+        (WTR::TestRunner::setBeaconAPIEnabled):
+        * WebKitTestRunner/InjectedBundle/TestRunner.h:
+
 2017-08-01  Aakash Jain  <aakash_jain@apple.com>
 
         Update Bot Watcher's Dashboard for Buildbot 0.9
index 0db42df..1c6d6bc 100644 (file)
@@ -847,6 +847,7 @@ static void enableExperimentalFeatures(WebPreferences* preferences)
     [preferences setMediaPreloadingEnabled:YES];
     // FIXME: InputEvents
     [preferences setWebAnimationsEnabled:YES];
+    [preferences setBeaconAPIEnabled:YES];
     [preferences setWebGL2Enabled:YES];
     [preferences setWebGPUEnabled:YES];
     // FIXME: AsyncFrameScrollingEnabled
index 48bfd8c..0f4a4a6 100644 (file)
@@ -720,6 +720,7 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio
     WKPreferencesSetMediaPreloadingEnabled(preferences, true);
     WKPreferencesSetMediaPlaybackAllowsInline(preferences, true);
     WKPreferencesSetInlineMediaPlaybackRequiresPlaysInlineAttribute(preferences, false);
+    WKPreferencesSetBeaconAPIEnabled(preferences, true);
 
     WKCookieManagerDeleteAllCookies(WKContextGetCookieManager(m_context.get()));