Add WKContentRuleList notify action type
authorachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 25 Sep 2017 19:17:28 +0000 (19:17 +0000)
committerachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 25 Sep 2017 19:17:28 +0000 (19:17 +0000)
https://bugs.webkit.org/show_bug.cgi?id=177013
<rdar://problem/31073936>

Reviewed by Darin Adler.

Source/WebCore:

Covered by new API tests.

* contentextensions/ContentExtensionActions.h:
(WebCore::ContentExtensions::hasStringArgument):
* contentextensions/ContentExtensionCompiler.cpp:
(WebCore::ContentExtensions::resolvePendingDisplayNoneActions):
(WebCore::ContentExtensions::serializeActions):
* contentextensions/ContentExtensionError.cpp:
(WebCore::ContentExtensions::contentExtensionErrorCategory):
* contentextensions/ContentExtensionError.h:
* contentextensions/ContentExtensionParser.cpp:
(WebCore::ContentExtensions::loadAction):
* contentextensions/ContentExtensionRule.cpp:
(WebCore::ContentExtensions::Action::deserialize):
(WebCore::ContentExtensions::Action::deserializeType):
(WebCore::ContentExtensions::Action::serializedLength):
* contentextensions/ContentExtensionRule.h:
(WebCore::ContentExtensions::Action::Action):
* contentextensions/ContentExtensionsBackend.cpp:
(WebCore::ContentExtensions::ContentExtensionsBackend::processContentExtensionRulesForLoad):
(WebCore::ContentExtensions::ContentExtensionsBackend::processContentExtensionRulesForPingLoad):
(WebCore::ContentExtensions::applyBlockedStatusToRequest):
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::loadResourceSynchronously):
* loader/PingLoader.cpp:
(WebCore::processContentExtensionRulesForLoad):
* loader/ResourceLoader.cpp:
(WebCore::ResourceLoader::willSendRequestInternal):
* loader/cache/CachedResourceLoader.cpp:
(WebCore::CachedResourceLoader::requestResource):
* loader/cache/CachedResourceRequest.cpp:
(WebCore::CachedResourceRequest::applyBlockedStatus):
* loader/cache/CachedResourceRequest.h:
* page/ChromeClient.h:

Source/WebKit:

* NetworkProcess/PingLoad.cpp:
(WebKit::PingLoad::processContentExtensionRulesForLoad):
* UIProcess/API/APINavigationClient.h:
(API::NavigationClient::contentRuleListNotification):
* UIProcess/API/C/WKPage.cpp:
(WKPageSetPageNavigationClient):
* UIProcess/API/C/WKPageNavigationClient.h:
* UIProcess/API/Cocoa/WKNavigationDelegatePrivate.h:
* UIProcess/Cocoa/NavigationState.h:
* UIProcess/Cocoa/NavigationState.mm:
(WebKit::NavigationState::setNavigationDelegate):
(WebKit::NavigationState::NavigationClient::contentRuleListNotification):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::contentRuleListNotification):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* WebProcess/WebCoreSupport/WebChromeClient.cpp:
(WebKit::WebChromeClient::contentRuleListNotification):
* WebProcess/WebCoreSupport/WebChromeClient.h:

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
(WebCore::ContentExtensions::operator<<):
(TestWebKitAPI::InMemoryCompiledContentExtension::create):
(TestWebKitAPI::InMemoryCompiledContentExtension::data):
(TestWebKitAPI::InMemoryCompiledContentExtension::InMemoryCompiledContentExtension):
(TestWebKitAPI::makeBackend):
(TestWebKitAPI::TEST_F):
(TestWebKitAPI::actionsEqual):
(TestWebKitAPI::sequenceInstances):
(TestWebKitAPI::InMemoryCompiledContentExtension::createFromFilter): Deleted.
(TestWebKitAPI::InMemoryCompiledContentExtension::~InMemoryCompiledContentExtension): Deleted.
* TestWebKitAPI/Tests/WebKitCocoa/ContentRuleListNotification.mm: Added.
(-[ContentRuleListNotificationDelegate _webView:URL:contentRuleListIdentifiers:notifications:]):
(-[ContentRuleListNotificationDelegate webView:startURLSchemeTask:]):
(-[ContentRuleListNotificationDelegate webView:stopURLSchemeTask:]):
(-[ContentRuleListNotificationDelegate webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:]):
(makeWarnContentRuleList):
(TEST):

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

33 files changed:
Source/WebCore/ChangeLog
Source/WebCore/contentextensions/ContentExtensionActions.h
Source/WebCore/contentextensions/ContentExtensionCompiler.cpp
Source/WebCore/contentextensions/ContentExtensionError.cpp
Source/WebCore/contentextensions/ContentExtensionError.h
Source/WebCore/contentextensions/ContentExtensionParser.cpp
Source/WebCore/contentextensions/ContentExtensionRule.cpp
Source/WebCore/contentextensions/ContentExtensionRule.h
Source/WebCore/contentextensions/ContentExtensionsBackend.cpp
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/loader/PingLoader.cpp
Source/WebCore/loader/ResourceLoader.cpp
Source/WebCore/loader/cache/CachedResourceLoader.cpp
Source/WebCore/loader/cache/CachedResourceRequest.cpp
Source/WebCore/loader/cache/CachedResourceRequest.h
Source/WebCore/page/ChromeClient.h
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/PingLoad.cpp
Source/WebKit/UIProcess/API/APINavigationClient.h
Source/WebKit/UIProcess/API/C/WKPage.cpp
Source/WebKit/UIProcess/API/C/WKPageNavigationClient.h
Source/WebKit/UIProcess/API/Cocoa/WKNavigationDelegatePrivate.h
Source/WebKit/UIProcess/Cocoa/NavigationState.h
Source/WebKit/UIProcess/Cocoa/NavigationState.mm
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebPageProxy.messages.in
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp
Tools/TestWebKitAPI/Tests/WebKitCocoa/ContentRuleListNotification.mm [new file with mode: 0644]

index 0ee405f..4a8dfde 100644 (file)
@@ -1,3 +1,46 @@
+2017-09-25  Alex Christensen  <achristensen@webkit.org>
+
+        Add WKContentRuleList notify action type
+        https://bugs.webkit.org/show_bug.cgi?id=177013
+        <rdar://problem/31073936>
+
+        Reviewed by Darin Adler.
+
+        Covered by new API tests.
+
+        * contentextensions/ContentExtensionActions.h:
+        (WebCore::ContentExtensions::hasStringArgument):
+        * contentextensions/ContentExtensionCompiler.cpp:
+        (WebCore::ContentExtensions::resolvePendingDisplayNoneActions):
+        (WebCore::ContentExtensions::serializeActions):
+        * contentextensions/ContentExtensionError.cpp:
+        (WebCore::ContentExtensions::contentExtensionErrorCategory):
+        * contentextensions/ContentExtensionError.h:
+        * contentextensions/ContentExtensionParser.cpp:
+        (WebCore::ContentExtensions::loadAction):
+        * contentextensions/ContentExtensionRule.cpp:
+        (WebCore::ContentExtensions::Action::deserialize):
+        (WebCore::ContentExtensions::Action::deserializeType):
+        (WebCore::ContentExtensions::Action::serializedLength):
+        * contentextensions/ContentExtensionRule.h:
+        (WebCore::ContentExtensions::Action::Action):
+        * contentextensions/ContentExtensionsBackend.cpp:
+        (WebCore::ContentExtensions::ContentExtensionsBackend::processContentExtensionRulesForLoad):
+        (WebCore::ContentExtensions::ContentExtensionsBackend::processContentExtensionRulesForPingLoad):
+        (WebCore::ContentExtensions::applyBlockedStatusToRequest):
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::loadResourceSynchronously):
+        * loader/PingLoader.cpp:
+        (WebCore::processContentExtensionRulesForLoad):
+        * loader/ResourceLoader.cpp:
+        (WebCore::ResourceLoader::willSendRequestInternal):
+        * loader/cache/CachedResourceLoader.cpp:
+        (WebCore::CachedResourceLoader::requestResource):
+        * loader/cache/CachedResourceRequest.cpp:
+        (WebCore::CachedResourceRequest::applyBlockedStatus):
+        * loader/cache/CachedResourceRequest.h:
+        * page/ChromeClient.h:
+
 2017-09-25  Sam Weinig  <sam@webkit.org>
 
         [WebIDL] Remove GetterMayThrowException
index 27cd84a..cb02ead 100644 (file)
 
 #if ENABLE(CONTENT_EXTENSIONS)
 
+#include <wtf/HashSet.h>
+#include <wtf/text/WTFString.h>
+
 namespace WebCore {
 
+class Page;
 class ResourceRequest;
 
 namespace ContentExtensions {
@@ -39,17 +43,35 @@ enum class ActionType : uint8_t {
     BlockLoad,
     BlockCookies,
     CSSDisplayNoneSelector,
-    IgnorePreviousRules = 3,
-    MakeHTTPS = 4,
+    Notify,
+    IgnorePreviousRules,
+    MakeHTTPS,
 };
 
+static inline bool hasStringArgument(ActionType actionType)
+{
+    switch (actionType) {
+    case ActionType::CSSDisplayNoneSelector:
+    case ActionType::Notify:
+        return true;
+    case ActionType::BlockLoad:
+    case ActionType::BlockCookies:
+    case ActionType::IgnorePreviousRules:
+    case ActionType::MakeHTTPS:
+        return false;
+    }
+    ASSERT_NOT_REACHED();
+    return false;
+}
+
 struct BlockedStatus {
     bool blockedLoad { false };
     bool blockedCookies { false };
     bool madeHTTPS { false };
+    HashSet<std::pair<String, String>> notifications;
 };
 
-WEBCORE_EXPORT void applyBlockedStatusToRequest(const BlockedStatus&, ResourceRequest&);
+WEBCORE_EXPORT void applyBlockedStatusToRequest(const BlockedStatus&, Page*, ResourceRequest&);
 
 } // namespace ContentExtensions
 
index fbfecf5..56dff6a 100644 (file)
@@ -68,48 +68,41 @@ static void serializeString(Vector<SerializedActionByte>& actions, const String&
     }
 }
 
+// css-display-none combining is special because we combine the string arguments with commas because we know they are css selectors.
 struct PendingDisplayNoneActions {
-    Vector<String> selectors;
-    Vector<unsigned> clientLocations;
+    StringBuilder combinedSelectors;
+    Vector<uint32_t> clientLocations;
 };
+
 using PendingDisplayNoneActionsMap = HashMap<Trigger, PendingDisplayNoneActions, TriggerHash, TriggerHashTraits>;
 
-static void resolvePendingDisplayNoneActions(Vector<SerializedActionByte>& actions, Vector<unsigned>& actionLocations, PendingDisplayNoneActionsMap& pendingDisplayNoneActionsMap)
+static void resolvePendingDisplayNoneActions(Vector<SerializedActionByte>& actions, Vector<uint32_t>& actionLocations, PendingDisplayNoneActionsMap& map)
 {
-    for (auto& slot : pendingDisplayNoneActionsMap) {
-        PendingDisplayNoneActions& pendingActions = slot.value;
-
-        StringBuilder combinedSelectors;
-        for (unsigned i = 0; i < pendingActions.selectors.size(); ++i) {
-            if (i)
-                combinedSelectors.append(',');
-            combinedSelectors.append(pendingActions.selectors[i]);
-        }
-
-        unsigned actionLocation = actions.size();
+    for (auto& pendingDisplayNoneActions : map.values()) {
+        uint32_t actionLocation = actions.size();
         actions.append(static_cast<SerializedActionByte>(ActionType::CSSDisplayNoneSelector));
-        serializeString(actions, combinedSelectors.toString());
-        for (unsigned clientLocation : pendingActions.clientLocations)
+        serializeString(actions, pendingDisplayNoneActions.combinedSelectors.toString());
+        for (uint32_t clientLocation : pendingDisplayNoneActions.clientLocations)
             actionLocations[clientLocation] = actionLocation;
     }
-    pendingDisplayNoneActionsMap.clear();
+    map.clear();
 }
 
 static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& ruleList, Vector<SerializedActionByte>& actions)
 {
     ASSERT(!actions.size());
 
-    Vector<unsigned> actionLocations;
+    Vector<uint32_t> actionLocations;
 
-    // Order only matters because of IgnorePreviousRules. All other identical actions can be combined between each IgnorePreviousRules
-    // and CSSDisplayNone strings can be combined if their triggers are identical.
     using ActionLocation = uint32_t;
     using ActionMap = HashMap<ResourceFlags, ActionLocation, DefaultHash<ResourceFlags>::Hash, WTF::UnsignedWithZeroKeyHashTraits<ResourceFlags>>;
+    using StringActionMap = HashMap<std::pair<String, ResourceFlags>, ActionLocation, DefaultHash<std::pair<String, ResourceFlags>>::Hash, PairHashTraits<HashTraits<String>, WTF::UnsignedWithZeroKeyHashTraits<ResourceFlags>>>;
     ActionMap blockLoadActionsMap;
     ActionMap blockCookiesActionsMap;
     PendingDisplayNoneActionsMap cssDisplayNoneActionsMap;
     ActionMap ignorePreviousRuleActionsMap;
     ActionMap makeHTTPSActionsMap;
+    StringActionMap notifyActionsMap;
 
     for (unsigned ruleIndex = 0; ruleIndex < ruleList.size(); ++ruleIndex) {
         const ContentExtensionRule& rule = ruleList[ruleIndex];
@@ -122,6 +115,7 @@ static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& rul
             blockCookiesActionsMap.clear();
             cssDisplayNoneActionsMap.clear();
             makeHTTPSActionsMap.clear();
+            notifyActionsMap.clear();
         } else
             ignorePreviousRuleActionsMap.clear();
 
@@ -131,16 +125,17 @@ static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& rul
             actionLocations.append(actions.size());
 
             actions.append(static_cast<SerializedActionByte>(actionType));
-            if (actionType == ActionType::CSSDisplayNoneSelector)
+            if (hasStringArgument(actionType))
                 serializeString(actions, rule.action().stringArgument());
+            else
+                ASSERT(rule.action().stringArgument().isNull());
             continue;
         }
 
         ResourceFlags flags = rule.trigger().flags;
         unsigned actionLocation = std::numeric_limits<unsigned>::max();
         
-        auto findOrMakeActionLocation = [&] (ActionMap& map) 
-        {
+        auto findOrMakeActionLocation = [&] (ActionMap& map) {
             const auto existingAction = map.find(flags);
             if (existingAction == map.end()) {
                 actionLocation = actions.size();
@@ -149,13 +144,27 @@ static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& rul
             } else
                 actionLocation = existingAction->value;
         };
+        
+        auto findOrMakeStringActionLocation = [&] (StringActionMap& map) {
+            const String& argument = rule.action().stringArgument();
+            auto existingAction = map.find(std::make_pair(argument, flags));
+            if (existingAction == map.end()) {
+                actionLocation = actions.size();
+                actions.append(static_cast<SerializedActionByte>(actionType));
+                serializeString(actions, argument);
+                map.set(std::make_pair(argument, flags), actionLocation);
+            } else
+                actionLocation = existingAction->value;
+        };
 
         switch (actionType) {
         case ActionType::CSSDisplayNoneSelector: {
             const auto addResult = cssDisplayNoneActionsMap.add(rule.trigger(), PendingDisplayNoneActions());
-            PendingDisplayNoneActions& pendingDisplayNoneActions = addResult.iterator->value;
-            pendingDisplayNoneActions.selectors.append(rule.action().stringArgument());
-            pendingDisplayNoneActions.clientLocations.append(actionLocations.size());
+            auto& pendingStringActions = addResult.iterator->value;
+            if (!pendingStringActions.combinedSelectors.isEmpty())
+                pendingStringActions.combinedSelectors.append(',');
+            pendingStringActions.combinedSelectors.append(rule.action().stringArgument());
+            pendingStringActions.clientLocations.append(actionLocations.size());
 
             actionLocation = std::numeric_limits<unsigned>::max();
             break;
@@ -172,6 +181,9 @@ static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& rul
         case ActionType::MakeHTTPS:
             findOrMakeActionLocation(makeHTTPSActionsMap);
             break;
+        case ActionType::Notify:
+            findOrMakeStringActionLocation(notifyActionsMap);
+            break;
         }
 
         actionLocations.append(actionLocation);
index 04b8f88..d88d851 100644 (file)
@@ -87,6 +87,8 @@ const std::error_category& contentExtensionErrorCategory()
                 return "A trigger cannot have more than one condition (if-domain, unless-domain, if-top-url, or unless-top-url)";
             case ContentExtensionError::JSONTopURLAndDomainConditions:
                 return "A list cannot have if-domain and unless-domain mixed with if-top-url and unless-top-url";
+            case ContentExtensionError::JSONInvalidNotification:
+                return "A notify action must have a string notification";
             }
 
             return std::string();
index 7c5c8bb..3ff3e79 100644 (file)
@@ -58,6 +58,7 @@ enum class ContentExtensionError {
     JSONInvalidAction,
     JSONInvalidActionType,
     JSONInvalidCSSDisplayNoneActionType,
+    JSONInvalidNotification,
     JSONInvalidRegex,
 };
 
index b83c8e9..4bb1d74 100644 (file)
@@ -248,11 +248,11 @@ static Expected<std::optional<Action>, std::error_code> loadAction(ExecState& ex
     auto scope = DECLARE_THROW_SCOPE(vm);
 
     const JSValue actionObject = ruleObject.get(&exec, Identifier::fromString(&exec, "action"));
-    if (!actionObject || scope.exception() || !actionObject.isObject())
+    if (scope.exception() || !actionObject.isObject())
         return makeUnexpected(ContentExtensionError::JSONInvalidAction);
 
     const JSValue typeObject = actionObject.get(&exec, Identifier::fromString(&exec, "type"));
-    if (!typeObject || scope.exception() || !typeObject.isString())
+    if (scope.exception() || !typeObject.isString())
         return makeUnexpected(ContentExtensionError::JSONInvalidActionType);
 
     String actionType = asString(typeObject)->value(&exec);
@@ -265,7 +265,7 @@ static Expected<std::optional<Action>, std::error_code> loadAction(ExecState& ex
         return {{ ActionType::BlockCookies }};
     if (actionType == "css-display-none") {
         JSValue selector = actionObject.get(&exec, Identifier::fromString(&exec, "selector"));
-        if (!selector || scope.exception() || !selector.isString())
+        if (scope.exception() || !selector.isString())
             return makeUnexpected(ContentExtensionError::JSONInvalidCSSDisplayNoneActionType);
 
         String selectorString = asString(selector)->value(&exec);
@@ -277,6 +277,12 @@ static Expected<std::optional<Action>, std::error_code> loadAction(ExecState& ex
     }
     if (actionType == "make-https")
         return {{ ActionType::MakeHTTPS }};
+    if (actionType == "notify") {
+        JSValue notification = actionObject.get(&exec, Identifier::fromString(&exec, "notification"));
+        if (scope.exception() || !notification.isString())
+            return makeUnexpected(ContentExtensionError::JSONInvalidNotification);
+        return { Action(ActionType::Notify, asString(notification)->value(&exec)) };
+    }
     return makeUnexpected(ContentExtensionError::JSONInvalidActionType);
 }
 
index 4a258de..145b6f2 100644 (file)
@@ -66,6 +66,7 @@ Action Action::deserialize(const SerializedActionByte* actions, const uint32_t a
     case ActionType::MakeHTTPS:
         return Action(actionType, location);
     case ActionType::CSSDisplayNoneSelector:
+    case ActionType::Notify:
         return Action(actionType, deserializeString(actions, actionsLength, location + sizeof(ActionType)), location);
     }
     RELEASE_ASSERT_NOT_REACHED();
@@ -78,6 +79,7 @@ ActionType Action::deserializeType(const SerializedActionByte* actions, const ui
     switch (type) {
     case ActionType::BlockCookies:
     case ActionType::BlockLoad:
+    case ActionType::Notify:
     case ActionType::IgnorePreviousRules:
     case ActionType::CSSDisplayNoneSelector:
     case ActionType::MakeHTTPS:
@@ -95,6 +97,7 @@ uint32_t Action::serializedLength(const SerializedActionByte* actions, const uin
     case ActionType::IgnorePreviousRules:
     case ActionType::MakeHTTPS:
         return sizeof(ActionType);
+    case ActionType::Notify:
     case ActionType::CSSDisplayNoneSelector: {
         uint32_t prefixLength = sizeof(ActionType) + sizeof(uint32_t) + sizeof(bool);
         uint32_t stringStartIndex = location + prefixLength;
index 78a97b8..c686748 100644 (file)
@@ -135,14 +135,14 @@ struct Action {
         , m_actionID(actionID)
         , m_stringArgument(stringArgument)
     {
-        ASSERT(type == ActionType::CSSDisplayNoneSelector);
+        ASSERT(hasStringArgument(type));
     }
 
     Action(ActionType type, uint32_t actionID = std::numeric_limits<uint32_t>::max())
         : m_type(type)
         , m_actionID(actionID)
     {
-        ASSERT(type != ActionType::CSSDisplayNoneSelector);
+        ASSERT(!hasStringArgument(type));
     }
 
     bool operator==(const Action& other) const
index b7d2fe6..3072101 100644 (file)
@@ -28,6 +28,8 @@
 
 #if ENABLE(CONTENT_EXTENSIONS)
 
+#include "Chrome.h"
+#include "ChromeClient.h"
 #include "CompiledContentExtension.h"
 #include "ContentExtension.h"
 #include "ContentExtensionsDebugging.h"
@@ -38,6 +40,7 @@
 #include "Frame.h"
 #include "FrameLoaderClient.h"
 #include "MainFrame.h"
+#include "Page.h"
 #include "ResourceLoadInfo.h"
 #include "URL.h"
 #include "UserContentController.h"
@@ -170,6 +173,7 @@ BlockedStatus ContentExtensionsBackend::processContentExtensionRulesForLoad(cons
     bool willBlockLoad = false;
     bool willBlockCookies = false;
     bool willMakeHTTPS = false;
+    HashSet<std::pair<String, String>> notifications;
     for (const auto& action : actions.first) {
         switch (action.type()) {
         case ContentExtensions::ActionType::BlockLoad:
@@ -184,6 +188,9 @@ BlockedStatus ContentExtensionsBackend::processContentExtensionRulesForLoad(cons
             else if (currentDocument)
                 currentDocument->extensionStyleSheets().addDisplayNoneSelector(action.extensionIdentifier(), action.stringArgument(), action.actionID());
             break;
+        case ContentExtensions::ActionType::Notify:
+            notifications.add(std::make_pair(action.extensionIdentifier(), action.stringArgument()));
+            break;
         case ContentExtensions::ActionType::MakeHTTPS: {
             if ((url.protocolIs("http") || url.protocolIs("ws"))
                 && (!url.port() || isDefaultPortForProtocol(url.port().value(), url.protocol())))
@@ -213,7 +220,7 @@ BlockedStatus ContentExtensionsBackend::processContentExtensionRulesForLoad(cons
         if (willBlockLoad)
             currentDocument->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Info, makeString("Content blocker prevented frame displaying ", mainDocumentURL.string(), " from loading a resource from ", url.string()));
     }
-    return { willBlockLoad, willBlockCookies, willMakeHTTPS };
+    return { willBlockLoad, willBlockCookies, willMakeHTTPS, WTFMove(notifications) };
 }
 
 BlockedStatus ContentExtensionsBackend::processContentExtensionRulesForPingLoad(const URL& url, const URL& mainDocumentURL)
@@ -240,13 +247,14 @@ BlockedStatus ContentExtensionsBackend::processContentExtensionRulesForPingLoad(
                 willMakeHTTPS = true;
             break;
         case ContentExtensions::ActionType::CSSDisplayNoneSelector:
+        case ContentExtensions::ActionType::Notify:
             break;
         case ContentExtensions::ActionType::IgnorePreviousRules:
             RELEASE_ASSERT_NOT_REACHED();
         }
     }
 
-    return { willBlockLoad, willBlockCookies, willMakeHTTPS };
+    return { willBlockLoad, willBlockCookies, willMakeHTTPS, { } };
 }
 
 const String& ContentExtensionsBackend::displayNoneCSSRule()
@@ -255,8 +263,11 @@ const String& ContentExtensionsBackend::displayNoneCSSRule()
     return rule;
 }
 
-void applyBlockedStatusToRequest(const BlockedStatus& status, ResourceRequest& request)
+void applyBlockedStatusToRequest(const BlockedStatus& status, Page* page, ResourceRequest& request)
 {
+    if (page && !status.notifications.isEmpty())
+        page->chrome().client().contentRuleListNotification(request.url(), status.notifications);
+
     if (status.blockedCookies)
         request.setAllowCookies(false);
 
index b63fa6c..95929a2 100644 (file)
@@ -2804,7 +2804,7 @@ unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& requ
         if (auto* page = m_frame.page()) {
             if (m_documentLoader) {
                 auto blockedStatus = page->userContentProvider().processContentExtensionRulesForLoad(newRequest.url(), ResourceType::Raw, *m_documentLoader);
-                applyBlockedStatusToRequest(blockedStatus, newRequest);
+                applyBlockedStatusToRequest(blockedStatus, page, newRequest);
                 if (blockedStatus.blockedLoad) {
                     newRequest = { };
                     error = ResourceError(errorDomainWebKitInternal, 0, initialRequest.url(), emptyString());
index 53aa257..80d67e3 100644 (file)
@@ -75,7 +75,7 @@ static bool processContentExtensionRulesForLoad(const Frame& frame, ResourceRequ
     if (!page)
         return false;
     auto status = page->userContentProvider().processContentExtensionRulesForLoad(request.url(), resourceType, *documentLoader);
-    applyBlockedStatusToRequest(status, request);
+    applyBlockedStatusToRequest(status, page, request);
     return status.blockedLoad;
 }
 
index d7e2547..a52cc32 100644 (file)
@@ -345,11 +345,11 @@ void ResourceLoader::willSendRequestInternal(ResourceRequest& request, const Res
     }
 
 #if ENABLE(CONTENT_EXTENSIONS)
-    if (frameLoader()) {
+    if (!redirectResponse.isNull() && frameLoader()) {
         Page* page = frameLoader()->frame().page();
         if (page && m_documentLoader) {
             auto blockedStatus = page->userContentProvider().processContentExtensionRulesForLoad(request.url(), m_resourceType, *m_documentLoader);
-            applyBlockedStatusToRequest(blockedStatus, request);
+            applyBlockedStatusToRequest(blockedStatus, page, request);
             if (blockedStatus.blockedLoad) {
                 request = { };
                 didFail(blockedByContentBlockerError());
index 0a678cc..64fa73e 100644 (file)
@@ -725,8 +725,9 @@ ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requ
 #if ENABLE(CONTENT_EXTENSIONS)
     if (frame() && frame()->mainFrame().page() && m_documentLoader) {
         const auto& resourceRequest = request.resourceRequest();
-        auto blockedStatus = frame()->mainFrame().page()->userContentProvider().processContentExtensionRulesForLoad(resourceRequest.url(), toResourceType(type), *m_documentLoader);
-        request.applyBlockedStatus(blockedStatus);
+        auto* page = frame()->mainFrame().page();
+        auto blockedStatus = page->userContentProvider().processContentExtensionRulesForLoad(resourceRequest.url(), toResourceType(type), *m_documentLoader);
+        request.applyBlockedStatus(blockedStatus, page);
         if (blockedStatus.blockedLoad) {
             RELEASE_LOG_IF_ALLOWED("requestResource: Resource blocked by content blocker (frame = %p)", frame());
             if (type == CachedResource::Type::MainResource) {
index 1362c33..7daed7c 100644 (file)
@@ -212,9 +212,9 @@ void CachedResourceRequest::removeFragmentIdentifierIfNeeded()
 
 #if ENABLE(CONTENT_EXTENSIONS)
 
-void CachedResourceRequest::applyBlockedStatus(const ContentExtensions::BlockedStatus& blockedStatus)
+void CachedResourceRequest::applyBlockedStatus(const ContentExtensions::BlockedStatus& blockedStatus, Page* page)
 {
-    ContentExtensions::applyBlockedStatusToRequest(blockedStatus, m_resourceRequest);
+    ContentExtensions::applyBlockedStatusToRequest(blockedStatus, page, m_resourceRequest);
 }
 
 #endif
index f507fcc..5c8df13 100644 (file)
@@ -76,7 +76,7 @@ public:
     void updateAccordingCacheMode();
     void removeFragmentIdentifierIfNeeded();
 #if ENABLE(CONTENT_EXTENSIONS)
-    void applyBlockedStatus(const ContentExtensions::BlockedStatus&);
+    void applyBlockedStatus(const ContentExtensions::BlockedStatus&, Page*);
 #endif
     void setDomainForCachePartition(Document&);
     void setDomainForCachePartition(const String&);
index dcd400a..2b69ff9 100644 (file)
@@ -370,6 +370,8 @@ public:
     virtual void enableSuddenTermination() { }
     virtual void disableSuddenTermination() { }
 
+    virtual void contentRuleListNotification(const WebCore::URL&, const HashSet<std::pair<String, String>>&) { };
+
 #if PLATFORM(WIN)
     virtual void setLastSetCursorToCurrentCursor() = 0;
     virtual void AXStartFrameLoad() = 0;
index b5934e5..01f3f64 100644 (file)
@@ -1,3 +1,31 @@
+2017-09-25  Alex Christensen  <achristensen@webkit.org>
+
+        Add WKContentRuleList notify action type
+        https://bugs.webkit.org/show_bug.cgi?id=177013
+        <rdar://problem/31073936>
+
+        Reviewed by Darin Adler.
+
+        * NetworkProcess/PingLoad.cpp:
+        (WebKit::PingLoad::processContentExtensionRulesForLoad):
+        * UIProcess/API/APINavigationClient.h:
+        (API::NavigationClient::contentRuleListNotification):
+        * UIProcess/API/C/WKPage.cpp:
+        (WKPageSetPageNavigationClient):
+        * UIProcess/API/C/WKPageNavigationClient.h:
+        * UIProcess/API/Cocoa/WKNavigationDelegatePrivate.h:
+        * UIProcess/Cocoa/NavigationState.h:
+        * UIProcess/Cocoa/NavigationState.mm:
+        (WebKit::NavigationState::setNavigationDelegate):
+        (WebKit::NavigationState::NavigationClient::contentRuleListNotification):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::contentRuleListNotification):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * WebProcess/WebCoreSupport/WebChromeClient.cpp:
+        (WebKit::WebChromeClient::contentRuleListNotification):
+        * WebProcess/WebCoreSupport/WebChromeClient.h:
+
 2017-09-25  Timothy Horton  <timothy_horton@apple.com>
 
         Make progress on getting Mac CMake building again
index 0942e80..d184fff 100644 (file)
@@ -324,7 +324,7 @@ ContentExtensions::ContentExtensionsBackend& PingLoad::contentExtensionsBackend(
 ContentExtensions::BlockedStatus PingLoad::processContentExtensionRulesForLoad(ResourceRequest& request)
 {
     auto status = contentExtensionsBackend().processContentExtensionRulesForPingLoad(request.url(), m_parameters.mainDocumentURL);
-    applyBlockedStatusToRequest(status, request);
+    applyBlockedStatusToRequest(status, nullptr, request);
     return status;
 }
 
index 61dc71d..e02eb99 100644 (file)
@@ -106,6 +106,8 @@ public:
         listener->use({ });
     }
     
+    virtual void contentRuleListNotification(WebKit::WebPageProxy&, WebCore::URL&&, Vector<WTF::String>&&, Vector<WTF::String>&&) { };
+    
 #if ENABLE(NETSCAPE_PLUGIN_API)
     virtual WebKit::PluginModuleLoadPolicy decidePolicyForPluginLoad(WebKit::WebPageProxy&, WebKit::PluginModuleLoadPolicy currentPluginLoadPolicy, Dictionary*, WTF::String&)
     {
index 1088612..cc7f6f7 100644 (file)
@@ -102,7 +102,7 @@ template<> struct ClientTraits<WKPageLoaderClientBase> {
 };
 
 template<> struct ClientTraits<WKPageNavigationClientBase> {
-    typedef std::tuple<WKPageNavigationClientV0, WKPageNavigationClientV1> Versions;
+    typedef std::tuple<WKPageNavigationClientV0, WKPageNavigationClientV1, WKPageNavigationClientV2> Versions;
 };
 
 template<> struct ClientTraits<WKPagePolicyClientBase> {
@@ -2295,6 +2295,21 @@ void WKPageSetPageNavigationClient(WKPageRef pageRef, const WKPageNavigationClie
             m_client.didRemoveNavigationGestureSnapshot(toAPI(&page), m_client.base.clientInfo);
         }
         
+        void contentRuleListNotification(WebPageProxy& page, URL&& url, Vector<String>&& listIdentifiers, Vector<String>&& notifications) final
+        {
+            if (!m_client.contentRuleListNotification)
+                return;
+
+            Vector<RefPtr<API::Object>> apiListIdentifiers;
+            for (const auto& identifier : listIdentifiers)
+                apiListIdentifiers.append(API::String::create(identifier));
+
+            Vector<RefPtr<API::Object>> apiNotifications;
+            for (const auto& notification : notifications)
+                apiNotifications.append(API::String::create(notification));
+
+            m_client.contentRuleListNotification(toAPI(&page), toURLRef(url.string().impl()), toAPI(API::Array::create(WTFMove(apiListIdentifiers)).ptr()), toAPI(API::Array::create(WTFMove(apiNotifications)).ptr()), m_client.base.clientInfo);
+        }
 #if ENABLE(NETSCAPE_PLUGIN_API)
         PluginModuleLoadPolicy decidePolicyForPluginLoad(WebPageProxy& page, PluginModuleLoadPolicy currentPluginLoadPolicy, API::Dictionary* pluginInformation, String& unavailabilityDescription) override
         {
index ed1548f..14843d2 100644 (file)
@@ -80,6 +80,7 @@ typedef void (*WKPageNavigationDidEndNavigationGesture)(WKPageRef page, WKBackFo
 
 typedef void (*WKPageNavigationDidRemoveNavigationGestureSnapshot)(WKPageRef page, const void* clientInfo);
 
+typedef void (*WKPageNavigationContentRuleListNotificationCallback)(WKPageRef, WKURLRef, WKArrayRef, WKArrayRef, const void* clientInfo);
 
 typedef struct WKPageNavigationClientBase {
     int version;
@@ -143,6 +144,39 @@ typedef struct WKPageNavigationClientV1 {
     WKPageNavigationWebProcessDidTerminateCallback webProcessDidTerminate;
 } WKPageNavigationClientV1;
 
+typedef struct WKPageNavigationClientV2 {
+    WKPageNavigationClientBase base;
+    
+    // Version 0.
+    WKPageNavigationDecidePolicyForNavigationActionCallback decidePolicyForNavigationAction;
+    WKPageNavigationDecidePolicyForNavigationResponseCallback decidePolicyForNavigationResponse;
+    WKPageNavigationDecidePolicyForPluginLoadCallback decidePolicyForPluginLoad;
+    WKPageNavigationDidStartProvisionalNavigationCallback didStartProvisionalNavigation;
+    WKPageNavigationDidReceiveServerRedirectForProvisionalNavigationCallback didReceiveServerRedirectForProvisionalNavigation;
+    WKPageNavigationDidFailProvisionalNavigationCallback didFailProvisionalNavigation;
+    WKPageNavigationDidCommitNavigationCallback didCommitNavigation;
+    WKPageNavigationDidFinishNavigationCallback didFinishNavigation;
+    WKPageNavigationDidFailNavigationCallback didFailNavigation;
+    WKPageNavigationDidFailProvisionalLoadInSubframeCallback didFailProvisionalLoadInSubframe;
+    WKPageNavigationDidFinishDocumentLoadCallback didFinishDocumentLoad;
+    WKPageNavigationDidSameDocumentNavigationCallback didSameDocumentNavigation;
+    WKPageNavigationRenderingProgressDidChangeCallback renderingProgressDidChange;
+    WKPageNavigationCanAuthenticateAgainstProtectionSpaceCallback canAuthenticateAgainstProtectionSpace;
+    WKPageNavigationDidReceiveAuthenticationChallengeCallback didReceiveAuthenticationChallenge;
+    WKPageNavigationWebProcessDidCrashCallback webProcessDidCrash;
+    WKPageNavigationCopyWebCryptoMasterKeyCallback copyWebCryptoMasterKey;
+    WKPageNavigationDidBeginNavigationGesture didBeginNavigationGesture;
+    WKPageNavigationWillEndNavigationGesture willEndNavigationGesture;
+    WKPageNavigationDidEndNavigationGesture didEndNavigationGesture;
+    WKPageNavigationDidRemoveNavigationGestureSnapshot didRemoveNavigationGestureSnapshot;
+    
+    // Version 1.
+    WKPageNavigationWebProcessDidTerminateCallback webProcessDidTerminate;
+
+    // Version 2.
+    WKPageNavigationContentRuleListNotificationCallback contentRuleListNotification;
+} WKPageNavigationClientV2;
+
 #ifdef __cplusplus
 }
 #endif
index c74a2e8..1dcfd28 100644 (file)
@@ -78,6 +78,8 @@ static const WKNavigationResponsePolicy _WKNavigationResponsePolicyBecomeDownloa
 - (void)_webViewDidRemoveNavigationGestureSnapshot:(WKWebView *)webView WK_API_AVAILABLE(macosx(10.12), ios(10.0));
 - (void)_webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy, _WKWebsitePolicies *))decisionHandler WK_API_AVAILABLE(macosx(10.12.3), ios(10.3));
 
+- (void)_webView:(WKWebView *)webView URL:(NSURL *)url contentRuleListIdentifiers:(NSArray<NSString *> *)identifiers notifications:(NSArray<NSString *> *)notifications WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 #if TARGET_OS_IPHONE
 - (void)_webView:(WKWebView *)webView didStartLoadForQuickLookDocumentInMainFrameWithFileName:(NSString *)fileName uti:(NSString *)uti;
 - (void)_webView:(WKWebView *)webView didFinishLoadForQuickLookDocumentInMainFrame:(NSData *)documentData;
index 807860b..4b4b720 100644 (file)
@@ -121,6 +121,7 @@ private:
         void resolveWebGLLoadPolicy(WebPageProxy&, const WebCore::URL&, WTF::Function<void(WebCore::WebGLLoadPolicy)>&& completionHandler) const final;
 #endif
 
+        void contentRuleListNotification(WebPageProxy&, WebCore::URL&&, Vector<String>&&, Vector<String>&&) final;
         void decidePolicyForNavigationAction(WebPageProxy&, Ref<API::NavigationAction>&&, Ref<WebFramePolicyListenerProxy>&&, API::Object* userData) override;
         void decidePolicyForNavigationResponse(WebPageProxy&, API::NavigationResponse&, Ref<WebFramePolicyListenerProxy>&&, API::Object* userData) override;
 
@@ -201,6 +202,7 @@ private:
         bool webViewDidEndNavigationGestureWithNavigationToBackForwardListItem : 1;
         bool webViewWillSnapshotBackForwardListItem : 1;
         bool webViewNavigationGestureSnapshotWasRemoved : 1;
+        bool webViewURLContentRuleListIdentifiersNotifications : 1;
 #if USE(QUICK_LOOK)
         bool webViewDidStartLoadForQuickLookDocumentInMainFrame : 1;
         bool webViewDidFinishLoadForQuickLookDocumentInMainFrame : 1;
index a91afb4..aa79fcd 100644 (file)
@@ -168,6 +168,7 @@ void NavigationState::setNavigationDelegate(id <WKNavigationDelegate> delegate)
     m_navigationDelegateMethods.webViewDidEndNavigationGestureWithNavigationToBackForwardListItem = [delegate respondsToSelector:@selector(_webViewDidEndNavigationGesture:withNavigationToBackForwardListItem:)];
     m_navigationDelegateMethods.webViewWillSnapshotBackForwardListItem = [delegate respondsToSelector:@selector(_webView:willSnapshotBackForwardListItem:)];
     m_navigationDelegateMethods.webViewNavigationGestureSnapshotWasRemoved = [delegate respondsToSelector:@selector(_webViewDidRemoveNavigationGestureSnapshot:)];
+    m_navigationDelegateMethods.webViewURLContentRuleListIdentifiersNotifications = [delegate respondsToSelector:@selector(_webView:URL:contentRuleListIdentifiers:notifications:)];
 #if USE(QUICK_LOOK)
     m_navigationDelegateMethods.webViewDidStartLoadForQuickLookDocumentInMainFrame = [delegate respondsToSelector:@selector(_webView:didStartLoadForQuickLookDocumentInMainFrameWithFileName:uti:)];
     m_navigationDelegateMethods.webViewDidFinishLoadForQuickLookDocumentInMainFrame = [delegate respondsToSelector:@selector(_webView:didFinishLoadForQuickLookDocumentInMainFrame:)];
@@ -458,6 +459,28 @@ void NavigationState::NavigationClient::decidePolicyForNavigationAction(WebPageP
     }
 }
 
+void NavigationState::NavigationClient::contentRuleListNotification(WebPageProxy&, WebCore::URL&& url, Vector<String>&& listIdentifiers, Vector<String>&& notifications)
+{
+    if (!m_navigationState.m_navigationDelegateMethods.webViewURLContentRuleListIdentifiersNotifications)
+        return;
+
+    auto navigationDelegate = m_navigationState.m_navigationDelegate.get();
+    if (!navigationDelegate)
+        return;
+
+    ASSERT(listIdentifiers.size() == notifications.size());
+
+    auto identifiers = adoptNS([[NSMutableArray alloc] initWithCapacity:listIdentifiers.size()]);
+    for (auto& identifier : listIdentifiers)
+        [identifiers addObject:identifier];
+
+    auto nsNotifications = adoptNS([[NSMutableArray alloc] initWithCapacity:notifications.size()]);
+    for (auto& notification : notifications)
+        [nsNotifications addObject:notification];
+    
+    [(id <WKNavigationDelegatePrivate>)navigationDelegate _webView:m_navigationState.m_webView URL:url contentRuleListIdentifiers:identifiers.get() notifications:nsNotifications.get()];
+}
+    
 void NavigationState::NavigationClient::decidePolicyForNavigationResponse(WebPageProxy&, API::NavigationResponse& navigationResponse, Ref<WebFramePolicyListenerProxy>&& listener, API::Object* userData)
 {
     if (!m_navigationState.m_navigationDelegateMethods.webViewDecidePolicyForNavigationResponseDecisionHandler) {
index f3b52fe..9ca0ef3 100644 (file)
@@ -3787,6 +3787,12 @@ void WebPageProxy::willSubmitForm(uint64_t frameID, uint64_t sourceFrameID, cons
     m_formClient->willSubmitForm(*this, *frame, *sourceFrame, textFieldValues, m_process->transformHandlesToObjects(userData.object()).get(), listener.get());
 }
 
+void WebPageProxy::contentRuleListNotification(WebCore::URL&& url, Vector<String>&& identifiers, Vector<String>&& notifications)
+{
+    if (m_navigationClient)
+        m_navigationClient->contentRuleListNotification(*this, WTFMove(url), WTFMove(identifiers), WTFMove(notifications));
+}
+    
 void WebPageProxy::didNavigateWithNavigationData(const WebNavigationDataStore& store, uint64_t frameID) 
 {
     PageClientProtector protector(m_pageClient);
index 7152822..02f7299 100644 (file)
@@ -1286,6 +1286,8 @@ private:
     void unableToImplementPolicy(uint64_t frameID, const WebCore::ResourceError&, const UserData&);
 
     void willSubmitForm(uint64_t frameID, uint64_t sourceFrameID, const Vector<std::pair<String, String>>& textFieldValues, uint64_t listenerID, const UserData&);
+        
+    void contentRuleListNotification(WebCore::URL&&, Vector<String>&& identifiers, Vector<String>&& notifications);
 
     // History client
     void didNavigateWithNavigationData(const WebNavigationDataStore&, uint64_t frameID);
index ca2a0f2..ccb1254 100644 (file)
@@ -458,6 +458,7 @@ messages -> WebPageProxy {
     HandleSynchronousMessage(String messageName, WebKit::UserData messageBody) -> (WebKit::UserData returnData) WantsConnection
 
     HandleAutoFillButtonClick(WebKit::UserData userData);
+    ContentRuleListNotification(WebCore::URL url, Vector<String> identifiers, Vector<String> notifications)
 
 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
     AddPlaybackTargetPickerClient(uint64_t contextId)
index 05c8f87..295fbb1 100644 (file)
@@ -874,6 +874,20 @@ void WebChromeClient::scheduleCompositingLayerFlush()
         m_page.drawingArea()->scheduleCompositingLayerFlush();
 }
 
+void WebChromeClient::contentRuleListNotification(const URL& url, const HashSet<std::pair<String, String>>& notificationPairs)
+{
+    Vector<String> identifiers;
+    Vector<String> notifications;
+    identifiers.reserveInitialCapacity(notificationPairs.size());
+    notifications.reserveInitialCapacity(notificationPairs.size());
+    for (auto& notification : notificationPairs) {
+        identifiers.uncheckedAppend(notification.first);
+        notifications.uncheckedAppend(notification.second);
+    }
+
+    m_page.send(Messages::WebPageProxy::ContentRuleListNotification(url, identifiers, notifications));
+}
+
 bool WebChromeClient::adjustLayerFlushThrottling(LayerFlushThrottleState::Flags flags)
 {
     return m_page.drawingArea() && m_page.drawingArea()->adjustLayerFlushThrottling(flags);
index d44afd8..d95e8ca 100644 (file)
@@ -208,6 +208,8 @@ private:
     void scheduleCompositingLayerFlush() final;
     bool adjustLayerFlushThrottling(WebCore::LayerFlushThrottleState::Flags) final;
 
+    void contentRuleListNotification(const WebCore::URL&, const HashSet<std::pair<String, String>>&) final;
+    
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     RefPtr<WebCore::DisplayRefreshMonitor> createDisplayRefreshMonitor(WebCore::PlatformDisplayID) const final;
 #endif
index 9424fa3..0cfb1e1 100644 (file)
@@ -1,3 +1,31 @@
+2017-09-25  Alex Christensen  <achristensen@webkit.org>
+
+        Add WKContentRuleList notify action type
+        https://bugs.webkit.org/show_bug.cgi?id=177013
+        <rdar://problem/31073936>
+
+        Reviewed by Darin Adler.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
+        (WebCore::ContentExtensions::operator<<):
+        (TestWebKitAPI::InMemoryCompiledContentExtension::create):
+        (TestWebKitAPI::InMemoryCompiledContentExtension::data):
+        (TestWebKitAPI::InMemoryCompiledContentExtension::InMemoryCompiledContentExtension):
+        (TestWebKitAPI::makeBackend):
+        (TestWebKitAPI::TEST_F):
+        (TestWebKitAPI::actionsEqual):
+        (TestWebKitAPI::sequenceInstances):
+        (TestWebKitAPI::InMemoryCompiledContentExtension::createFromFilter): Deleted.
+        (TestWebKitAPI::InMemoryCompiledContentExtension::~InMemoryCompiledContentExtension): Deleted.
+        * TestWebKitAPI/Tests/WebKitCocoa/ContentRuleListNotification.mm: Added.
+        (-[ContentRuleListNotificationDelegate _webView:URL:contentRuleListIdentifiers:notifications:]):
+        (-[ContentRuleListNotificationDelegate webView:startURLSchemeTask:]):
+        (-[ContentRuleListNotificationDelegate webView:stopURLSchemeTask:]):
+        (-[ContentRuleListNotificationDelegate webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:]):
+        (makeWarnContentRuleList):
+        (TEST):
+
 2017-09-25  Carlos Alberto Lopez Perez  <clopez@igalia.com>
 
         REGRESSION(r222160) [GTK] [Debug] Internal compiler error on the buildbot (huge memory usage by GCC)
index ae89428..ce10be9 100644 (file)
                5C9E59421D3EB5AC00E3C62E /* ApplicationCache.db-shm in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5C9E593F1D3EB1DE00E3C62E /* ApplicationCache.db-shm */; };
                5C9E59431D3EB5AC00E3C62E /* ApplicationCache.db-wal in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5C9E59401D3EB1DE00E3C62E /* ApplicationCache.db-wal */; };
                5CA1DEC81F71F70100E71BD3 /* HTTPHeaderField.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5CA1DEC71F71F40700E71BD3 /* HTTPHeaderField.cpp */; };
+               5CA1DED91F74A91A00E71BD3 /* ContentRuleListNotification.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CA1DED81F74A87100E71BD3 /* ContentRuleListNotification.mm */; };
                5CB18BA81F5645E300EE23C4 /* ClickAutoFillButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CB18BA71F5645B200EE23C4 /* ClickAutoFillButton.mm */; };
                5CB40B4E1F4B98D3007DC7B9 /* UIDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CB40B4D1F4B98BE007DC7B9 /* UIDelegate.mm */; };
                5CE354D91E70DA5C00BEFE3B /* WKContentExtensionStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CE354D81E70D9C300BEFE3B /* WKContentExtensionStore.mm */; };
                5C9E593F1D3EB1DE00E3C62E /* ApplicationCache.db-shm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ApplicationCache.db-shm"; sourceTree = "<group>"; };
                5C9E59401D3EB1DE00E3C62E /* ApplicationCache.db-wal */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ApplicationCache.db-wal"; sourceTree = "<group>"; };
                5CA1DEC71F71F40700E71BD3 /* HTTPHeaderField.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPHeaderField.cpp; sourceTree = "<group>"; };
+               5CA1DED81F74A87100E71BD3 /* ContentRuleListNotification.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ContentRuleListNotification.mm; sourceTree = "<group>"; };
                5CB18BA71F5645B200EE23C4 /* ClickAutoFillButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ClickAutoFillButton.mm; sourceTree = "<group>"; };
                5CB40B4D1F4B98BE007DC7B9 /* UIDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = UIDelegate.mm; sourceTree = "<group>"; };
                5CE354D81E70D9C300BEFE3B /* WKContentExtensionStore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKContentExtensionStore.mm; sourceTree = "<group>"; };
                                A18AA8CC1C3FA218009B2B97 /* ContentFiltering.h */,
                                A14FC5861B8991B600D107EB /* ContentFiltering.mm */,
                                A14FC5891B89927100D107EB /* ContentFilteringPlugIn.mm */,
+                               5CA1DED81F74A87100E71BD3 /* ContentRuleListNotification.mm */,
                                5C2936911D5BF63E00DEAB1E /* CookieAcceptPolicy.mm */,
                                9999108A1F393C8B008AD455 /* Copying.mm */,
                                2DC4CF761D2D9DD800ECCC94 /* DataDetection.mm */,
                                7CB184C61AA3F2100066EDFD /* ContentExtensions.cpp in Sources */,
                                A1146A8D1D2D7115000FE710 /* ContentFiltering.mm in Sources */,
                                A14FC5881B8991BF00D107EB /* ContentFiltering.mm in Sources */,
+                               5CA1DED91F74A91A00E71BD3 /* ContentRuleListNotification.mm in Sources */,
                                7CCE7EB81A411A7E00447C4C /* ContextMenuCanCopyURL.mm in Sources */,
                                37FB72971DB2E82F00E41BE4 /* ContextMenuDefaultItemsHaveTags.mm in Sources */,
                                8349D3C21DB96DDE004A9F65 /* ContextMenuDownload.mm in Sources */,
index ea079ac..dcce468 100644 (file)
@@ -56,6 +56,8 @@ inline std::ostream& operator<<(std::ostream& os, const ActionType& action)
         return os << "ActionType::BlockCookies";
     case ActionType::CSSDisplayNoneSelector:
         return os << "ActionType::CSSDisplayNone";
+    case ActionType::Notify:
+        return os << "ActionType::Notify";
     case ActionType::IgnorePreviousRules:
         return os << "ActionType::IgnorePreviousRules";
     case ActionType::MakeHTTPS:
@@ -97,6 +99,7 @@ public:
         EXPECT_EQ(data.topURLFilters.size(), 0ull);
     }
 
+private:
     void writeSource(const String&) final { }
 
     void writeActions(Vector<ContentExtensions::SerializedActionByte>&& actions, bool conditionsApplyOnlyToDomain) final
@@ -137,35 +140,27 @@ public:
         finalized = true;
     }
 
-private:
     CompiledContentExtensionData& m_data;
     bool finalized { false };
 };
 
 class InMemoryCompiledContentExtension : public ContentExtensions::CompiledContentExtension {
 public:
-    static Ref<InMemoryCompiledContentExtension> createFromFilter(String&& filter)
+    static Ref<InMemoryCompiledContentExtension> create(String&& filter)
     {
         CompiledContentExtensionData extensionData;
         InMemoryContentExtensionCompilationClient client(extensionData);
         auto compilerError = ContentExtensions::compileRuleList(client, WTFMove(filter));
-        if (compilerError) {
-            // Compiling should always succeed here. We have other tests for compile failures.
-            EXPECT_TRUE(false);
-        }
 
-        return InMemoryCompiledContentExtension::create(WTFMove(extensionData));
-    }
+        // Compiling should always succeed here. We have other tests for compile failures.
+        EXPECT_FALSE(compilerError);
 
-    static Ref<InMemoryCompiledContentExtension> create(CompiledContentExtensionData&& data)
-    {
-        return adoptRef(*new InMemoryCompiledContentExtension(WTFMove(data)));
+        return adoptRef(*new InMemoryCompiledContentExtension(WTFMove(extensionData)));
     }
 
-    virtual ~InMemoryCompiledContentExtension()
-    {
-    }
+    const CompiledContentExtensionData& data() { return m_data; };
 
+private:
     const ContentExtensions::SerializedActionByte* actions() const final { return m_data.actions.data(); }
     unsigned actionsLength() const final { return m_data.actions.size(); }
     const ContentExtensions::DFABytecode* filtersWithoutConditionsBytecode() const final { return m_data.filtersWithoutConditions.data(); }
@@ -176,11 +171,9 @@ public:
     unsigned topURLFiltersBytecodeLength() const final { return m_data.topURLFilters.size(); }
     bool conditionsApplyOnlyToDomain() const final { return m_data.conditionsApplyOnlyToDomain; }
 
-private:
     InMemoryCompiledContentExtension(CompiledContentExtensionData&& data)
         : m_data(WTFMove(data))
-    {
-    }
+    { }
 
     CompiledContentExtensionData m_data;
 };
@@ -211,7 +204,7 @@ static ResourceLoadInfo subResourceRequest(const char* url, const char* mainDocu
 ContentExtensions::ContentExtensionsBackend makeBackend(const char* json)
 {
     AtomicString::init();
-    auto extension = InMemoryCompiledContentExtension::createFromFilter(json);
+    auto extension = InMemoryCompiledContentExtension::create(json);
     ContentExtensions::ContentExtensionsBackend backend;
     backend.addContentExtension("testFilter", WTFMove(extension));
     return backend;
@@ -856,8 +849,8 @@ TEST_F(ContentExtensionTest, TopURL)
 
 TEST_F(ContentExtensionTest, MultipleExtensions)
 {
-    auto extension1 = InMemoryCompiledContentExtension::createFromFilter("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"block_load\"}}]");
-    auto extension2 = InMemoryCompiledContentExtension::createFromFilter("[{\"action\":{\"type\":\"block-cookies\"},\"trigger\":{\"url-filter\":\"block_cookies\"}}]");
+    auto extension1 = InMemoryCompiledContentExtension::create("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"block_load\"}}]");
+    auto extension2 = InMemoryCompiledContentExtension::create("[{\"action\":{\"type\":\"block-cookies\"},\"trigger\":{\"url-filter\":\"block_cookies\"}}]");
     ContentExtensions::ContentExtensionsBackend backend;
     backend.addContentExtension("testFilter1", WTFMove(extension1));
     backend.addContentExtension("testFilter2", WTFMove(extension2));
@@ -868,9 +861,9 @@ TEST_F(ContentExtensionTest, MultipleExtensions)
     testRequest(backend, mainDocumentRequest("http://webkit.org/block_load/block_cookies.html"), { ContentExtensions::ActionType::BlockCookies, ContentExtensions::ActionType::BlockLoad }, 2);
     testRequest(backend, mainDocumentRequest("http://webkit.org/block_cookies/block_load.html"), { ContentExtensions::ActionType::BlockCookies, ContentExtensions::ActionType::BlockLoad }, 2);
     
-    auto ignoreExtension1 = InMemoryCompiledContentExtension::createFromFilter("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"block_load\"}},"
+    auto ignoreExtension1 = InMemoryCompiledContentExtension::create("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"block_load\"}},"
         "{\"action\":{\"type\":\"ignore-previous-rules\"},\"trigger\":{\"url-filter\":\"ignore1\"}}]");
-    auto ignoreExtension2 = InMemoryCompiledContentExtension::createFromFilter("[{\"action\":{\"type\":\"block-cookies\"},\"trigger\":{\"url-filter\":\"block_cookies\"}},"
+    auto ignoreExtension2 = InMemoryCompiledContentExtension::create("[{\"action\":{\"type\":\"block-cookies\"},\"trigger\":{\"url-filter\":\"block_cookies\"}},"
         "{\"action\":{\"type\":\"ignore-previous-rules\"},\"trigger\":{\"url-filter\":\"ignore2\"}}]");
     ContentExtensions::ContentExtensionsBackend backendWithIgnore;
     backendWithIgnore.addContentExtension("testFilter1", WTFMove(ignoreExtension1));
@@ -884,6 +877,121 @@ TEST_F(ContentExtensionTest, MultipleExtensions)
     testRequest(backendWithIgnore, mainDocumentRequest("http://webkit.org/block_load/block_cookies/ignore1/ignore2.html"), { }, 0);
 }
 
+static bool actionsEqual(const std::pair<Vector<WebCore::ContentExtensions::Action>, Vector<String>>& actual, Vector<WebCore::ContentExtensions::Action>&& expected, bool ignorePreviousRules = false)
+{
+    if (ignorePreviousRules) {
+        if (actual.second.size())
+            return false;
+    } else {
+        if (actual.second.size() != 1)
+            return false;
+        if (actual.second[0] != "testFilter")
+            return false;
+    }
+
+    if (actual.first.size() != expected.size())
+        return false;
+    for (size_t i = 0; i < expected.size(); ++i) {
+        if (actual.first[i].type() != expected[i].type())
+            return false;
+        if (actual.first[i].stringArgument() != expected[i].stringArgument())
+            return false;
+    }
+    return true;
+}
+
+static const char* jsonWithStringsToCombine = "["
+    "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"A\"}},"
+    "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"A\"}},"
+    "{\"action\":{\"type\":\"notify\",\"notification\":\"BBB\"},\"trigger\":{\"url-filter\":\"B\"}},"
+    "{\"action\":{\"type\":\"css-display-none\",\"selector\":\"CCC\"},\"trigger\":{\"url-filter\":\"C\"}},"
+    "{\"action\":{\"type\":\"css-display-none\",\"selector\":\"selectorCombinedWithC\"},\"trigger\":{\"url-filter\":\"C\"}},"
+    "{\"action\":{\"type\":\"css-display-none\",\"selector\":\"DDD\"},\"trigger\":{\"url-filter\":\"D\"}},"
+    "{\"action\":{\"type\":\"ignore-previous-rules\"},\"trigger\":{\"url-filter\":\"E\"}},"
+    "{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"F\"}},"
+    "{\"action\":{\"type\":\"notify\",\"notification\":\"GGG\"},\"trigger\":{\"url-filter\":\"G\"}},"
+    "{\"action\":{\"type\":\"notify\",\"notification\":\"GGG\"},\"trigger\":{\"url-filter\":\"I\"}},"
+    "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"J\"}},"
+    "{\"action\":{\"type\":\"notify\",\"notification\":\"GGG\"},\"trigger\":{\"url-filter\":\"K\"}}"
+"]";
+
+TEST_F(ContentExtensionTest, StringParameters)
+{
+    auto backend1 = makeBackend("[{\"action\":{\"type\":\"notify\",\"notification\":\"testnotification\"},\"trigger\":{\"url-filter\":\"matches\"}}]");
+    ASSERT_TRUE(actionsEqual(backend1.actionsForResourceLoad(mainDocumentRequest("test:///matches")), {{ ContentExtensions::ActionType::Notify, "testnotification" }}));
+
+    auto backend2 = makeBackend(jsonWithStringsToCombine);
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://A")), {{ ContentExtensions::ActionType::Notify, "AAA" }}));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://B")), {{ ContentExtensions::ActionType::Notify, "BBB" }}));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://C")), {{ ContentExtensions::ActionType::CSSDisplayNoneSelector, "CCC,selectorCombinedWithC" }}));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://D")), {{ ContentExtensions::ActionType::CSSDisplayNoneSelector, "DDD" }}));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://E")), { }, true));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://F")), { ContentExtensions::ActionType::BlockLoad }));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://G")), {{ ContentExtensions::ActionType::Notify, "GGG" }}));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://GIK")), {{ ContentExtensions::ActionType::Notify, "GGG" }}));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://AJ")), {
+        { ContentExtensions::ActionType::Notify, "AAA" },
+        { ContentExtensions::ActionType::Notify, "AAA" } // ignore-previous-rules makes the AAA actions need to be unique.
+    }));
+    // FIXME: Add a test that matches actions with AAA with ignore-previous-rules between them and makes sure we only get one notification.
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://AE")), { }, true));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://ABCDE")), { }, true));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://ABCDEFG")), {
+        { ContentExtensions::ActionType::Notify, "GGG" },
+        { ContentExtensions::ActionType::BlockLoad }
+    }, true));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://FG")), {
+        { ContentExtensions::ActionType::Notify, "GGG" },
+        { ContentExtensions::ActionType::BlockLoad }
+    }));
+    ASSERT_TRUE(actionsEqual(backend2.actionsForResourceLoad(mainDocumentRequest("http://EFG")), {
+        { ContentExtensions::ActionType::Notify, "GGG" },
+        { ContentExtensions::ActionType::BlockLoad }
+    }, true));
+}
+
+template<typename T, size_t cStringLength>
+static int sequenceInstances(const Vector<T> vector, const char (&sequence)[cStringLength])
+{
+    static_assert(sizeof(T) == sizeof(char), "sequenceInstances should only be used for various byte vectors.");
+
+    size_t sequenceLength = cStringLength - 1;
+    size_t instances = 0;
+    for (size_t i = 0; i <= vector.size() - sequenceLength; ++i) {
+        for (size_t j = 0; j < sequenceLength; j++) {
+            if (vector[i + j] != sequence[j])
+                break;
+            if (j == sequenceLength - 1)
+                instances++;
+        }
+    }
+    return instances;
+}
+
+TEST_F(ContentExtensionTest, StringCombining)
+{
+    auto extension = InMemoryCompiledContentExtension::create(jsonWithStringsToCombine);
+    const auto& data = extension->data();
+
+    ASSERT_EQ(sequenceInstances(data.actions, "AAA"), 2);
+    ASSERT_EQ(sequenceInstances(data.actions, "GGG"), 1);
+
+    ASSERT_EQ(data.actions.size(), 78u);
+    ASSERT_EQ(data.filtersWithoutConditions.size(),  313u);
+    ASSERT_EQ(data.filtersWithConditions.size(),  5u);
+    ASSERT_EQ(data.topURLFilters.size(),  5u);
+    ASSERT_FALSE(data.conditionsApplyOnlyToDomain);
+
+    auto extensionWithFlags = InMemoryCompiledContentExtension::create("["
+        "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"A\"}},"
+        "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"C\"}},"
+        "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"A\",\"resource-type\":[\"document\"]}},"
+        "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"A\",\"resource-type\":[\"document\",\"font\"]}},"
+        "{\"action\":{\"type\":\"notify\",\"notification\":\"AAA\"},\"trigger\":{\"url-filter\":\"B\",\"resource-type\":[\"document\"]}}"
+    "]");
+    ASSERT_EQ(sequenceInstances(extensionWithFlags->data().actions, "AAA"), 3); // There are 3 sets of unique flags for AAA actions.
+}
+
 TEST_F(ContentExtensionTest, TermsKnownToMatchAnything)
 {
     auto backend = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"^pre1.*post1$\"}},"
@@ -1377,6 +1485,12 @@ TEST_F(ContentExtensionTest, InvalidJSON)
     checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"resource-type\":false}}]",
         ContentExtensions::ContentExtensionError::JSONInvalidTriggerFlagsArray);
     
+    checkCompilerError("[{\"action\":{\"type\":\"notify\"},\"trigger\":{\"url-filter\":\".*\"}}]", ContentExtensions::ContentExtensionError::JSONInvalidNotification);
+    checkCompilerError("[{\"action\":{\"type\":\"notify\",\"notification\":5},\"trigger\":{\"url-filter\":\".*\"}}]", ContentExtensions::ContentExtensionError::JSONInvalidNotification);
+    checkCompilerError("[{\"action\":{\"type\":\"notify\",\"notification\":[]},\"trigger\":{\"url-filter\":\".*\"}}]", ContentExtensions::ContentExtensionError::JSONInvalidNotification);
+    checkCompilerError("[{\"action\":{\"type\":\"notify\",\"notification\":\"here's my notification\"},\"trigger\":{\"url-filter\":\".*\"}}]", { });
+    checkCompilerError("[{\"action\":{\"type\":\"notify\",\"notification\":\"\\u1234\"},\"trigger\":{\"url-filter\":\".*\"}}]", { });
+    
     StringBuilder rules;
     rules.append("[");
     for (unsigned i = 0; i < 49999; ++i)
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ContentRuleListNotification.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ContentRuleListNotification.mm
new file mode 100644 (file)
index 0000000..aa0e483
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#if WK_API_ENABLED
+
+#import "PlatformUtilities.h"
+#import <WebKit/WKContentRuleListStore.h>
+#import <WebKit/WKNavigationDelegatePrivate.h>
+#import <WebKit/WKURLSchemeHandler.h>
+#import <WebKit/WKUserContentController.h>
+#import <WebKit/WKWebView.h>
+#import <wtf/RetainPtr.h>
+
+static bool receivedNotification;
+static bool receivedAlert;
+
+@interface ContentRuleListNotificationDelegate : NSObject <WKNavigationDelegatePrivate, WKURLSchemeHandler, WKUIDelegate>
+@end
+
+@implementation ContentRuleListNotificationDelegate
+
+- (void)_webView:(WKWebView *)webView URL:(NSURL *)url contentRuleListIdentifiers:(NSArray<NSString *> *)identifiers notifications:(NSArray<NSString *> *)notifications
+{
+    EXPECT_STREQ(url.absoluteString.UTF8String, "apitest:///match");
+    EXPECT_EQ(identifiers.count, 1u);
+    EXPECT_STREQ([identifiers objectAtIndex:0].UTF8String, "testidentifier");
+    EXPECT_EQ(notifications.count, 1u);
+    EXPECT_STREQ([notifications objectAtIndex:0].UTF8String, "testnotification");
+    receivedNotification = true;
+}
+
+- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
+{
+    [urlSchemeTask didReceiveResponse:[[[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil] autorelease]];
+    [urlSchemeTask didFinish];
+}
+
+- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
+{
+}
+
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
+{
+    receivedAlert = true;
+    completionHandler();
+}
+
+@end
+
+static RetainPtr<WKContentRuleList> makeWarnContentRuleList()
+{
+    __block bool doneCompiling = false;
+    __block RetainPtr<WKContentRuleList> contentRuleList;
+    [[WKContentRuleListStore defaultStore] compileContentRuleListForIdentifier:@"testidentifier" encodedContentRuleList:@"[{\"action\":{\"type\":\"notify\",\"notification\":\"testnotification\"},\"trigger\":{\"url-filter\":\"match\"}}]" completionHandler:^(WKContentRuleList *list, NSError *error) {
+        EXPECT_TRUE(list);
+        contentRuleList = list;
+        doneCompiling = true;
+    }];
+    TestWebKitAPI::Util::run(&doneCompiling);
+    return contentRuleList;
+}
+
+TEST(WebKit, ContentRuleListNotificationMainResource)
+{
+    auto delegate = adoptNS([[ContentRuleListNotificationDelegate alloc] init]);
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [[configuration userContentController] addContentRuleList:makeWarnContentRuleList().get()];
+    [configuration setURLSchemeHandler:delegate.get() forURLScheme:@"apitest"];
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView setNavigationDelegate:delegate.get()];
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"apitest:///match"]]];
+    TestWebKitAPI::Util::run(&receivedNotification);
+}
+
+TEST(WebKit, ContentRuleListNotificationSubresource)
+{
+    auto delegate = adoptNS([[ContentRuleListNotificationDelegate alloc] init]);
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [[configuration userContentController] addContentRuleList:makeWarnContentRuleList().get()];
+    [configuration setURLSchemeHandler:delegate.get() forURLScheme:@"apitest"];
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView setNavigationDelegate:delegate.get()];
+    [webView setUIDelegate:delegate.get()];
+    [webView loadHTMLString:@"<script>fetch('match').then(function(response){alert('fetch complete')})</script>" baseURL:[NSURL URLWithString:@"apitest:///"]];
+    TestWebKitAPI::Util::run(&receivedAlert);
+    EXPECT_TRUE(receivedNotification);
+}
+
+#endif // WK_API_ENABLED