[Content Filtering] Blocked page is not always displayed when it should be
authoraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Apr 2015 07:52:14 +0000 (07:52 +0000)
committeraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Apr 2015 07:52:14 +0000 (07:52 +0000)
https://bugs.webkit.org/show_bug.cgi?id=143410
Source/WebCore:

rdar://problem/20211099

Reviewed by Andreas Kling.

These tests now pass: contentfiltering/block-after-add-data.html
                      contentfiltering/block-after-response.html

There were several problems with how ContentFilter loaded replacement data:
(1) Replacement data was delivered to DocumentLoader as if it were the original document's data. This assumes
    that the original data was a UTF-8 encoded HTML document, which is not always true. We had a way to reset
    the encoding, but not the content type.
(2) Replacement data was never delivered when the filter blocks in DocumentLoader::responseReceived().
(3) The main resource load was cancelled before the replacement data could be rendered when the filter blocks
    in DocumentLoader::dataReceived().
The result was that only when the load was blocked after DocumentLoader::notifyFinished() would the replacement
data be shown properly, and only when problem (1) wasn't occurring.

This patch addresses these issues by using the substitute data mechanism to deliver replacement data. By using
substitute data, we can ensure that the original load is cancelled at the earliest opportunity and that the
replacement data is loaded with the proper content type and encoding.

Accomplishing this required changing the way ContentFilter interacts with DocumentLoader. Instead of placing
ContentFilter hooks throughout DocumentLoader, this patch makes ContentFilter itself the client of the
CachedRawResource for the duration of the filtering. If the filter decides to allow the load, DocumentLoader
adds itself as a client causing CachedRawResource to deliver to it the response and buffered data. If the
filter decides to block the load, DocumentLoader schedules a substitute data load. An added benefit of this
approach is that ContentFilter can reuse CachedRawResource's original data buffer instead of keeping its own.

* loader/ContentFilter.cpp:
(WebCore::ContentFilter::createIfNeeded): Changed to take a DecisionFunction rather than a ResourceResponse and DocumentLoader.
(WebCore::ContentFilter::ContentFilter): Ditto.
(WebCore::ContentFilter::~ContentFilter): Removed ourself as a CachedRawResource client if needed.
(WebCore::ContentFilter::startFilteringMainResource): Became the client of the CachedRawResource in order to start the filtering process.
(WebCore::ContentFilter::unblockHandler): Returned the unblock handler.
(WebCore::ContentFilter::replacementData): Returned the replacement data.
(WebCore::ContentFilter::unblockRequestDeniedScript): Returned the unblock request denied script.
(WebCore::ContentFilter::responseReceived): Called responseReceived() on each filter using forEachContentFilterUntilBlocked().
(WebCore::ContentFilter::dataReceived): Ditto for dataReceived().
(WebCore::ContentFilter::notifyFinished): Ditto for finishedLoading().
(WebCore::ContentFilter::forEachContentFilterUntilBlocked): For each filter that needs more data, called the function.
If the filter blocked the load, called didDecide() with State::Blocked.
If all filters allowed the load, called didDecide() with State::Allowed.
(WebCore::ContentFilter::didDecide): Set m_state and called m_decisionFunction().
(WebCore::ContentFilter::addData): Deleted.
(WebCore::ContentFilter::finishedAddingData): Deleted.
(WebCore::ContentFilter::needsMoreData): Deleted.
(WebCore::ContentFilter::didBlockData): Deleted.
(WebCore::ContentFilter::getReplacementData): Deleted.
* loader/ContentFilter.h:
(WebCore::ContentFilter::type):
* loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::DocumentLoader): Called ContentFilter::createIfNeeded() if not loading substitute data.
(WebCore::DocumentLoader::finishedLoading): Removed old ContentFilter code.
(WebCore::DocumentLoader::responseReceived): Ditto.
(WebCore::DocumentLoader::commitData): Ditto.
(WebCore::DocumentLoader::dataReceived): Ditto.
(WebCore::DocumentLoader::detachFromFrame): Set m_contentFilter to nullptr.
(WebCore::DocumentLoader::startLoadingMainResource): Called becomeMainResourceClientIfFilterAllows() instead of
becoming m_mainResource's client.
(WebCore::DocumentLoader::clearMainResource): Set m_contentFilter to nullptr.
(WebCore::DocumentLoader::becomeMainResourceClientIfFilterAllows): If ContentFilter is initialized, called
ContentFilter::startFilteringMainResource(). Otherwise added ourself as a client of m_mainResource.
(WebCore::DocumentLoader::installContentFilterUnblockHandler): Added a helper for creating and notifying
FrameLoaderClient of the unblock handler.
(WebCore::DocumentLoader::contentFilterDidDecide): Set m_contentFilter to nullptr. If the content filter
allowed the load, then added ourself as the CachedRawResource's client. Otherwise, installed the unblock handler
and scheduled a substitute data load with the replacement data.
* loader/DocumentLoader.h:
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::prepareForLoadStart): Removed call to PolicyChecker::prepareForLoadStart().
* loader/NavigationScheduler.cpp:
(WebCore::ScheduledSubstituteDataLoad::ScheduledSubstituteDataLoad): Added a ScheduledNavigation subclass that
calls FrameLoader::load() with a FrameLoadRequest containing substitute data.
(WebCore::NavigationScheduler::scheduleSubstituteDataLoad): Scheduled a substitute data load.
* loader/NavigationScheduler.h:
* loader/PolicyChecker.cpp:
(WebCore::PolicyChecker::checkNavigationPolicy): Reset m_contentFilterUnblockHandler if it couldn't handle the request.
(WebCore::PolicyChecker::prepareForLoadStart): Deleted.
* loader/PolicyChecker.h:
* platform/ContentFilterUnblockHandler.h:
(WebCore::ContentFilterUnblockHandler::unreachableURL):
(WebCore::ContentFilterUnblockHandler::setUnreachableURL):
(WebCore::ContentFilterUnblockHandler::unblockURLScheme): Renamed to ContentFilter::urlScheme().
* platform/PlatformContentFilter.h:
* platform/cocoa/ContentFilterUnblockHandlerCocoa.mm:
(WebCore::ContentFilterUnblockHandler::wrapWithDecisionHandler): Added a helper to wrap the unblock handler with an outer DecisionHandlerFunction.
(WebCore::ContentFilterUnblockHandler::encode): Added m_unreachableURL to the encoding.
(WebCore::ContentFilterUnblockHandler::decode): Ditto for the decoding.
(WebCore::ContentFilterUnblockHandler::canHandleRequest): Changed to call ContentFilter::urlScheme().
* platform/cocoa/NetworkExtensionContentFilter.h:
* platform/cocoa/NetworkExtensionContentFilter.mm:
(replacementDataFromDecisionInfo): Added a helper to extract replacement data from the decisionInfo dictionary.
(WebCore::NetworkExtensionContentFilter::enabled): Renamed from canHandleReponse(); only checks if the filter is enabled.
(WebCore::NetworkExtensionContentFilter::create): Created a new object.
(WebCore::NetworkExtensionContentFilter::NetworkExtensionContentFilter): Created a NEFilterSource immediately when using the modern API.
(WebCore::NetworkExtensionContentFilter::responseReceived): Created a NEFilterSource when using the legacy API.
Called -[NEFilterSource receivedResponse:decisionHandler:] when using the modern API.
(WebCore::NetworkExtensionContentFilter::addData): Stopped buffering the original data.
(WebCore::NetworkExtensionContentFilter::replacementData): Returned a SharedBuffer instead of a char* and int&.
(WebCore::NetworkExtensionContentFilter::canHandleResponse): Deleted.
(WebCore::createNEFilterSource): Deleted.
(WebCore::NetworkExtensionContentFilter::getReplacementData): Deleted.
* platform/cocoa/ParentalControlsContentFilter.h:
* platform/cocoa/ParentalControlsContentFilter.mm:
(WebCore::ParentalControlsContentFilter::enabled): Renamed from canHandleReponse(); only checks if the filter is enabled.
(WebCore::ParentalControlsContentFilter::create): Created a new object.
(WebCore::ParentalControlsContentFilter::ParentalControlsContentFilter): Initialized m_filterState to kWFEStateBuffering.
(WebCore::canHandleResponse): Added a helper to check if the response can be filtered.
(WebCore::ParentalControlsContentFilter::responseReceived): If !canHandleResponse(), set m_filterState to kWFEStateAllowed and return.
Otherwise created a new WebFilterEvaluator with the response.
(WebCore::ParentalControlsContentFilter::addData): Called updateFilterState().
(WebCore::ParentalControlsContentFilter::finishedAddingData): Ditto.
(WebCore::ParentalControlsContentFilter::needsMoreData): Changed to check m_filterState.
(WebCore::ParentalControlsContentFilter::didBlockData): Ditto.
(WebCore::ParentalControlsContentFilter::replacementData): Returned a SharedBuffer instead of a char* and int&.
(WebCore::ParentalControlsContentFilter::updateFilterState): Updated m_filterState by calling -[WebFilterEvaluator filterState].
(WebCore::ParentalControlsContentFilter::canHandleResponse): Deleted.
(WebCore::ParentalControlsContentFilter::getReplacementData): Deleted.
* platform/spi/cocoa/NEFilterSourceSPI.h:
* platform/spi/cocoa/WebFilterEvaluatorSPI.h:
* testing/MockContentFilter.cpp:
(WebCore::MockContentFilter::enabled): Renamed from canHandleReponse(); only checks if the filter is enabled.
(WebCore::MockContentFilter::create): Created a new object.
(WebCore::MockContentFilter::responseReceived): Called maybeDetermineStatus().
(WebCore::MockContentFilter::addData): Stopped buffering the original data.
(WebCore::MockContentFilter::replacementData): Returned a SharedBuffer instead of a char* and int&.
(WebCore::MockContentFilter::unblockHandler): Asserted that we blocked data.
(WebCore::MockContentFilter::canHandleResponse): Deleted.
(WebCore::MockContentFilter::MockContentFilter): Deleted.
(WebCore::MockContentFilter::getReplacementData): Deleted.
* testing/MockContentFilter.h:
* testing/MockContentFilterSettings.cpp:
(WebCore::MockContentFilterSettings::unblockRequestURL): Changed to use a StringBuilder.

Source/WebKit2:

Reviewed by Andreas Kling.

* UIProcess/WebFrameProxy.cpp:
(WebKit::WebFrameProxy::didStartProvisionalLoad): Stopped clearing m_contentFilterUnblockHandler here.
(WebKit::WebFrameProxy::didHandleContentFilterUnblockNavigation): Started doing it here instead.

LayoutTests:

Reviewed by Andreas Kling.

* TestExpectations: Unskipped block-after-add-data.html.
* contentfiltering/block-after-add-data-expected.html: Added a passing expectation.
* contentfiltering/block-after-response-expected.html: Ditto.

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

28 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/contentfiltering/block-after-add-data-expected.html
LayoutTests/contentfiltering/block-after-response-expected.html
Source/WebCore/ChangeLog
Source/WebCore/loader/ContentFilter.cpp
Source/WebCore/loader/ContentFilter.h
Source/WebCore/loader/DocumentLoader.cpp
Source/WebCore/loader/DocumentLoader.h
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/loader/NavigationScheduler.cpp
Source/WebCore/loader/NavigationScheduler.h
Source/WebCore/loader/PolicyChecker.cpp
Source/WebCore/loader/PolicyChecker.h
Source/WebCore/platform/ContentFilterUnblockHandler.h
Source/WebCore/platform/PlatformContentFilter.h
Source/WebCore/platform/cocoa/ContentFilterUnblockHandlerCocoa.mm
Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.h
Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.mm
Source/WebCore/platform/cocoa/ParentalControlsContentFilter.h
Source/WebCore/platform/cocoa/ParentalControlsContentFilter.mm
Source/WebCore/platform/spi/cocoa/NEFilterSourceSPI.h
Source/WebCore/platform/spi/cocoa/WebFilterEvaluatorSPI.h
Source/WebCore/testing/MockContentFilter.cpp
Source/WebCore/testing/MockContentFilter.h
Source/WebCore/testing/MockContentFilterSettings.cpp
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/WebFrameProxy.cpp

index b25ba7d..ecaa96f 100644 (file)
@@ -1,3 +1,14 @@
+2015-04-04  Andy Estes  <aestes@apple.com>
+
+        [Content Filtering] Blocked page is not always displayed when it should be
+        https://bugs.webkit.org/show_bug.cgi?id=143410
+
+        Reviewed by Andreas Kling.
+
+        * TestExpectations: Unskipped block-after-add-data.html.
+        * contentfiltering/block-after-add-data-expected.html: Added a passing expectation.
+        * contentfiltering/block-after-response-expected.html: Ditto.
+
 2015-04-04  Chris Fleizach  <cfleizach@apple.com>
 
         AX: Heuristic: Avoid exposing an element as clickable if mouse event delegation is handled on an AXElement with more than one descendant AXElement
index 9ce73a4..1cee54b 100644 (file)
@@ -497,9 +497,6 @@ webkit.org/b/114280 svg/animations/smil-leak-element-instances-noBaseValRef.svg
 webkit.org/b/114280 svg/animations/smil-leak-element-instances.svg [ Pass Failure ]
 webkit.org/b/114280 svg/animations/smil-leak-elements.svg [ Pass Failure ]
 
-# contentfiltering/block-after-add-data.html times out unexpectedly
-webkit.org/b/142894 contentfiltering/block-after-add-data.html [ Skip ]
-
 webkit.org/b/143085 media/track/track-mode.html [ Pass Timeout ]
 
 # In ES6, Object type restrictions on a first parameter of several Object.* functions are relaxed.
index ba2f133..fbd92ad 100644 (file)
@@ -1,2 +1,2 @@
 <!DOCTYPE html>
-<iframe src="data:text/html,<!DOCTYPE html><body>"></iframe>
+<iframe src="data:text/html,<!DOCTYPE html><body>PASS"></iframe>
index abde098..fbd92ad 100644 (file)
@@ -1,2 +1,2 @@
 <!DOCTYPE html>
-<iframe src="data:text/html,<!DOCTYPE html><body>FAIL"></iframe>
+<iframe src="data:text/html,<!DOCTYPE html><body>PASS"></iframe>
index bc20f5f..1b5a10a 100644 (file)
@@ -1,3 +1,141 @@
+2015-04-04  Andy Estes  <aestes@apple.com>
+
+        [Content Filtering] Blocked page is not always displayed when it should be
+        https://bugs.webkit.org/show_bug.cgi?id=143410
+        rdar://problem/20211099
+
+        Reviewed by Andreas Kling.
+
+        These tests now pass: contentfiltering/block-after-add-data.html
+                              contentfiltering/block-after-response.html
+
+        There were several problems with how ContentFilter loaded replacement data:
+        (1) Replacement data was delivered to DocumentLoader as if it were the original document's data. This assumes
+            that the original data was a UTF-8 encoded HTML document, which is not always true. We had a way to reset
+            the encoding, but not the content type.
+        (2) Replacement data was never delivered when the filter blocks in DocumentLoader::responseReceived().
+        (3) The main resource load was cancelled before the replacement data could be rendered when the filter blocks
+            in DocumentLoader::dataReceived().
+        The result was that only when the load was blocked after DocumentLoader::notifyFinished() would the replacement
+        data be shown properly, and only when problem (1) wasn't occurring.
+
+        This patch addresses these issues by using the substitute data mechanism to deliver replacement data. By using
+        substitute data, we can ensure that the original load is cancelled at the earliest opportunity and that the
+        replacement data is loaded with the proper content type and encoding.
+
+        Accomplishing this required changing the way ContentFilter interacts with DocumentLoader. Instead of placing
+        ContentFilter hooks throughout DocumentLoader, this patch makes ContentFilter itself the client of the
+        CachedRawResource for the duration of the filtering. If the filter decides to allow the load, DocumentLoader
+        adds itself as a client causing CachedRawResource to deliver to it the response and buffered data. If the
+        filter decides to block the load, DocumentLoader schedules a substitute data load. An added benefit of this
+        approach is that ContentFilter can reuse CachedRawResource's original data buffer instead of keeping its own.
+
+        * loader/ContentFilter.cpp:
+        (WebCore::ContentFilter::createIfNeeded): Changed to take a DecisionFunction rather than a ResourceResponse and DocumentLoader.
+        (WebCore::ContentFilter::ContentFilter): Ditto.
+        (WebCore::ContentFilter::~ContentFilter): Removed ourself as a CachedRawResource client if needed.
+        (WebCore::ContentFilter::startFilteringMainResource): Became the client of the CachedRawResource in order to start the filtering process.
+        (WebCore::ContentFilter::unblockHandler): Returned the unblock handler.
+        (WebCore::ContentFilter::replacementData): Returned the replacement data.
+        (WebCore::ContentFilter::unblockRequestDeniedScript): Returned the unblock request denied script.
+        (WebCore::ContentFilter::responseReceived): Called responseReceived() on each filter using forEachContentFilterUntilBlocked().
+        (WebCore::ContentFilter::dataReceived): Ditto for dataReceived().
+        (WebCore::ContentFilter::notifyFinished): Ditto for finishedLoading().
+        (WebCore::ContentFilter::forEachContentFilterUntilBlocked): For each filter that needs more data, called the function.
+        If the filter blocked the load, called didDecide() with State::Blocked.
+        If all filters allowed the load, called didDecide() with State::Allowed.
+        (WebCore::ContentFilter::didDecide): Set m_state and called m_decisionFunction().
+        (WebCore::ContentFilter::addData): Deleted.
+        (WebCore::ContentFilter::finishedAddingData): Deleted.
+        (WebCore::ContentFilter::needsMoreData): Deleted.
+        (WebCore::ContentFilter::didBlockData): Deleted.
+        (WebCore::ContentFilter::getReplacementData): Deleted.
+        * loader/ContentFilter.h:
+        (WebCore::ContentFilter::type):
+        * loader/DocumentLoader.cpp:
+        (WebCore::DocumentLoader::DocumentLoader): Called ContentFilter::createIfNeeded() if not loading substitute data.
+        (WebCore::DocumentLoader::finishedLoading): Removed old ContentFilter code.
+        (WebCore::DocumentLoader::responseReceived): Ditto.
+        (WebCore::DocumentLoader::commitData): Ditto.
+        (WebCore::DocumentLoader::dataReceived): Ditto.
+        (WebCore::DocumentLoader::detachFromFrame): Set m_contentFilter to nullptr.
+        (WebCore::DocumentLoader::startLoadingMainResource): Called becomeMainResourceClientIfFilterAllows() instead of
+        becoming m_mainResource's client.
+        (WebCore::DocumentLoader::clearMainResource): Set m_contentFilter to nullptr.
+        (WebCore::DocumentLoader::becomeMainResourceClientIfFilterAllows): If ContentFilter is initialized, called
+        ContentFilter::startFilteringMainResource(). Otherwise added ourself as a client of m_mainResource.
+        (WebCore::DocumentLoader::installContentFilterUnblockHandler): Added a helper for creating and notifying
+        FrameLoaderClient of the unblock handler.
+        (WebCore::DocumentLoader::contentFilterDidDecide): Set m_contentFilter to nullptr. If the content filter
+        allowed the load, then added ourself as the CachedRawResource's client. Otherwise, installed the unblock handler
+        and scheduled a substitute data load with the replacement data.
+        * loader/DocumentLoader.h:
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::prepareForLoadStart): Removed call to PolicyChecker::prepareForLoadStart().
+        * loader/NavigationScheduler.cpp:
+        (WebCore::ScheduledSubstituteDataLoad::ScheduledSubstituteDataLoad): Added a ScheduledNavigation subclass that
+        calls FrameLoader::load() with a FrameLoadRequest containing substitute data.
+        (WebCore::NavigationScheduler::scheduleSubstituteDataLoad): Scheduled a substitute data load.
+        * loader/NavigationScheduler.h:
+        * loader/PolicyChecker.cpp:
+        (WebCore::PolicyChecker::checkNavigationPolicy): Reset m_contentFilterUnblockHandler if it couldn't handle the request.
+        (WebCore::PolicyChecker::prepareForLoadStart): Deleted.
+        * loader/PolicyChecker.h:
+        * platform/ContentFilterUnblockHandler.h:
+        (WebCore::ContentFilterUnblockHandler::unreachableURL):
+        (WebCore::ContentFilterUnblockHandler::setUnreachableURL):
+        (WebCore::ContentFilterUnblockHandler::unblockURLScheme): Renamed to ContentFilter::urlScheme().
+        * platform/PlatformContentFilter.h:
+        * platform/cocoa/ContentFilterUnblockHandlerCocoa.mm:
+        (WebCore::ContentFilterUnblockHandler::wrapWithDecisionHandler): Added a helper to wrap the unblock handler with an outer DecisionHandlerFunction.
+        (WebCore::ContentFilterUnblockHandler::encode): Added m_unreachableURL to the encoding.
+        (WebCore::ContentFilterUnblockHandler::decode): Ditto for the decoding.
+        (WebCore::ContentFilterUnblockHandler::canHandleRequest): Changed to call ContentFilter::urlScheme().
+        * platform/cocoa/NetworkExtensionContentFilter.h:
+        * platform/cocoa/NetworkExtensionContentFilter.mm:
+        (replacementDataFromDecisionInfo): Added a helper to extract replacement data from the decisionInfo dictionary.
+        (WebCore::NetworkExtensionContentFilter::enabled): Renamed from canHandleReponse(); only checks if the filter is enabled.
+        (WebCore::NetworkExtensionContentFilter::create): Created a new object.
+        (WebCore::NetworkExtensionContentFilter::NetworkExtensionContentFilter): Created a NEFilterSource immediately when using the modern API.
+        (WebCore::NetworkExtensionContentFilter::responseReceived): Created a NEFilterSource when using the legacy API.
+        Called -[NEFilterSource receivedResponse:decisionHandler:] when using the modern API.
+        (WebCore::NetworkExtensionContentFilter::addData): Stopped buffering the original data.
+        (WebCore::NetworkExtensionContentFilter::replacementData): Returned a SharedBuffer instead of a char* and int&.
+        (WebCore::NetworkExtensionContentFilter::canHandleResponse): Deleted.
+        (WebCore::createNEFilterSource): Deleted.
+        (WebCore::NetworkExtensionContentFilter::getReplacementData): Deleted.
+        * platform/cocoa/ParentalControlsContentFilter.h:
+        * platform/cocoa/ParentalControlsContentFilter.mm:
+        (WebCore::ParentalControlsContentFilter::enabled): Renamed from canHandleReponse(); only checks if the filter is enabled.
+        (WebCore::ParentalControlsContentFilter::create): Created a new object.
+        (WebCore::ParentalControlsContentFilter::ParentalControlsContentFilter): Initialized m_filterState to kWFEStateBuffering.
+        (WebCore::canHandleResponse): Added a helper to check if the response can be filtered.
+        (WebCore::ParentalControlsContentFilter::responseReceived): If !canHandleResponse(), set m_filterState to kWFEStateAllowed and return.
+        Otherwise created a new WebFilterEvaluator with the response.
+        (WebCore::ParentalControlsContentFilter::addData): Called updateFilterState().
+        (WebCore::ParentalControlsContentFilter::finishedAddingData): Ditto.
+        (WebCore::ParentalControlsContentFilter::needsMoreData): Changed to check m_filterState.
+        (WebCore::ParentalControlsContentFilter::didBlockData): Ditto.
+        (WebCore::ParentalControlsContentFilter::replacementData): Returned a SharedBuffer instead of a char* and int&.
+        (WebCore::ParentalControlsContentFilter::updateFilterState): Updated m_filterState by calling -[WebFilterEvaluator filterState].
+        (WebCore::ParentalControlsContentFilter::canHandleResponse): Deleted.
+        (WebCore::ParentalControlsContentFilter::getReplacementData): Deleted.
+        * platform/spi/cocoa/NEFilterSourceSPI.h:
+        * platform/spi/cocoa/WebFilterEvaluatorSPI.h:
+        * testing/MockContentFilter.cpp:
+        (WebCore::MockContentFilter::enabled): Renamed from canHandleReponse(); only checks if the filter is enabled.
+        (WebCore::MockContentFilter::create): Created a new object.
+        (WebCore::MockContentFilter::responseReceived): Called maybeDetermineStatus().
+        (WebCore::MockContentFilter::addData): Stopped buffering the original data.
+        (WebCore::MockContentFilter::replacementData): Returned a SharedBuffer instead of a char* and int&.
+        (WebCore::MockContentFilter::unblockHandler): Asserted that we blocked data.
+        (WebCore::MockContentFilter::canHandleResponse): Deleted.
+        (WebCore::MockContentFilter::MockContentFilter): Deleted.
+        (WebCore::MockContentFilter::getReplacementData): Deleted.
+        * testing/MockContentFilter.h:
+        * testing/MockContentFilterSettings.cpp:
+        (WebCore::MockContentFilterSettings::unblockRequestURL): Changed to use a StringBuilder.
+
 2015-04-04  Chris Fleizach  <cfleizach@apple.com>
 
         AX: Heuristic: Avoid exposing an element as clickable if mouse event delegation is handled on an AXElement with more than one descendant AXElement
index 27ec0da..de028fe 100644 (file)
 
 #if ENABLE(CONTENT_FILTERING)
 
-#include "DocumentLoader.h"
-#include "Frame.h"
+#include "CachedRawResource.h"
+#include "ContentFilterUnblockHandler.h"
 #include "NetworkExtensionContentFilter.h"
 #include "ParentalControlsContentFilter.h"
-#include "ScriptController.h"
-#include <bindings/ScriptValue.h>
+#include "SharedBuffer.h"
 #include <wtf/NeverDestroyed.h>
 #include <wtf/Vector.h>
 
@@ -52,107 +51,134 @@ Vector<ContentFilter::Type>& ContentFilter::types()
     return types;
 }
 
-std::unique_ptr<ContentFilter> ContentFilter::createIfNeeded(const ResourceResponse& response, DocumentLoader& documentLoader)
+std::unique_ptr<ContentFilter> ContentFilter::createIfNeeded(DecisionFunction decisionFunction)
 {
     Container filters;
     for (auto& type : types()) {
-        if (type.canHandleResponse(response))
-            filters.append(type.create(response));
+        if (!type.enabled())
+            continue;
+
+        auto filter = type.create();
+        ASSERT(filter);
+        filters.append(WTF::move(filter));
     }
 
     if (filters.isEmpty())
         return nullptr;
 
-    return std::make_unique<ContentFilter>(WTF::move(filters), documentLoader);
+    return std::make_unique<ContentFilter>(WTF::move(filters), WTF::move(decisionFunction));
 }
 
-ContentFilter::ContentFilter(Container contentFilters, DocumentLoader& documentLoader)
+ContentFilter::ContentFilter(Container contentFilters, DecisionFunction decisionFunction)
     : m_contentFilters { WTF::move(contentFilters) }
-    , m_documentLoader { documentLoader }
+    , m_decisionFunction { WTF::move(decisionFunction) }
 {
     ASSERT(!m_contentFilters.isEmpty());
 }
 
-void ContentFilter::addData(const char* data, int length)
+ContentFilter::~ContentFilter()
 {
-    ASSERT(needsMoreData());
-
-    for (auto& contentFilter : m_contentFilters)
-        contentFilter->addData(data, length);
+    if (!m_mainResource)
+        return;
+    ASSERT(m_mainResource->hasClient(this));
+    m_mainResource->removeClient(this);
 }
-    
-void ContentFilter::finishedAddingData()
-{
-    ASSERT(needsMoreData());
 
-    for (auto& contentFilter : m_contentFilters)
-        contentFilter->finishedAddingData();
+void ContentFilter::startFilteringMainResource(CachedRawResource& resource)
+{
+    ASSERT(m_state == State::Initialized);
+    m_state = State::Filtering;
+    ASSERT(!m_mainResource);
+    m_mainResource = &resource;
+    ASSERT(!m_mainResource->hasClient(this));
+    m_mainResource->addClient(this);
+}
 
-    ASSERT(!needsMoreData());
+ContentFilterUnblockHandler ContentFilter::unblockHandler() const
+{
+    ASSERT(m_state == State::Blocked);
+    ASSERT(m_blockingContentFilter);
+    ASSERT(m_blockingContentFilter->didBlockData());
+    return m_blockingContentFilter->unblockHandler();
 }
 
-bool ContentFilter::needsMoreData() const
+Ref<SharedBuffer> ContentFilter::replacementData() const
 {
-    for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->needsMoreData())
-            return true;
-    }
+    ASSERT(m_state == State::Blocked);
+    ASSERT(m_blockingContentFilter);
+    ASSERT(m_blockingContentFilter->didBlockData());
+    return m_blockingContentFilter->replacementData();
+}
 
-    return false;
+String ContentFilter::unblockRequestDeniedScript() const
+{
+    ASSERT(m_state == State::Blocked);
+    ASSERT(m_blockingContentFilter);
+    ASSERT(m_blockingContentFilter->didBlockData());
+    return m_blockingContentFilter->unblockRequestDeniedScript();
 }
 
-bool ContentFilter::didBlockData() const
+void ContentFilter::responseReceived(CachedResource* resource, const ResourceResponse& response)
 {
-    for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->didBlockData())
-            return true;
-    }
+    ASSERT(m_state == State::Filtering);
+    ASSERT_UNUSED(resource, resource == m_mainResource.get());
+    forEachContentFilterUntilBlocked([&response](PlatformContentFilter& contentFilter) {
+        contentFilter.responseReceived(response);
+    });
+}
 
-    return false;
+void ContentFilter::dataReceived(CachedResource* resource, const char* data, int length)
+{
+    ASSERT(m_state == State::Filtering);
+    ASSERT_UNUSED(resource, resource == m_mainResource.get());
+    forEachContentFilterUntilBlocked([data, length](PlatformContentFilter& contentFilter) {
+        contentFilter.addData(data, length);
+    });
 }
 
-const char* ContentFilter::getReplacementData(int& length) const
+void ContentFilter::notifyFinished(CachedResource* resource)
 {
-    ASSERT(!needsMoreData());
+    ASSERT(m_state == State::Filtering);
+    ASSERT_UNUSED(resource, resource == m_mainResource.get());
+    forEachContentFilterUntilBlocked([](PlatformContentFilter& contentFilter) {
+        contentFilter.finishedAddingData();
+    });
+}
 
+void ContentFilter::forEachContentFilterUntilBlocked(std::function<void(PlatformContentFilter&)> function)
+{
+    bool allFiltersAllowedLoad { true };
     for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->didBlockData())
-            return contentFilter->getReplacementData(length);
+        if (!contentFilter->needsMoreData()) {
+            ASSERT(!contentFilter->didBlockData());
+            continue;
+        }
+
+        function(*contentFilter);
+
+        if (contentFilter->didBlockData()) {
+            ASSERT(!m_blockingContentFilter);
+            m_blockingContentFilter = contentFilter.get();
+            didDecide(State::Blocked);
+            return;
+        } else if (contentFilter->needsMoreData())
+            allFiltersAllowedLoad = false;
     }
 
-    return m_contentFilters[0]->getReplacementData(length);
+    if (allFiltersAllowedLoad)
+        didDecide(State::Allowed);
 }
 
-ContentFilterUnblockHandler ContentFilter::unblockHandler() const
+void ContentFilter::didDecide(State state)
 {
-    ASSERT(didBlockData());
-
-    PlatformContentFilter* blockingFilter = nullptr;
-    for (auto& contentFilter : m_contentFilters) {
-        if (contentFilter->didBlockData()) {
-            blockingFilter = contentFilter.get();
-            break;
-        }
-    }
-    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());
-            });
-        }
-    };
+    ASSERT(m_state != State::Allowed);
+    ASSERT(m_state != State::Blocked);
+    ASSERT(state == State::Allowed || state == State::Blocked);
+    m_state = state;
+
+    // Calling m_decisionFunction might delete |this|.
+    if (m_decisionFunction)
+        m_decisionFunction();
 }
 
 } // namespace WebCore
index 2e8ccad..072925b 100644 (file)
 
 #if ENABLE(CONTENT_FILTERING)
 
-#include "PlatformContentFilter.h"
+#include "CachedRawResourceClient.h"
+#include "CachedResourceHandle.h"
+#include <functional>
 #include <wtf/Vector.h>
 
 namespace WebCore {
 
-class DocumentLoader;
-class ResourceResponse;
+class CachedRawResource;
+class ContentFilterUnblockHandler;
+class PlatformContentFilter;
+class SharedBuffer;
+
+class ContentFilter final : private CachedRawResourceClient {
+    WTF_MAKE_FAST_ALLOCATED;
+    WTF_MAKE_NONCOPYABLE(ContentFilter);
 
-class ContentFilter final : public PlatformContentFilter {
 public:
     template <typename T> static void addType() { types().append(type<T>()); }
-    static std::unique_ptr<ContentFilter> createIfNeeded(const ResourceResponse&, DocumentLoader&);
 
-    void addData(const char* data, int length) override;
-    void finishedAddingData() override;
-    bool needsMoreData() const override;
-    bool didBlockData() const override;
-    const char* getReplacementData(int& length) const override;
-    ContentFilterUnblockHandler unblockHandler() const override;
+    using DecisionFunction = std::function<void()>;
+    static std::unique_ptr<ContentFilter> createIfNeeded(DecisionFunction);
+    ~ContentFilter() override;
+
+    static const char* urlScheme() { return "x-apple-content-filter"; }
+
+    void startFilteringMainResource(CachedRawResource&);
+
+    enum class State {
+        Initialized,
+        Filtering,
+        Allowed,
+        Blocked
+    };
+    State state() const { return m_state; }
+    ContentFilterUnblockHandler unblockHandler() const;
+    Ref<SharedBuffer> replacementData() const;
+    String unblockRequestDeniedScript() const;
 
 private:
     struct Type {
-        const std::function<bool(const ResourceResponse&)> canHandleResponse;
-        const std::function<std::unique_ptr<PlatformContentFilter>(const ResourceResponse&)> create;
+        const std::function<bool()> enabled;
+        const std::function<std::unique_ptr<PlatformContentFilter>()> create;
     };
     template <typename T> static Type type();
     WEBCORE_EXPORT static Vector<Type>& types();
 
     using Container = Vector<std::unique_ptr<PlatformContentFilter>>;
-    friend std::unique_ptr<ContentFilter> std::make_unique<ContentFilter>(Container&&, DocumentLoader&);
-    explicit ContentFilter(Container, DocumentLoader&);
+    friend std::unique_ptr<ContentFilter> std::make_unique<ContentFilter>(Container&&, DecisionFunction&&);
+    ContentFilter(Container, DecisionFunction);
+
+    // CachedRawResourceClient
+    void responseReceived(CachedResource*, const ResourceResponse&) override;
+    void dataReceived(CachedResource*, const char* data, int length) override;
+
+    // CachedResourceClient
+    void notifyFinished(CachedResource*) override;
+
+    void forEachContentFilterUntilBlocked(std::function<void(PlatformContentFilter&)>);
+    void didDecide(State);
 
-    Container m_contentFilters;
-    DocumentLoader& m_documentLoader;
+    const Container m_contentFilters;
+    const DecisionFunction m_decisionFunction;
+    CachedResourceHandle<CachedRawResource> m_mainResource;
+    PlatformContentFilter* m_blockingContentFilter { nullptr };
+    State m_state { State::Initialized };
 };
 
 template <typename T>
 ContentFilter::Type ContentFilter::type()
 {
     static_assert(std::is_base_of<PlatformContentFilter, T>::value, "Type must be a PlatformContentFilter.");
-    return { T::canHandleResponse, T::create };
+    return { T::enabled, T::create };
 }
 
 } // namespace WebCore
index b109e74..3f5e12d 100644 (file)
@@ -58,6 +58,7 @@
 #include "ProgressTracker.h"
 #include "ResourceHandle.h"
 #include "SchemeRegistry.h"
+#include "ScriptController.h"
 #include "SecurityPolicy.h"
 #include "Settings.h"
 #include "SubresourceLoader.h"
@@ -140,6 +141,9 @@ DocumentLoader::DocumentLoader(const ResourceRequest& req, const SubstituteData&
     , m_waitingForContentPolicy(false)
     , m_subresourceLoadersArePageCacheAcceptable(false)
     , m_applicationCacheHost(adoptPtr(new ApplicationCacheHost(*this)))
+#if ENABLE(CONTENT_FILTERING)
+    , m_contentFilter(!substituteData.isValid() ? ContentFilter::createIfNeeded(std::bind(&DocumentLoader::contentFilterDidDecide, this)) : nullptr)
+#endif
 {
 }
 
@@ -401,21 +405,6 @@ void DocumentLoader::finishedLoading(double finishTime)
         frameLoader()->notifier().dispatchDidFinishLoading(this, identifier, finishTime);
     }
 
-#if ENABLE(CONTENT_FILTERING)
-    if (m_contentFilter && m_contentFilter->needsMoreData()) {
-        m_contentFilter->finishedAddingData();
-        int length;
-        const char* data = m_contentFilter->getReplacementData(length);
-        if (data)
-            dataReceived(m_mainResource.get(), data, length);
-
-        if (m_contentFilter->didBlockData()) {
-            frameLoader()->client().contentFilterDidBlockLoad(m_contentFilter->unblockHandler());
-            m_contentFilter = nullptr;
-        }
-    }
-#endif
-
     maybeFinishLoadingMultipartContent();
 
     double responseEndTime = finishTime;
@@ -662,10 +651,6 @@ void DocumentLoader::responseReceived(CachedResource* resource, const ResourceRe
     }
 #endif
 
-#if ENABLE(CONTENT_FILTERING)
-    m_contentFilter = ContentFilter::createIfNeeded(response, *this);
-#endif
-
     frameLoader()->policyChecker().checkContentPolicy(m_response, [this](PolicyAction policy) {
         continueAfterContentPolicy(policy);
     });
@@ -818,14 +803,6 @@ void DocumentLoader::commitData(const char* bytes, size_t length)
 
         bool userChosen;
         String encoding;
-#if ENABLE(CONTENT_FILTERING)
-        // The content filter's replacement data has a known encoding that might
-        // differ from the response's encoding.
-        if (m_contentFilter && m_contentFilter->didBlockData()) {
-            ASSERT(!m_contentFilter->needsMoreData());
-            userChosen = false;
-        } else
-#endif
         if (overrideEncoding().isNull()) {
             userChosen = false;
             encoding = response().textEncodingName();
@@ -870,26 +847,6 @@ void DocumentLoader::dataReceived(CachedResource* resource, const char* data, in
     ASSERT(!mainResourceLoader() || !mainResourceLoader()->defersLoading());
 #endif
 
-#if ENABLE(CONTENT_FILTERING)
-    bool loadWasBlockedBeforeFinishing = false;
-    if (m_contentFilter && m_contentFilter->needsMoreData()) {
-        m_contentFilter->addData(data, length);
-
-        if (m_contentFilter->needsMoreData()) {
-            // Since the filter still needs more data to make a decision,
-            // avoid committing this data to prevent partial rendering of
-            // content that might later be blocked.
-            return;
-        }
-
-        data = m_contentFilter->getReplacementData(length);
-        loadWasBlockedBeforeFinishing = m_contentFilter->didBlockData();
-
-        if (loadWasBlockedBeforeFinishing)
-            frameLoader()->client().contentFilterDidBlockLoad(m_contentFilter->unblockHandler());
-    }
-#endif
-
     if (m_identifierForLoadWithoutResourceLoader)
         frameLoader()->notifier().dispatchDidReceiveData(this, m_identifierForLoadWithoutResourceLoader, data, length, -1);
 
@@ -898,13 +855,6 @@ void DocumentLoader::dataReceived(CachedResource* resource, const char* data, in
 
     if (!isMultipartReplacingLoad())
         commitLoad(data, length);
-
-#if ENABLE(CONTENT_FILTERING)
-    if (loadWasBlockedBeforeFinishing) {
-        cancelMainResourceLoad(frameLoader()->cancelledError(m_request));
-        m_contentFilter = nullptr;
-    }
-#endif
 }
 
 void DocumentLoader::setupForReplace()
@@ -960,6 +910,9 @@ void DocumentLoader::detachFromFrame()
     stopLoading();
     if (m_mainResource && m_mainResource->hasClient(this))
         m_mainResource->removeClient(this);
+#if ENABLE(CONTENT_FILTERING)
+    m_contentFilter = nullptr;
+#endif
 
     m_applicationCacheHost->setDOMApplicationCache(nullptr);
     InspectorInstrumentation::loaderDetachedFromFrame(*m_frame, *this);
@@ -1471,7 +1424,12 @@ void DocumentLoader::startLoadingMainResource()
         frameLoader()->notifier().assignIdentifierToInitialRequest(m_identifierForLoadWithoutResourceLoader, this, request);
         frameLoader()->notifier().dispatchWillSendRequest(this, m_identifierForLoadWithoutResourceLoader, request, ResourceResponse());
     }
+
+#if ENABLE(CONTENT_FILTERING)
+    becomeMainResourceClientIfFilterAllows();
+#else
     m_mainResource->addClient(this);
+#endif
 
     // A bunch of headers are set when the underlying ResourceLoader is created, and m_request needs to include those.
     if (mainResourceLoader())
@@ -1507,6 +1465,9 @@ void DocumentLoader::clearMainResource()
 {
     if (m_mainResource && m_mainResource->hasClient(this))
         m_mainResource->removeClient(this);
+#if ENABLE(CONTENT_FILTERING)
+    m_contentFilter = nullptr;
+#endif
 
     m_mainResource = 0;
 }
@@ -1600,4 +1561,56 @@ void DocumentLoader::addPendingContentExtensionSheet(StyleSheetContents& sheet)
 }
 #endif
 
+#if ENABLE(CONTENT_FILTERING)
+void DocumentLoader::becomeMainResourceClientIfFilterAllows()
+{
+    ASSERT(m_mainResource);
+    if (m_contentFilter) {
+        ASSERT(m_contentFilter->state() == ContentFilter::State::Initialized);
+        m_contentFilter->startFilteringMainResource(*m_mainResource);
+    } else
+        m_mainResource->addClient(this);
+}
+
+void DocumentLoader::installContentFilterUnblockHandler(ContentFilter& contentFilter)
+{
+    ContentFilterUnblockHandler unblockHandler { contentFilter.unblockHandler() };
+    unblockHandler.setUnreachableURL(documentURL());
+    RefPtr<Frame> frame { this->frame() };
+    String unblockRequestDeniedScript { contentFilter.unblockRequestDeniedScript() };
+    if (!unblockRequestDeniedScript.isEmpty() && frame) {
+        static_assert(std::is_base_of<ThreadSafeRefCounted<Frame>, Frame>::value, "Frame must be ThreadSafeRefCounted.");
+        StringCapture capturedScript { unblockRequestDeniedScript };
+        unblockHandler.wrapWithDecisionHandler([frame, capturedScript](bool unblocked) {
+            if (!unblocked)
+                frame->script().executeScript(capturedScript.string());
+        });
+    }
+    frameLoader()->client().contentFilterDidBlockLoad(WTF::move(unblockHandler));
+}
+
+void DocumentLoader::contentFilterDidDecide()
+{
+    using State = ContentFilter::State;
+    ASSERT(m_contentFilter);
+    ASSERT(m_contentFilter->state() == State::Blocked || m_contentFilter->state() == State::Allowed);
+    std::unique_ptr<ContentFilter> contentFilter;
+    std::swap(contentFilter, m_contentFilter);
+    if (contentFilter->state() == State::Allowed) {
+        if (m_mainResource)
+            m_mainResource->addClient(this);
+        return;
+    }
+
+    installContentFilterUnblockHandler(*contentFilter);
+
+    URL blockedURL;
+    blockedURL.setProtocol(ContentFilter::urlScheme());
+    blockedURL.setHost(ASCIILiteral("blocked-page"));
+    SubstituteData substituteData { contentFilter->replacementData(), ASCIILiteral("text/html"), ASCIILiteral("UTF-8"), documentURL() };
+    frame()->navigationScheduler().scheduleSubstituteDataLoad(blockedURL, substituteData);
+}
+#endif
+
+
 } // namespace WebCore
index b03affe..ab57ee9 100644 (file)
@@ -64,7 +64,6 @@ namespace WebCore {
     class ArchiveResourceCollection;
     class CachedRawResource;
     class CachedResourceLoader;
-    class ContentFilter;
     class FormState;
     class Frame;
     class FrameLoader;
@@ -73,6 +72,10 @@ namespace WebCore {
     class SharedBuffer;
     class SubstituteResource;
 
+#if ENABLE(CONTENT_FILTERING)
+    class ContentFilter;
+#endif
+
     typedef HashMap<unsigned long, RefPtr<ResourceLoader>> ResourceLoaderMap;
     typedef Vector<ResourceResponse> ResponseVector;
 
@@ -330,6 +333,12 @@ namespace WebCore {
 
         void clearMainResource();
 
+#if ENABLE(CONTENT_FILTERING)
+        void becomeMainResourceClientIfFilterAllows();
+        void installContentFilterUnblockHandler(ContentFilter&);
+        void contentFilterDidDecide();
+#endif
+
         Frame* m_frame;
         Ref<CachedResourceLoader> m_cachedResourceLoader;
 
index 354ea15..5343224 100644 (file)
@@ -1124,7 +1124,6 @@ void FrameLoader::started()
 
 void FrameLoader::prepareForLoadStart()
 {
-    policyChecker().prepareForLoadStart();
     m_progressTracker->progressStarted();
     m_client.dispatchDidStartProvisionalLoad();
 
index 3af28db..4cad682 100644 (file)
@@ -278,6 +278,26 @@ private:
     bool m_haveToldClient;
 };
 
+class ScheduledSubstituteDataLoad : public ScheduledNavigation {
+public:
+    ScheduledSubstituteDataLoad(const URL& baseURL, const SubstituteData& substituteData)
+        : ScheduledNavigation { 0, LockHistory::No, LockBackForwardList::No, false, false }
+        , m_baseURL { baseURL }
+        , m_substituteData { substituteData }
+    {
+    }
+
+    void fire(Frame& frame) override
+    {
+        UserGestureIndicator gestureIndicator { wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture };
+        frame.loader().load(FrameLoadRequest { &frame, m_baseURL, m_substituteData });
+    }
+
+private:
+    URL m_baseURL;
+    SubstituteData m_substituteData;
+};
+
 NavigationScheduler::NavigationScheduler(Frame& frame)
     : m_frame(frame)
     , m_timer(*this, &NavigationScheduler::timerFired)
@@ -430,6 +450,12 @@ void NavigationScheduler::scheduleHistoryNavigation(int steps)
     schedule(std::make_unique<ScheduledHistoryNavigation>(steps));
 }
 
+void NavigationScheduler::scheduleSubstituteDataLoad(const URL& baseURL, const SubstituteData& substituteData)
+{
+    if (shouldScheduleNavigation())
+        schedule(std::make_unique<ScheduledSubstituteDataLoad>(baseURL, substituteData));
+}
+
 void NavigationScheduler::timerFired()
 {
     if (!m_frame.page())
index 8522840..4ae5b10 100644 (file)
@@ -41,6 +41,7 @@ class FormSubmission;
 class Frame;
 class ScheduledNavigation;
 class SecurityOrigin;
+class SubstituteData;
 class URL;
 
 class NavigationDisablerForBeforeUnload {
@@ -73,6 +74,7 @@ public:
     void scheduleFormSubmission(PassRefPtr<FormSubmission>);
     void scheduleRefresh();
     void scheduleHistoryNavigation(int steps);
+    void scheduleSubstituteDataLoad(const URL& baseURL, const SubstituteData&);
 
     void startTimer();
 
index 1215121..a3c96af 100644 (file)
@@ -56,13 +56,6 @@ 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));
@@ -120,7 +113,9 @@ void PolicyChecker::checkNavigationPolicy(const ResourceRequest& request, Docume
                 frame->loader().reload();
         });
         continueAfterNavigationPolicy(PolicyIgnore);
+        return;
     }
+    m_contentFilterUnblockHandler = { };
 #endif
 
     m_delegateIsDecidingNavigationPolicy = true;
index 3ca4ea8..5283ad4 100644 (file)
@@ -54,7 +54,6 @@ 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);
index 35c2655..7d044cc 100644 (file)
@@ -28,6 +28,7 @@
 
 #if ENABLE(CONTENT_FILTERING)
 
+#include "URL.h"
 #include <functional>
 #include <wtf/RetainPtr.h>
 #include <wtf/text/WTFString.h>
@@ -47,8 +48,6 @@ public:
     using DecisionHandlerFunction = std::function<void(bool unblocked)>;
     using UnblockRequesterFunction = std::function<void(DecisionHandlerFunction)>;
 
-    static const char* unblockURLScheme() { return "x-apple-content-filter"; }
-
     ContentFilterUnblockHandler() = default;
     WEBCORE_EXPORT ContentFilterUnblockHandler(String unblockURLHost, UnblockRequesterFunction);
 #if PLATFORM(IOS)
@@ -60,11 +59,15 @@ public:
     WEBCORE_EXPORT static bool decode(NSCoder *, ContentFilterUnblockHandler&);
     WEBCORE_EXPORT bool canHandleRequest(const ResourceRequest&) const;
     WEBCORE_EXPORT void requestUnblockAsync(DecisionHandlerFunction) const;
+    void wrapWithDecisionHandler(const DecisionHandlerFunction&);
 
     const String& unblockURLHost() const { return m_unblockURLHost; }
+    const URL& unreachableURL() const { return m_unreachableURL; }
+    void setUnreachableURL(const URL& url) { m_unreachableURL = url; }
 
 private:
     String m_unblockURLHost;
+    URL m_unreachableURL;
     UnblockRequesterFunction m_unblockRequester;
 #if PLATFORM(IOS)
     RetainPtr<WebFilterEvaluator> m_webFilterEvaluator;
index 2ee408a..f1d39cd 100644 (file)
 #ifndef PlatformContentFilter_h
 #define PlatformContentFilter_h
 
-#include "ContentFilterUnblockHandler.h"
+#include <wtf/Ref.h>
 #include <wtf/text/WTFString.h>
 
 namespace WebCore {
 
+class ContentFilterUnblockHandler;
 class ResourceResponse;
+class SharedBuffer;
 
 class PlatformContentFilter {
     WTF_MAKE_FAST_ALLOCATED;
@@ -42,11 +44,12 @@ protected:
 
 public:
     virtual ~PlatformContentFilter() { }
+    virtual void responseReceived(const ResourceResponse&) = 0;
     virtual void addData(const char* data, int length) = 0;
     virtual void finishedAddingData() = 0;
     virtual bool needsMoreData() const = 0;
     virtual bool didBlockData() const = 0;
-    virtual const char* getReplacementData(int& length) const = 0;
+    virtual Ref<SharedBuffer> replacementData() const = 0;
     virtual ContentFilterUnblockHandler unblockHandler() const = 0;
     virtual String unblockRequestDeniedScript() const { return emptyString(); }
 };
index 298b22c..b46cff8 100644 (file)
@@ -29,6 +29,7 @@
 #if ENABLE(CONTENT_FILTERING)
 
 #import "BlockExceptions.h"
+#import "ContentFilter.h"
 #import "ResourceRequest.h"
 
 #if PLATFORM(IOS)
@@ -43,6 +44,7 @@ static NSString * const webFilterEvaluatorKey { @"webFilterEvaluator" };
 #endif
 
 static NSString * const unblockURLHostKey { @"unblockURLHost" };
+static NSString * const unreachableURLKey { @"unreachableURL" };
 
 namespace WebCore {
 
@@ -60,6 +62,21 @@ ContentFilterUnblockHandler::ContentFilterUnblockHandler(String unblockURLHost,
 }
 #endif
 
+void ContentFilterUnblockHandler::wrapWithDecisionHandler(const DecisionHandlerFunction& decisionHandler)
+{
+    ContentFilterUnblockHandler wrapped { *this };
+    UnblockRequesterFunction wrappedRequester { [wrapped, decisionHandler](DecisionHandlerFunction wrappedDecisionHandler) {
+        wrapped.requestUnblockAsync([wrappedDecisionHandler, decisionHandler](bool unblocked) {
+            wrappedDecisionHandler(unblocked);
+            decisionHandler(unblocked);
+        });
+    }};
+#if PLATFORM(IOS)
+    m_webFilterEvaluator = nullptr;
+#endif
+    std::swap(m_unblockRequester, wrappedRequester);
+}
+
 bool ContentFilterUnblockHandler::needsUIProcess() const
 {
 #if PLATFORM(IOS)
@@ -74,6 +91,7 @@ void ContentFilterUnblockHandler::encode(NSCoder *coder) const
     ASSERT_ARG(coder, coder.allowsKeyedCoding && coder.requiresSecureCoding);
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
     [coder encodeObject:m_unblockURLHost forKey:unblockURLHostKey];
+    [coder encodeObject:(NSURL *)m_unreachableURL forKey:unreachableURLKey];
 #if PLATFORM(IOS)
     [coder encodeObject:m_webFilterEvaluator.get() forKey:webFilterEvaluatorKey];
 #endif
@@ -85,6 +103,7 @@ bool ContentFilterUnblockHandler::decode(NSCoder *coder, ContentFilterUnblockHan
     ASSERT_ARG(coder, coder.allowsKeyedCoding && coder.requiresSecureCoding);
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
     unblockHandler.m_unblockURLHost = [coder decodeObjectOfClass:[NSString class] forKey:unblockURLHostKey];
+    unblockHandler.m_unreachableURL = [coder decodeObjectOfClass:[NSURL class] forKey:unreachableURLKey];
 #if PLATFORM(IOS)
     unblockHandler.m_webFilterEvaluator = [coder decodeObjectOfClass:getWebFilterEvaluatorClass() forKey:webFilterEvaluatorKey];
 #endif
@@ -104,7 +123,7 @@ bool ContentFilterUnblockHandler::canHandleRequest(const ResourceRequest& reques
 #endif
     }
 
-    return request.url().protocolIs(unblockURLScheme()) && equalIgnoringCase(request.url().host(), m_unblockURLHost);
+    return request.url().protocolIs(ContentFilter::urlScheme()) && equalIgnoringCase(request.url().host(), m_unblockURLHost);
 }
 
 static inline void dispatchToMainThread(void (^block)())
index 26dfe5d..52efcbc 100644 (file)
 #define NetworkExtensionContentFilter_h
 
 #include "PlatformContentFilter.h"
-#include "SharedBuffer.h"
 #include <objc/NSObjCRuntime.h>
 #include <wtf/Compiler.h>
 #include <wtf/OSObjectPtr.h>
-#include <wtf/Ref.h>
 #include <wtf/RetainPtr.h>
 
 #define HAVE_NETWORK_EXTENSION PLATFORM(IOS) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && CPU(X86_64))
@@ -42,31 +40,31 @@ OBJC_CLASS NEFilterSource;
 OBJC_CLASS NSData;
 OBJC_CLASS NSDictionary;
 OBJC_CLASS NSMutableData;
-
+OBJC_CLASS NSString;
 namespace WebCore {
 
 class NetworkExtensionContentFilter final : public PlatformContentFilter {
-    friend std::unique_ptr<NetworkExtensionContentFilter> std::make_unique<NetworkExtensionContentFilter>(const ResourceResponse&);
+    friend std::unique_ptr<NetworkExtensionContentFilter> std::make_unique<NetworkExtensionContentFilter>();
 
 public:
-    static bool canHandleResponse(const ResourceResponse&);
-    static std::unique_ptr<NetworkExtensionContentFilter> create(const ResourceResponse&);
+    static bool enabled();
+    static std::unique_ptr<NetworkExtensionContentFilter> create();
 
+    void responseReceived(const ResourceResponse&) override;
     void addData(const char* data, int length) override;
     void finishedAddingData() override;
     bool needsMoreData() const override;
     bool didBlockData() const override;
-    const char* getReplacementData(int& length) const override;
+    Ref<SharedBuffer> replacementData() const override;
     ContentFilterUnblockHandler unblockHandler() const override;
 
 private:
-    explicit NetworkExtensionContentFilter(const ResourceResponse&);
+    NetworkExtensionContentFilter();
     void handleDecision(NEFilterSourceStatus, NSData *replacementData);
 
     NEFilterSourceStatus m_status;
     OSObjectPtr<dispatch_queue_t> m_queue;
     OSObjectPtr<dispatch_semaphore_t> m_semaphore;
-    Ref<SharedBuffer> m_originalData;
     RetainPtr<NSData> m_replacementData;
     RetainPtr<NEFilterSource> m_neFilterSource;
 };
index 3c11c75..9d7e66f 100644 (file)
 
 #if HAVE(NETWORK_EXTENSION)
 
+#import "ContentFilterUnblockHandler.h"
 #import "NEFilterSourceSPI.h"
 #import "ResourceResponse.h"
+#import "SharedBuffer.h"
 #import "SoftLinking.h"
+#import "URL.h"
 #import <objc/runtime.h>
 
 SOFT_LINK_FRAMEWORK(NetworkExtension);
 SOFT_LINK_CLASS(NetworkExtension, NEFilterSource);
 
 #if HAVE(MODERN_NE_FILTER_SOURCE)
-// FIXME: <rdar://problem/20165664> Expose decisionHandler dictionary keys as NSString constants in NEFilterSource.h
-static NSString * const optionsPageData = @"PageData";
-
 static inline NSData *replacementDataFromDecisionInfo(NSDictionary *decisionInfo)
 {
-    id replacementData = decisionInfo[optionsPageData];
+    id replacementData = decisionInfo[NEFilterSourceOptionsPageData];
     ASSERT(!replacementData || [replacementData isKindOfClass:[NSData class]]);
     return replacementData;
 }
@@ -50,37 +50,38 @@ static inline NSData *replacementDataFromDecisionInfo(NSDictionary *decisionInfo
 
 namespace WebCore {
 
-bool NetworkExtensionContentFilter::canHandleResponse(const ResourceResponse& response)
+bool NetworkExtensionContentFilter::enabled()
 {
-    return response.url().protocolIsInHTTPFamily() && [getNEFilterSourceClass() filterRequired];
+    return [getNEFilterSourceClass() filterRequired];
 }
 
-std::unique_ptr<NetworkExtensionContentFilter> NetworkExtensionContentFilter::create(const ResourceResponse& response)
+std::unique_ptr<NetworkExtensionContentFilter> NetworkExtensionContentFilter::create()
 {
-    return std::make_unique<NetworkExtensionContentFilter>(response);
+    return std::make_unique<NetworkExtensionContentFilter>();
 }
 
-static inline RetainPtr<NEFilterSource> createNEFilterSource(const URL& url, dispatch_queue_t decisionQueue)
-{
-#if HAVE(MODERN_NE_FILTER_SOURCE)
-    UNUSED_PARAM(url);
-    return adoptNS([allocNEFilterSourceInstance() initWithDecisionQueue:decisionQueue]);
-#else
-    UNUSED_PARAM(decisionQueue);
-    return adoptNS([allocNEFilterSourceInstance() initWithURL:url direction:NEFilterSourceDirectionInbound socketIdentifier:0]);
-#endif
-}
-
-NetworkExtensionContentFilter::NetworkExtensionContentFilter(const ResourceResponse& response)
+NetworkExtensionContentFilter::NetworkExtensionContentFilter()
     : m_status { NEFilterSourceStatusNeedsMoreData }
     , m_queue { adoptOSObject(dispatch_queue_create("com.apple.WebCore.NEFilterSourceQueue", DISPATCH_QUEUE_SERIAL)) }
     , m_semaphore { adoptOSObject(dispatch_semaphore_create(0)) }
-    , m_originalData { *SharedBuffer::create() }
-    , m_neFilterSource { createNEFilterSource(response.url(), m_queue.get()) }
+#if HAVE(MODERN_NE_FILTER_SOURCE)
+    , m_neFilterSource { adoptNS([allocNEFilterSourceInstance() initWithDecisionQueue:m_queue.get()]) }
+#endif
 {
     ASSERT([getNEFilterSourceClass() filterRequired]);
+}
 
-#if HAVE(MODERN_NE_FILTER_SOURCE)
+void NetworkExtensionContentFilter::responseReceived(const ResourceResponse& response)
+{
+    if (!response.url().protocolIsInHTTPFamily()) {
+        m_status = NEFilterSourceStatusPass;
+        return;
+    }
+
+#if !HAVE(MODERN_NE_FILTER_SOURCE)
+    ASSERT(!m_neFilterSource);
+    m_neFilterSource = adoptNS([allocNEFilterSourceInstance() initWithURL:response.url() direction:NEFilterSourceDirectionInbound socketIdentifier:0]);
+#else
     [m_neFilterSource receivedResponse:response.nsURLResponse() decisionHandler:[this](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
         handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
     }];
@@ -96,12 +97,6 @@ void NetworkExtensionContentFilter::addData(const char* data, int length)
 {
     RetainPtr<NSData> copiedData { [NSData dataWithBytes:(void*)data length:length] };
 
-    // FIXME: NEFilterSource doesn't buffer data like WebFilterEvaluator does,
-    // so we need to do it ourselves so getReplacementData() can return the
-    // original bytes back to the loader. We should find a way to remove this
-    // additional copy.
-    m_originalData->append((CFDataRef)copiedData.get());
-
 #if HAVE(MODERN_NE_FILTER_SOURCE)
     [m_neFilterSource receivedData:copiedData.get() decisionHandler:[this](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
         handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
@@ -148,15 +143,10 @@ bool NetworkExtensionContentFilter::didBlockData() const
     return m_status == NEFilterSourceStatusBlock;
 }
 
-const char* NetworkExtensionContentFilter::getReplacementData(int& length) const
+Ref<SharedBuffer> NetworkExtensionContentFilter::replacementData() const
 {
-    if (didBlockData()) {
-        length = [m_replacementData length];
-        return static_cast<const char*>([m_replacementData bytes]);
-    }
-
-    length = m_originalData->size();
-    return m_originalData->data();
+    ASSERT(didBlockData());
+    return adoptRef(*SharedBuffer::wrapNSData(m_replacementData.get()).leakRef());
 }
 
 ContentFilterUnblockHandler NetworkExtensionContentFilter::unblockHandler() const
index ea0a4e2..7990ad2 100644 (file)
@@ -36,22 +36,25 @@ OBJC_CLASS WebFilterEvaluator;
 namespace WebCore {
 
 class ParentalControlsContentFilter final : public PlatformContentFilter {
-    friend std::unique_ptr<ParentalControlsContentFilter> std::make_unique<ParentalControlsContentFilter>(const ResourceResponse&);
+    friend std::unique_ptr<ParentalControlsContentFilter> std::make_unique<ParentalControlsContentFilter>();
 
 public:
-    static bool canHandleResponse(const ResourceResponse&);
-    static std::unique_ptr<ParentalControlsContentFilter> create(const ResourceResponse&);
+    static bool enabled();
+    static std::unique_ptr<ParentalControlsContentFilter> create();
 
+    void responseReceived(const ResourceResponse&) override;
     void addData(const char* data, int length) override;
     void finishedAddingData() override;
     bool needsMoreData() const override;
     bool didBlockData() const override;
-    const char* getReplacementData(int& length) const override;
+    Ref<SharedBuffer> replacementData() const override;
     ContentFilterUnblockHandler unblockHandler() const override;
 
 private:
-    explicit ParentalControlsContentFilter(const ResourceResponse&);
+    ParentalControlsContentFilter();
+    void updateFilterState();
 
+    OSStatus m_filterState;
     RetainPtr<WebFilterEvaluator> m_webFilterEvaluator;
     RetainPtr<NSData> m_replacementData;
 };
index 904cfcf..f50d710 100644 (file)
@@ -26,7 +26,9 @@
 #import "config.h"
 #import "ParentalControlsContentFilter.h"
 
+#import "ContentFilterUnblockHandler.h"
 #import "ResourceResponse.h"
+#import "SharedBuffer.h"
 #import "SoftLinking.h"
 #import "WebFilterEvaluatorSPI.h"
 #import <objc/runtime.h>
@@ -36,36 +38,49 @@ SOFT_LINK_CLASS(WebContentAnalysis, WebFilterEvaluator);
 
 namespace WebCore {
 
-bool ParentalControlsContentFilter::canHandleResponse(const ResourceResponse& response)
+bool ParentalControlsContentFilter::enabled()
 {
-    if (!response.url().protocolIsInHTTPFamily())
-        return false;
+    return [getWebFilterEvaluatorClass() isManagedSession];
+}
 
-    if ([getWebFilterEvaluatorClass() isManagedSession]) {
-#if PLATFORM(MAC)
-        if (response.url().protocolIs("https"))
-#endif
-            return true;
-    }
+std::unique_ptr<ParentalControlsContentFilter> ParentalControlsContentFilter::create()
+{
+    return std::make_unique<ParentalControlsContentFilter>();
+}
 
-    return false;
+ParentalControlsContentFilter::ParentalControlsContentFilter()
+    : m_filterState { kWFEStateBuffering }
+{
+    ASSERT([getWebFilterEvaluatorClass() isManagedSession]);
 }
 
-std::unique_ptr<ParentalControlsContentFilter> ParentalControlsContentFilter::create(const ResourceResponse& response)
+static inline bool canHandleResponse(const ResourceResponse& response)
 {
-    return std::make_unique<ParentalControlsContentFilter>(response);
+#if PLATFORM(MAC)
+    return response.url().protocolIs("https");
+#else
+    return response.url().protocolIsInHTTPFamily();
+#endif
 }
 
-ParentalControlsContentFilter::ParentalControlsContentFilter(const ResourceResponse& response)
-    : m_webFilterEvaluator { adoptNS([allocWebFilterEvaluatorInstance() initWithResponse:response.nsURLResponse()]) }
+void ParentalControlsContentFilter::responseReceived(const ResourceResponse& response)
 {
-    ASSERT([getWebFilterEvaluatorClass() isManagedSession]);
+    ASSERT(!m_webFilterEvaluator);
+
+    if (!canHandleResponse(response)) {
+        m_filterState = kWFEStateAllowed;
+        return;
+    }
+
+    m_webFilterEvaluator = adoptNS([allocWebFilterEvaluatorInstance() initWithResponse:response.nsURLResponse()]);
+    updateFilterState();
 }
 
 void ParentalControlsContentFilter::addData(const char* data, int length)
 {
     ASSERT(![m_replacementData.get() length]);
     m_replacementData = [m_webFilterEvaluator addData:[NSData dataWithBytesNoCopy:(void*)data length:length freeWhenDone:NO]];
+    updateFilterState();
     ASSERT(needsMoreData() || [m_replacementData.get() length]);
 }
 
@@ -73,22 +88,23 @@ void ParentalControlsContentFilter::finishedAddingData()
 {
     ASSERT(![m_replacementData.get() length]);
     m_replacementData = [m_webFilterEvaluator dataComplete];
+    updateFilterState();
 }
 
 bool ParentalControlsContentFilter::needsMoreData() const
 {
-    return [m_webFilterEvaluator filterState] == kWFEStateBuffering;
+    return m_filterState == kWFEStateBuffering;
 }
 
 bool ParentalControlsContentFilter::didBlockData() const
 {
-    return [m_webFilterEvaluator wasBlocked];
+    return m_filterState == kWFEStateBlocked;
 }
 
-const char* ParentalControlsContentFilter::getReplacementData(int& length) const
+Ref<SharedBuffer> ParentalControlsContentFilter::replacementData() const
 {
-    length = [m_replacementData length];
-    return static_cast<const char*>([m_replacementData bytes]);
+    ASSERT(didBlockData());
+    return adoptRef(*SharedBuffer::wrapNSData(m_replacementData.get()).leakRef());
 }
 
 ContentFilterUnblockHandler ParentalControlsContentFilter::unblockHandler() const
@@ -100,4 +116,9 @@ ContentFilterUnblockHandler ParentalControlsContentFilter::unblockHandler() cons
 #endif
 }
 
+void ParentalControlsContentFilter::updateFilterState()
+{
+    m_filterState = [m_webFilterEvaluator filterState];
+}
+
 } // namespace WebCore
index a76c432..2eb31a1 100644 (file)
@@ -68,6 +68,7 @@ typedef void (^NEFilterSourceDecisionHandler)(NEFilterSourceStatus, NSDictionary
 - (void)finishedLoadingWithDecisionHandler:(NEFilterSourceDecisionHandler)decisionHandler;
 - (void)remediateWithDecisionHandler:(NEFilterSourceDecisionHandler)decisionHandler;
 @end
+
 #endif
 
 #endif
index 9586a8e..a5debc9 100644 (file)
 #import <TargetConditionals.h>
 
 enum {
-    kWFEStateBuffering = 2
+    kWFEStateAllowed = 0,
+    kWFEStateBlocked = 1,
+    kWFEStateBuffering = 2,
+    kWFEStateEvaluating = 3
 };
 
 @interface WebFilterEvaluator : NSObject
index 8490923..7de50f9 100644 (file)
 #if ENABLE(CONTENT_FILTERING)
 
 #include "ContentFilter.h"
+#include "ContentFilterUnblockHandler.h"
+#include "SharedBuffer.h"
 #include <mutex>
 #include <wtf/text/CString.h>
+#include <wtf/text/StringBuilder.h>
 
 namespace WebCore {
 
@@ -50,24 +53,23 @@ static inline MockContentFilterSettings& settings()
     return MockContentFilterSettings::singleton();
 }
 
-bool MockContentFilter::canHandleResponse(const ResourceResponse&)
+bool MockContentFilter::enabled()
 {
     return settings().enabled();
 }
 
-std::unique_ptr<MockContentFilter> MockContentFilter::create(const ResourceResponse& response)
+std::unique_ptr<MockContentFilter> MockContentFilter::create()
 {
-    return std::make_unique<MockContentFilter>(response);
+    return std::make_unique<MockContentFilter>();
 }
 
-MockContentFilter::MockContentFilter(const ResourceResponse&)
+void MockContentFilter::responseReceived(const ResourceResponse&)
 {
     maybeDetermineStatus(DecisionPoint::AfterResponse);
 }
 
-void MockContentFilter::addData(const char* data, int length)
+void MockContentFilter::addData(const char*, int)
 {
-    m_replacementData.append(data, length);
     maybeDetermineStatus(DecisionPoint::AfterAddData);
 }
 
@@ -86,14 +88,15 @@ bool MockContentFilter::didBlockData() const
     return m_status == Status::Blocked;
 }
 
-const char* MockContentFilter::getReplacementData(int& length) const
+Ref<SharedBuffer> MockContentFilter::replacementData() const
 {
-    length = m_replacementData.size();
-    return m_replacementData.data();
+    ASSERT(didBlockData());
+    return adoptRef(*SharedBuffer::create(m_replacementData.data(), m_replacementData.size()).leakRef());
 }
 
 ContentFilterUnblockHandler MockContentFilter::unblockHandler() const
 {
+    ASSERT(didBlockData());
     using DecisionHandlerFunction = ContentFilterUnblockHandler::DecisionHandlerFunction;
 
     return ContentFilterUnblockHandler {
index 0fa1358..d9053b0 100644 (file)
 namespace WebCore {
 
 class MockContentFilter final : public PlatformContentFilter {
-    friend std::unique_ptr<MockContentFilter> std::make_unique<MockContentFilter>(const ResourceResponse&);
+    friend std::unique_ptr<MockContentFilter> std::make_unique<MockContentFilter>();
 
 public:
     static void ensureInstalled();
-    static bool canHandleResponse(const ResourceResponse&);
-    static std::unique_ptr<MockContentFilter> create(const ResourceResponse&);
+    static bool enabled();
+    static std::unique_ptr<MockContentFilter> create();
 
+    void responseReceived(const ResourceResponse&) override;
     void addData(const char* data, int length) override;
     void finishedAddingData() override;
     bool needsMoreData() const override;
     bool didBlockData() const override;
-    const char* getReplacementData(int& length) const override;
+    Ref<SharedBuffer> replacementData() const override;
     ContentFilterUnblockHandler unblockHandler() const override;
     String unblockRequestDeniedScript() const override;
 
@@ -54,7 +55,7 @@ private:
         Blocked
     };
 
-    explicit MockContentFilter(const ResourceResponse&);
+    MockContentFilter() = default;
     void maybeDetermineStatus(MockContentFilterSettings::DecisionPoint);
 
     Vector<char> m_replacementData;
index a47d4ec..086862b 100644 (file)
 
 #if ENABLE(CONTENT_FILTERING)
 
+#include "ContentFilter.h"
 #include "ContentFilterUnblockHandler.h"
 #include <mutex>
 #include <wtf/NeverDestroyed.h>
+#include <wtf/text/StringBuilder.h>
 
 namespace WebCore {
 
@@ -50,9 +52,11 @@ 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());
+        StringBuilder unblockRequestURLBuilder;
+        unblockRequestURLBuilder.append(ContentFilter::urlScheme());
+        unblockRequestURLBuilder.append("://");
+        unblockRequestURLBuilder.append(unblockURLHost());
+        unblockRequestURL.construct(unblockRequestURLBuilder.toString());
     });
     return unblockRequestURL;
 }
index 17124d5..ff3160c 100644 (file)
@@ -1,3 +1,14 @@
+2015-04-04  Andy Estes  <aestes@apple.com>
+
+        [Content Filtering] Blocked page is not always displayed when it should be
+        https://bugs.webkit.org/show_bug.cgi?id=143410
+
+        Reviewed by Andreas Kling.
+
+        * UIProcess/WebFrameProxy.cpp:
+        (WebKit::WebFrameProxy::didStartProvisionalLoad): Stopped clearing m_contentFilterUnblockHandler here.
+        (WebKit::WebFrameProxy::didHandleContentFilterUnblockNavigation): Started doing it here instead.
+
 2015-04-04  Chris Dumez  <cdumez@apple.com>
 
         [WK2][Cocoa] Add a way to temporarily disable the WebKit Network Cache for testing
index 2e2785d..7b7dac2 100644 (file)
@@ -127,9 +127,6 @@ bool WebFrameProxy::isDisplayingPDFDocument() const
 void WebFrameProxy::didStartProvisionalLoad(const String& url)
 {
     m_frameLoadState.didStartProvisionalLoad(url);
-#if ENABLE(CONTENT_FILTERING)
-    m_contentFilterUnblockHandler = { };
-#endif
 }
 
 void WebFrameProxy::didReceiveServerRedirectForProvisionalLoad(const String& url)
@@ -236,8 +233,10 @@ void WebFrameProxy::setUnreachableURL(const String& unreachableURL)
 #if ENABLE(CONTENT_FILTERING)
 bool WebFrameProxy::didHandleContentFilterUnblockNavigation(const WebCore::ResourceRequest& request)
 {
-    if (!m_contentFilterUnblockHandler.canHandleRequest(request))
+    if (!m_contentFilterUnblockHandler.canHandleRequest(request)) {
+        m_contentFilterUnblockHandler = { };
         return false;
+    }
 
     RefPtr<WebPageProxy> page { m_page };
     ASSERT(page);