Add SPI to WKURLSchemeTask for redirection
authorachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 23 Jun 2017 17:25:43 +0000 (17:25 +0000)
committerachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 23 Jun 2017 17:25:43 +0000 (17:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=173730

Reviewed by Brady Eidson.

Source/WebCore:

* platform/network/CacheValidation.cpp:
(WebCore::computeFreshnessLifetimeForHTTPFamily):
Asserting that redirects are always http/https URLs is no longer valid.
If there's a custom scheme redirect, give it no freshness lifetime in the cache.

Source/WebKit2:

This is needed for testing an upcoming fix for redirect callbacks, and it might be
necessary for loading with custom schemes in general. Right now just responding with
 an HTTP 301/302/307/308 response code doesn't work because there is nothing that
synthesizes an NSURLRequest from the Location header like we do in
SynchronousResourceHandleCFURLConnectionDelegate::willSendRequest et al. for HSTS,
and that would require using an NSHTTPURLResponse for non-HTTP responses, which is
conceptually wrong.  Instead of waiting for a completion handler in the API, we are
following the pattern of WKNavigationDelegate.didReceiveServerRedirectForProvisionalNavigation
and allowing the SPI to indicate that a redirect has happened to update the state of
WebKit, but not allowing the SPI to wait for a processed request and slow down loading.

This adds WKURLSchemeTaskPrivate._didPerformRedirection which is covered by new API tests.

* UIProcess/API/Cocoa/WKURLSchemeTask.mm:
(raiseExceptionIfNecessary):
(-[WKURLSchemeTaskImpl _didPerformRedirection:newRequest:completionHandler:]):
* UIProcess/API/Cocoa/WKURLSchemeTaskInternal.h:
* UIProcess/API/Cocoa/WKURLSchemeTaskPrivate.h: Added.
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::startURLSchemeTask):
(WebKit::WebPageProxy::stopURLSchemeTask):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* UIProcess/WebURLSchemeHandler.cpp:
(WebKit::WebURLSchemeHandler::startTask):
(WebKit::WebURLSchemeHandler::stopTask):
* UIProcess/WebURLSchemeHandler.h:
* UIProcess/WebURLSchemeTask.cpp:
(WebKit::WebURLSchemeTask::didReceiveResponse): Deleted.
(WebKit::WebURLSchemeTask::didReceiveData): Deleted.
(WebKit::WebURLSchemeTask::didComplete): Deleted.
* UIProcess/WebURLSchemeTask.h:
* WebKit2.xcodeproj/project.pbxproj:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::urlSchemeTaskDidPerformRedirection):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/WebURLSchemeHandlerProxy.cpp:
(WebKit::WebURLSchemeHandlerProxy::taskDidPerformRedirection):
* WebProcess/WebPage/WebURLSchemeHandlerProxy.h:
* WebProcess/WebPage/WebURLSchemeTaskProxy.cpp:
(WebKit::WebURLSchemeTaskProxy::didPerformRedirection):
* WebProcess/WebPage/WebURLSchemeTaskProxy.h:

Tools:

* TestWebKitAPI/Tests/WebKit2Cocoa/WKURLSchemeHandler-1.mm:
(-[SchemeHandler webView:startURLSchemeTask:]):
(-[SchemeHandler webView:stopURLSchemeTask:]):
(TEST):
(-[RedirectSchemeHandler webView:startURLSchemeTask:]):
(-[RedirectSchemeHandler webView:stopURLSchemeTask:]):
(-[RedirectSchemeHandler webView:didReceiveServerRedirectForProvisionalNavigation:]):
(-[RedirectSchemeHandler webView:decidePolicyForNavigationResponse:decisionHandler:]):
(-[RedirectSchemeHandler userContentController:didReceiveScriptMessage:]):

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

23 files changed:
Source/WebCore/ChangeLog
Source/WebCore/platform/network/CacheValidation.cpp
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/Cocoa/WKURLSchemeTask.mm
Source/WebKit2/UIProcess/API/Cocoa/WKURLSchemeTaskInternal.h
Source/WebKit2/UIProcess/API/Cocoa/WKURLSchemeTaskPrivate.h [new file with mode: 0644]
Source/WebKit2/UIProcess/WebPageProxy.cpp
Source/WebKit2/UIProcess/WebPageProxy.h
Source/WebKit2/UIProcess/WebPageProxy.messages.in
Source/WebKit2/UIProcess/WebURLSchemeHandler.cpp
Source/WebKit2/UIProcess/WebURLSchemeHandler.h
Source/WebKit2/UIProcess/WebURLSchemeTask.cpp
Source/WebKit2/UIProcess/WebURLSchemeTask.h
Source/WebKit2/WebKit2.xcodeproj/project.pbxproj
Source/WebKit2/WebProcess/WebPage/WebPage.cpp
Source/WebKit2/WebProcess/WebPage/WebPage.h
Source/WebKit2/WebProcess/WebPage/WebPage.messages.in
Source/WebKit2/WebProcess/WebPage/WebURLSchemeHandlerProxy.cpp
Source/WebKit2/WebProcess/WebPage/WebURLSchemeHandlerProxy.h
Source/WebKit2/WebProcess/WebPage/WebURLSchemeTaskProxy.cpp
Source/WebKit2/WebProcess/WebPage/WebURLSchemeTaskProxy.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/WKURLSchemeHandler-1.mm

index 9570eea..e16adbb 100644 (file)
@@ -1,3 +1,15 @@
+2017-06-23  Alex Christensen  <achristensen@webkit.org>
+
+        Add SPI to WKURLSchemeTask for redirection
+        https://bugs.webkit.org/show_bug.cgi?id=173730
+
+        Reviewed by Brady Eidson.
+
+        * platform/network/CacheValidation.cpp:
+        (WebCore::computeFreshnessLifetimeForHTTPFamily):
+        Asserting that redirects are always http/https URLs is no longer valid.
+        If there's a custom scheme redirect, give it no freshness lifetime in the cache.
+
 2017-06-23  Konstantin Tokarev  <annulen@yandex.ru>
 
         Remove excessive headers from WebCore/{editing,fileapi,history,html,loader,page}
index f67caa8..c526c41 100644 (file)
@@ -118,7 +118,8 @@ std::chrono::microseconds computeFreshnessLifetimeForHTTPFamily(const ResourceRe
 {
     using namespace std::chrono;
 
-    ASSERT(response.url().protocolIsInHTTPFamily());
+    if (!response.url().protocolIsInHTTPFamily())
+        return 0us;
 
     // Freshness Lifetime:
     // http://tools.ietf.org/html/rfc7234#section-4.2.1
index 1cfe2bf..35b92d7 100644 (file)
@@ -1,3 +1,54 @@
+2017-06-23  Alex Christensen  <achristensen@webkit.org>
+
+        Add SPI to WKURLSchemeTask for redirection
+        https://bugs.webkit.org/show_bug.cgi?id=173730
+
+        Reviewed by Brady Eidson.
+
+        This is needed for testing an upcoming fix for redirect callbacks, and it might be
+        necessary for loading with custom schemes in general. Right now just responding with
+         an HTTP 301/302/307/308 response code doesn't work because there is nothing that
+        synthesizes an NSURLRequest from the Location header like we do in 
+        SynchronousResourceHandleCFURLConnectionDelegate::willSendRequest et al. for HSTS, 
+        and that would require using an NSHTTPURLResponse for non-HTTP responses, which is 
+        conceptually wrong.  Instead of waiting for a completion handler in the API, we are
+        following the pattern of WKNavigationDelegate.didReceiveServerRedirectForProvisionalNavigation
+        and allowing the SPI to indicate that a redirect has happened to update the state of
+        WebKit, but not allowing the SPI to wait for a processed request and slow down loading.
+
+        This adds WKURLSchemeTaskPrivate._didPerformRedirection which is covered by new API tests.
+
+        * UIProcess/API/Cocoa/WKURLSchemeTask.mm:
+        (raiseExceptionIfNecessary):
+        (-[WKURLSchemeTaskImpl _didPerformRedirection:newRequest:completionHandler:]):
+        * UIProcess/API/Cocoa/WKURLSchemeTaskInternal.h:
+        * UIProcess/API/Cocoa/WKURLSchemeTaskPrivate.h: Added.
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::startURLSchemeTask):
+        (WebKit::WebPageProxy::stopURLSchemeTask):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * UIProcess/WebURLSchemeHandler.cpp:
+        (WebKit::WebURLSchemeHandler::startTask):
+        (WebKit::WebURLSchemeHandler::stopTask):
+        * UIProcess/WebURLSchemeHandler.h:
+        * UIProcess/WebURLSchemeTask.cpp:
+        (WebKit::WebURLSchemeTask::didReceiveResponse): Deleted.
+        (WebKit::WebURLSchemeTask::didReceiveData): Deleted.
+        (WebKit::WebURLSchemeTask::didComplete): Deleted.
+        * UIProcess/WebURLSchemeTask.h:
+        * WebKit2.xcodeproj/project.pbxproj:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::urlSchemeTaskDidPerformRedirection):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/WebURLSchemeHandlerProxy.cpp:
+        (WebKit::WebURLSchemeHandlerProxy::taskDidPerformRedirection):
+        * WebProcess/WebPage/WebURLSchemeHandlerProxy.h:
+        * WebProcess/WebPage/WebURLSchemeTaskProxy.cpp:
+        (WebKit::WebURLSchemeTaskProxy::didPerformRedirection):
+        * WebProcess/WebPage/WebURLSchemeTaskProxy.h:
+
 2017-06-23  Brent Fulgham  <bfulgham@apple.com>
 
         [WK2][macOS] Support Mac Mini Flash Player Features
index 0fef689..f0deea5 100644 (file)
 
 #if WK_API_ENABLED
 
-#include "WebURLSchemeTask.h"
-#include <WebCore/ResourceError.h>
-#include <WebCore/ResourceResponse.h>
-#include <WebCore/SharedBuffer.h>
+#import "WebURLSchemeTask.h"
+#import <WebCore/ResourceError.h>
+#import <WebCore/ResourceResponse.h>
+#import <WebCore/SharedBuffer.h>
+#import <wtf/BlockPtr.h>
 
 using namespace WebCore;
 
@@ -52,6 +53,9 @@ static void raiseExceptionIfNecessary(WebKit::WebURLSchemeTask::ExceptionType ex
     case WebKit::WebURLSchemeTask::ExceptionType::NoResponseSent:
         [NSException raise:NSInternalInconsistencyException format:@"No response has been sent for this task"];
         break;
+    case WebKit::WebURLSchemeTask::ExceptionType::RedirectAfterResponse:
+        [NSException raise:NSInternalInconsistencyException format:@"No redirects are allowed after the response"];
+        break;
     }
 }
 
@@ -86,6 +90,12 @@ static void raiseExceptionIfNecessary(WebKit::WebURLSchemeTask::ExceptionType ex
     raiseExceptionIfNecessary(result);
 }
 
+- (void)_didPerformRedirection:(NSURLResponse *)response newRequest:(NSURLRequest *)request
+{
+    auto result = _urlSchemeTask->task().didPerformRedirection(response, request);
+    raiseExceptionIfNecessary(result);
+}
+
 #pragma mark WKObject protocol implementation
 
 - (API::Object&)_apiObject
index 2fd61ff..5797580 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import "WKURLSchemeTask.h"
+#import "WKURLSchemeTaskPrivate.h"
 
 #if WK_API_ENABLED
 
 #import "APIURLSchemeTask.h"
 #import "WKObject.h"
 
-@interface WKURLSchemeTaskImpl : NSObject <WKURLSchemeTask>
+@interface WKURLSchemeTaskImpl : NSObject <WKURLSchemeTaskPrivate>
 @end
 
 namespace WebKit {
diff --git a/Source/WebKit2/UIProcess/API/Cocoa/WKURLSchemeTaskPrivate.h b/Source/WebKit2/UIProcess/API/Cocoa/WKURLSchemeTaskPrivate.h
new file mode 100644 (file)
index 0000000..46678b8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#import <WebKit/WKURLSchemeTask.h>
+
+#if WK_API_ENABLED
+
+WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA))
+@protocol WKURLSchemeTaskPrivate <WKURLSchemeTask>
+
+- (void)_didPerformRedirection:(NSURLResponse *)response newRequest:(NSURLRequest *)request;
+
+@end
+
+#endif
+
index 20c51d3..4ecc8a9 100644 (file)
@@ -6879,20 +6879,20 @@ WebURLSchemeHandler* WebPageProxy::urlSchemeHandlerForScheme(const String& schem
     return m_urlSchemeHandlersByScheme.get(scheme);
 }
 
-void WebPageProxy::startURLSchemeTask(uint64_t handlerIdentifier, uint64_t resourceIdentifier, const WebCore::ResourceRequest& request)
+void WebPageProxy::startURLSchemeTask(uint64_t handlerIdentifier, uint64_t taskIdentifier, const WebCore::ResourceRequest& request)
 {
     auto iterator = m_urlSchemeHandlersByIdentifier.find(handlerIdentifier);
     ASSERT(iterator != m_urlSchemeHandlersByIdentifier.end());
 
-    iterator->value->startTask(*this, resourceIdentifier, request);
+    iterator->value->startTask(*this, taskIdentifier, request);
 }
 
-void WebPageProxy::stopURLSchemeTask(uint64_t handlerIdentifier, uint64_t resourceIdentifier)
+void WebPageProxy::stopURLSchemeTask(uint64_t handlerIdentifier, uint64_t taskIdentifier)
 {
     auto iterator = m_urlSchemeHandlersByIdentifier.find(handlerIdentifier);
     ASSERT(iterator != m_urlSchemeHandlersByIdentifier.end());
 
-    iterator->value->stopTask(*this, resourceIdentifier);
+    iterator->value->stopTask(*this, taskIdentifier);
 }
 
 void WebPageProxy::setAvoidsUnsafeArea(bool avoidsUnsafeArea)
index e015192..4445d53 100644 (file)
@@ -1607,8 +1607,8 @@ private:
 #endif
 #endif
 
-    void startURLSchemeTask(uint64_t handlerIdentifier, uint64_t resourceIdentifier, const WebCore::ResourceRequest&);
-    void stopURLSchemeTask(uint64_t handlerIdentifier, uint64_t resourceIdentifier);
+    void startURLSchemeTask(uint64_t handlerIdentifier, uint64_t taskIdentifier, const WebCore::ResourceRequest&);
+    void stopURLSchemeTask(uint64_t handlerIdentifier, uint64_t taskIdentifier);
 
     void handleAutoFillButtonClick(const UserData&);
 
index 2c9cac4..a791dab 100644 (file)
@@ -495,6 +495,6 @@ messages -> WebPageProxy {
 
     SetIsUsingHighPerformanceWebGL(bool isUsingHighPerformanceWebGL)
 
-    StartURLSchemeTask(uint64_t loaderIdentifier, uint64_t resourceIdentifier, WebCore::ResourceRequest request)
-    StopURLSchemeTask(uint64_t loaderIdentifier, uint64_t resourceIdentifier)
+    StartURLSchemeTask(uint64_t handlerIdentifier, uint64_t taskIdentifier, WebCore::ResourceRequest request)
+    StopURLSchemeTask(uint64_t handlerIdentifier, uint64_t taskIdentifier)
 }
index 740c396..490855b 100644 (file)
@@ -48,17 +48,17 @@ WebURLSchemeHandler::~WebURLSchemeHandler()
     ASSERT(m_tasks.isEmpty());
 }
 
-void WebURLSchemeHandler::startTask(WebPageProxy& page, uint64_t resourceIdentifier, const ResourceRequest& request)
+void WebURLSchemeHandler::startTask(WebPageProxy& page, uint64_t taskIdentifier, const ResourceRequest& request)
 {
-    auto result = m_tasks.add(resourceIdentifier, WebURLSchemeTask::create(*this, page, resourceIdentifier, request));
+    auto result = m_tasks.add(taskIdentifier, WebURLSchemeTask::create(*this, page, taskIdentifier, request));
     ASSERT(result.isNewEntry);
 
     platformStartTask(page, result.iterator->value);
 }
 
-void WebURLSchemeHandler::stopTask(WebPageProxy& page, uint64_t resourceIdentifier)
+void WebURLSchemeHandler::stopTask(WebPageProxy& page, uint64_t taskIdentifier)
 {
-    auto iterator = m_tasks.find(resourceIdentifier);
+    auto iterator = m_tasks.find(taskIdentifier);
     if (iterator == m_tasks.end())
         return;
 
index 3f4eafb..7179dcb 100644 (file)
@@ -45,8 +45,8 @@ public:
 
     uint64_t identifier() const { return m_identifier; }
 
-    void startTask(WebPageProxy&, uint64_t resourceIdentifier, const WebCore::ResourceRequest&);
-    void stopTask(WebPageProxy&, uint64_t resourceIdentifier);
+    void startTask(WebPageProxy&, uint64_t taskIdentifier, const WebCore::ResourceRequest&);
+    void stopTask(WebPageProxy&, uint64_t taskIdentifier);
 
 protected:
     WebURLSchemeHandler();
index 3d335bc..3739485 100644 (file)
@@ -48,54 +48,74 @@ WebURLSchemeTask::WebURLSchemeTask(WebURLSchemeHandler& handler, WebPageProxy& p
 {
 }
 
-WebURLSchemeTask::ExceptionType WebURLSchemeTask::didReceiveResponse(const ResourceResponse& response)
+auto WebURLSchemeTask::didPerformRedirection(WebCore::ResourceResponse&& response, WebCore::ResourceRequest&& request) -> ExceptionType
 {
     if (m_stopped)
-        return WebURLSchemeTask::ExceptionType::TaskAlreadyStopped;
+        return ExceptionType::TaskAlreadyStopped;
+    
+    if (m_completed)
+        return ExceptionType::CompleteAlreadyCalled;
+    
+    if (m_dataSent)
+        return ExceptionType::DataAlreadySent;
+    
+    if (m_responseSent)
+        return ExceptionType::RedirectAfterResponse;
+    
+    m_request = request;
+    m_page->send(Messages::WebPage::URLSchemeTaskDidPerformRedirection(m_urlSchemeHandler->identifier(), m_identifier, response, request));
+
+    return ExceptionType::None;
+}
+
+auto WebURLSchemeTask::didReceiveResponse(const ResourceResponse& response) -> ExceptionType
+{
+    if (m_stopped)
+        return ExceptionType::TaskAlreadyStopped;
 
     if (m_completed)
-        return WebURLSchemeTask::ExceptionType::CompleteAlreadyCalled;
+        return ExceptionType::CompleteAlreadyCalled;
 
     if (m_dataSent)
-        return WebURLSchemeTask::ExceptionType::DataAlreadySent;
+        return ExceptionType::DataAlreadySent;
 
     m_responseSent = true;
 
     response.includeCertificateInfo();
     m_page->send(Messages::WebPage::URLSchemeTaskDidReceiveResponse(m_urlSchemeHandler->identifier(), m_identifier, response));
-    return WebURLSchemeTask::ExceptionType::None;
+    return ExceptionType::None;
 }
 
-WebURLSchemeTask::ExceptionType WebURLSchemeTask::didReceiveData(Ref<SharedBuffer> buffer)
+auto WebURLSchemeTask::didReceiveData(Ref<SharedBuffer> buffer) -> ExceptionType
 {
     if (m_stopped)
-        return WebURLSchemeTask::ExceptionType::TaskAlreadyStopped;
+        return ExceptionType::TaskAlreadyStopped;
 
     if (m_completed)
-        return WebURLSchemeTask::ExceptionType::CompleteAlreadyCalled;
+        return ExceptionType::CompleteAlreadyCalled;
 
     if (!m_responseSent)
-        return WebURLSchemeTask::ExceptionType::NoResponseSent;
+        return ExceptionType::NoResponseSent;
 
     m_dataSent = true;
     m_page->send(Messages::WebPage::URLSchemeTaskDidReceiveData(m_urlSchemeHandler->identifier(), m_identifier, IPC::SharedBufferDataReference(buffer.ptr())));
-    return WebURLSchemeTask::ExceptionType::None;
+    return ExceptionType::None;
 }
 
-WebURLSchemeTask::ExceptionType WebURLSchemeTask::didComplete(const ResourceError& error)
+auto WebURLSchemeTask::didComplete(const ResourceError& error) -> ExceptionType
 {
     if (m_stopped)
-        return WebURLSchemeTask::ExceptionType::TaskAlreadyStopped;
+        return ExceptionType::TaskAlreadyStopped;
 
     if (m_completed)
-        return WebURLSchemeTask::ExceptionType::CompleteAlreadyCalled;
+        return ExceptionType::CompleteAlreadyCalled;
 
     if (!m_responseSent && error.isNull())
-        return WebURLSchemeTask::ExceptionType::NoResponseSent;
+        return ExceptionType::NoResponseSent;
 
     m_completed = true;
     m_page->send(Messages::WebPage::URLSchemeTaskDidComplete(m_urlSchemeHandler->identifier(), m_identifier, error));
-    return WebURLSchemeTask::ExceptionType::None;
+    return ExceptionType::None;
 }
 
 void WebURLSchemeTask::pageDestroyed()
index 353c19d..c6f92c8 100644 (file)
@@ -52,10 +52,12 @@ public:
     enum class ExceptionType {
         DataAlreadySent,
         CompleteAlreadyCalled,
+        RedirectAfterResponse,
         TaskAlreadyStopped,
         NoResponseSent,
         None,
     };
+    ExceptionType didPerformRedirection(WebCore::ResourceResponse&&, WebCore::ResourceRequest&&);
     ExceptionType didReceiveResponse(const WebCore::ResourceResponse&);
     ExceptionType didReceiveData(Ref<WebCore::SharedBuffer>);
     ExceptionType didComplete(const WebCore::ResourceError&);
index d3f137a..37ee588 100644 (file)
                5C20CB9D1BB0DCFA00895BB1 /* NetworkSessionCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5C20CB9B1BB0DCD200895BB1 /* NetworkSessionCocoa.mm */; };
                5C20CBA01BB1ECD800895BB1 /* NetworkSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C20CB9E1BB0DD1800895BB1 /* NetworkSession.h */; };
                5C298DA01C3DF02100470AFE /* PendingDownload.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C298D9E1C3DEF2900470AFE /* PendingDownload.h */; };
+               5C62FDF91EFC271C00CE072E /* WKURLSchemeTaskPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C62FDF81EFC263C00CE072E /* WKURLSchemeTaskPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                5C7706741D1138380012700F /* WebSocketProvider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5C7706731D111D8B0012700F /* WebSocketProvider.cpp */; };
                5C85C7881C3F23CE0061A4FA /* PendingDownload.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5C85C7861C3F23C50061A4FA /* PendingDownload.cpp */; };
                5C9E56821DF7F1AB00C9EE33 /* WKWebsitePolicies.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5C9E56801DF7F05500C9EE33 /* WKWebsitePolicies.cpp */; };
                5C20CB9B1BB0DCD200895BB1 /* NetworkSessionCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NetworkSessionCocoa.mm; path = NetworkProcess/cocoa/NetworkSessionCocoa.mm; sourceTree = "<group>"; };
                5C20CB9E1BB0DD1800895BB1 /* NetworkSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NetworkSession.h; path = NetworkProcess/NetworkSession.h; sourceTree = "<group>"; };
                5C298D9E1C3DEF2900470AFE /* PendingDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PendingDownload.h; path = NetworkProcess/Downloads/PendingDownload.h; sourceTree = "<group>"; };
+               5C62FDF81EFC263C00CE072E /* WKURLSchemeTaskPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKURLSchemeTaskPrivate.h; sourceTree = "<group>"; };
                5C7706731D111D8B0012700F /* WebSocketProvider.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebSocketProvider.cpp; path = Network/WebSocketProvider.cpp; sourceTree = "<group>"; };
                5C7C88DC1D0F41A0009D2F6D /* WebSocketProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebSocketProvider.h; path = Network/WebSocketProvider.h; sourceTree = "<group>"; };
                5C85C7861C3F23C50061A4FA /* PendingDownload.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PendingDownload.cpp; path = NetworkProcess/Downloads/PendingDownload.cpp; sourceTree = "<group>"; };
                                51D1242F1E6DDDD7002B2820 /* WKURLSchemeTask.h */,
                                51D124301E6DDDD7002B2820 /* WKURLSchemeTask.mm */,
                                51D124371E6DFD2A002B2820 /* WKURLSchemeTaskInternal.h */,
+                               5C62FDF81EFC263C00CE072E /* WKURLSchemeTaskPrivate.h */,
                                1AFA3AC718E61C61003CCBAE /* WKUserContentController.h */,
                                1AFA3AC618E61C61003CCBAE /* WKUserContentController.mm */,
                                1AAF08A3192682DA00B6390C /* WKUserContentControllerInternal.h */,
                                37F623B812A57B6200E3FDF6 /* WKFindOptions.h in Headers */,
                                C54256B518BEC18C00DE4179 /* WKFormInputControl.h in Headers */,
                                C54256B718BEC18C00DE4179 /* WKFormPeripheral.h in Headers */,
+                               5C62FDF91EFC271C00CE072E /* WKURLSchemeTaskPrivate.h in Headers */,
                                C54256B818BEC18C00DE4179 /* WKFormPopover.h in Headers */,
                                C54256BA18BEC18C00DE4179 /* WKFormSelectControl.h in Headers */,
                                0F08CF521D63C13A00B48DF1 /* WKFormSelectPicker.h in Headers */,
index 76272cc..ac30d9b 100644 (file)
@@ -5905,13 +5905,21 @@ WebURLSchemeHandlerProxy* WebPage::urlSchemeHandlerForScheme(const String& schem
 
 void WebPage::registerURLSchemeHandler(uint64_t handlerIdentifier, const String& scheme)
 {
-    auto schemeResult = m_schemeToURLSchemeHandlerProxyMap.add(scheme, std::make_unique<WebURLSchemeHandlerProxy>(*this, handlerIdentifier));
+    auto schemeResult = m_schemeToURLSchemeHandlerProxyMap.add(scheme, WebURLSchemeHandlerProxy::create(*this, handlerIdentifier));
     ASSERT(schemeResult.isNewEntry);
 
     auto identifierResult = m_identifierToURLSchemeHandlerProxyMap.add(handlerIdentifier, schemeResult.iterator->value.get());
     ASSERT_UNUSED(identifierResult, identifierResult.isNewEntry);
 }
 
+void WebPage::urlSchemeTaskDidPerformRedirection(uint64_t handlerIdentifier, uint64_t taskIdentifier, ResourceResponse&& response, ResourceRequest&& request)
+{
+    auto* handler = m_identifierToURLSchemeHandlerProxyMap.get(handlerIdentifier);
+    ASSERT(handler);
+    
+    handler->taskDidPerformRedirection(taskIdentifier, WTFMove(response), WTFMove(request));
+}
+    
 void WebPage::urlSchemeTaskDidReceiveResponse(uint64_t handlerIdentifier, uint64_t taskIdentifier, const ResourceResponse& response)
 {
     auto* handler = m_identifierToURLSchemeHandlerProxyMap.get(handlerIdentifier);
index 89b2e0f..ca129fa 100644 (file)
@@ -1276,6 +1276,7 @@ private:
 
     void registerURLSchemeHandler(uint64_t identifier, const String& scheme);
 
+    void urlSchemeTaskDidPerformRedirection(uint64_t handlerIdentifier, uint64_t taskIdentifier, WebCore::ResourceResponse&&, WebCore::ResourceRequest&&);
     void urlSchemeTaskDidReceiveResponse(uint64_t handlerIdentifier, uint64_t taskIdentifier, const WebCore::ResourceResponse&);
     void urlSchemeTaskDidReceiveData(uint64_t handlerIdentifier, uint64_t taskIdentifier, const IPC::DataReference&);
     void urlSchemeTaskDidComplete(uint64_t handlerIdentifier, uint64_t taskIdentifier, const WebCore::ResourceError&);
@@ -1568,7 +1569,7 @@ private:
     const String m_overrideContentSecurityPolicy;
     const std::optional<double> m_cpuLimit;
 
-    HashMap<String, std::unique_ptr<WebURLSchemeHandlerProxy>> m_schemeToURLSchemeHandlerProxyMap;
+    HashMap<String, RefPtr<WebURLSchemeHandlerProxy>> m_schemeToURLSchemeHandlerProxyMap;
     HashMap<uint64_t, WebURLSchemeHandlerProxy*> m_identifierToURLSchemeHandlerProxyMap;
 };
 
index 6beb61c..f8a1975 100644 (file)
@@ -475,7 +475,8 @@ messages -> WebPage LegacyReceiver {
 
     RegisterURLSchemeHandler(uint64_t identifier, String scheme)
 
-    URLSchemeTaskDidReceiveResponse(uint64_t providerIdentifier, uint64_t taskIdentifier, WebCore::ResourceResponse response)
-    URLSchemeTaskDidReceiveData(uint64_t providerIdentifier, uint64_t taskIdentifier, IPC::DataReference data)
-    URLSchemeTaskDidComplete(uint64_t providerIdentifier, uint64_t taskIdentifier, WebCore::ResourceError error)
+    URLSchemeTaskDidPerformRedirection(uint64_t handlerIdentifier, uint64_t taskIdentifier, WebCore::ResourceResponse response, WebCore::ResourceRequest request)
+    URLSchemeTaskDidReceiveResponse(uint64_t handlerIdentifier, uint64_t taskIdentifier, WebCore::ResourceResponse response)
+    URLSchemeTaskDidReceiveData(uint64_t handlerIdentifier, uint64_t taskIdentifier, IPC::DataReference data)
+    URLSchemeTaskDidComplete(uint64_t handlerIdentifier, uint64_t taskIdentifier, WebCore::ResourceError error)
 }
index 7d02367..5afb754 100644 (file)
@@ -48,13 +48,21 @@ WebURLSchemeHandlerProxy::~WebURLSchemeHandlerProxy()
 
 void WebURLSchemeHandlerProxy::startNewTask(ResourceLoader& loader)
 {
-    auto result = m_tasks.add(loader.identifier(), std::make_unique<WebURLSchemeTaskProxy>(*this, loader));
+    auto result = m_tasks.add(loader.identifier(), WebURLSchemeTaskProxy::create(*this, loader));
     ASSERT(result.isNewEntry);
 
     WebProcess::singleton().webLoaderStrategy().addURLSchemeTaskProxy(*result.iterator->value);
     result.iterator->value->startLoading();
 }
 
+void WebURLSchemeHandlerProxy::taskDidPerformRedirection(uint64_t taskIdentifier, WebCore::ResourceResponse&& redirectResponse, WebCore::ResourceRequest&& newRequest)
+{
+    auto* task = m_tasks.get(taskIdentifier);
+    if (!task)
+        return;
+    
+    task->didPerformRedirection(WTFMove(redirectResponse), WTFMove(newRequest));
+}
 
 void WebURLSchemeHandlerProxy::taskDidReceiveResponse(uint64_t taskIdentifier, const ResourceResponse& response)
 {
index a031763..3138676 100644 (file)
 
 #include "WebURLSchemeTaskProxy.h"
 #include <wtf/HashMap.h>
+#include <wtf/RefCounted.h>
 
 namespace WebCore {
 class ResourceError;
 class ResourceLoader;
 class ResourceResponse;
+class ResourceRequest;
 }
 
 namespace WebKit {
 
 class WebPage;
 
-class WebURLSchemeHandlerProxy {
-    WTF_MAKE_NONCOPYABLE(WebURLSchemeHandlerProxy);
+class WebURLSchemeHandlerProxy : public RefCounted<WebURLSchemeHandlerProxy> {
 public:
-    WebURLSchemeHandlerProxy(WebPage&, uint64_t identifier);
+    static Ref<WebURLSchemeHandlerProxy> create(WebPage& page, uint64_t identifier)
+    {
+        return adoptRef(*new WebURLSchemeHandlerProxy(page, identifier));
+    }
     ~WebURLSchemeHandlerProxy();
 
     void startNewTask(WebCore::ResourceLoader&);
@@ -49,16 +53,18 @@ public:
     uint64_t identifier() const { return m_identifier; }
     WebPage& page() { return m_webPage; }
 
+    void taskDidPerformRedirection(uint64_t taskIdentifier, WebCore::ResourceResponse&&, WebCore::ResourceRequest&&);
     void taskDidReceiveResponse(uint64_t taskIdentifier, const WebCore::ResourceResponse&);
     void taskDidReceiveData(uint64_t taskIdentifier, size_t, const uint8_t* data);
     void taskDidComplete(uint64_t taskIdentifier, const WebCore::ResourceError&);
     void taskDidStopLoading(WebURLSchemeTaskProxy&);
 
 private:
+    WebURLSchemeHandlerProxy(WebPage&, uint64_t identifier);
     WebPage& m_webPage;
     uint64_t m_identifier { 0 };
 
-    HashMap<unsigned long, std::unique_ptr<WebURLSchemeTaskProxy>> m_tasks;
+    HashMap<uint64_t, RefPtr<WebURLSchemeTaskProxy>> m_tasks;
 }; // class WebURLSchemeHandlerProxy
 
 } // namespace WebKit
index 01d74e5..135fb1f 100644 (file)
@@ -62,9 +62,32 @@ void WebURLSchemeTaskProxy::stopLoading()
     // This line will result in this being deleted.
     m_urlSchemeHandler.taskDidStopLoading(*this);
 }
+    
+void WebURLSchemeTaskProxy::didPerformRedirection(WebCore::ResourceResponse&& redirectResponse, WebCore::ResourceRequest&& request)
+{
+    if (!hasLoader())
+        return;
+    
+    auto completionHandler = [this, protectedThis = makeRef(*this), originalRequest = request] (ResourceRequest&& request) {
+        m_waitingForRedirectCompletionHandler = false;
+        // We do not inform the UIProcess of WebKit's new request with the given suggested request.
+        // We do want to know if WebKit would have generated a request that differs from the suggested request, though.
+        if (request.url() != originalRequest.url())
+            WTFLogAlways("Redirected scheme task would have been sent to a different URL.");
+    };
+    
+    if (m_waitingForRedirectCompletionHandler)
+        WTFLogAlways("Received redirect during previous redirect processing.");
+    m_waitingForRedirectCompletionHandler = true;
+
+    m_coreLoader->willSendRequest(WTFMove(request), redirectResponse, WTFMove(completionHandler));
+}
 
 void WebURLSchemeTaskProxy::didReceiveResponse(const ResourceResponse& response)
 {
+    if (m_waitingForRedirectCompletionHandler)
+        WTFLogAlways("Received response during redirect processing.");
+    
     if (!hasLoader())
         return;
 
index 72b6d6f..a82d0d3 100644 (file)
@@ -26,6 +26,7 @@
 #pragma once
 
 #include <WebCore/ResourceRequest.h>
+#include <wtf/RefCounted.h>
 
 namespace WebCore {
 class ResourceError;
@@ -37,16 +38,19 @@ namespace WebKit {
 
 class WebURLSchemeHandlerProxy;
 
-class WebURLSchemeTaskProxy {
-    WTF_MAKE_NONCOPYABLE(WebURLSchemeTaskProxy);
+class WebURLSchemeTaskProxy : public RefCounted<WebURLSchemeTaskProxy> {
 public:
-    WebURLSchemeTaskProxy(WebURLSchemeHandlerProxy&, WebCore::ResourceLoader&);
-
+    static Ref<WebURLSchemeTaskProxy> create(WebURLSchemeHandlerProxy& handler, WebCore::ResourceLoader& loader)
+    {
+        return adoptRef(*new WebURLSchemeTaskProxy(handler, loader));
+    }
+    
     const WebCore::ResourceRequest& request() const { return m_request; }
 
     void startLoading();
     void stopLoading();
 
+    void didPerformRedirection(WebCore::ResourceResponse&&, WebCore::ResourceRequest&&);
     void didReceiveResponse(const WebCore::ResourceResponse&);
     void didReceiveData(size_t, const uint8_t* data);
     void didComplete(const WebCore::ResourceError&);
@@ -54,12 +58,14 @@ public:
     unsigned long identifier() const { return m_identifier; }
 
 private:
+    WebURLSchemeTaskProxy(WebURLSchemeHandlerProxy&, WebCore::ResourceLoader&);
     bool hasLoader();
 
     WebURLSchemeHandlerProxy& m_urlSchemeHandler;
     RefPtr<WebCore::ResourceLoader> m_coreLoader;
     WebCore::ResourceRequest m_request;
     unsigned long m_identifier;
+    bool m_waitingForRedirectCompletionHandler { };
 };
 
 } // namespace WebKit
index 11e5af1..52022ac 100644 (file)
@@ -1,3 +1,20 @@
+2017-06-23  Alex Christensen  <achristensen@webkit.org>
+
+        Add SPI to WKURLSchemeTask for redirection
+        https://bugs.webkit.org/show_bug.cgi?id=173730
+
+        Reviewed by Brady Eidson.
+
+        * TestWebKitAPI/Tests/WebKit2Cocoa/WKURLSchemeHandler-1.mm:
+        (-[SchemeHandler webView:startURLSchemeTask:]):
+        (-[SchemeHandler webView:stopURLSchemeTask:]):
+        (TEST):
+        (-[RedirectSchemeHandler webView:startURLSchemeTask:]):
+        (-[RedirectSchemeHandler webView:stopURLSchemeTask:]):
+        (-[RedirectSchemeHandler webView:didReceiveServerRedirectForProvisionalNavigation:]):
+        (-[RedirectSchemeHandler webView:decidePolicyForNavigationResponse:decisionHandler:]):
+        (-[RedirectSchemeHandler userContentController:didReceiveScriptMessage:]):
+
 2017-06-23  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [WPE] Use JSC API to send script messages from web extension in tests
index 9fd04dd..35af06a 100644 (file)
 #import "PlatformUtilities.h"
 #import "Test.h"
 #import <WebKit/WKURLSchemeHandler.h>
-#import <WebKit/WKURLSchemeTask.h>
+#import <WebKit/WKURLSchemeTaskPrivate.h>
 #import <WebKit/WKWebViewConfigurationPrivate.h>
 #import <WebKit/WebKit.h>
 #import <wtf/RetainPtr.h>
+#import <wtf/Vector.h>
 
 #if WK_API_ENABLED
 
-static bool receivedScriptMessage;
+static bool done;
 
 @interface SchemeHandler : NSObject <WKURLSchemeHandler>
 @property (readonly) NSMutableArray<NSURL *> *startedURLs;
@@ -76,7 +77,7 @@ static bool receivedScriptMessage;
     // Always fail the image load.
     if ([task.request.URL.absoluteString isEqualToString:@"testing:image"]) {
         [task didFailWithError:[NSError errorWithDomain:@"TestWebKitAPI" code:1 userInfo:nil]];
-        receivedScriptMessage = true;
+        done = true;
         return;
     }
 
@@ -90,7 +91,7 @@ static bool receivedScriptMessage;
 {
     [_stoppedURLs addObject:task.request.URL];
 
-    receivedScriptMessage = true;
+    done = true;
 }
 
 @end
@@ -102,7 +103,7 @@ static const char mainBytes[] =
 
 TEST(URLSchemeHandler, Basic)
 {
-    receivedScriptMessage = false;
+    done = false;
 
     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
 
@@ -114,7 +115,7 @@ TEST(URLSchemeHandler, Basic)
     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"testing:main"]];
     [webView loadRequest:request];
 
-    TestWebKitAPI::Util::run(&receivedScriptMessage);
+    TestWebKitAPI::Util::run(&done);
 
     EXPECT_EQ([handler.get().startedURLs count], 2u);
     EXPECT_TRUE([[handler.get().startedURLs objectAtIndex:0] isEqual:[NSURL URLWithString:@"testing:main"]]);
@@ -127,7 +128,7 @@ TEST(URLSchemeHandler, NoMIMEType)
     // Since there's no MIMEType, and no NavigationDelegate to tell WebKit to do the load anyways, WebKit will ignore (silently fail) the load.
     // This test makes sure that is communicated back to the URLSchemeHandler.
 
-    receivedScriptMessage = false;
+    done = false;
 
     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
 
@@ -139,7 +140,7 @@ TEST(URLSchemeHandler, NoMIMEType)
     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"testing:main"]];
     [webView loadRequest:request];
 
-    TestWebKitAPI::Util::run(&receivedScriptMessage);
+    TestWebKitAPI::Util::run(&done);
 
     EXPECT_EQ([handler.get().startedURLs count], 1u);
     EXPECT_TRUE([[handler.get().startedURLs objectAtIndex:0] isEqual:[NSURL URLWithString:@"testing:main"]]);
@@ -191,5 +192,166 @@ TEST(URLSchemeHandler, BuiltinSchemes)
     }
 }
 
+static bool receivedRedirect;
+static bool responsePolicyDecided;
+
+@interface RedirectSchemeHandler : NSObject <WKURLSchemeHandler, WKNavigationDelegate, WKScriptMessageHandler>
+@end
+
+@implementation RedirectSchemeHandler { }
+
+- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+    ASSERT_STREQ(task.request.URL.absoluteString.UTF8String, "testing:///initial");
+    NSURLResponse *response = [[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:nil expectedContentLength:0 textEncodingName:nil] autorelease];
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"testing:///redirected"]];
+    [(id<WKURLSchemeTaskPrivate>)task _didPerformRedirection:response newRequest:request];
+    ASSERT_FALSE(receivedRedirect);
+    ASSERT_STREQ(task.request.URL.absoluteString.UTF8String, "testing:///redirected");
+    NSString *html = @"<script>window.webkit.messageHandlers.testHandler.postMessage('Document URL: ' + document.URL);</script>";
+    [task didReceiveResponse:[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:html.length textEncodingName:nil]];
+    [task didReceiveData:[html dataUsingEncoding:NSUTF8StringEncoding]];
+    [task didFinish];
+}
+
+- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+    ASSERT_TRUE(false);
+}
+
+- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation
+{
+    ASSERT_FALSE(receivedRedirect);
+    receivedRedirect = true;
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
+{
+    ASSERT_TRUE(receivedRedirect);
+    ASSERT_STREQ(navigationResponse.response.URL.absoluteString.UTF8String, "testing:///redirected");
+    ASSERT_FALSE(responsePolicyDecided);
+    responsePolicyDecided = true;
+    decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+    EXPECT_WK_STREQ(@"Document URL: testing:///redirected", [message body]);
+    done = true;
+}
+@end
+
+TEST(URLSchemeHandler, Redirection)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto handler = adoptNS([[RedirectSchemeHandler alloc] init]);
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"testing"];
+    [[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView setNavigationDelegate:handler.get()];
+    
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"testing:///initial"]];
+    [webView loadRequest:request];
+    
+    TestWebKitAPI::Util::run(&done);
+    
+    EXPECT_TRUE(responsePolicyDecided);
+    EXPECT_STREQ(webView.get().URL.absoluteString.UTF8String, "testing:///redirected");
+}
+
+enum class Command {
+    Redirect,
+    Response,
+    Data,
+    Finish,
+    Error,
+};
+
+@interface TaskSchemeHandler : NSObject <WKURLSchemeHandler>
+- (instancetype)initWithCommands:(Vector<Command>&&)commandVector expectedException:(bool)expected;
+@end
+
+@implementation TaskSchemeHandler {
+    Vector<Command> commands;
+    bool expectedException;
+}
+
+- (instancetype)initWithCommands:(Vector<Command>&&)commandVector expectedException:(bool)expected
+{
+    self = [super init];
+    if (!self)
+        return nil;
+    
+    self->commands = WTFMove(commandVector);
+    self->expectedException = expected;
+    
+    return self;
+}
+
+- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+    bool caughtException = false;
+    @try {
+        for (auto command : commands) {
+            switch (command) {
+            case Command::Redirect:
+                [(id<WKURLSchemeTaskPrivate>)task _didPerformRedirection:[[[NSURLResponse alloc] init] autorelease] newRequest:[[[NSURLRequest alloc] init] autorelease]];
+                break;
+            case Command::Response:
+                [task didReceiveResponse:[[[NSURLResponse alloc] init] autorelease]];
+                break;
+            case Command::Data:
+                [task didReceiveData:[[[NSData alloc] init] autorelease]];
+                break;
+            case Command::Finish:
+                [task didFinish];
+                break;
+            case Command::Error:
+                [task didFailWithError:[[[NSError alloc] init] autorelease]];
+                break;
+            }
+        }
+    }
+    @catch(NSException *exception)
+    {
+        caughtException = true;
+    }
+    ASSERT_EQ(caughtException, expectedException);
+    done = true;
+}
+
+- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+}
+@end
+
+enum class ShouldRaiseException { No, Yes };
+
+static void checkCallSequence(Vector<Command>&& commands, ShouldRaiseException shouldRaiseException)
+{
+    done = false;
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto handler = adoptNS([[TaskSchemeHandler alloc] initWithCommands:WTFMove(commands) expectedException:shouldRaiseException == ShouldRaiseException::Yes]);
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"testing"];
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"testing:///initial"]]];
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(URLSchemeHandler, Exceptions)
+{
+    checkCallSequence({Command::Response, Command::Data, Command::Finish}, ShouldRaiseException::No);
+    checkCallSequence({Command::Response, Command::Redirect}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Redirect, Command::Response}, ShouldRaiseException::No);
+    checkCallSequence({Command::Data, Command::Finish}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Error}, ShouldRaiseException::No);
+    checkCallSequence({Command::Error, Command::Error}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Error, Command::Data}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Response, Command::Finish, Command::Data}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Response, Command::Finish, Command::Redirect}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Response, Command::Finish, Command::Response}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Response, Command::Finish, Command::Finish}, ShouldRaiseException::Yes);
+    checkCallSequence({Command::Response, Command::Finish, Command::Error}, ShouldRaiseException::Yes);
+}
 
 #endif