[Content Filtering] Add tests for unblock requests
authoraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 20 Mar 2015 08:42:59 +0000 (08:42 +0000)
committeraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 20 Mar 2015 08:42:59 +0000 (08:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142900

Reviewed by Andreas Kling.

Source/WebCore:

Currently the iOS Parental Controls content filter has a mechanism for requesting that a page be unblocked.
WebKit implements this by listening for navigations originating from the filter's error page to a special URL,
and requesting the page be unblocked via platform API, which might cause UI to be displayed. If the unblock is
successful then we schedule a reload of the frame in order to display the unblocked document.

NetworkExtension also supports unblock requests, so in preparation for adopting its API, this patch allows
content filters to specify their own unblock request method, teaches MockContentFilter to provide such a method,
and writes tests to cover both allowed and denied unblock requests.

The content filter that blocks a load creates a ContentFilterUnblockHandler, passing it a lambda that is executed
when a navigation matches the filter's special unblock URL. Filters can also specify that a script be executed in
the context of its error page if the unblock is denied.

All platform content filters can handle unblock requests like this with the exception of iOS Parental Controls in WebKit2.
Since UI can be displayed by the system in this case, the request must be made from within the UI process. Therefore the
existing method is retained of serializing a WebFilterEvaluator and intercepting navigation policy calls in the UI process.

Tests: contentfiltering/allow-after-unblock-request.html
       contentfiltering/block-after-unblock-request.html

* bindings/js/JSMockContentFilterSettingsCustom.cpp:
(WebCore::JSMockContentFilterSettings::decisionPoint): Added some using statements for clarity.
(WebCore::JSMockContentFilterSettings::setDecisionPoint): Ditto.
(WebCore::toJSValue): Returns a JSValue from a Decision.
(WebCore::toDecision): Returns a Decision from a JSValue.
(WebCore::JSMockContentFilterSettings::decision): Used toJSValue.
(WebCore::JSMockContentFilterSettings::setDecision): Used toDecision.
(WebCore::JSMockContentFilterSettings::unblockRequestDecision): Used toJSValue.
(WebCore::JSMockContentFilterSettings::setUnblockRequestDecision): Used toDecision.
* loader/ContentFilter.cpp:
(WebCore::ContentFilter::createIfNeeded): Passed a reference to the owning DocumentLoader.
(WebCore::ContentFilter::ContentFilter): Ditto.
(WebCore::ContentFilter::unblockHandler): If the unblockHandler requests that a script be executed when an
unblock request is denied, create a wrapper unblockHandler that executes that script in m_documentLoader's frame.
* loader/ContentFilter.h:
* loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::responseReceived): Passed this to ContentFilter::createIfNeeded.
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::prepareForLoadStart): Called PolicyChecker::prepareForLoadStart.
* loader/PolicyChecker.cpp:
(WebCore::PolicyChecker::prepareForLoadStart): Reset m_contentFilterUnblockHandler.
(WebCore::PolicyChecker::checkNavigationPolicy): Moved logic to here from WebKit1's WebFrameLoaderClient.
Placing it here allows it to be shared between WebKit1 and WebKit2 (when the unblock handler does not need to
be called in the UI process).
* loader/PolicyChecker.h:
(WebCore::PolicyChecker::setContentFilterUnblockHandler): Added.
* page/Frame.h: Made Frame ThreadSafeRefCounted, since RefPtr<Frames> are captured in lambdas that can be
copied by background threads managed by the underlying platform.
* platform/ContentFilterUnblockHandler.h:
(WebCore::ContentFilterUnblockHandler::unblockURLScheme): Returned the Apple content filter scheme.
(WebCore::ContentFilterUnblockHandler::unblockURLHost): Returned the unblock URL host.
(WebCore::ContentFilterUnblockHandler::clear): Deleted.
* platform/PlatformContentFilter.h:
(WebCore::PlatformContentFilter::unblockRequestDeniedScript): Returned the unblock request denied script.
* platform/cocoa/ContentFilterUnblockHandlerCocoa.mm:
(WebCore::ContentFilterUnblockHandler::ContentFilterUnblockHandler): Added a constructor that takes an
unblockURLHost and a UnblockRequesterFunction. Added an alternate constructor for iOS Parental Controls on
WebKit2 that takes an unblockURLHost and a WebFilterEvaluator.
(WebCore::ContentFilterUnblockHandler::needsUIProcess): Returned true if m_webFilterEvaluator is non-null.
(WebCore::ContentFilterUnblockHandler::encode): Encoded m_unblockURLHost in addition to m_webFilterEvaluator.
(WebCore::ContentFilterUnblockHandler::decode): Decoded m_unblockURLHost in addition to m_webFilterEvaluator.
(WebCore::ContentFilterUnblockHandler::canHandleRequest): Returned true if there is a either a m_unblockRequester
or a m_webFilterEvaluator and the request's host and scheme match those of the unblock request URL.
(WebCore::dispatchToMainThread): Added a helper to dispatch a block to the main thread. Then if the web thread
is enabled on iOS, dispatch it there.
(WebCore::ContentFilterUnblockHandler::requestUnblockAsync): Renamed from handleUnblockRequestAndDispatchIfSuccessful.
Requested an unblock using either m_unblockRequester or m_webFilterEvaluator, then called decisionHandler with the response.
(WebCore::scheme): Moved to ContentFilterUnblockHandler::unblockURLScheme.
(WebCore::ContentFilterUnblockHandler::handleUnblockRequestAndDispatchIfSuccessful): Renamed to requestUnblockAsync.
* platform/cocoa/ParentalControlsContentFilter.mm:
(WebCore::ParentalControlsContentFilter::unblockHandler): Returned an unblock handler using the WebFilterEvaluator constructor.
* testing/MockContentFilter.cpp: Added using statments for clarity.
(WebCore::settings): Added a helper to get MockContentFilterSettings::singleton().
(WebCore::MockContentFilter::canHandleResponse): Used the helper.
(WebCore::MockContentFilter::MockContentFilter): Took advantage of the using statements.
(WebCore::MockContentFilter::addData): Ditto.
(WebCore::MockContentFilter::finishedAddingData): Ditto.
(WebCore::MockContentFilter::unblockHandler): Returned a ContentFilterUnblockHandler that checks settings() for its decision.
(WebCore::MockContentFilter::unblockRequestDeniedScript): Returned the script to execute in MockContentFilter's
error page when an unblock request is denied.
(WebCore::MockContentFilter::maybeDetermineStatus): Took advantage of settings() and using statements.
* testing/MockContentFilterSettings.cpp:
(WebCore::MockContentFilterSettings::unblockRequestURL): Constructed a static unblock URL and returned it.
* testing/MockContentFilterSettings.h:
(WebCore::MockContentFilterSettings::unblockURLHost): Returned the filter's unblock URL host.
(WebCore::MockContentFilterSettings::unblockRequestDecision): Returns the decision to make for an unblock request.
(WebCore::MockContentFilterSettings::setUnblockRequestDecision): Sets the decision to make for an unblock request.
* testing/MockContentFilterSettings.idl: Added the unblockRequestDecision and unblockRequestURL attributes.

Source/WebKit/mac:

* WebCoreSupport/WebFrameLoaderClient.mm:
(WebFrameLoaderClient::dispatchDidStartProvisionalLoad): This now happens in PolicyChecker.
(WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction): Ditto.
* WebView/WebFrame.mm:
(-[WebFrame _contentFilterDidHandleNavigationAction:]): Deleted.
* WebView/WebFrameInternal.h: Removed contentFilterUnblockHandler from WebFramePrivate.

Source/WebKit2:

* UIProcess/Cocoa/WebPageProxyCocoa.mm:
(WebKit::WebPageProxy::contentFilterDidBlockLoadForFrame): Called WebFrameProxy::contentFilterDidBlockLoad.
* UIProcess/WebFrameProxy.cpp:
(WebKit::WebFrameProxy::didStartProvisionalLoad): Assigned a default-constructed ContentFilterUnblockHandler instead of calling clear().
(WebKit::WebFrameProxy::didHandleContentFilterUnblockNavigation): Renamed from contentFilterDidHandleNavigationAction.
Updated to use ContentFilterUnblockHandler's new API.
(WebKit::WebFrameProxy::contentFilterDidHandleNavigationAction): Deleted.
* UIProcess/WebFrameProxy.h:
(WebKit::WebFrameProxy::contentFilterDidBlockLoad): Renamed from setContentFilterUnblockHandler.
(WebKit::WebFrameProxy::setContentFilterUnblockHandler): Deleted.
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::decidePolicyForNavigationAction): Called WebFrameProxy::didHandleContentFilterUnblockNavigation.
* WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
(WebKit::WebFrameLoaderClient::contentFilterDidBlockLoad): If the unblock handler needs the UI process,
send WebPageProxy::ContentFilterDidBlockLoadForFrame. Oterwise, call PolicyChecker::setContentFilterUnblockHandler.

LayoutTests:

Taught contentfiltering.js how to perform an unblock request test, and added tests for both allowed and blocked requests.

* contentfiltering/allow-after-unblock-request-expected.html: Added.
* contentfiltering/allow-after-unblock-request.html: Added.
* contentfiltering/block-after-unblock-request-expected.html: Added.
* contentfiltering/block-after-unblock-request.html: Added.
* contentfiltering/resources/contentfiltering.js:
(testContentFiltering): Added an argument specifying if the decision applies to the initial load or the unblock request.
(_doTest): When testing unblock handling, navigate the test iframe to settings.unblockRequestURL when the error page is displayed.
If the unblock is denied, the test harness will call window.unblockRequestDenied(). If the unblock is successful,
the iframe will reload, which we detect by listening for its load event.

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

34 files changed:
LayoutTests/ChangeLog
LayoutTests/contentfiltering/allow-after-unblock-request-expected.html [new file with mode: 0644]
LayoutTests/contentfiltering/allow-after-unblock-request.html [new file with mode: 0644]
LayoutTests/contentfiltering/block-after-unblock-request-expected.html [new file with mode: 0644]
LayoutTests/contentfiltering/block-after-unblock-request.html [new file with mode: 0644]
LayoutTests/contentfiltering/resources/contentfiltering.js
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSMockContentFilterSettingsCustom.cpp
Source/WebCore/loader/ContentFilter.cpp
Source/WebCore/loader/ContentFilter.h
Source/WebCore/loader/DocumentLoader.cpp
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/loader/PolicyChecker.cpp
Source/WebCore/loader/PolicyChecker.h
Source/WebCore/page/Frame.h
Source/WebCore/platform/ContentFilterUnblockHandler.h
Source/WebCore/platform/PlatformContentFilter.h
Source/WebCore/platform/cocoa/ContentFilterUnblockHandlerCocoa.mm
Source/WebCore/platform/cocoa/ParentalControlsContentFilter.mm
Source/WebCore/testing/MockContentFilter.cpp
Source/WebCore/testing/MockContentFilter.h
Source/WebCore/testing/MockContentFilterSettings.cpp
Source/WebCore/testing/MockContentFilterSettings.h
Source/WebCore/testing/MockContentFilterSettings.idl
Source/WebKit/mac/ChangeLog
Source/WebKit/mac/WebCoreSupport/WebFrameLoaderClient.mm
Source/WebKit/mac/WebView/WebFrame.mm
Source/WebKit/mac/WebView/WebFrameInternal.h
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/Cocoa/WebPageProxyCocoa.mm
Source/WebKit2/UIProcess/WebFrameProxy.cpp
Source/WebKit2/UIProcess/WebFrameProxy.h
Source/WebKit2/UIProcess/WebPageProxy.cpp
Source/WebKit2/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp

index 9dd09f2..7ff7dce 100644 (file)
@@ -1,5 +1,24 @@
 2015-03-19  Andy Estes  <aestes@apple.com>
 
+        [Content Filtering] Add tests for unblock requests
+        https://bugs.webkit.org/show_bug.cgi?id=142900
+
+        Reviewed by Andreas Kling.
+
+        Taught contentfiltering.js how to perform an unblock request test, and added tests for both allowed and blocked requests.
+
+        * contentfiltering/allow-after-unblock-request-expected.html: Added.
+        * contentfiltering/allow-after-unblock-request.html: Added.
+        * contentfiltering/block-after-unblock-request-expected.html: Added.
+        * contentfiltering/block-after-unblock-request.html: Added.
+        * contentfiltering/resources/contentfiltering.js:
+        (testContentFiltering): Added an argument specifying if the decision applies to the initial load or the unblock request.
+        (_doTest): When testing unblock handling, navigate the test iframe to settings.unblockRequestURL when the error page is displayed.
+        If the unblock is denied, the test harness will call window.unblockRequestDenied(). If the unblock is successful,
+        the iframe will reload, which we detect by listening for its load event.
+
+2015-03-19  Andy Estes  <aestes@apple.com>
+
         [Content Filtering] Give contentfiltering tests a JavaScript harness
         https://bugs.webkit.org/show_bug.cgi?id=142899
 
diff --git a/LayoutTests/contentfiltering/allow-after-unblock-request-expected.html b/LayoutTests/contentfiltering/allow-after-unblock-request-expected.html
new file mode 100644 (file)
index 0000000..067bfa1
--- /dev/null
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe src="data:text/html,PASS"></iframe>
diff --git a/LayoutTests/contentfiltering/allow-after-unblock-request.html b/LayoutTests/contentfiltering/allow-after-unblock-request.html
new file mode 100644 (file)
index 0000000..74ef542
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="resources/contentfiltering.js"></script>
+<script>
+var internals = window.internals;
+if (internals) {
+    var settings = internals.mockContentFilterSettings;
+    testContentFiltering(/* decisionPoint */settings.DECISION_POINT_AFTER_FINISHED_ADDING_DATA, /* decision */settings.DECISION_ALLOW, /* decideAfterUnblockRequest */true);
+}
+</script>
diff --git a/LayoutTests/contentfiltering/block-after-unblock-request-expected.html b/LayoutTests/contentfiltering/block-after-unblock-request-expected.html
new file mode 100644 (file)
index 0000000..067bfa1
--- /dev/null
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe src="data:text/html,PASS"></iframe>
diff --git a/LayoutTests/contentfiltering/block-after-unblock-request.html b/LayoutTests/contentfiltering/block-after-unblock-request.html
new file mode 100644 (file)
index 0000000..1f55881
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="resources/contentfiltering.js"></script>
+<script>
+var internals = window.internals;
+if (internals) {
+    var settings = internals.mockContentFilterSettings;
+    testContentFiltering(/* decisionPoint */settings.DECISION_POINT_AFTER_FINISHED_ADDING_DATA, /* decision */settings.DECISION_BLOCK, /* decideAfterUnblockRequest */true);
+}
+</script>
index 735395b..47e1c4f 100644 (file)
@@ -1,22 +1,37 @@
-function _doTest(decisionPoint, decision)
+function _doTest(decisionPoint, decision, decideAfterUnblockRequest)
 {
     var settings = window.internals.mockContentFilterSettings;
     settings.enabled = true;
     settings.decisionPoint = decisionPoint;
-    settings.decision = decision;
+    settings.decision = (decideAfterUnblockRequest ? settings.DECISION_BLOCK : decision);
     
     var blockedStringText = (decision === settings.DECISION_ALLOW ? "FAIL" : "PASS");
-    settings.blockedString = "<!DOCTYPE html><body>" + blockedStringText;
+    if (decideAfterUnblockRequest) {
+        settings.unblockRequestDecision = decision;
+        settings.blockedString = "<!DOCTYPE html><script>function unblockRequestDenied() { window.top.postMessage('unblockrequestdenied', '*'); }</script><body>" + blockedStringText;
+    } else
+        settings.blockedString = "<!DOCTYPE html><body>" + blockedStringText;
 
+    var isUnblocking = false;
     var iframe = document.createElement("iframe");
     document.body.appendChild(iframe);
     iframe.addEventListener("load", function(event) {
-        window.testRunner.notifyDone();
+        if (isUnblocking || !decideAfterUnblockRequest) {
+            window.testRunner.notifyDone();
+            return;
+        }
+
+        isUnblocking = true;
+        window.addEventListener("message", function(event) {
+            if (event.data === "unblockrequestdenied")
+                window.testRunner.notifyDone();
+        }, false);
+        iframe.contentDocument.location = settings.unblockRequestURL;
     }, false);
     iframe.src = "data:text/html,<!DOCTYPE html><body>" + (blockedStringText === "FAIL" ? "PASS" : "FAIL");
 }
 
-function testContentFiltering(decisionPoint, decision)
+function testContentFiltering(decisionPoint, decision, decideAfterUnblockRequest)
 {
     if (!window.internals) {
         console.log("This test requires window.internals");
@@ -30,6 +45,6 @@ function testContentFiltering(decisionPoint, decision)
 
     window.testRunner.waitUntilDone();
     window.addEventListener("load", function(event) {
-        _doTest(decisionPoint, decision);
+        _doTest(decisionPoint, decision, decideAfterUnblockRequest);
     }, false);
 }
\ No newline at end of file
index 87859bf..396ff45 100644 (file)
@@ -1,3 +1,99 @@
+2015-03-19  Andy Estes  <aestes@apple.com>
+
+        [Content Filtering] Add tests for unblock requests
+        https://bugs.webkit.org/show_bug.cgi?id=142900
+
+        Reviewed by Andreas Kling.
+
+        Currently the iOS Parental Controls content filter has a mechanism for requesting that a page be unblocked.
+        WebKit implements this by listening for navigations originating from the filter's error page to a special URL,
+        and requesting the page be unblocked via platform API, which might cause UI to be displayed. If the unblock is
+        successful then we schedule a reload of the frame in order to display the unblocked document.
+
+        NetworkExtension also supports unblock requests, so in preparation for adopting its API, this patch allows
+        content filters to specify their own unblock request method, teaches MockContentFilter to provide such a method,
+        and writes tests to cover both allowed and denied unblock requests.
+
+        The content filter that blocks a load creates a ContentFilterUnblockHandler, passing it a lambda that is executed
+        when a navigation matches the filter's special unblock URL. Filters can also specify that a script be executed in
+        the context of its error page if the unblock is denied.
+
+        All platform content filters can handle unblock requests like this with the exception of iOS Parental Controls in WebKit2.
+        Since UI can be displayed by the system in this case, the request must be made from within the UI process. Therefore the
+        existing method is retained of serializing a WebFilterEvaluator and intercepting navigation policy calls in the UI process.
+
+        Tests: contentfiltering/allow-after-unblock-request.html
+               contentfiltering/block-after-unblock-request.html
+
+        * bindings/js/JSMockContentFilterSettingsCustom.cpp:
+        (WebCore::JSMockContentFilterSettings::decisionPoint): Added some using statements for clarity.
+        (WebCore::JSMockContentFilterSettings::setDecisionPoint): Ditto.
+        (WebCore::toJSValue): Returns a JSValue from a Decision.
+        (WebCore::toDecision): Returns a Decision from a JSValue.
+        (WebCore::JSMockContentFilterSettings::decision): Used toJSValue.
+        (WebCore::JSMockContentFilterSettings::setDecision): Used toDecision.
+        (WebCore::JSMockContentFilterSettings::unblockRequestDecision): Used toJSValue.
+        (WebCore::JSMockContentFilterSettings::setUnblockRequestDecision): Used toDecision.
+        * loader/ContentFilter.cpp:
+        (WebCore::ContentFilter::createIfNeeded): Passed a reference to the owning DocumentLoader.
+        (WebCore::ContentFilter::ContentFilter): Ditto.
+        (WebCore::ContentFilter::unblockHandler): If the unblockHandler requests that a script be executed when an
+        unblock request is denied, create a wrapper unblockHandler that executes that script in m_documentLoader's frame.
+        * loader/ContentFilter.h:
+        * loader/DocumentLoader.cpp:
+        (WebCore::DocumentLoader::responseReceived): Passed this to ContentFilter::createIfNeeded.
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::prepareForLoadStart): Called PolicyChecker::prepareForLoadStart.
+        * loader/PolicyChecker.cpp:
+        (WebCore::PolicyChecker::prepareForLoadStart): Reset m_contentFilterUnblockHandler.
+        (WebCore::PolicyChecker::checkNavigationPolicy): Moved logic to here from WebKit1's WebFrameLoaderClient.
+        Placing it here allows it to be shared between WebKit1 and WebKit2 (when the unblock handler does not need to
+        be called in the UI process).
+        * loader/PolicyChecker.h:
+        (WebCore::PolicyChecker::setContentFilterUnblockHandler): Added.
+        * page/Frame.h: Made Frame ThreadSafeRefCounted, since RefPtr<Frames> are captured in lambdas that can be
+        copied by background threads managed by the underlying platform.
+        * platform/ContentFilterUnblockHandler.h:
+        (WebCore::ContentFilterUnblockHandler::unblockURLScheme): Returned the Apple content filter scheme.
+        (WebCore::ContentFilterUnblockHandler::unblockURLHost): Returned the unblock URL host.
+        (WebCore::ContentFilterUnblockHandler::clear): Deleted.
+        * platform/PlatformContentFilter.h:
+        (WebCore::PlatformContentFilter::unblockRequestDeniedScript): Returned the unblock request denied script.
+        * platform/cocoa/ContentFilterUnblockHandlerCocoa.mm:
+        (WebCore::ContentFilterUnblockHandler::ContentFilterUnblockHandler): Added a constructor that takes an
+        unblockURLHost and a UnblockRequesterFunction. Added an alternate constructor for iOS Parental Controls on
+        WebKit2 that takes an unblockURLHost and a WebFilterEvaluator.
+        (WebCore::ContentFilterUnblockHandler::needsUIProcess): Returned true if m_webFilterEvaluator is non-null.
+        (WebCore::ContentFilterUnblockHandler::encode): Encoded m_unblockURLHost in addition to m_webFilterEvaluator.
+        (WebCore::ContentFilterUnblockHandler::decode): Decoded m_unblockURLHost in addition to m_webFilterEvaluator.
+        (WebCore::ContentFilterUnblockHandler::canHandleRequest): Returned true if there is a either a m_unblockRequester
+        or a m_webFilterEvaluator and the request's host and scheme match those of the unblock request URL.
+        (WebCore::dispatchToMainThread): Added a helper to dispatch a block to the main thread. Then if the web thread
+        is enabled on iOS, dispatch it there.
+        (WebCore::ContentFilterUnblockHandler::requestUnblockAsync): Renamed from handleUnblockRequestAndDispatchIfSuccessful.
+        Requested an unblock using either m_unblockRequester or m_webFilterEvaluator, then called decisionHandler with the response.
+        (WebCore::scheme): Moved to ContentFilterUnblockHandler::unblockURLScheme.
+        (WebCore::ContentFilterUnblockHandler::handleUnblockRequestAndDispatchIfSuccessful): Renamed to requestUnblockAsync.
+        * platform/cocoa/ParentalControlsContentFilter.mm:
+        (WebCore::ParentalControlsContentFilter::unblockHandler): Returned an unblock handler using the WebFilterEvaluator constructor.
+        * testing/MockContentFilter.cpp: Added using statments for clarity.
+        (WebCore::settings): Added a helper to get MockContentFilterSettings::singleton().
+        (WebCore::MockContentFilter::canHandleResponse): Used the helper.
+        (WebCore::MockContentFilter::MockContentFilter): Took advantage of the using statements.
+        (WebCore::MockContentFilter::addData): Ditto.
+        (WebCore::MockContentFilter::finishedAddingData): Ditto.
+        (WebCore::MockContentFilter::unblockHandler): Returned a ContentFilterUnblockHandler that checks settings() for its decision.
+        (WebCore::MockContentFilter::unblockRequestDeniedScript): Returned the script to execute in MockContentFilter's
+        error page when an unblock request is denied.
+        (WebCore::MockContentFilter::maybeDetermineStatus): Took advantage of settings() and using statements.
+        * testing/MockContentFilterSettings.cpp:
+        (WebCore::MockContentFilterSettings::unblockRequestURL): Constructed a static unblock URL and returned it.
+        * testing/MockContentFilterSettings.h:
+        (WebCore::MockContentFilterSettings::unblockURLHost): Returned the filter's unblock URL host.
+        (WebCore::MockContentFilterSettings::unblockRequestDecision): Returns the decision to make for an unblock request.
+        (WebCore::MockContentFilterSettings::setUnblockRequestDecision): Sets the decision to make for an unblock request.
+        * testing/MockContentFilterSettings.idl: Added the unblockRequestDecision and unblockRequestURL attributes.
+
 2015-03-20  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GTK] Crash due to empty drag image during drag-and-drop
index 220b218..703e4b7 100644 (file)
@@ -35,6 +35,9 @@ using namespace JSC;
 
 namespace WebCore {
 
+using Decision = MockContentFilterSettings::Decision;
+using DecisionPoint = MockContentFilterSettings::DecisionPoint;
+
 // Must be kept in sync with values in MockContentFilterSettings.idl.
 const uint8_t decisionPointAfterResponse = 0;
 const uint8_t decisionPointAfterAddData = 1;
@@ -42,22 +45,22 @@ const uint8_t decisionPointAfterFinishedAddingData = 2;
 const uint8_t decisionAllow = 0;
 const uint8_t decisionBlock = 1;
 
-JSC::JSValue JSMockContentFilterSettings::decisionPoint(JSC::ExecState*) const
+JSValue JSMockContentFilterSettings::decisionPoint(ExecState*) const
 {
     switch (impl().decisionPoint()) {
-    case MockContentFilterSettings::DecisionPoint::AfterResponse:
+    case DecisionPoint::AfterResponse:
         return jsNumber(decisionPointAfterResponse);
-    case MockContentFilterSettings::DecisionPoint::AfterAddData:
+    case DecisionPoint::AfterAddData:
         return jsNumber(decisionPointAfterAddData);
-    case MockContentFilterSettings::DecisionPoint::AfterFinishedAddingData:
+    case DecisionPoint::AfterFinishedAddingData:
         return jsNumber(decisionPointAfterFinishedAddingData);
     }
 
     ASSERT_NOT_REACHED();
-    return { };
+    return jsUndefined();
 }
 
-void JSMockContentFilterSettings::setDecisionPoint(JSC::ExecState* exec, JSC::JSValue value)
+void JSMockContentFilterSettings::setDecisionPoint(ExecState* exec, JSValue value)
 {
     uint8_t nativeValue { toUInt8(exec, value, EnforceRange) };
     if (exec->hadException())
@@ -65,48 +68,75 @@ void JSMockContentFilterSettings::setDecisionPoint(JSC::ExecState* exec, JSC::JS
 
     switch (nativeValue) {
     case decisionPointAfterResponse:
-        impl().setDecisionPoint(MockContentFilterSettings::DecisionPoint::AfterResponse);
+        impl().setDecisionPoint(DecisionPoint::AfterResponse);
         return;
     case decisionPointAfterAddData:
-        impl().setDecisionPoint(MockContentFilterSettings::DecisionPoint::AfterAddData);
+        impl().setDecisionPoint(DecisionPoint::AfterAddData);
         return;
     case decisionPointAfterFinishedAddingData:
-        impl().setDecisionPoint(MockContentFilterSettings::DecisionPoint::AfterFinishedAddingData);
+        impl().setDecisionPoint(DecisionPoint::AfterFinishedAddingData);
         return;
-    default:
-        throwTypeError(exec, String::format("%u is not a valid decisionPoint value.", nativeValue));
     }
+
+    throwTypeError(exec, String::format("%u is not a valid decisionPoint value.", nativeValue));
 }
 
-JSC::JSValue JSMockContentFilterSettings::decision(JSC::ExecState*) const
+static inline JSValue toJSValue(Decision decision)
 {
-    switch (impl().decision()) {
-    case MockContentFilterSettings::Decision::Allow:
+    switch (decision) {
+    case Decision::Allow:
         return jsNumber(decisionAllow);
-    case MockContentFilterSettings::Decision::Block:
+    case Decision::Block:
         return jsNumber(decisionBlock);
     }
 
     ASSERT_NOT_REACHED();
-    return { };
+    return jsUndefined();
 }
 
-void JSMockContentFilterSettings::setDecision(JSC::ExecState* exec, JSC::JSValue value)
+static inline Decision toDecision(ExecState* exec, JSValue value)
 {
     uint8_t nativeValue { toUInt8(exec, value, EnforceRange) };
     if (exec->hadException())
-        return;
+        return Decision::Allow;
 
     switch (nativeValue) {
     case decisionAllow:
-        impl().setDecision(MockContentFilterSettings::Decision::Allow);
-        return;
+        return Decision::Allow;
     case decisionBlock:
-        impl().setDecision(MockContentFilterSettings::Decision::Block);
-        return;
-    default:
-        throwTypeError(exec, String::format("%u is not a valid decision value.", nativeValue));
+        return Decision::Block;
     }
+
+    throwTypeError(exec, String::format("%u is not a valid decision value.", nativeValue));
+    return Decision::Allow;
+}
+
+JSValue JSMockContentFilterSettings::decision(ExecState*) const
+{
+    return toJSValue(impl().decision());
+}
+
+void JSMockContentFilterSettings::setDecision(ExecState* exec, JSValue value)
+{
+    Decision decision { toDecision(exec, value) };
+    if (exec->hadException())
+        return;
+
+    impl().setDecision(decision);
+}
+
+JSValue JSMockContentFilterSettings::unblockRequestDecision(ExecState*) const
+{
+    return toJSValue(impl().unblockRequestDecision());
+}
+
+void JSMockContentFilterSettings::setUnblockRequestDecision(ExecState* exec, JSValue value)
+{
+    Decision unblockRequestDecision { toDecision(exec, value) };
+    if (exec->hadException())
+        return;
+
+    impl().setUnblockRequestDecision(unblockRequestDecision);
 }
 
 }; // namespace WebCore
index 728bef9..27ec0da 100644 (file)
 
 #if ENABLE(CONTENT_FILTERING)
 
+#include "DocumentLoader.h"
+#include "Frame.h"
 #include "NetworkExtensionContentFilter.h"
 #include "ParentalControlsContentFilter.h"
+#include "ScriptController.h"
+#include <bindings/ScriptValue.h>
 #include <wtf/NeverDestroyed.h>
+#include <wtf/Vector.h>
 
 namespace WebCore {
 
@@ -47,7 +52,7 @@ Vector<ContentFilter::Type>& ContentFilter::types()
     return types;
 }
 
-std::unique_ptr<ContentFilter> ContentFilter::createIfNeeded(const ResourceResponse& response)
+std::unique_ptr<ContentFilter> ContentFilter::createIfNeeded(const ResourceResponse& response, DocumentLoader& documentLoader)
 {
     Container filters;
     for (auto& type : types()) {
@@ -58,11 +63,12 @@ std::unique_ptr<ContentFilter> ContentFilter::createIfNeeded(const ResourceRespo
     if (filters.isEmpty())
         return nullptr;
 
-    return std::make_unique<ContentFilter>(WTF::move(filters));
+    return std::make_unique<ContentFilter>(WTF::move(filters), documentLoader);
 }
 
-ContentFilter::ContentFilter(Container contentFilters)
+ContentFilter::ContentFilter(Container contentFilters, DocumentLoader& documentLoader)
     : m_contentFilters { WTF::move(contentFilters) }
+    , m_documentLoader { documentLoader }
 {
     ASSERT(!m_contentFilters.isEmpty());
 }
@@ -121,13 +127,32 @@ ContentFilterUnblockHandler ContentFilter::unblockHandler() const
 {
     ASSERT(didBlockData());
 
+    PlatformContentFilter* blockingFilter = nullptr;
     for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->didBlockData())
-            return contentFilter->unblockHandler();
+        if (contentFilter->didBlockData()) {
+            blockingFilter = contentFilter.get();
+            break;
+        }
     }
-
-    ASSERT_NOT_REACHED();
-    return { };
+    ASSERT(blockingFilter);
+
+    StringCapture unblockRequestDeniedScript { blockingFilter->unblockRequestDeniedScript() };
+    if (unblockRequestDeniedScript.string().isEmpty())
+        return blockingFilter->unblockHandler();
+
+    // It would be a layering violation for the unblock handler to access its frame,
+    // so we will execute the unblock denied script on its behalf.
+    ContentFilterUnblockHandler unblockHandler { blockingFilter->unblockHandler() };
+    RefPtr<Frame> frame { m_documentLoader.frame() };
+    return ContentFilterUnblockHandler {
+        unblockHandler.unblockURLHost(), [unblockHandler, frame, unblockRequestDeniedScript](ContentFilterUnblockHandler::DecisionHandlerFunction decisionHandler) {
+            unblockHandler.requestUnblockAsync([decisionHandler, frame, unblockRequestDeniedScript](bool unblocked) {
+                decisionHandler(unblocked);
+                if (!unblocked && frame)
+                    frame->script().executeScript(unblockRequestDeniedScript.string());
+            });
+        }
+    };
 }
 
 } // namespace WebCore
index 622a491..2e8ccad 100644 (file)
 
 namespace WebCore {
 
+class DocumentLoader;
 class ResourceResponse;
 
 class ContentFilter final : public PlatformContentFilter {
 public:
     template <typename T> static void addType() { types().append(type<T>()); }
-    static std::unique_ptr<ContentFilter> createIfNeeded(const ResourceResponse&);
+    static std::unique_ptr<ContentFilter> createIfNeeded(const ResourceResponse&, DocumentLoader&);
 
     void addData(const char* data, int length) override;
     void finishedAddingData() override;
@@ -56,10 +57,11 @@ private:
     WEBCORE_EXPORT static Vector<Type>& types();
 
     using Container = Vector<std::unique_ptr<PlatformContentFilter>>;
-    friend std::unique_ptr<ContentFilter> std::make_unique<ContentFilter>(Container&&);
-    explicit ContentFilter(Container);
+    friend std::unique_ptr<ContentFilter> std::make_unique<ContentFilter>(Container&&, DocumentLoader&);
+    explicit ContentFilter(Container, DocumentLoader&);
 
     Container m_contentFilters;
+    DocumentLoader& m_documentLoader;
 };
 
 template <typename T>
index d644e5c..eec9438 100644 (file)
@@ -663,7 +663,7 @@ void DocumentLoader::responseReceived(CachedResource* resource, const ResourceRe
 #endif
 
 #if ENABLE(CONTENT_FILTERING)
-    m_contentFilter = ContentFilter::createIfNeeded(response);
+    m_contentFilter = ContentFilter::createIfNeeded(response, *this);
 #endif
 
     frameLoader()->policyChecker().checkContentPolicy(m_response, [this](PolicyAction policy) {
index 8c34459..b43b738 100644 (file)
@@ -1124,6 +1124,7 @@ void FrameLoader::started()
 
 void FrameLoader::prepareForLoadStart()
 {
+    policyChecker().prepareForLoadStart();
     m_progressTracker->progressStarted();
     m_client.dispatchDidStartProvisionalLoad();
 
index 1d1aa3e..1215121 100644 (file)
@@ -56,6 +56,13 @@ PolicyChecker::PolicyChecker(Frame& frame)
 {
 }
 
+void PolicyChecker::prepareForLoadStart()
+{
+#if ENABLE(CONTENT_FILTERING)
+    m_contentFilterUnblockHandler = { };
+#endif
+}
+
 void PolicyChecker::checkNavigationPolicy(const ResourceRequest& newRequest, NavigationPolicyDecisionFunction function)
 {
     checkNavigationPolicy(newRequest, m_frame.loader().activeDocumentLoader(), nullptr, WTF::move(function));
@@ -105,6 +112,17 @@ void PolicyChecker::checkNavigationPolicy(const ResourceRequest& request, Docume
     }
 #endif
 
+#if ENABLE(CONTENT_FILTERING)
+    if (m_contentFilterUnblockHandler.canHandleRequest(request)) {
+        RefPtr<Frame> frame { &m_frame };
+        m_contentFilterUnblockHandler.requestUnblockAsync([frame](bool unblocked) {
+            if (unblocked)
+                frame->loader().reload();
+        });
+        continueAfterNavigationPolicy(PolicyIgnore);
+    }
+#endif
+
     m_delegateIsDecidingNavigationPolicy = true;
     m_frame.loader().client().dispatchDecidePolicyForNavigationAction(action, request, formState, [this](PolicyAction action) {
         continueAfterNavigationPolicy(action);
index f24ccdf..3ca4ea8 100644 (file)
 #include <wtf/PassRefPtr.h>
 #include <wtf/text/WTFString.h>
 
+#if ENABLE(CONTENT_FILTERING)
+#include "ContentFilterUnblockHandler.h"
+#endif
+
 namespace WebCore {
 
 class DocumentLoader;
@@ -50,6 +54,7 @@ class PolicyChecker {
 public:
     explicit PolicyChecker(Frame&);
 
+    void prepareForLoadStart();
     void checkNavigationPolicy(const ResourceRequest&, DocumentLoader*, PassRefPtr<FormState>, NavigationPolicyDecisionFunction);
     void checkNavigationPolicy(const ResourceRequest&, NavigationPolicyDecisionFunction);
     void checkNewWindowPolicy(const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, NewWindowPolicyDecisionFunction);
@@ -74,6 +79,10 @@ public:
     // the heart to hack on all the platforms to make that happen right now.
     void continueLoadAfterWillSubmitForm(PolicyAction);
 
+#if ENABLE(CONTENT_FILTERING)
+    void setContentFilterUnblockHandler(ContentFilterUnblockHandler unblockHandler) { m_contentFilterUnblockHandler = WTF::move(unblockHandler); }
+#endif
+
 private:
     void continueAfterNavigationPolicy(PolicyAction);
     void continueAfterNewWindowPolicy(PolicyAction);
@@ -91,6 +100,10 @@ private:
     // on navigation action delegate callbacks.
     FrameLoadType m_loadType;
     PolicyCallback m_callback;
+
+#if ENABLE(CONTENT_FILTERING)
+    ContentFilterUnblockHandler m_contentFilterUnblockHandler;
+#endif
 };
 
 } // namespace WebCore
index 5534d34..b3c8bb5 100644 (file)
@@ -36,7 +36,7 @@
 #include "ScrollTypes.h"
 #include "UserScriptTypes.h"
 #include <memory>
-#include <wtf/RefCounted.h>
+#include <wtf/ThreadSafeRefCounted.h>
 
 #if PLATFORM(IOS)
 #include "ViewportArguments.h"
@@ -113,7 +113,7 @@ namespace WebCore {
     };
     typedef unsigned LayerTreeFlags;
 
-    class Frame : public RefCounted<Frame> {
+    class Frame : public ThreadSafeRefCounted<Frame> {
     public:
         WEBCORE_EXPORT static Ref<Frame> create(Page*, HTMLFrameOwnerElement*, FrameLoaderClient*);
 
index 87514b6..35c2655 100644 (file)
 
 #include <functional>
 #include <wtf/RetainPtr.h>
+#include <wtf/text/WTFString.h>
 
-OBJC_CLASS NSKeyedArchiver;
-OBJC_CLASS NSKeyedUnarchiver;
+OBJC_CLASS NSCoder;
+
+#if PLATFORM(IOS)
 OBJC_CLASS WebFilterEvaluator;
+#endif
 
 namespace WebCore {
 
@@ -41,19 +44,31 @@ class ResourceRequest;
 
 class ContentFilterUnblockHandler {
 public:
-    ContentFilterUnblockHandler() = default;
-    explicit ContentFilterUnblockHandler(WebFilterEvaluator *evaluator) : m_webFilterEvaluator { evaluator } { }
+    using DecisionHandlerFunction = std::function<void(bool unblocked)>;
+    using UnblockRequesterFunction = std::function<void(DecisionHandlerFunction)>;
 
-    void clear() { m_webFilterEvaluator = nullptr; }
-    WEBCORE_EXPORT void encode(NSKeyedArchiver *) const;
-    WEBCORE_EXPORT static bool decode(NSKeyedUnarchiver *, ContentFilterUnblockHandler&);
+    static const char* unblockURLScheme() { return "x-apple-content-filter"; }
 
+    ContentFilterUnblockHandler() = default;
+    WEBCORE_EXPORT ContentFilterUnblockHandler(String unblockURLHost, UnblockRequesterFunction);
 #if PLATFORM(IOS)
-    WEBCORE_EXPORT bool handleUnblockRequestAndDispatchIfSuccessful(const ResourceRequest&, std::function<void()>);
+    ContentFilterUnblockHandler(String unblockURLHost, RetainPtr<WebFilterEvaluator>);
 #endif
 
+    WEBCORE_EXPORT bool needsUIProcess() const;
+    WEBCORE_EXPORT void encode(NSCoder *) const;
+    WEBCORE_EXPORT static bool decode(NSCoder *, ContentFilterUnblockHandler&);
+    WEBCORE_EXPORT bool canHandleRequest(const ResourceRequest&) const;
+    WEBCORE_EXPORT void requestUnblockAsync(DecisionHandlerFunction) const;
+
+    const String& unblockURLHost() const { return m_unblockURLHost; }
+
 private:
+    String m_unblockURLHost;
+    UnblockRequesterFunction m_unblockRequester;
+#if PLATFORM(IOS)
     RetainPtr<WebFilterEvaluator> m_webFilterEvaluator;
+#endif
 };
 
 } // namespace WebCore
index b0be019..2ee408a 100644 (file)
@@ -48,6 +48,7 @@ public:
     virtual bool didBlockData() const = 0;
     virtual const char* getReplacementData(int& length) const = 0;
     virtual ContentFilterUnblockHandler unblockHandler() const = 0;
+    virtual String unblockRequestDeniedScript() const { return emptyString(); }
 };
 
 } // namespace WebCore
index 3063934..298b22c 100644 (file)
 
 #if ENABLE(CONTENT_FILTERING)
 
+#import "BlockExceptions.h"
 #import "ResourceRequest.h"
+
+#if PLATFORM(IOS)
 #import "SoftLinking.h"
+#import "WebCoreThreadRun.h"
 #import "WebFilterEvaluatorSPI.h"
-#import <objc/runtime.h>
 
 SOFT_LINK_PRIVATE_FRAMEWORK(WebContentAnalysis);
 SOFT_LINK_CLASS(WebContentAnalysis, WebFilterEvaluator);
 
+static NSString * const webFilterEvaluatorKey { @"webFilterEvaluator" };
+#endif
+
+static NSString * const unblockURLHostKey { @"unblockURLHost" };
+
 namespace WebCore {
 
-static NSString * const platformContentFilterKey = @"platformContentFilter";
+ContentFilterUnblockHandler::ContentFilterUnblockHandler(String unblockURLHost, UnblockRequesterFunction unblockRequester)
+    : m_unblockURLHost { WTF::move(unblockURLHost) }
+    , m_unblockRequester { WTF::move(unblockRequester) }
+{
+}
 
-void ContentFilterUnblockHandler::encode(NSKeyedArchiver *archiver) const
+#if PLATFORM(IOS)
+ContentFilterUnblockHandler::ContentFilterUnblockHandler(String unblockURLHost, RetainPtr<WebFilterEvaluator> evaluator)
+    : m_unblockURLHost { WTF::move(unblockURLHost) }
+    , m_webFilterEvaluator { WTF::move(evaluator) }
 {
-    if ([getWebFilterEvaluatorClass() conformsToProtocol:@protocol(NSSecureCoding)])
-        [archiver encodeObject:m_webFilterEvaluator.get() forKey:platformContentFilterKey];
 }
+#endif
 
-bool ContentFilterUnblockHandler::decode(NSKeyedUnarchiver *unarchiver, ContentFilterUnblockHandler& unblockHandler)
+bool ContentFilterUnblockHandler::needsUIProcess() const
 {
-    @try {
-        if ([getWebFilterEvaluatorClass() conformsToProtocol:@protocol(NSSecureCoding)])
-            unblockHandler.m_webFilterEvaluator = (WebFilterEvaluator *)[unarchiver decodeObjectOfClass:getWebFilterEvaluatorClass() forKey:platformContentFilterKey];
-        return true;
-    } @catch (NSException *exception) {
-        LOG_ERROR("The platform content filter being decoded is not a WebFilterEvaluator.");
-    }
-    
+#if PLATFORM(IOS)
+    return m_webFilterEvaluator;
+#else
     return false;
+#endif
 }
 
-#if PLATFORM(IOS)
-static inline const char* scheme()
+void ContentFilterUnblockHandler::encode(NSCoder *coder) const
 {
-    return "x-apple-content-filter";
+    ASSERT_ARG(coder, coder.allowsKeyedCoding && coder.requiresSecureCoding);
+    BEGIN_BLOCK_OBJC_EXCEPTIONS;
+    [coder encodeObject:m_unblockURLHost forKey:unblockURLHostKey];
+#if PLATFORM(IOS)
+    [coder encodeObject:m_webFilterEvaluator.get() forKey:webFilterEvaluatorKey];
+#endif
+    END_BLOCK_OBJC_EXCEPTIONS;
 }
 
-bool ContentFilterUnblockHandler::handleUnblockRequestAndDispatchIfSuccessful(const ResourceRequest& request, std::function<void()> function)
+bool ContentFilterUnblockHandler::decode(NSCoder *coder, ContentFilterUnblockHandler& unblockHandler)
 {
-    if (!m_webFilterEvaluator)
-        return false;
+    ASSERT_ARG(coder, coder.allowsKeyedCoding && coder.requiresSecureCoding);
+    BEGIN_BLOCK_OBJC_EXCEPTIONS;
+    unblockHandler.m_unblockURLHost = [coder decodeObjectOfClass:[NSString class] forKey:unblockURLHostKey];
+#if PLATFORM(IOS)
+    unblockHandler.m_webFilterEvaluator = [coder decodeObjectOfClass:getWebFilterEvaluatorClass() forKey:webFilterEvaluatorKey];
+#endif
+    return true;
+    END_BLOCK_OBJC_EXCEPTIONS;
+    return false;
+}
 
-    if (!request.url().protocolIs(scheme()))
+bool ContentFilterUnblockHandler::canHandleRequest(const ResourceRequest& request) const
+{
+    if (!m_unblockRequester) {
+#if PLATFORM(IOS)
+        if (!m_webFilterEvaluator)
+            return false;
+#else
         return false;
+#endif
+    }
 
-    if (!equalIgnoringCase(request.url().host(), "unblock"))
-        return false;
+    return request.url().protocolIs(unblockURLScheme()) && equalIgnoringCase(request.url().host(), m_unblockURLHost);
+}
 
-    [m_webFilterEvaluator unblockWithCompletion:^(BOOL unblocked, NSError *) {
-        if (unblocked)
-            function();
-    }];
-    return true;
+static inline void dispatchToMainThread(void (^block)())
+{
+    dispatch_async(dispatch_get_main_queue(), ^{
+#if PLATFORM(IOS)
+        WebThreadRun(block);
+#else
+        block();
+#endif
+    });
 }
+
+void ContentFilterUnblockHandler::requestUnblockAsync(DecisionHandlerFunction decisionHandler) const
+{
+#if PLATFORM(IOS)
+    if (m_webFilterEvaluator) {
+        [m_webFilterEvaluator unblockWithCompletion:[decisionHandler](BOOL unblocked, NSError *) {
+            dispatchToMainThread([decisionHandler, unblocked] {
+                decisionHandler(unblocked);
+            });
+        }];
+        return;
+    }
 #endif
 
+    if (m_unblockRequester) {
+        m_unblockRequester([decisionHandler](bool unblocked) {
+            dispatchToMainThread([decisionHandler, unblocked] {
+                decisionHandler(unblocked);
+            });
+        });
+    }
+}
+
 } // namespace WebCore
 
 #endif // PLATFORM(IOS) && ENABLE(CONTENT_FILTERING)
index 85cd95c..cc9b3f5 100644 (file)
@@ -93,7 +93,11 @@ const char* ParentalControlsContentFilter::getReplacementData(int& length) const
 
 ContentFilterUnblockHandler ParentalControlsContentFilter::unblockHandler() const
 {
-    return ContentFilterUnblockHandler { m_webFilterEvaluator.get() };
+#if PLATFORM(IOS)
+    return ContentFilterUnblockHandler { "unblock", m_webFilterEvaluator };
+#else
+    return { };
+#endif
 }
 
 } // namespace WebCore
index b4b9b35..8490923 100644 (file)
@@ -34,6 +34,9 @@
 
 namespace WebCore {
 
+using Decision = MockContentFilterSettings::Decision;
+using DecisionPoint = MockContentFilterSettings::DecisionPoint;
+
 void MockContentFilter::ensureInstalled()
 {
     static std::once_flag onceFlag;
@@ -42,9 +45,14 @@ void MockContentFilter::ensureInstalled()
     });
 }
 
+static inline MockContentFilterSettings& settings()
+{
+    return MockContentFilterSettings::singleton();
+}
+
 bool MockContentFilter::canHandleResponse(const ResourceResponse&)
 {
-    return MockContentFilterSettings::singleton().enabled();
+    return settings().enabled();
 }
 
 std::unique_ptr<MockContentFilter> MockContentFilter::create(const ResourceResponse& response)
@@ -54,18 +62,18 @@ std::unique_ptr<MockContentFilter> MockContentFilter::create(const ResourceRespo
 
 MockContentFilter::MockContentFilter(const ResourceResponse&)
 {
-    maybeDetermineStatus(MockContentFilterSettings::DecisionPoint::AfterResponse);
+    maybeDetermineStatus(DecisionPoint::AfterResponse);
 }
 
 void MockContentFilter::addData(const char* data, int length)
 {
     m_replacementData.append(data, length);
-    maybeDetermineStatus(MockContentFilterSettings::DecisionPoint::AfterAddData);
+    maybeDetermineStatus(DecisionPoint::AfterAddData);
 }
 
 void MockContentFilter::finishedAddingData()
 {
-    maybeDetermineStatus(MockContentFilterSettings::DecisionPoint::AfterFinishedAddingData);
+    maybeDetermineStatus(DecisionPoint::AfterFinishedAddingData);
 }
 
 bool MockContentFilter::needsMoreData() const
@@ -86,20 +94,34 @@ const char* MockContentFilter::getReplacementData(int& length) const
 
 ContentFilterUnblockHandler MockContentFilter::unblockHandler() const
 {
-    return { };
+    using DecisionHandlerFunction = ContentFilterUnblockHandler::DecisionHandlerFunction;
+
+    return ContentFilterUnblockHandler {
+        MockContentFilterSettings::unblockURLHost(), [](DecisionHandlerFunction decisionHandler) {
+            bool shouldAllow { settings().unblockRequestDecision() == Decision::Allow };
+            if (shouldAllow)
+                settings().setDecision(Decision::Allow);
+            decisionHandler(shouldAllow);
+        }
+    };
+}
+
+String MockContentFilter::unblockRequestDeniedScript() const
+{
+    return ASCIILiteral("unblockRequestDenied()");
 }
 
-void MockContentFilter::maybeDetermineStatus(MockContentFilterSettings::DecisionPoint decisionPoint)
+void MockContentFilter::maybeDetermineStatus(DecisionPoint decisionPoint)
 {
-    if (m_status != Status::NeedsMoreData || decisionPoint != MockContentFilterSettings::singleton().decisionPoint())
+    if (m_status != Status::NeedsMoreData || decisionPoint != settings().decisionPoint())
         return;
 
-    m_status = MockContentFilterSettings::singleton().decision() == MockContentFilterSettings::Decision::Allow ? Status::Allowed : Status::Blocked;
+    m_status = settings().decision() == Decision::Allow ? Status::Allowed : Status::Blocked;
     if (m_status != Status::Blocked)
         return;
 
     m_replacementData.clear();
-    const CString utf8BlockedString = MockContentFilterSettings::singleton().blockedString().utf8();
+    const CString utf8BlockedString = settings().blockedString().utf8();
     m_replacementData.append(utf8BlockedString.data(), utf8BlockedString.length());
 }
 
index 112ebd9..0fa1358 100644 (file)
@@ -45,6 +45,7 @@ public:
     bool didBlockData() const override;
     const char* getReplacementData(int& length) const override;
     ContentFilterUnblockHandler unblockHandler() const override;
+    String unblockRequestDeniedScript() const override;
 
 private:
     enum class Status {
index aaabb93..a47d4ec 100644 (file)
@@ -28,6 +28,8 @@
 
 #if ENABLE(CONTENT_FILTERING)
 
+#include "ContentFilterUnblockHandler.h"
+#include <mutex>
 #include <wtf/NeverDestroyed.h>
 
 namespace WebCore {
@@ -43,6 +45,18 @@ void MockContentFilterSettings::reset()
     singleton() = MockContentFilterSettings();
 }
 
+const String& MockContentFilterSettings::unblockRequestURL() const
+{
+    static LazyNeverDestroyed<String> unblockRequestURL;
+    static std::once_flag onceFlag;
+    std::call_once(onceFlag, [] {
+        unblockRequestURL.construct(ContentFilterUnblockHandler::unblockURLScheme());
+        unblockRequestURL.get().append("://");
+        unblockRequestURL.get().append(unblockURLHost());
+    });
+    return unblockRequestURL;
+}
+
 }; // namespace WebCore
 
 #endif // ENABLE(CONTENT_FILTERING)
index 2102767..8663d12 100644 (file)
@@ -32,8 +32,8 @@
 namespace WebCore {
 
 class MockContentFilterSettings {
-    WTF_MAKE_FAST_ALLOCATED;
     friend class NeverDestroyed<MockContentFilterSettings>;
+
 public:
     enum class DecisionPoint {
         AfterResponse,
@@ -48,6 +48,7 @@ public:
 
     static MockContentFilterSettings& singleton();
     static void reset();
+    static const char* unblockURLHost() { return "mock-unblock"; }
 
     // Trick the generated bindings into thinking we're RefCounted.
     void ref() { }
@@ -65,6 +66,11 @@ public:
     Decision decision() const { return m_decision; }
     void setDecision(Decision decision) { m_decision = decision; }
 
+    Decision unblockRequestDecision() const { return m_unblockRequestDecision; }
+    void setUnblockRequestDecision(Decision unblockRequestDecision) { m_unblockRequestDecision = unblockRequestDecision; }
+
+    const String& unblockRequestURL() const;
+
 private:
     MockContentFilterSettings() = default;
     MockContentFilterSettings(const MockContentFilterSettings&) = delete;
@@ -73,6 +79,7 @@ private:
     bool m_enabled { false };
     DecisionPoint m_decisionPoint { DecisionPoint::AfterResponse };
     Decision m_decision { Decision::Allow };
+    Decision m_unblockRequestDecision { Decision::Block };
     String m_blockedString;
 };
 
index ac06ded..e86b312 100644 (file)
@@ -39,4 +39,7 @@
     const octet DECISION_ALLOW = 0;
     const octet DECISION_BLOCK = 1;
     [Custom] attribute octet decision;
+    [Custom] attribute octet unblockRequestDecision;
+
+    readonly attribute DOMString unblockRequestURL;
 };
index 730de3b..80a22dc 100644 (file)
@@ -1,3 +1,17 @@
+2015-03-19  Andy Estes  <aestes@apple.com>
+
+        [Content Filtering] Add tests for unblock requests
+        https://bugs.webkit.org/show_bug.cgi?id=142900
+
+        Reviewed by Andreas Kling.
+
+        * WebCoreSupport/WebFrameLoaderClient.mm:
+        (WebFrameLoaderClient::dispatchDidStartProvisionalLoad): This now happens in PolicyChecker.
+        (WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction): Ditto.
+        * WebView/WebFrame.mm:
+        (-[WebFrame _contentFilterDidHandleNavigationAction:]): Deleted.
+        * WebView/WebFrameInternal.h: Removed contentFilterUnblockHandler from WebFramePrivate.
+
 2015-03-19  Enrica Casucci  <enrica@apple.com>
 
         <attachment> should put URLs on the pasteboard so that Finder can accept drops.
index 3e1b956..a381e50 100644 (file)
 #import <WebCore/RuntimeApplicationChecksIOS.h>
 #endif
 
+#if ENABLE(CONTENT_FILTERING)
+#import <WebCore/PolicyChecker.h>
+#endif
+
 using namespace WebCore;
 using namespace HTMLNames;
 
@@ -665,7 +669,6 @@ void WebFrameLoaderClient::dispatchDidStartProvisionalLoad()
 {
     ASSERT(!m_webFrame->_private->provisionalURL);
     m_webFrame->_private->provisionalURL = core(m_webFrame.get())->loader().provisionalDocumentLoader()->url().string();
-    m_webFrame->_private->contentFilterUnblockHandler.clear();
 
     WebView *webView = getWebView(m_webFrame.get());
 #if !PLATFORM(IOS)
@@ -880,11 +883,6 @@ void WebFrameLoaderClient::dispatchDecidePolicyForNewWindowAction(const Navigati
 
 void WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction(const NavigationAction& action, const ResourceRequest& request, PassRefPtr<FormState> formState, FramePolicyFunction function)
 {
-    if ([m_webFrame _contentFilterDidHandleNavigationAction:request]) {
-        function(PolicyIgnore);
-        return;
-    }
-
     WebView *webView = getWebView(m_webFrame.get());
     [[webView _policyDelegateForwarder] webView:webView
                 decidePolicyForNavigationAction:actionDictionary(action, formState)
@@ -2242,10 +2240,12 @@ void WebFrameLoaderClient::didCreateQuickLookHandle(WebCore::QuickLookHandle& ha
 }
 #endif
 
+#if ENABLE(CONTENT_FILTERING)
 void WebFrameLoaderClient::contentFilterDidBlockLoad(WebCore::ContentFilterUnblockHandler unblockHandler)
 {
-    m_webFrame->_private->contentFilterUnblockHandler = WTF::move(unblockHandler);
+    core(m_webFrame.get())->loader().policyChecker().setContentFilterUnblockHandler(WTF::move(unblockHandler));
 }
+#endif
 
 @implementation WebFramePolicyListener
 
index 97a21e2..2852801 100644 (file)
@@ -1002,20 +1002,6 @@ static inline WebDataSource *dataSource(DocumentLoader* loader)
     _private->coreFrame->loader().documentLoader()->commitData((const char *)[data bytes], [data length]);
 }
 
-- (BOOL)_contentFilterDidHandleNavigationAction:(const WebCore::ResourceRequest &)request
-{
-#if PLATFORM(IOS)
-    RetainPtr<WebFrame> retainedMainFrame = [[self webView] mainFrame];
-    return _private->contentFilterUnblockHandler.handleUnblockRequestAndDispatchIfSuccessful(request, [retainedMainFrame] {
-        WebThreadRun(^ {
-            [retainedMainFrame reload];
-        });
-    });
-#else
-    return NO;
-#endif
-}
-
 @end
 
 @implementation WebFrame (WebPrivate)
index dcc5406..0aafb1d 100644 (file)
@@ -30,7 +30,6 @@
 
 #import "WebFramePrivate.h"
 #import "WebPreferencesPrivate.h"
-#import <WebCore/ContentFilterUnblockHandler.h>
 #import <WebCore/EditAction.h>
 #import <WebCore/FrameLoaderTypes.h>
 #import <WebCore/FrameSelection.h>
@@ -92,7 +91,6 @@ WebView *getWebView(WebFrame *webFrame);
 #if PLATFORM(IOS)
     BOOL isCommitting;
 #endif
-    WebCore::ContentFilterUnblockHandler contentFilterUnblockHandler;
 }
 @end
 
@@ -184,8 +182,6 @@ WebView *getWebView(WebFrame *webFrame);
 
 - (void)_commitData:(NSData *)data;
 
-- (BOOL)_contentFilterDidHandleNavigationAction:(const WebCore::ResourceRequest&)request;
-
 @end
 
 @interface NSObject (WebInternalFrameLoadDelegate)
index c49878d..5a2bb5c 100644 (file)
@@ -1,3 +1,26 @@
+2015-03-19  Andy Estes  <aestes@apple.com>
+
+        [Content Filtering] Add tests for unblock requests
+        https://bugs.webkit.org/show_bug.cgi?id=142900
+
+        Reviewed by Andreas Kling.
+
+        * UIProcess/Cocoa/WebPageProxyCocoa.mm:
+        (WebKit::WebPageProxy::contentFilterDidBlockLoadForFrame): Called WebFrameProxy::contentFilterDidBlockLoad.
+        * UIProcess/WebFrameProxy.cpp:
+        (WebKit::WebFrameProxy::didStartProvisionalLoad): Assigned a default-constructed ContentFilterUnblockHandler instead of calling clear().
+        (WebKit::WebFrameProxy::didHandleContentFilterUnblockNavigation): Renamed from contentFilterDidHandleNavigationAction.
+        Updated to use ContentFilterUnblockHandler's new API.
+        (WebKit::WebFrameProxy::contentFilterDidHandleNavigationAction): Deleted.
+        * UIProcess/WebFrameProxy.h:
+        (WebKit::WebFrameProxy::contentFilterDidBlockLoad): Renamed from setContentFilterUnblockHandler.
+        (WebKit::WebFrameProxy::setContentFilterUnblockHandler): Deleted.
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::decidePolicyForNavigationAction): Called WebFrameProxy::didHandleContentFilterUnblockNavigation.
+        * WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
+        (WebKit::WebFrameLoaderClient::contentFilterDidBlockLoad): If the unblock handler needs the UI process,
+        send WebPageProxy::ContentFilterDidBlockLoadForFrame. Oterwise, call PolicyChecker::setContentFilterUnblockHandler.
+
 2015-03-20  Zan Dobersek  <zdobersek@igalia.com>
 
         [GTK] Properly guard X11-specific code in BackingStore::createBackend()
index c3567de..b6cf7be 100644 (file)
@@ -75,7 +75,7 @@ void WebPageProxy::loadRecentSearches(const String& name, Vector<String>& search
 void WebPageProxy::contentFilterDidBlockLoadForFrame(const WebCore::ContentFilterUnblockHandler& unblockHandler, uint64_t frameID)
 {
     if (WebFrameProxy* frame = m_process->webFrame(frameID))
-        frame->setContentFilterUnblockHandler(unblockHandler);
+        frame->contentFilterDidBlockLoad(unblockHandler);
 }
 
 }
index f832747..2e2785d 100644 (file)
@@ -128,7 +128,7 @@ void WebFrameProxy::didStartProvisionalLoad(const String& url)
 {
     m_frameLoadState.didStartProvisionalLoad(url);
 #if ENABLE(CONTENT_FILTERING)
-    m_contentFilterUnblockHandler.clear();
+    m_contentFilterUnblockHandler = { };
 #endif
 }
 
@@ -234,16 +234,18 @@ void WebFrameProxy::setUnreachableURL(const String& unreachableURL)
 }
 
 #if ENABLE(CONTENT_FILTERING)
-bool WebFrameProxy::contentFilterDidHandleNavigationAction(const WebCore::ResourceRequest& request)
+bool WebFrameProxy::didHandleContentFilterUnblockNavigation(const WebCore::ResourceRequest& request)
 {
-#if PLATFORM(IOS)
-    RefPtr<WebPageProxy> retainedPage = m_page;
-    return m_contentFilterUnblockHandler.handleUnblockRequestAndDispatchIfSuccessful(request, [retainedPage] {
-        retainedPage->reload(false);
+    if (!m_contentFilterUnblockHandler.canHandleRequest(request))
+        return false;
+
+    RefPtr<WebPageProxy> page { m_page };
+    ASSERT(page);
+    m_contentFilterUnblockHandler.requestUnblockAsync([page](bool unblocked) {
+        if (unblocked)
+            page->reload(false);
     });
-#else
-    return false;
-#endif
+    return true;
 }
 #endif
 
index 88a02d8..419812d 100644 (file)
@@ -123,8 +123,8 @@ public:
     WebFormSubmissionListenerProxy& setUpFormSubmissionListenerProxy(uint64_t listenerID);
 
 #if ENABLE(CONTENT_FILTERING)
-    void setContentFilterUnblockHandler(WebCore::ContentFilterUnblockHandler contentFilterUnblockHandler) { m_contentFilterUnblockHandler = WTF::move(contentFilterUnblockHandler); }
-    bool contentFilterDidHandleNavigationAction(const WebCore::ResourceRequest&);
+    void contentFilterDidBlockLoad(WebCore::ContentFilterUnblockHandler contentFilterUnblockHandler) { m_contentFilterUnblockHandler = WTF::move(contentFilterUnblockHandler); }
+    bool didHandleContentFilterUnblockNavigation(const WebCore::ResourceRequest&);
 #endif
 
 private:
index 2946b02..4bf29a7 100644 (file)
@@ -3092,7 +3092,7 @@ void WebPageProxy::decidePolicyForNavigationAction(uint64_t frameID, uint64_t na
     }
 
 #if ENABLE(CONTENT_FILTERING)
-    if (frame->contentFilterDidHandleNavigationAction(request)) {
+    if (frame->didHandleContentFilterUnblockNavigation(request)) {
         receivedPolicyAction = true;
         policyAction = PolicyIgnore;
         return;
index 9e8572e..f46c707 100644 (file)
@@ -1648,7 +1648,12 @@ PassRefPtr<FrameNetworkingContext> WebFrameLoaderClient::createNetworkingContext
 #if ENABLE(CONTENT_FILTERING)
 void WebFrameLoaderClient::contentFilterDidBlockLoad(WebCore::ContentFilterUnblockHandler unblockHandler)
 {
-    if (WebPage* webPage = m_frame->page())
+    if (!unblockHandler.needsUIProcess()) {
+        m_frame->coreFrame()->loader().policyChecker().setContentFilterUnblockHandler(WTF::move(unblockHandler));
+        return;
+    }
+
+    if (WebPage* webPage { m_frame->page() })
         webPage->send(Messages::WebPageProxy::ContentFilterDidBlockLoadForFrame(unblockHandler, m_frame->frameID()));
 }
 #endif