Prevent synchronous XHR in beforeunload / unload event handlers
https://bugs.webkit.org/show_bug.cgi?id=204912
<rdar://problem/57676394>

Reviewed by Darin Adler.

Source/WebCore:

Prevent synchronous XHR in beforeunload / unload event handlers. They are terrible for performance
and the Beacon API (or Fetch keepalive) are more efficient & supported alternatives.

In particular, this would cause hangs when trying to navigate away from a site or when closing
attempt, which would result in terrible user experience.

Chrome and Edge have expressed public support for this. Chrome has actually been testing this behavior
for a while now:
https://www.chromestatus.com/feature/4664843055398912

I added this new behavior behind an experimental feature flag, enabled by default.

Tests: http/tests/xmlhttprequest/sync-xhr-in-beforeunload.html
       http/tests/xmlhttprequest/sync-xhr-in-unload.html

* loader/DocumentThreadableLoader.cpp:
(WebCore::DocumentThreadableLoader::DocumentThreadableLoader):
* loader/FrameLoader.cpp:
(WebCore::PageLevelForbidScope::PageLevelForbidScope):
(WebCore::ForbidPromptsScope::ForbidPromptsScope):
(WebCore::ForbidPromptsScope::~ForbidPromptsScope):
(WebCore::ForbidSynchronousLoadsScope::ForbidSynchronousLoadsScope):
(WebCore::ForbidSynchronousLoadsScope::~ForbidSynchronousLoadsScope):
(WebCore::FrameLoader::dispatchUnloadEvents):
(WebCore::FrameLoader::dispatchBeforeUnloadEvent):
* page/Page.cpp:
(WebCore::Page::forbidSynchronousLoads):
(WebCore::Page::allowSynchronousLoads):
(WebCore::Page::areSynchronousLoadsAllowed):
* page/Page.h:

LayoutTests:

Add layout test coverage.

* http/tests/xmlhttprequest/resources/sync-xhr-in-beforeunload-window.html: Added.
* http/tests/xmlhttprequest/resources/sync-xhr-in-unload-window.html: Added.
* http/tests/xmlhttprequest/sync-xhr-in-beforeunload-expected.txt: Added.
* http/tests/xmlhttprequest/sync-xhr-in-beforeunload.html: Added.
* http/tests/xmlhttprequest/sync-xhr-in-unload-expected.txt: Added.
* http/tests/xmlhttprequest/sync-xhr-in-unload.html: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@253213 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/loader/DocumentThreadableLoader.cpp b/Source/WebCore/loader/DocumentThreadableLoader.cpp
index f5e5515..3d26893 100644
--- a/Source/WebCore/loader/DocumentThreadableLoader.cpp
+++ b/Source/WebCore/loader/DocumentThreadableLoader.cpp
@@ -55,6 +55,7 @@
 #include "RuntimeApplicationChecks.h"
 #include "RuntimeEnabledFeatures.h"
 #include "SecurityOrigin.h"
+#include "Settings.h"
 #include "SharedBuffer.h"
 #include "SubresourceIntegrity.h"
 #include "SubresourceLoader.h"
@@ -131,6 +132,11 @@
     // Setting a referrer header is only supported in the async code path.
     ASSERT(m_async || m_referrer.isEmpty());
 
+    if (document.settings().disallowSyncXHRDuringPageDismissalEnabled() && !m_async && (!document.page() || !document.page()->areSynchronousLoadsAllowed())) {
+        logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, request.url(), "Synchronous loads are not allowed at this time"));
+        return;
+    }
+
     // Referrer and Origin headers should be set after the preflight if any.
     ASSERT(!request.hasHTTPReferrer() && !request.hasHTTPOrigin());