+2017-09-27 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-27 Wenson Hsieh <wenson_hsieh@apple.com>
Unreviewed, fix the internal build after r222596 and r222595.
#if ENABLE(CONTENT_EXTENSIONS)
+#include <wtf/HashSet.h>
+#include <wtf/text/WTFString.h>
+
namespace WebCore {
+class Page;
class ResourceRequest;
namespace ContentExtensions {
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
}
}
+// 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];
blockCookiesActionsMap.clear();
cssDisplayNoneActionsMap.clear();
makeHTTPSActionsMap.clear();
+ notifyActionsMap.clear();
} else
ignorePreviousRuleActionsMap.clear();
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();
} 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;
case ActionType::MakeHTTPS:
findOrMakeActionLocation(makeHTTPSActionsMap);
break;
+ case ActionType::Notify:
+ findOrMakeStringActionLocation(notifyActionsMap);
+ break;
}
actionLocations.append(actionLocation);
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();
JSONInvalidAction,
JSONInvalidActionType,
JSONInvalidCSSDisplayNoneActionType,
+ JSONInvalidNotification,
JSONInvalidRegex,
};
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);
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);
}
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);
}
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();
switch (type) {
case ActionType::BlockCookies:
case ActionType::BlockLoad:
+ case ActionType::Notify:
case ActionType::IgnorePreviousRules:
case ActionType::CSSDisplayNoneSelector:
case ActionType::MakeHTTPS:
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;
, 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
#if ENABLE(CONTENT_EXTENSIONS)
+#include "Chrome.h"
+#include "ChromeClient.h"
#include "CompiledContentExtension.h"
#include "ContentExtension.h"
#include "ContentExtensionsDebugging.h"
#include "Frame.h"
#include "FrameLoaderClient.h"
#include "MainFrame.h"
+#include "Page.h"
#include "ResourceLoadInfo.h"
#include "URL.h"
#include "UserContentController.h"
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:
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())))
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)
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()
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);
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());
if (!page)
return false;
auto status = page->userContentProvider().processContentExtensionRulesForLoad(request.url(), resourceType, *documentLoader);
- applyBlockedStatusToRequest(status, request);
+ applyBlockedStatusToRequest(status, page, request);
return status.blockedLoad;
}
}
#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());
#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) {
#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
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&);
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;
+2017-09-27 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-27 Wenson Hsieh <wenson_hsieh@apple.com>
Drag event DataTransfer has unexpected types "dyn.ah62d4..."
ContentExtensions::BlockedStatus PingLoad::processContentExtensionRulesForLoad(ResourceRequest& request)
{
auto status = contentExtensionsBackend().processContentExtensionRulesForPingLoad(request.url(), m_parameters.mainDocumentURL);
- applyBlockedStatusToRequest(status, request);
+ applyBlockedStatusToRequest(status, nullptr, request);
return status;
}
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&)
{
};
template<> struct ClientTraits<WKPageNavigationClientBase> {
- typedef std::tuple<WKPageNavigationClientV0, WKPageNavigationClientV1> Versions;
+ typedef std::tuple<WKPageNavigationClientV0, WKPageNavigationClientV1, WKPageNavigationClientV2> Versions;
};
template<> struct ClientTraits<WKPagePolicyClientBase> {
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
{
typedef void (*WKPageNavigationDidRemoveNavigationGestureSnapshot)(WKPageRef page, const void* clientInfo);
+typedef void (*WKPageNavigationContentRuleListNotificationCallback)(WKPageRef, WKURLRef, WKArrayRef, WKArrayRef, const void* clientInfo);
typedef struct WKPageNavigationClientBase {
int version;
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
- (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;
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;
bool webViewDidEndNavigationGestureWithNavigationToBackForwardListItem : 1;
bool webViewWillSnapshotBackForwardListItem : 1;
bool webViewNavigationGestureSnapshotWasRemoved : 1;
+ bool webViewURLContentRuleListIdentifiersNotifications : 1;
#if USE(QUICK_LOOK)
bool webViewDidStartLoadForQuickLookDocumentInMainFrame : 1;
bool webViewDidFinishLoadForQuickLookDocumentInMainFrame : 1;
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:)];
}
}
+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) {
});
}
+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);
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);
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)
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);
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
+2017-09-27 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-27 Wenson Hsieh <wenson_hsieh@apple.com>
Drag event DataTransfer has unexpected types "dyn.ah62d4..."
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 */,
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:
EXPECT_EQ(data.topURLFilters.size(), 0ull);
}
+private:
void writeSource(const String&) final { }
void writeActions(Vector<ContentExtensions::SerializedActionByte>&& actions, bool conditionsApplyOnlyToDomain) final
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(); }
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;
};
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;
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));
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));
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$\"}},"
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)
--- /dev/null
+/*
+ * 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