Storage Access API: Allow documents that have been granted storage access to also...
authorbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 May 2018 18:16:56 +0000 (18:16 +0000)
committerbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 May 2018 18:16:56 +0000 (18:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=185615
<rdar://problem/39105791>

Reviewed by Chris Dumez.

Source/WebCore:

* dom/Document.cpp:
(WebCore::Document::consumeTemporaryUserGesture): Added. Clear the document's active one-time user
activity (for window opening) state.
(WebCore::Document::enableTemporaryUserGesture): Added. Establish a new active one-time user
activity (for window opening) state.
(WebCore::Document::requestStorageAccess): If the user approves Storage Access, establish a new
UserInteraction scope, then resolve the promise. Also post a task to clear the one-time user
gesture state.

LayoutTests:

* http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window-expected.txt: Added.
* http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window.html: Added.
* http/tests/storageAccess/resources/request-storage-access-iframe-and-pop-window.html: Added.
* http/tests/storageAccess/resources/request-storage-access-second-window.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window.html [new file with mode: 0644]
LayoutTests/http/tests/storageAccess/resources/request-storage-access-iframe-and-pop-window.html [new file with mode: 0644]
LayoutTests/http/tests/storageAccess/resources/request-storage-access-second-window.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h

index d4162ff..e70ab50 100644 (file)
@@ -1,3 +1,16 @@
+2018-05-17  Brent Fulgham  <bfulgham@apple.com>
+
+        Storage Access API: Allow documents that have been granted storage access to also do a popup
+        https://bugs.webkit.org/show_bug.cgi?id=185615
+        <rdar://problem/39105791>
+
+        Reviewed by Chris Dumez.
+
+        * http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window-expected.txt: Added.
+        * http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window.html: Added.
+        * http/tests/storageAccess/resources/request-storage-access-iframe-and-pop-window.html: Added.
+        * http/tests/storageAccess/resources/request-storage-access-second-window.html: Added.
+
 2018-05-17  Antoine Quint  <graouts@apple.com>
 
         [modern-media-controls] AirPlaySupport should be disabled by default
diff --git a/LayoutTests/http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window-expected.txt b/LayoutTests/http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window-expected.txt
new file mode 100644 (file)
index 0000000..d7ec4cf
--- /dev/null
@@ -0,0 +1,10 @@
+Tests that cross-origin iframe can display a window if storage access is granted.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS Window was successfully opened with user interaction.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window.html b/LayoutTests/http/tests/storageAccess/request-and-grant-storage-access-cross-origin-non-sandboxed-iframe-pop-window.html
new file mode 100644 (file)
index 0000000..d1e1813
--- /dev/null
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/js-test-resources/js-test.js"></script>
+    <script src="/js-test-resources/ui-helper.js"></script>
+    <script>
+        description("Tests that cross-origin iframe can display a window if storage access is granted.");
+        jsTestIsAsync = true;
+
+        const hostUnderTest = "localhost:8000";
+        const statisticsUrl = "http://" + hostUnderTest + "/temp";
+
+        window.addEventListener("message", receiveMessage, false);
+
+        function setEnableFeature(enable) {
+            if (!window.testRunner)
+                return;
+
+            if (!enable)
+                testRunner.statisticsResetToConsistentState();
+            internals.setResourceLoadStatisticsEnabled(enable);
+            testRunner.setCookieStoragePartitioningEnabled(enable);
+            testRunner.setStorageAccessAPIEnabled(enable);
+        }
+
+        function receiveMessage(event) {
+            if (event.origin === "http://localhost:8000") {
+                if (event.data.indexOf("PASS ") !== -1)
+                    testPassed(event.data.replace("PASS ", ""));
+                else
+                    testFailed(event.data);
+            } else
+                testFailed("Received a message from an unexpected origin: " + event.origin);
+            finishJSTest();
+            setEnableFeature(false);
+        }
+
+        function activateElement(elementId) {
+            var element = document.getElementById(elementId);
+            var centerX = element.offsetLeft + element.offsetWidth / 2;
+            var centerY = element.offsetTop + element.offsetHeight / 2;
+            UIHelper.activateAt(centerX, centerY).then(
+                function () {
+                    if (window.eventSender)
+                        eventSender.keyDown("escape");
+                    else {
+                        testFailed("No eventSender.");
+                        finishJSTest();
+                        setEnableFeature(false);
+                    }
+                },
+                function () {
+                    testFailed("Promise rejected.");
+                    finishJSTest();
+                    setEnableFeature(false);
+                }
+            );
+        }
+
+        function runTest() {
+            setEnableFeature(true);
+
+            if (!window.testRunner)
+                return;
+
+            testRunner.setCanOpenWindows(false);
+            testRunner.setPopupBlockingEnabled(true);
+            testRunner.setStatisticsPrevalentResource(statisticsUrl, true);
+            if (!testRunner.isStatisticsPrevalentResource(statisticsUrl))
+                testFailed("Host did not get set as prevalent resource.");
+            testRunner.setStatisticsHasHadNonRecentUserInteraction(statisticsUrl);
+            if (!testRunner.isStatisticsHasHadUserInteraction(statisticsUrl))
+                testFailed("Host did not get logged for user interaction.");
+            testRunner.statisticsUpdateCookiePartitioning();
+            activateElement("theIframe");
+        }
+    </script>
+</head>
+<body>
+    <iframe onload="runTest()" id="theIframe" src="http://localhost:8000/storageAccess/resources/request-storage-access-iframe-and-pop-window.html#userShouldGrantAccess,userShouldBeConsulted,policyShouldGrantAccess,isNotSameOriginIframe"></iframe>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/http/tests/storageAccess/resources/request-storage-access-iframe-and-pop-window.html b/LayoutTests/http/tests/storageAccess/resources/request-storage-access-iframe-and-pop-window.html
new file mode 100644 (file)
index 0000000..8da52bf
--- /dev/null
@@ -0,0 +1,63 @@
+<html>
+<head>
+    <script>
+        const hashArguments = document.location.hash.substring(1).split(",");
+        const userShouldGrantAccess = hashArguments[0] === "userShouldGrantAccess";
+        const userShouldBeConsulted = hashArguments[1] === "userShouldBeConsulted";
+        const policyShouldGrantAccess = hashArguments[2] === "policyShouldGrantAccess";
+        const isSameOriginIframe = hashArguments[3] === "isSameOriginIframe";
+        const originIsNull = hashArguments[4] === "originIsNull";
+
+        if (window.internals)
+            internals.setUserGrantsStorageAccess(userShouldGrantAccess);
+
+        if (window.testRunner) {
+            testRunner.setCanOpenWindows(false);
+            testRunner.setPopupBlockingEnabled(true);
+            testRunner.setCloseRemainingWindowsWhenComplete(true);
+        }
+
+        var requestStorageAccessResolved;
+
+        function messageToTop(message) {
+            top.postMessage(message, "http://127.0.0.1:8000");
+        }
+
+        function windowWasOpened()
+        {
+            if (userShouldGrantAccess)
+                messageToTop("PASS Window was successfully opened with user interaction.");
+            else
+                messageToTop("Window was opened even though the user declined permission.");
+        }
+
+        function makeRequestWithUserGesture() {
+            var promise = document.requestStorageAccess();
+            promise.then(
+                function () {
+                    requestStorageAccessResolved = true;
+                    continueAfterRequestWithUserGesture();
+                },
+                function () {
+                    requestStorageAccessResolved = false;
+                    continueAfterRequestWithUserGesture();
+                }
+            );
+        }
+
+        function continueAfterRequestWithUserGesture() {
+            var win = window.open("request-storage-access-second-window.html", "test window");
+            if (!win) {
+                if (userShouldGrantAccess)
+                    messageToTop("Window was not opened even though the user granted permission.");
+                else
+                    messageToTop("PASS Window was blocked from opening.");
+            } else if (!userShouldGrantAccess) {
+                messageToTop("Window was opened even though the user did not grant permission.");
+            }
+        }
+    </script>
+</head>
+<body onclick="makeRequestWithUserGesture()">
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/http/tests/storageAccess/resources/request-storage-access-second-window.html b/LayoutTests/http/tests/storageAccess/resources/request-storage-access-second-window.html
new file mode 100644 (file)
index 0000000..adcc576
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE html>\r
+<html>\r
+<title>Event handlers in isolated worlds for user gesture generated events should should the same permissions as handlers within the page (resource)</title>\r
+<script>\r
+\r
+// success!\r
+window.opener.windowWasOpened();\r
+window.close();\r
+\r
+</script>\r
+<body>\r
+</body>\r
+</html>\r
index 385c13a..524b678 100644 (file)
@@ -1,3 +1,20 @@
+2018-05-17  Brent Fulgham  <bfulgham@apple.com>
+
+        Storage Access API: Allow documents that have been granted storage access to also do a popup
+        https://bugs.webkit.org/show_bug.cgi?id=185615
+        <rdar://problem/39105791>
+
+        Reviewed by Chris Dumez.
+
+        * dom/Document.cpp:
+        (WebCore::Document::consumeTemporaryUserGesture): Added. Clear the document's active one-time user
+        activity (for window opening) state.
+        (WebCore::Document::enableTemporaryUserGesture): Added. Establish a new active one-time user
+        activity (for window opening) state.
+        (WebCore::Document::requestStorageAccess): If the user approves Storage Access, establish a new
+        UserInteraction scope, then resolve the promise. Also post a task to clear the one-time user
+        gesture state.
+
 2018-05-17  Zalan Bujtas  <zalan@apple.com>
 
         [LFC] Introduce DisplayBox::Style
index ce96ae1..cceab71 100644 (file)
 #include "MediaQueryList.h"
 #include "MediaQueryMatcher.h"
 #include "MessageEvent.h"
+#include "Microtasks.h"
 #include "MouseEventWithHitTestResults.h"
 #include "MutationEvent.h"
 #include "NameNodeList.h"
@@ -7609,14 +7610,22 @@ void Document::requestStorageAccess(Ref<DeferredPromise>&& promise)
         return;
     }
 
-    page->chrome().client().requestStorageAccess(WTFMove(iframeHost), WTFMove(topHost), frameID.value(), pageID.value(), [documentReference = m_weakFactory.createWeakPtr(*this), promise = WTFMove(promise)] (bool wasGranted) {
+    page->chrome().client().requestStorageAccess(WTFMove(iframeHost), WTFMove(topHost), frameID.value(), pageID.value(), [documentReference = m_weakFactory.createWeakPtr(*this), promise = WTFMove(promise)] (bool wasGranted) mutable {
         Document* document = documentReference.get();
         if (!document)
             return;
         
         if (wasGranted) {
             document->setHasFrameSpecificStorageAccess(true);
+            MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>([documentReference = document->m_weakFactory.createWeakPtr(*document)] () {
+                if (auto* document = documentReference.get())
+                    document->enableTemporaryTimeUserGesture();
+            }));
             promise->resolve();
+            MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>([documentReference = WTFMove(documentReference)] () {
+                if (auto* document = documentReference.get())
+                    document->consumeTemporaryTimeUserGesture();
+            }));
         } else
             promise->reject();
     });
@@ -7625,6 +7634,16 @@ void Document::requestStorageAccess(Ref<DeferredPromise>&& promise)
 #endif
 }
 
+void Document::enableTemporaryTimeUserGesture()
+{
+    m_temporaryUserGesture = std::make_unique<UserGestureIndicator>(ProcessingUserGesture, this);
+}
+
+void Document::consumeTemporaryTimeUserGesture()
+{
+    m_temporaryUserGesture = nullptr;
+}
+
 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
 bool Document::hasFrameSpecificStorageAccess() const
 {
index 8e8cab9..b002cac 100644 (file)
@@ -1429,6 +1429,8 @@ public:
 
     String signedPublicKeyAndChallengeString(unsigned keySizeIndex, const String& challengeString, const URL&);
 
+    void consumeTemporaryTimeUserGesture();
+
 protected:
     enum ConstructionFlags { Synthesized = 1, NonRenderedPlaceholder = 1 << 1 };
     Document(Frame*, const URL&, unsigned = DefaultDocumentClass, unsigned constructionFlags = 0);
@@ -1542,6 +1544,8 @@ private:
 
     bool domainIsRegisterable(const String&) const;
 
+    void enableTemporaryTimeUserGesture();
+
     const Ref<Settings> m_settings;
 
     std::unique_ptr<StyleResolver> m_userAgentShadowTreeStyleResolver;
@@ -1919,6 +1923,8 @@ private:
 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
     String m_primaryDomainRequestedPageSpecificStorageAccessWithUserInteraction { };
 #endif
+    
+    std::unique_ptr<UserGestureIndicator> m_temporaryUserGesture;
 };
 
 Element* eventTargetElementForDocument(Document*);