Title: [213669] trunk
Revision
213669
Author
[email protected]
Date
2017-03-09 14:17:13 -0800 (Thu, 09 Mar 2017)

Log Message

[Content Extensions] Introduce if-top-url and unless-top-url
https://bugs.webkit.org/show_bug.cgi?id=169433

Reviewed by Brady Eidson.

Source/WebCore:

In r184116 I added if-domain and unless-domain to control whether a rule applies
based on the domain of the main document URL.  I'm expanding this by adding if-top-url
and unless-top-url that run regular expressions on the entire main document URL so that
example.com/user1content can be distinguished from example.com/user2content.
To not add to the number of passes we make on the URLs for each load and to maintain JSON
backwards compatibility, I've made it so that if-top-url and unless-top-url can be used
instead of if-domain and unless-domain (which continue to work) but the two condition types
cannot be used together since running regular expressions on the entire main document URL
is strictly more powerful than checking the domain and subdomains.
As a minor detail, content extension regexes are by default ASCII-case-insensitive, so I've
done the same with top URL regexes, adding top-url-filter-is-case-sensitive to mirror the existing
url-filter-is-case-sensitive if the JSON author decides to make regexes case sensitive.

Covered by new API tests.

* contentextensions/CompiledContentExtension.h:
* contentextensions/ContentExtension.cpp:
(WebCore::ContentExtensions::ContentExtension::populateConditionCacheIfNeeded):
(WebCore::ContentExtensions::ContentExtension::topURLActions):
(WebCore::ContentExtensions::ContentExtension::cachedConditionedActions): Deleted.
* contentextensions/ContentExtension.h:
* contentextensions/ContentExtensionCompiler.cpp:
(WebCore::ContentExtensions::addUniversalActionsToDFA):
(WebCore::ContentExtensions::compileToBytecode):
We had three copies of compiling to bytecode that were almost the same and would've been made into three copies of the same code.
I moved them to one helper function that is called three times.
(WebCore::ContentExtensions::compileRuleList):
* contentextensions/ContentExtensionCompiler.h:
* contentextensions/ContentExtensionError.cpp:
(WebCore::ContentExtensions::contentExtensionErrorCategory):
Add the new error type for JSON that tries to use if-top-url and unless-top-url with if-domain and unless-domain.
* contentextensions/ContentExtensionError.h:
* contentextensions/ContentExtensionParser.cpp:
(WebCore::ContentExtensions::loadTrigger):
Parse the new values if-top-url, unless-top-url, and top-url-filter-is-case-sensitive.
* contentextensions/ContentExtensionRule.h:
(WebCore::ContentExtensions::Trigger::~Trigger):
* contentextensions/ContentExtensionsBackend.cpp:
(WebCore::ContentExtensions::ContentExtensionsBackend::actionsForResourceLoad):
* contentextensions/DFABytecodeInterpreter.cpp:
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpretAppendAction):
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpretTestFlagsAndAppendAction):
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpretWithConditions):
* contentextensions/DFABytecodeInterpreter.h:

Source/WebKit2:

Rename conditionedFilters to topURLFilters to reflect the fact that they are the filters
that are run on the top URL, and possibly just the domain of the top url.
I was a bit too aggressive when renaming domain* to condition* in r213533.

* Shared/WebCompiledContentExtension.cpp:
(WebKit::WebCompiledContentExtension::conditionsApplyOnlyToDomain):
(WebKit::WebCompiledContentExtension::topURLFiltersBytecode):
(WebKit::WebCompiledContentExtension::topURLFiltersBytecodeLength):
(WebKit::WebCompiledContentExtension::conditionedFiltersBytecode): Deleted.
(WebKit::WebCompiledContentExtension::conditionedFiltersBytecodeLength): Deleted.
* Shared/WebCompiledContentExtension.h:
* Shared/WebCompiledContentExtensionData.cpp:
(WebKit::WebCompiledContentExtensionData::encode):
(WebKit::WebCompiledContentExtensionData::decode):
* Shared/WebCompiledContentExtensionData.h:
(WebKit::WebCompiledContentExtensionData::WebCompiledContentExtensionData):
* UIProcess/API/APIUserContentExtensionStore.cpp:
(API::encodeContentExtensionMetaData):
(API::decodeContentExtensionMetaData):
(API::compiledToFile):
(API::createExtension):
(API::UserContentExtensionStore::invalidateContentExtensionVersion):
(API::userContentExtensionStoreErrorCategory):
* UIProcess/API/APIUserContentExtensionStore.h:
Increment CurrentContentExtensionFileVersion because we have changed the format of the binary on disk.
We only added 4 bytes, but that's binary incompatible and requires re-compiling any existing content extensions.

Tools:

* TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
(TestWebKitAPI::TEST_F):
Add tests for new functionality and new failure types.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (213668 => 213669)


--- trunk/Source/WebCore/ChangeLog	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/ChangeLog	2017-03-09 22:17:13 UTC (rev 213669)
@@ -1,3 +1,55 @@
+2017-03-09  Alex Christensen  <[email protected]>
+
+        [Content Extensions] Introduce if-top-url and unless-top-url
+        https://bugs.webkit.org/show_bug.cgi?id=169433
+
+        Reviewed by Brady Eidson.
+
+        In r184116 I added if-domain and unless-domain to control whether a rule applies
+        based on the domain of the main document URL.  I'm expanding this by adding if-top-url
+        and unless-top-url that run regular expressions on the entire main document URL so that
+        example.com/user1content can be distinguished from example.com/user2content.
+        To not add to the number of passes we make on the URLs for each load and to maintain JSON
+        backwards compatibility, I've made it so that if-top-url and unless-top-url can be used
+        instead of if-domain and unless-domain (which continue to work) but the two condition types
+        cannot be used together since running regular expressions on the entire main document URL
+        is strictly more powerful than checking the domain and subdomains.
+        As a minor detail, content extension regexes are by default ASCII-case-insensitive, so I've
+        done the same with top URL regexes, adding top-url-filter-is-case-sensitive to mirror the existing
+        url-filter-is-case-sensitive if the JSON author decides to make regexes case sensitive.
+
+        Covered by new API tests.
+
+        * contentextensions/CompiledContentExtension.h:
+        * contentextensions/ContentExtension.cpp:
+        (WebCore::ContentExtensions::ContentExtension::populateConditionCacheIfNeeded):
+        (WebCore::ContentExtensions::ContentExtension::topURLActions):
+        (WebCore::ContentExtensions::ContentExtension::cachedConditionedActions): Deleted.
+        * contentextensions/ContentExtension.h:
+        * contentextensions/ContentExtensionCompiler.cpp:
+        (WebCore::ContentExtensions::addUniversalActionsToDFA):
+        (WebCore::ContentExtensions::compileToBytecode):
+        We had three copies of compiling to bytecode that were almost the same and would've been made into three copies of the same code.
+        I moved them to one helper function that is called three times.
+        (WebCore::ContentExtensions::compileRuleList):
+        * contentextensions/ContentExtensionCompiler.h:
+        * contentextensions/ContentExtensionError.cpp:
+        (WebCore::ContentExtensions::contentExtensionErrorCategory):
+        Add the new error type for JSON that tries to use if-top-url and unless-top-url with if-domain and unless-domain.
+        * contentextensions/ContentExtensionError.h:
+        * contentextensions/ContentExtensionParser.cpp:
+        (WebCore::ContentExtensions::loadTrigger):
+        Parse the new values if-top-url, unless-top-url, and top-url-filter-is-case-sensitive.
+        * contentextensions/ContentExtensionRule.h:
+        (WebCore::ContentExtensions::Trigger::~Trigger):
+        * contentextensions/ContentExtensionsBackend.cpp:
+        (WebCore::ContentExtensions::ContentExtensionsBackend::actionsForResourceLoad):
+        * contentextensions/DFABytecodeInterpreter.cpp:
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpretAppendAction):
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpretTestFlagsAndAppendAction):
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpretWithConditions):
+        * contentextensions/DFABytecodeInterpreter.h:
+
 2017-03-09  Nikita Vasilyev  <[email protected]>
 
         Web Inspector: Show individual messages in the content pane for a WebSocket

Modified: trunk/Source/WebCore/contentextensions/CompiledContentExtension.h (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/CompiledContentExtension.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/CompiledContentExtension.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -42,10 +42,11 @@
     virtual unsigned filtersWithoutConditionsBytecodeLength() const = 0;
     virtual const DFABytecode* filtersWithConditionsBytecode() const = 0;
     virtual unsigned filtersWithConditionsBytecodeLength() const = 0;
-    virtual const DFABytecode* conditionedFiltersBytecode() const = 0;
-    virtual unsigned conditionedFiltersBytecodeLength() const = 0;
+    virtual const DFABytecode* topURLFiltersBytecode() const = 0;
+    virtual unsigned topURLFiltersBytecodeLength() const = 0;
     virtual const SerializedActionByte* actions() const = 0;
     virtual unsigned actionsLength() const = 0;
+    virtual bool conditionsApplyOnlyToDomain() const = 0;
 };
 
 } // namespace ContentExtensions

Modified: trunk/Source/WebCore/contentextensions/ContentExtension.cpp (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtension.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtension.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -118,31 +118,33 @@
 
 void ContentExtension::populateConditionCacheIfNeeded(const URL& topURL)
 {
-    String domain = topURL.host();
-    if (m_cachedDomain != domain) {
-        DFABytecodeInterpreter interpreter(m_compiledExtension->conditionedFiltersBytecode(), m_compiledExtension->conditionedFiltersBytecodeLength());
+    if (m_cachedTopURL != topURL) {
+        DFABytecodeInterpreter interpreter(m_compiledExtension->topURLFiltersBytecode(), m_compiledExtension->topURLFiltersBytecodeLength());
         const uint16_t allLoadTypesAndResourceTypes = LoadTypeMask | ResourceTypeMask;
-        auto domainActions = interpreter.interpret(domain.utf8(), allLoadTypesAndResourceTypes);
+        String string = m_compiledExtension->conditionsApplyOnlyToDomain() ? topURL.host() : topURL.string();
+        auto topURLActions = interpreter.interpret(string.utf8(), allLoadTypesAndResourceTypes);
         
-        m_cachedConditionedActions.clear();
-        for (uint64_t action : domainActions)
-            m_cachedConditionedActions.add(action);
+        m_cachedTopURLActions.clear();
+        for (uint64_t action : topURLActions)
+            m_cachedTopURLActions.add(action);
+        for (uint64_t action : interpreter.actionsMatchingEverything())
+            m_cachedTopURLActions.add(action);
         
         m_cachedUniversalConditionedActions.clear();
         for (uint64_t action : m_universalActionsWithConditions) {
-        ASSERT_WITH_MESSAGE((action & ~IfConditionFlag) == static_cast<uint32_t>(action), "Universal actions with domains should not have flags.");
-            if (!!(action & IfConditionFlag) == m_cachedConditionedActions.contains(action))
+        ASSERT_WITH_MESSAGE((action & ~IfConditionFlag) == static_cast<uint32_t>(action), "Universal actions with conditions should not have flags.");
+            if (!!(action & IfConditionFlag) == m_cachedTopURLActions.contains(action))
                 m_cachedUniversalConditionedActions.append(static_cast<uint32_t>(action));
         }
         m_cachedUniversalConditionedActions.shrinkToFit();
-        m_cachedDomain = domain;
+        m_cachedTopURL = topURL;
     }
 }
 
-const DFABytecodeInterpreter::Actions& ContentExtension::cachedConditionedActions(const URL& topURL)
+const DFABytecodeInterpreter::Actions& ContentExtension::topURLActions(const URL& topURL)
 {
     populateConditionCacheIfNeeded(topURL);
-    return m_cachedConditionedActions;
+    return m_cachedTopURLActions;
 }
 
 const Vector<uint32_t>& ContentExtension::universalActionsWithConditions(const URL& topURL)

Modified: trunk/Source/WebCore/contentextensions/ContentExtension.h (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtension.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtension.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -46,7 +46,7 @@
     const String& identifier() const { return m_identifier; }
     const CompiledContentExtension& compiledExtension() const { return m_compiledExtension.get(); }
     StyleSheetContents* globalDisplayNoneStyleSheet();
-    const DFABytecodeInterpreter::Actions& cachedConditionedActions(const URL& topURL);
+    const DFABytecodeInterpreter::Actions& topURLActions(const URL& topURL);
     const Vector<uint32_t>& universalActionsWithoutConditions() { return m_universalActionsWithoutConditions; }
     const Vector<uint32_t>& universalActionsWithConditions(const URL& topURL);
 
@@ -60,9 +60,9 @@
     RefPtr<StyleSheetContents> m_globalDisplayNoneStyleSheet;
     void compileGlobalDisplayNoneStyleSheet();
 
-    String m_cachedDomain;
+    URL m_cachedTopURL;
     void populateConditionCacheIfNeeded(const URL& topURL);
-    DFABytecodeInterpreter::Actions m_cachedConditionedActions;
+    DFABytecodeInterpreter::Actions m_cachedTopURLActions;
     Vector<uint32_t> m_cachedUniversalConditionedActions;
 
     Vector<uint32_t> m_universalActionsWithoutConditions;

Modified: trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.cpp (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -187,7 +187,7 @@
 
 typedef HashSet<uint64_t, DefaultHash<uint64_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<uint64_t>> UniversalActionSet;
 
-static void addUniversalActionsToDFA(DFA& dfa, const UniversalActionSet& universalActions)
+static void addUniversalActionsToDFA(DFA& dfa, UniversalActionSet&& universalActions)
 {
     if (universalActions.isEmpty())
         return;
@@ -205,6 +205,78 @@
     root.setActions(actionsStart, static_cast<uint16_t>(actionsLength));
 }
 
+template<typename Functor>
+static void compileToBytecode(CombinedURLFilters&& filters, UniversalActionSet&& universalActions, Functor writeBytecodeToClient)
+{
+    // Smaller maxNFASizes risk high compiling and interpreting times from having too many DFAs,
+    // larger maxNFASizes use too much memory when compiling.
+    const unsigned maxNFASize = 75000;
+
+    bool firstNFASeen = false;
+
+    auto lowerDFAToBytecode = [&](DFA&& dfa)
+    {
+#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
+        dataLogF("DFA\n");
+        dfa.debugPrintDot();
+#endif
+        ASSERT_WITH_MESSAGE(!dfa.nodes[dfa.root].hasActions(), "All actions on the DFA root should come from regular expressions that match everything.");
+
+        if (!firstNFASeen) {
+            // Put all the universal actions on the first DFA.
+            addUniversalActionsToDFA(dfa, WTFMove(universalActions));
+        }
+
+        Vector<DFABytecode> bytecode;
+        DFABytecodeCompiler compiler(dfa, bytecode);
+        compiler.compile();
+        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
+        writeBytecodeToClient(WTFMove(bytecode));
+        firstNFASeen = true;
+    };
+
+    const unsigned smallDFASize = 100;
+    DFACombiner smallDFACombiner;
+    filters.processNFAs(maxNFASize, [&](NFA&& nfa) {
+#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
+        dataLogF("NFA\n");
+        nfa.debugPrintDot();
+#endif
+        LOG_LARGE_STRUCTURES(nfa, nfa.memoryUsed());
+        DFA dfa = NFAToDFA::convert(nfa);
+        LOG_LARGE_STRUCTURES(dfa, dfa.memoryUsed());
+
+        if (dfa.graphSize() < smallDFASize)
+            smallDFACombiner.addDFA(WTFMove(dfa));
+        else {
+            dfa.minimize();
+            lowerDFAToBytecode(WTFMove(dfa));
+        }
+    });
+
+    smallDFACombiner.combineDFAs(smallDFASize, [&](DFA&& dfa) {
+        LOG_LARGE_STRUCTURES(dfa, dfa.memoryUsed());
+        lowerDFAToBytecode(WTFMove(dfa));
+    });
+
+    ASSERT(filters.isEmpty());
+
+    if (!firstNFASeen) {
+        // Our bytecode interpreter expects to have at least one DFA, so if we haven't seen any
+        // create a dummy one and add any universal actions.
+
+        DFA dummyDFA = DFA::empty();
+        addUniversalActionsToDFA(dummyDFA, WTFMove(universalActions));
+
+        Vector<DFABytecode> bytecode;
+        DFABytecodeCompiler compiler(dummyDFA, bytecode);
+        compiler.compile();
+        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
+        writeBytecodeToClient(WTFMove(bytecode));
+    }
+    LOG_LARGE_STRUCTURES(universalActions, universalActions.capacity() * sizeof(unsigned));
+}
+
 std::error_code compileRuleList(ContentExtensionCompilationClient& client, String&& ruleJSON)
 {
     auto ruleList = parseRuleList(WTFMove(ruleJSON));
@@ -212,6 +284,25 @@
         return ruleList.error();
     Vector<ContentExtensionRule> parsedRuleList = WTFMove(ruleList.value());
 
+    bool domainConditionSeen = false;
+    bool topURLConditionSeen = false;
+    for (const auto& rule : parsedRuleList) {
+        switch (rule.trigger().conditionType) {
+        case Trigger::ConditionType::None:
+            break;
+        case Trigger::ConditionType::IfDomain:
+        case Trigger::ConditionType::UnlessDomain:
+            domainConditionSeen = true;
+            break;
+        case Trigger::ConditionType::IfTopURL:
+        case Trigger::ConditionType::UnlessTopURL:
+            topURLConditionSeen = true;
+            break;
+        }
+    }
+    if (topURLConditionSeen && domainConditionSeen)
+        return ContentExtensionError::JSONTopURLAndDomainConditions;
+
 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
     double patternPartitioningStart = monotonicallyIncreasingTime();
 #endif
@@ -218,19 +309,20 @@
 
     Vector<SerializedActionByte> actions;
     Vector<unsigned> actionLocations = serializeActions(parsedRuleList, actions);
-    client.writeActions(WTFMove(actions));
     LOG_LARGE_STRUCTURES(actions, actions.capacity() * sizeof(SerializedActionByte));
-    actions.clear();
+    client.writeActions(WTFMove(actions), domainConditionSeen);
 
     UniversalActionSet universalActionsWithoutConditions;
     UniversalActionSet universalActionsWithConditions;
+    UniversalActionSet universalTopURLActions;
 
     // FIXME: These don't all need to be in memory at the same time.
     CombinedURLFilters filtersWithoutConditions;
     CombinedURLFilters filtersWithConditions;
-    CombinedURLFilters conditionFilters;
+    CombinedURLFilters topURLFilters;
     URLFilterParser filtersWithoutConditionParser(filtersWithoutConditions);
     URLFilterParser filtersWithConditionParser(filtersWithConditions);
+    URLFilterParser topURLFilterParser(topURLFilters);
     
     for (unsigned ruleIndex = 0; ruleIndex < parsedRuleList.size(); ++ruleIndex) {
         const ContentExtensionRule& contentExtensionRule = parsedRuleList[ruleIndex];
@@ -256,10 +348,12 @@
         } else {
             switch (trigger.conditionType) {
             case Trigger::ConditionType::IfDomain:
+            case Trigger::ConditionType::IfTopURL:
                 actionLocationAndFlags |= IfConditionFlag;
                 break;
             case Trigger::ConditionType::None:
             case Trigger::ConditionType::UnlessDomain:
+            case Trigger::ConditionType::UnlessTopURL:
                 ASSERT(!(actionLocationAndFlags & IfConditionFlag));
                 break;
             }
@@ -273,8 +367,23 @@
                 dataLogF("Error while parsing %s: %s\n", trigger.urlFilter.utf8().data(), URLFilterParser::statusString(status).utf8().data());
                 return ContentExtensionError::JSONInvalidRegex;
             }
-            for (const String& condition : trigger.conditions)
-                conditionFilters.addDomain(actionLocationAndFlags, condition);
+            for (const String& condition : trigger.conditions) {
+                if (domainConditionSeen) {
+                    ASSERT(!topURLConditionSeen);
+                    topURLFilters.addDomain(actionLocationAndFlags, condition);
+                } else {
+                    ASSERT(topURLConditionSeen);
+                    status = topURLFilterParser.addPattern(condition, trigger.topURLConditionIsCaseSensitive, actionLocationAndFlags);
+                    if (status == URLFilterParser::MatchesEverything) {
+                        universalTopURLActions.add(actionLocationAndFlags);
+                        status = URLFilterParser::Ok;
+                    }
+                    if (status != URLFilterParser::Ok) {
+                        dataLogF("Error while parsing %s: %s\n", condition.utf8().data(), URLFilterParser::statusString(status).utf8().data());
+                        return ContentExtensionError::JSONInvalidRegex;
+                    }
+                }
+            }
         }
         ASSERT(status == URLFilterParser::Ok);
     }
@@ -290,182 +399,22 @@
 
     LOG_LARGE_STRUCTURES(filtersWithoutConditions, filtersWithoutConditions.memoryUsed());
     LOG_LARGE_STRUCTURES(filtersWithConditions, filtersWithConditions.memoryUsed());
-    LOG_LARGE_STRUCTURES(conditionFilters, conditionFilters.memoryUsed());
+    LOG_LARGE_STRUCTURES(topURLFilters, topURLFilters.memoryUsed());
 
 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
-    unsigned machinesWithoutConditionsCount = 0;
-    unsigned totalBytecodeSizeForMachinesWithoutConditions = 0;
-    unsigned machinesWithConditionsCount = 0;
-    unsigned totalBytecodeSizeForMachinesWithConditions = 0;
     double totalNFAToByteCodeBuildTimeStart = monotonicallyIncreasingTime();
 #endif
 
-    // Smaller maxNFASizes risk high compiling and interpreting times from having too many DFAs,
-    // larger maxNFASizes use too much memory when compiling.
-    const unsigned maxNFASize = 75000;
-    
-    bool firstNFAWithoutConditionsSeen = false;
-
-    auto lowerFiltersWithoutConditionsDFAToBytecode = [&](DFA&& dfa)
-    {
-#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
-        dataLogF("filtersWithoutConditions DFA\n");
-        dfa.debugPrintDot();
-#endif
-        ASSERT_WITH_MESSAGE(!dfa.nodes[dfa.root].hasActions(), "All actions on the DFA root should come from regular expressions that match everything.");
-
-        if (!firstNFAWithoutConditionsSeen) {
-            // Put all the universal actions on the first DFA.
-            addUniversalActionsToDFA(dfa, universalActionsWithoutConditions);
-        }
-
-        Vector<DFABytecode> bytecode;
-        DFABytecodeCompiler compiler(dfa, bytecode);
-        compiler.compile();
-        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
-#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
-        ++machinesWithoutConditionsCount;
-        totalBytecodeSizeForMachinesWithoutConditions += bytecode.size();
-#endif
+    compileToBytecode(WTFMove(filtersWithoutConditions), WTFMove(universalActionsWithoutConditions), [&](Vector<DFABytecode>&& bytecode) {
         client.writeFiltersWithoutConditionsBytecode(WTFMove(bytecode));
-
-        firstNFAWithoutConditionsSeen = true;
-    };
-
-    const unsigned smallDFASize = 100;
-    DFACombiner smallFiltersWithoutConditionsDFACombiner;
-    filtersWithoutConditions.processNFAs(maxNFASize, [&](NFA&& nfa) {
-#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
-        dataLogF("filtersWithoutConditions NFA\n");
-        nfa.debugPrintDot();
-#endif
-
-        LOG_LARGE_STRUCTURES(nfa, nfa.memoryUsed());
-        DFA dfa = NFAToDFA::convert(nfa);
-        LOG_LARGE_STRUCTURES(dfa, dfa.memoryUsed());
-
-        if (dfa.graphSize() < smallDFASize)
-            smallFiltersWithoutConditionsDFACombiner.addDFA(WTFMove(dfa));
-        else {
-            dfa.minimize();
-            lowerFiltersWithoutConditionsDFAToBytecode(WTFMove(dfa));
-        }
     });
-
-
-    smallFiltersWithoutConditionsDFACombiner.combineDFAs(smallDFASize, [&](DFA&& dfa) {
-        LOG_LARGE_STRUCTURES(dfa, dfa.memoryUsed());
-        lowerFiltersWithoutConditionsDFAToBytecode(WTFMove(dfa));
-    });
-
-    ASSERT(filtersWithoutConditions.isEmpty());
-
-    if (!firstNFAWithoutConditionsSeen) {
-        // Our bytecode interpreter expects to have at least one DFA, so if we haven't seen any
-        // create a dummy one and add any universal actions.
-
-        DFA dummyDFA = DFA::empty();
-        addUniversalActionsToDFA(dummyDFA, universalActionsWithoutConditions);
-
-        Vector<DFABytecode> bytecode;
-        DFABytecodeCompiler compiler(dummyDFA, bytecode);
-        compiler.compile();
-        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
-        client.writeFiltersWithoutConditionsBytecode(WTFMove(bytecode));
-    }
-    LOG_LARGE_STRUCTURES(universalActionsWithoutConditions, universalActionsWithoutConditions.capacity() * sizeof(unsigned));
-    universalActionsWithoutConditions.clear();
-    
-    bool firstNFAWithConditionsSeen = false;
-    auto lowerFiltersWithConditionsDFAToBytecode = [&](DFA&& dfa)
-    {
-        if (!firstNFAWithConditionsSeen) {
-            // Put all the universal actions on the first DFA.
-            addUniversalActionsToDFA(dfa, universalActionsWithConditions);
-        }
-
-        Vector<DFABytecode> bytecode;
-        DFABytecodeCompiler compiler(dfa, bytecode);
-        compiler.compile();
-        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
-#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
-        ++machinesWithConditionsCount;
-        totalBytecodeSizeForMachinesWithConditions += bytecode.size();
-#endif
+    compileToBytecode(WTFMove(filtersWithConditions), WTFMove(universalActionsWithConditions), [&](Vector<DFABytecode>&& bytecode) {
         client.writeFiltersWithConditionsBytecode(WTFMove(bytecode));
-
-        firstNFAWithConditionsSeen = true;
-    };
-
-    DFACombiner smallFiltersWithConditionsDFACombiner;
-    filtersWithConditions.processNFAs(maxNFASize, [&](NFA&& nfa) {
-#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
-        dataLogF("filtersWithConditions NFA\n");
-        nfa.debugPrintDot();
-#endif
-        LOG_LARGE_STRUCTURES(nfa, nfa.memoryUsed());
-        DFA dfa = NFAToDFA::convert(nfa);
-#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
-        dataLogF("filtersWithConditions PRE MINIMIZING DFA\n");
-        dfa.debugPrintDot();
-#endif
-        LOG_LARGE_STRUCTURES(dfa, dfa.memoryUsed());
-
-        ASSERT_WITH_MESSAGE(!dfa.nodes[dfa.root].hasActions(), "Filters with Conditions that match everything are not allowed right now.");
-
-        if (dfa.graphSize() < smallDFASize)
-            smallFiltersWithConditionsDFACombiner.addDFA(WTFMove(dfa));
-        else {
-            dfa.minimize();
-            lowerFiltersWithConditionsDFAToBytecode(WTFMove(dfa));
-        }
     });
-    smallFiltersWithConditionsDFACombiner.combineDFAs(smallDFASize, [&](DFA&& dfa) {
-        LOG_LARGE_STRUCTURES(dfa, dfa.memoryUsed());
-        lowerFiltersWithConditionsDFAToBytecode(WTFMove(dfa));
+    compileToBytecode(WTFMove(topURLFilters), WTFMove(universalTopURLActions), [&](Vector<DFABytecode>&& bytecode) {
+        client.writeTopURLFiltersBytecode(WTFMove(bytecode));
     });
-    ASSERT(filtersWithConditions.isEmpty());
     
-    if (!firstNFAWithConditionsSeen) {
-        // Our bytecode interpreter expects to have at least one DFA, so if we haven't seen any
-        // create a dummy one and add any universal actions.
-
-        DFA dummyDFA = DFA::empty();
-        addUniversalActionsToDFA(dummyDFA, universalActionsWithConditions);
-        
-        Vector<DFABytecode> bytecode;
-        DFABytecodeCompiler compiler(dummyDFA, bytecode);
-        compiler.compile();
-        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
-        client.writeFiltersWithConditionsBytecode(WTFMove(bytecode));
-    }
-    LOG_LARGE_STRUCTURES(universalActionsWithConditions, universalActionsWithConditions.capacity() * sizeof(unsigned));
-    universalActionsWithConditions.clear();
-
-    conditionFilters.processNFAs(maxNFASize, [&](NFA&& nfa) {
-#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
-        dataLogF("conditionFilters NFA\n");
-        nfa.debugPrintDot();
-#endif
-        LOG_LARGE_STRUCTURES(nfa, nfa.memoryUsed());
-        DFA dfa = NFAToDFA::convert(nfa);
-#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
-        dataLogF("conditionFilters DFA\n");
-        dfa.debugPrintDot();
-#endif
-        LOG_LARGE_STRUCTURES(dfa, dfa.memoryUsed());
-        // Minimizing this DFA would not be effective because all actions are unique
-        // and because of the tree-like structure of this DFA.
-        ASSERT_WITH_MESSAGE(!dfa.nodes[dfa.root].hasActions(), "There should not be any conditions that match everything.");
-
-        Vector<DFABytecode> bytecode;
-        DFABytecodeCompiler compiler(dfa, bytecode);
-        compiler.compile();
-        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
-        client.writeConditionedFiltersBytecode(WTFMove(bytecode));
-    });
-    ASSERT(conditionFilters.isEmpty());
-    
 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
     double totalNFAToByteCodeBuildTimeEnd = monotonicallyIncreasingTime();
     dataLogF("    Time spent building and compiling the DFAs: %f\n", (totalNFAToByteCodeBuildTimeEnd - totalNFAToByteCodeBuildTimeStart));

Modified: trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.h (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -40,10 +40,10 @@
     virtual ~ContentExtensionCompilationClient() { }
     
     // Functions should be called in this order. All except writeActions and finalize can be called multiple times, though.
-    virtual void writeActions(Vector<SerializedActionByte>&&) = 0;
+    virtual void writeActions(Vector<SerializedActionByte>&&, bool conditionsApplyOnlyToDomain) = 0;
     virtual void writeFiltersWithoutConditionsBytecode(Vector<DFABytecode>&&) = 0;
     virtual void writeFiltersWithConditionsBytecode(Vector<DFABytecode>&&) = 0;
-    virtual void writeConditionedFiltersBytecode(Vector<DFABytecode>&&) = 0;
+    virtual void writeTopURLFiltersBytecode(Vector<DFABytecode>&&) = 0;
     virtual void finalize() = 0;
 };
 

Modified: trunk/Source/WebCore/contentextensions/ContentExtensionError.cpp (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtensionError.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionError.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -78,13 +78,15 @@
             case ContentExtensionError::JSONInvalidRegex:
                 return "Invalid or unsupported regular _expression_.";
             case ContentExtensionError::JSONInvalidConditionList:
-                return "Invalid list of if-domain or unless-domain conditions.";
+                return "Invalid list of if-domain, unless-domain, if-top-url, or unless-top-url conditions.";
             case ContentExtensionError::JSONTooManyRules:
                 return "Too many rules in JSON array.";
             case ContentExtensionError::JSONDomainNotLowerCaseASCII:
                 return "Domains must be lower case ASCII. Use punycode to encode non-ASCII characters.";
             case ContentExtensionError::JSONMultipleConditions:
-                return "A trigger cannot have more than one condition (if-domain or unless-domain)";
+                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";
             }
 
             return std::string();

Modified: trunk/Source/WebCore/contentextensions/ContentExtensionError.h (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtensionError.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionError.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -52,6 +52,7 @@
     JSONInvalidConditionList,
     JSONDomainNotLowerCaseASCII,
     JSONMultipleConditions,
+    JSONTopURLAndDomainConditions,
     JSONTooManyRules,
     
     JSONInvalidAction,

Modified: trunk/Source/WebCore/contentextensions/ContentExtensionParser.cpp (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtensionParser.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionParser.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -153,6 +153,10 @@
     if (urlFilterCaseValue && !scope.exception() && urlFilterCaseValue.isBoolean())
         trigger.urlFilterIsCaseSensitive = urlFilterCaseValue.toBoolean(&exec);
 
+    const JSValue topURLFilterCaseValue = triggerObject.get(&exec, Identifier::fromString(&exec, "top-url-filter-is-case-sensitive"));
+    if (topURLFilterCaseValue && !scope.exception() && topURLFilterCaseValue.isBoolean())
+        trigger.topURLConditionIsCaseSensitive = topURLFilterCaseValue.toBoolean(&exec);
+
     const JSValue resourceTypeValue = triggerObject.get(&exec, Identifier::fromString(&exec, "resource-type"));
     if (!scope.exception() && resourceTypeValue.isObject()) {
         auto typeFlagsError = getTypeFlags(exec, resourceTypeValue, trigger.flags, readResourceType);
@@ -196,6 +200,34 @@
     } else if (!unlessDomainValue.isUndefined())
         return makeUnexpected(ContentExtensionError::JSONInvalidConditionList);
 
+    const JSValue ifTopURLValue = triggerObject.get(&exec, Identifier::fromString(&exec, "if-top-url"));
+    if (!scope.exception() && ifTopURLValue.isObject()) {
+        if (trigger.conditionType != Trigger::ConditionType::None)
+            return makeUnexpected(ContentExtensionError::JSONMultipleConditions);
+        auto ifTopURL = getStringList(exec, asObject(ifTopURLValue));
+        if (!ifTopURL.hasValue())
+            return makeUnexpected(ifTopURL.error());
+        trigger.conditions = WTFMove(ifTopURL.value());
+        if (trigger.conditions.isEmpty())
+            return makeUnexpected(ContentExtensionError::JSONInvalidConditionList);
+        trigger.conditionType = Trigger::ConditionType::IfTopURL;
+    } else if (!ifTopURLValue.isUndefined())
+        return makeUnexpected(ContentExtensionError::JSONInvalidConditionList);
+
+    const JSValue unlessTopURLValue = triggerObject.get(&exec, Identifier::fromString(&exec, "unless-top-url"));
+    if (!scope.exception() && unlessTopURLValue.isObject()) {
+        if (trigger.conditionType != Trigger::ConditionType::None)
+            return makeUnexpected(ContentExtensionError::JSONMultipleConditions);
+        auto unlessTopURL = getStringList(exec, asObject(unlessTopURLValue));
+        if (!unlessTopURL.hasValue())
+            return makeUnexpected(unlessTopURL.error());
+        trigger.conditions = WTFMove(unlessTopURL.value());
+        if (trigger.conditions.isEmpty())
+            return makeUnexpected(ContentExtensionError::JSONInvalidConditionList);
+        trigger.conditionType = Trigger::ConditionType::UnlessTopURL;
+    } else if (!unlessTopURLValue.isUndefined())
+        return makeUnexpected(ContentExtensionError::JSONInvalidConditionList);
+
     return WTFMove(trigger);
 }
 

Modified: trunk/Source/WebCore/contentextensions/ContentExtensionRule.h (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtensionRule.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionRule.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -43,6 +43,7 @@
 struct Trigger {
     String urlFilter;
     bool urlFilterIsCaseSensitive { false };
+    bool topURLConditionIsCaseSensitive { false };
     ResourceFlags flags { 0 };
     Vector<String> conditions;
     enum class ConditionType {
@@ -49,11 +50,16 @@
         None,
         IfDomain,
         UnlessDomain,
-    } conditionType { ConditionType::None };
+        IfTopURL,
+        UnlessTopURL,
+    };
+    ConditionType conditionType { ConditionType::None };
 
     ~Trigger()
     {
         ASSERT(conditions.isEmpty() == (conditionType == ConditionType::None));
+        if (topURLConditionIsCaseSensitive)
+            ASSERT(conditionType == ConditionType::IfTopURL || conditionType == ConditionType::UnlessTopURL);
     }
 
     bool isEmpty() const

Modified: trunk/Source/WebCore/contentextensions/ContentExtensionsBackend.cpp (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/ContentExtensionsBackend.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionsBackend.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -96,7 +96,7 @@
         
         URL topURL = resourceLoadInfo.mainDocumentURL;
         DFABytecodeInterpreter withConditionsInterpreter(compiledExtension.filtersWithConditionsBytecode(), compiledExtension.filtersWithConditionsBytecodeLength());
-        DFABytecodeInterpreter::Actions withConditionsActions = withConditionsInterpreter.interpretWithConditions(urlCString, flags, contentExtension->cachedConditionedActions(topURL));
+        DFABytecodeInterpreter::Actions withConditionsActions = withConditionsInterpreter.interpretWithConditions(urlCString, flags, contentExtension->topURLActions(topURL));
         
         const SerializedActionByte* actions = compiledExtension.actions();
         const unsigned actionsLength = compiledExtension.actionsLength();

Modified: trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -98,7 +98,7 @@
     ASSERT(getInstruction(m_bytecode, m_bytecodeLength, programCounter) == DFABytecodeInstruction::AppendAction
         || getInstruction(m_bytecode, m_bytecodeLength, programCounter) == DFABytecodeInstruction::AppendActionWithIfCondition);
     uint64_t action = "" ? IfConditionFlag : 0) | static_cast<uint64_t>(getBits<uint32_t>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecodeInstruction)));
-    if (!m_conditionActions || matchesCondition(action, *m_conditionActions))
+    if (!m_topURLActions || matchesCondition(action, *m_topURLActions))
         actions.add(action);
     
     programCounter += instructionSizeWithArguments(DFABytecodeInstruction::AppendAction);
@@ -119,7 +119,7 @@
     
     if (loadTypeMatches && resourceTypeMatches) {
         uint64_t actionAndFlags = (ifCondition ? IfConditionFlag : 0) | (static_cast<uint64_t>(flagsToCheck) << 32) | static_cast<uint64_t>(getBits<uint32_t>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecodeInstruction) + sizeof(uint16_t)));
-        if (!m_conditionActions || matchesCondition(actionAndFlags, *m_conditionActions))
+        if (!m_topURLActions || matchesCondition(actionAndFlags, *m_topURLActions))
             actions.add(actionAndFlags);
     }
     programCounter += instructionSizeWithArguments(DFABytecodeInstruction::TestFlagsAndAppendAction);
@@ -169,12 +169,12 @@
     return actions;
 }
     
-DFABytecodeInterpreter::Actions DFABytecodeInterpreter::interpretWithConditions(const CString& urlCString, uint16_t flags, const DFABytecodeInterpreter::Actions& conditionActions)
+DFABytecodeInterpreter::Actions DFABytecodeInterpreter::interpretWithConditions(const CString& urlCString, uint16_t flags, const DFABytecodeInterpreter::Actions& topURLActions)
 {
-    ASSERT(!m_conditionActions);
-    m_conditionActions = &conditionActions;
+    ASSERT(!m_topURLActions);
+    m_topURLActions = &topURLActions;
     DFABytecodeInterpreter::Actions actions = interpret(urlCString, flags);
-    m_conditionActions = nullptr;
+    m_topURLActions = nullptr;
     return actions;
 }
 

Modified: trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.h (213668 => 213669)


--- trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -60,7 +60,7 @@
 
     const DFABytecode* m_bytecode;
     const unsigned m_bytecodeLength;
-    const DFABytecodeInterpreter::Actions* m_conditionActions { nullptr };
+    const DFABytecodeInterpreter::Actions* m_topURLActions { nullptr };
 };
 
 } // namespace ContentExtensions    

Modified: trunk/Source/WebKit2/ChangeLog (213668 => 213669)


--- trunk/Source/WebKit2/ChangeLog	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebKit2/ChangeLog	2017-03-09 22:17:13 UTC (rev 213669)
@@ -1,3 +1,37 @@
+2017-03-09  Alex Christensen  <[email protected]>
+
+        [Content Extensions] Introduce if-top-url and unless-top-url
+        https://bugs.webkit.org/show_bug.cgi?id=169433
+
+        Reviewed by Brady Eidson.
+
+        Rename conditionedFilters to topURLFilters to reflect the fact that they are the filters
+        that are run on the top URL, and possibly just the domain of the top url.
+        I was a bit too aggressive when renaming domain* to condition* in r213533.
+
+        * Shared/WebCompiledContentExtension.cpp:
+        (WebKit::WebCompiledContentExtension::conditionsApplyOnlyToDomain):
+        (WebKit::WebCompiledContentExtension::topURLFiltersBytecode):
+        (WebKit::WebCompiledContentExtension::topURLFiltersBytecodeLength):
+        (WebKit::WebCompiledContentExtension::conditionedFiltersBytecode): Deleted.
+        (WebKit::WebCompiledContentExtension::conditionedFiltersBytecodeLength): Deleted.
+        * Shared/WebCompiledContentExtension.h:
+        * Shared/WebCompiledContentExtensionData.cpp:
+        (WebKit::WebCompiledContentExtensionData::encode):
+        (WebKit::WebCompiledContentExtensionData::decode):
+        * Shared/WebCompiledContentExtensionData.h:
+        (WebKit::WebCompiledContentExtensionData::WebCompiledContentExtensionData):
+        * UIProcess/API/APIUserContentExtensionStore.cpp:
+        (API::encodeContentExtensionMetaData):
+        (API::decodeContentExtensionMetaData):
+        (API::compiledToFile):
+        (API::createExtension):
+        (API::UserContentExtensionStore::invalidateContentExtensionVersion):
+        (API::userContentExtensionStoreErrorCategory):
+        * UIProcess/API/APIUserContentExtensionStore.h:
+        Increment CurrentContentExtensionFileVersion because we have changed the format of the binary on disk.
+        We only added 4 bytes, but that's binary incompatible and requires re-compiling any existing content extensions.
+
 2017-03-09  Brent Fulgham  <[email protected]>
 
         [WK2][iOS] Extend WebProcess sandbox to support audio and video compression/decompression

Modified: trunk/Source/WebKit2/Shared/WebCompiledContentExtension.cpp (213668 => 213669)


--- trunk/Source/WebKit2/Shared/WebCompiledContentExtension.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebKit2/Shared/WebCompiledContentExtension.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -44,6 +44,11 @@
 {
 }
 
+bool WebCompiledContentExtension::conditionsApplyOnlyToDomain() const
+{
+    return *reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(m_data.data->data()) + m_data.conditionsApplyOnlyToDomainOffset);
+}
+
 const WebCore::ContentExtensions::DFABytecode* WebCompiledContentExtension::filtersWithoutConditionsBytecode() const
 {
     return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.data->data()) + m_data.filtersWithoutConditionsBytecodeOffset;
@@ -64,14 +69,14 @@
     return m_data.filtersWithConditionsBytecodeSize;
 }
 
-const WebCore::ContentExtensions::DFABytecode* WebCompiledContentExtension::conditionedFiltersBytecode() const
+const WebCore::ContentExtensions::DFABytecode* WebCompiledContentExtension::topURLFiltersBytecode() const
 {
-    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.data->data()) + m_data.conditionedFiltersBytecodeOffset;
+    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.data->data()) + m_data.topURLFiltersBytecodeOffset;
 }
 
-unsigned WebCompiledContentExtension::conditionedFiltersBytecodeLength() const
+unsigned WebCompiledContentExtension::topURLFiltersBytecodeLength() const
 {
-    return m_data.conditionedFiltersBytecodeSize;
+    return m_data.topURLFiltersBytecodeSize;
 }
 
 const WebCore::ContentExtensions::SerializedActionByte* WebCompiledContentExtension::actions() const

Modified: trunk/Source/WebKit2/Shared/WebCompiledContentExtension.h (213668 => 213669)


--- trunk/Source/WebKit2/Shared/WebCompiledContentExtension.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebKit2/Shared/WebCompiledContentExtension.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -47,8 +47,9 @@
     unsigned filtersWithoutConditionsBytecodeLength() const final;
     const WebCore::ContentExtensions::DFABytecode* filtersWithConditionsBytecode() const final;
     unsigned filtersWithConditionsBytecodeLength() const final;
-    const WebCore::ContentExtensions::DFABytecode* conditionedFiltersBytecode() const final;
-    unsigned conditionedFiltersBytecodeLength() const final;
+    const WebCore::ContentExtensions::DFABytecode* topURLFiltersBytecode() const final;
+    unsigned topURLFiltersBytecodeLength() const final;
+    bool conditionsApplyOnlyToDomain() const final;
     
     const WebCore::ContentExtensions::SerializedActionByte* actions() const final;
     unsigned actionsLength() const final;

Modified: trunk/Source/WebKit2/Shared/WebCompiledContentExtensionData.cpp (213668 => 213669)


--- trunk/Source/WebKit2/Shared/WebCompiledContentExtensionData.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebKit2/Shared/WebCompiledContentExtensionData.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -44,8 +44,8 @@
     encoder << filtersWithoutConditionsBytecodeSize;
     encoder << filtersWithConditionsBytecodeOffset;
     encoder << filtersWithConditionsBytecodeSize;
-    encoder << conditionedFiltersBytecodeOffset;
-    encoder << conditionedFiltersBytecodeSize;
+    encoder << topURLFiltersBytecodeOffset;
+    encoder << topURLFiltersBytecodeSize;
 }
 
 bool WebCompiledContentExtensionData::decode(IPC::Decoder& decoder, WebCompiledContentExtensionData& compiledContentExtensionData)
@@ -67,9 +67,9 @@
         return false;
     if (!decoder.decode(compiledContentExtensionData.filtersWithConditionsBytecodeSize))
         return false;
-    if (!decoder.decode(compiledContentExtensionData.conditionedFiltersBytecodeOffset))
+    if (!decoder.decode(compiledContentExtensionData.topURLFiltersBytecodeOffset))
         return false;
-    if (!decoder.decode(compiledContentExtensionData.conditionedFiltersBytecodeSize))
+    if (!decoder.decode(compiledContentExtensionData.topURLFiltersBytecodeSize))
         return false;
 
     return true;

Modified: trunk/Source/WebKit2/Shared/WebCompiledContentExtensionData.h (213668 => 213669)


--- trunk/Source/WebKit2/Shared/WebCompiledContentExtensionData.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebKit2/Shared/WebCompiledContentExtensionData.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -42,9 +42,10 @@
 public:
     WebCompiledContentExtensionData() = default;
 
-    WebCompiledContentExtensionData(RefPtr<SharedMemory>&& data, NetworkCache::Data fileData, unsigned actionsOffset, unsigned actionsSize, unsigned filtersWithoutConditionsBytecodeOffset, unsigned filtersWithoutConditionsBytecodeSize, unsigned filtersWithConditionsBytecodeOffset, unsigned filtersWithConditionsBytecodeSize, unsigned conditionedFiltersBytecodeOffset, unsigned conditionedFiltersBytecodeSize)
+    WebCompiledContentExtensionData(RefPtr<SharedMemory>&& data, NetworkCache::Data fileData, unsigned conditionsApplyOnlyToDomainOffset, unsigned actionsOffset, unsigned actionsSize, unsigned filtersWithoutConditionsBytecodeOffset, unsigned filtersWithoutConditionsBytecodeSize, unsigned filtersWithConditionsBytecodeOffset, unsigned filtersWithConditionsBytecodeSize, unsigned topURLFiltersBytecodeOffset, unsigned topURLFiltersBytecodeSize)
         : data(WTFMove(data))
         , fileData(fileData)
+        , conditionsApplyOnlyToDomainOffset(conditionsApplyOnlyToDomainOffset)
         , actionsOffset(actionsOffset)
         , actionsSize(actionsSize)
         , filtersWithoutConditionsBytecodeOffset(filtersWithoutConditionsBytecodeOffset)
@@ -51,8 +52,8 @@
         , filtersWithoutConditionsBytecodeSize(filtersWithoutConditionsBytecodeSize)
         , filtersWithConditionsBytecodeOffset(filtersWithConditionsBytecodeOffset)
         , filtersWithConditionsBytecodeSize(filtersWithConditionsBytecodeSize)
-        , conditionedFiltersBytecodeOffset(conditionedFiltersBytecodeOffset)
-        , conditionedFiltersBytecodeSize(conditionedFiltersBytecodeSize)
+        , topURLFiltersBytecodeOffset(topURLFiltersBytecodeOffset)
+        , topURLFiltersBytecodeSize(topURLFiltersBytecodeSize)
     {
     }
 
@@ -61,6 +62,7 @@
 
     RefPtr<SharedMemory> data;
     NetworkCache::Data fileData;
+    unsigned conditionsApplyOnlyToDomainOffset { 0 };
     unsigned actionsOffset { 0 };
     unsigned actionsSize { 0 };
     unsigned filtersWithoutConditionsBytecodeOffset { 0 };
@@ -67,8 +69,8 @@
     unsigned filtersWithoutConditionsBytecodeSize { 0 };
     unsigned filtersWithConditionsBytecodeOffset { 0 };
     unsigned filtersWithConditionsBytecodeSize { 0 };
-    unsigned conditionedFiltersBytecodeOffset { 0 };
-    unsigned conditionedFiltersBytecodeSize { 0 };
+    unsigned topURLFiltersBytecodeOffset { 0 };
+    unsigned topURLFiltersBytecodeSize { 0 };
 };
 
 }

Modified: trunk/Source/WebKit2/UIProcess/API/APIUserContentExtensionStore.cpp (213668 => 213669)


--- trunk/Source/WebKit2/UIProcess/API/APIUserContentExtensionStore.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebKit2/UIProcess/API/APIUserContentExtensionStore.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -79,7 +79,11 @@
     return WebCore::pathByAppendingComponent(base, "ContentExtension-" + WebCore::encodeForFileName(identifier));
 }
 
-const size_t ContentExtensionFileHeaderSize = sizeof(uint32_t) + 4 * sizeof(uint64_t);
+// The size and offset of the densely packed bytes in the file, not sizeof and offsetof, which would
+// represent the size and offset of the structure in memory, possibly with compiler-added padding.
+const size_t ContentExtensionFileHeaderSize = 2 * sizeof(uint32_t) + 4 * sizeof(uint64_t);
+const size_t ConditionsApplyOnlyToDomainOffset = sizeof(uint32_t) + 4 * sizeof(uint64_t);
+
 struct ContentExtensionMetaData {
     uint32_t version { UserContentExtensionStore::CurrentContentExtensionFileVersion };
     uint64_t actionsSize { 0 };
@@ -86,6 +90,7 @@
     uint64_t filtersWithoutConditionsBytecodeSize { 0 };
     uint64_t filtersWithConditionsBytecodeSize { 0 };
     uint64_t conditionedFiltersBytecodeSize { 0 };
+    uint32_t conditionsApplyOnlyToDomain { false };
     
     size_t fileSize() const
     {
@@ -106,6 +111,7 @@
     encoder << metaData.filtersWithoutConditionsBytecodeSize;
     encoder << metaData.filtersWithConditionsBytecodeSize;
     encoder << metaData.conditionedFiltersBytecodeSize;
+    encoder << metaData.conditionsApplyOnlyToDomain;
 
     ASSERT(encoder.bufferSize() == ContentExtensionFileHeaderSize);
     return Data(encoder.buffer(), encoder.bufferSize());
@@ -131,6 +137,8 @@
             return false;
         if (!decoder.decode(metaData.conditionedFiltersBytecodeSize))
             return false;
+        if (!decoder.decode(metaData.conditionsApplyOnlyToDomain))
+            return false;
         success = true;
         return false;
     });
@@ -177,9 +185,10 @@
             ASSERT(!metaData.filtersWithoutConditionsBytecodeSize);
             ASSERT(!metaData.filtersWithConditionsBytecodeSize);
             ASSERT(!metaData.conditionedFiltersBytecodeSize);
+            ASSERT(!metaData.conditionsApplyOnlyToDomain);
         }
         
-        void writeFiltersWithoutConditionsBytecode(Vector<DFABytecode>&& bytecode) override
+        void writeFiltersWithoutConditionsBytecode(Vector<DFABytecode>&& bytecode) final
         {
             ASSERT(!m_filtersWithConditionBytecodeWritten);
             ASSERT(!m_conditionFiltersBytecodeWritten);
@@ -187,7 +196,7 @@
             writeToFile(Data(bytecode.data(), bytecode.size()));
         }
         
-        void writeFiltersWithConditionsBytecode(Vector<DFABytecode>&& bytecode) override
+        void writeFiltersWithConditionsBytecode(Vector<DFABytecode>&& bytecode) final
         {
             ASSERT(!m_conditionFiltersBytecodeWritten);
             m_filtersWithConditionBytecodeWritten += bytecode.size();
@@ -194,13 +203,13 @@
             writeToFile(Data(bytecode.data(), bytecode.size()));
         }
         
-        void writeConditionedFiltersBytecode(Vector<DFABytecode>&& bytecode) override
+        void writeTopURLFiltersBytecode(Vector<DFABytecode>&& bytecode) final
         {
             m_conditionFiltersBytecodeWritten += bytecode.size();
             writeToFile(Data(bytecode.data(), bytecode.size()));
         }
 
-        void writeActions(Vector<SerializedActionByte>&& actions) override
+        void writeActions(Vector<SerializedActionByte>&& actions, bool conditionsApplyOnlyToDomain) final
         {
             ASSERT(!m_filtersWithoutConditionsBytecodeWritten);
             ASSERT(!m_filtersWithConditionBytecodeWritten);
@@ -207,15 +216,17 @@
             ASSERT(!m_conditionFiltersBytecodeWritten);
             ASSERT(!m_actionsWritten);
             m_actionsWritten += actions.size();
+            m_conditionsApplyOnlyToDomain = conditionsApplyOnlyToDomain;
             writeToFile(Data(actions.data(), actions.size()));
         }
         
-        void finalize() override
+        void finalize() final
         {
             m_metaData.actionsSize = m_actionsWritten;
             m_metaData.filtersWithoutConditionsBytecodeSize = m_filtersWithoutConditionsBytecodeWritten;
             m_metaData.filtersWithConditionsBytecodeSize = m_filtersWithConditionBytecodeWritten;
             m_metaData.conditionedFiltersBytecodeSize = m_conditionFiltersBytecodeWritten;
+            m_metaData.conditionsApplyOnlyToDomain = m_conditionsApplyOnlyToDomain;
             
             Data header = encodeContentExtensionMetaData(m_metaData);
             if (!m_fileError && WebCore::seekFile(m_fileHandle, 0ll, WebCore::FileSeekOrigin::SeekFromBeginning) == -1) {
@@ -242,6 +253,7 @@
         size_t m_filtersWithConditionBytecodeWritten { 0 };
         size_t m_conditionFiltersBytecodeWritten { 0 };
         size_t m_actionsWritten { 0 };
+        bool m_conditionsApplyOnlyToDomain { false };
         bool m_fileError { false };
     };
 
@@ -285,6 +297,7 @@
     auto compiledContentExtensionData = WebKit::WebCompiledContentExtensionData(
         WTFMove(sharedMemory),
         fileData,
+        ConditionsApplyOnlyToDomainOffset,
         ContentExtensionFileHeaderSize,
         metaData.actionsSize,
         ContentExtensionFileHeaderSize
@@ -383,7 +396,7 @@
     auto file = WebCore::openFile(constructedPath(m_storePath, identifier), WebCore::OpenForWrite);
     if (file == WebCore::invalidPlatformFileHandle)
         return;
-    ContentExtensionMetaData invalidHeader = {0, 0, 0, 0, 0};
+    ContentExtensionMetaData invalidHeader;
     auto bytesWritten = WebCore::writeToFile(file, reinterpret_cast<const char*>(&invalidHeader), sizeof(invalidHeader));
     ASSERT_UNUSED(bytesWritten, bytesWritten == sizeof(invalidHeader));
     WebCore::closeFile(file);
@@ -392,12 +405,12 @@
 const std::error_category& userContentExtensionStoreErrorCategory()
 {
     class UserContentExtensionStoreErrorCategory : public std::error_category {
-        const char* name() const noexcept override
+        const char* name() const noexcept final
         {
             return "user content extension store";
         }
 
-        std::string message(int errorCode) const override
+        std::string message(int errorCode) const final
         {
             switch (static_cast<UserContentExtensionStore::Error>(errorCode)) {
             case UserContentExtensionStore::Error::LookupFailed:

Modified: trunk/Source/WebKit2/UIProcess/API/APIUserContentExtensionStore.h (213668 => 213669)


--- trunk/Source/WebKit2/UIProcess/API/APIUserContentExtensionStore.h	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Source/WebKit2/UIProcess/API/APIUserContentExtensionStore.h	2017-03-09 22:17:13 UTC (rev 213669)
@@ -50,7 +50,7 @@
     
     // This should be incremented every time a functional change is made to the bytecode, file format, etc.
     // to prevent crashing while loading old data.
-    const static uint32_t CurrentContentExtensionFileVersion = 7;
+    const static uint32_t CurrentContentExtensionFileVersion = 8;
 
     static UserContentExtensionStore& defaultStore();
     static Ref<UserContentExtensionStore> storeWithPath(const WTF::String& storePath);

Modified: trunk/Tools/ChangeLog (213668 => 213669)


--- trunk/Tools/ChangeLog	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Tools/ChangeLog	2017-03-09 22:17:13 UTC (rev 213669)
@@ -1,3 +1,14 @@
+2017-03-09  Alex Christensen  <[email protected]>
+
+        [Content Extensions] Introduce if-top-url and unless-top-url
+        https://bugs.webkit.org/show_bug.cgi?id=169433
+
+        Reviewed by Brady Eidson.
+
+        * TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
+        (TestWebKitAPI::TEST_F):
+        Add tests for new functionality and new failure types.
+
 2017-03-09  Srinivasan Vijayaraghavan  <[email protected]>
 
         JSC EWS repeat the log message thrice about patch not being relevant

Modified: trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp (213668 => 213669)


--- trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp	2017-03-09 22:07:46 UTC (rev 213668)
+++ trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp	2017-03-09 22:17:13 UTC (rev 213669)
@@ -85,7 +85,8 @@
     Vector<ContentExtensions::SerializedActionByte> actions;
     Vector<ContentExtensions::DFABytecode> filtersWithoutConditions;
     Vector<ContentExtensions::DFABytecode> filtersWithConditions;
-    Vector<ContentExtensions::DFABytecode> conditionedFilters;
+    Vector<ContentExtensions::DFABytecode> topURLFilters;
+    bool conditionsApplyOnlyToDomain { false };
 };
 
 class InMemoryContentExtensionCompilationClient final : public ContentExtensions::ContentExtensionCompilationClient {
@@ -96,17 +97,19 @@
         EXPECT_EQ(data.actions.size(), 0ull);
         EXPECT_EQ(data.filtersWithoutConditions.size(), 0ull);
         EXPECT_EQ(data.filtersWithConditions.size(), 0ull);
-        EXPECT_EQ(data.conditionedFilters.size(), 0ull);
+        EXPECT_EQ(data.topURLFilters.size(), 0ull);
     }
 
-    void writeActions(Vector<ContentExtensions::SerializedActionByte>&& actions) final
+    void writeActions(Vector<ContentExtensions::SerializedActionByte>&& actions, bool conditionsApplyOnlyToDomain) final
     {
         EXPECT_FALSE(finalized);
         EXPECT_EQ(m_data.actions.size(), 0ull);
         EXPECT_EQ(m_data.filtersWithoutConditions.size(), 0ull);
         EXPECT_EQ(m_data.filtersWithConditions.size(), 0ull);
-        EXPECT_EQ(m_data.conditionedFilters.size(), 0ull);
+        EXPECT_EQ(m_data.topURLFilters.size(), 0ull);
+        EXPECT_EQ(m_data.actions.size(), 0ull);
         m_data.actions.appendVector(actions);
+        m_data.conditionsApplyOnlyToDomain = conditionsApplyOnlyToDomain;
     }
     
     void writeFiltersWithoutConditionsBytecode(Vector<ContentExtensions::DFABytecode>&& bytecode) final
@@ -113,7 +116,7 @@
     {
         EXPECT_FALSE(finalized);
         EXPECT_EQ(m_data.filtersWithConditions.size(), 0ull);
-        EXPECT_EQ(m_data.conditionedFilters.size(), 0ull);
+        EXPECT_EQ(m_data.topURLFilters.size(), 0ull);
         m_data.filtersWithoutConditions.appendVector(bytecode);
     }
     
@@ -120,14 +123,14 @@
     void writeFiltersWithConditionsBytecode(Vector<ContentExtensions::DFABytecode>&& bytecode) final
     {
         EXPECT_FALSE(finalized);
-        EXPECT_EQ(m_data.conditionedFilters.size(), 0ull);
+        EXPECT_EQ(m_data.topURLFilters.size(), 0ull);
         m_data.filtersWithConditions.appendVector(bytecode);
     }
     
-    void writeConditionedFiltersBytecode(Vector<ContentExtensions::DFABytecode>&& bytecode) final
+    void writeTopURLFiltersBytecode(Vector<ContentExtensions::DFABytecode>&& bytecode) final
     {
         EXPECT_FALSE(finalized);
-        m_data.conditionedFilters.appendVector(bytecode);
+        m_data.topURLFilters.appendVector(bytecode);
     }
     
     void finalize() final
@@ -171,8 +174,9 @@
     unsigned filtersWithoutConditionsBytecodeLength() const final { return m_data.filtersWithoutConditions.size(); }
     const ContentExtensions::DFABytecode* filtersWithConditionsBytecode() const final { return m_data.filtersWithConditions.data(); }
     unsigned filtersWithConditionsBytecodeLength() const final { return m_data.filtersWithConditions.size(); }
-    const ContentExtensions::DFABytecode* conditionedFiltersBytecode() const final { return m_data.conditionedFilters.data(); }
-    unsigned conditionedFiltersBytecodeLength() const final { return m_data.conditionedFilters.size(); }
+    const ContentExtensions::DFABytecode* topURLFiltersBytecode() const final { return m_data.topURLFilters.data(); }
+    unsigned topURLFiltersBytecodeLength() const final { return m_data.topURLFilters.size(); }
+    bool conditionsApplyOnlyToDomain() const final { return m_data.conditionsApplyOnlyToDomain; }
 
 private:
     InMemoryCompiledContentExtension(CompiledContentExtensionData&& data)
@@ -788,7 +792,72 @@
     testRequest(backend, mainDocumentRequest("http://webkit.org/except-this-ignore-previous-trigger-on-scripts.html", ResourceType::Script), { ContentExtensions::ActionType::BlockCookies, ContentExtensions::ActionType::CSSDisplayNoneSelector });
     testRequest(backend, mainDocumentRequest("http://notwebkit.org/except-this-ignore-previous-trigger-on-scripts.html.test.html", ResourceType::Script), { ContentExtensions::ActionType::BlockCookies, ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockCookies });
     testRequest(backend, mainDocumentRequest("http://webkit.org/except-this-ignore-previous-trigger-on-scripts.html.test.html", ResourceType::Script), { ContentExtensions::ActionType::BlockCookies, ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad, ContentExtensions::ActionType::BlockCookies });
+}
 
+TEST_F(ContentExtensionTest, TopURL)
+{
+    const Vector<ContentExtensions::ActionType> blockLoad = { ContentExtensions::ActionType::BlockLoad };
+    const Vector<ContentExtensions::ActionType> blockCookies = { ContentExtensions::ActionType::BlockCookies };
+
+    auto ifTopURL = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"if-top-url\":[\"^http://web.*kit.org\"]}}]");
+    testRequest(ifTopURL, mainDocumentRequest("http://webkit.org/test.html"), blockLoad);
+    testRequest(ifTopURL, mainDocumentRequest("http://webkit.org/test.not_html"), { });
+    testRequest(ifTopURL, mainDocumentRequest("http://WEBKIT.org/test.html"), blockLoad);
+    testRequest(ifTopURL, mainDocumentRequest("http://webkit.org/TEST.html"), blockLoad);
+    testRequest(ifTopURL, mainDocumentRequest("http://web__kit.org/test.html"), blockLoad);
+    testRequest(ifTopURL, mainDocumentRequest("http://webk__it.org/test.html"), { });
+    testRequest(ifTopURL, mainDocumentRequest("http://web__kit.org/test.not_html"), { });
+    testRequest(ifTopURL, mainDocumentRequest("http://not_webkit.org/test.html"), { });
+    testRequest(ifTopURL, subResourceRequest("http://not_webkit.org/test.html", "http://webkit.org/not_test.html"), blockLoad);
+    testRequest(ifTopURL, subResourceRequest("http://not_webkit.org/test.html", "http://not_webkit.org/not_test.html"), { });
+    testRequest(ifTopURL, subResourceRequest("http://webkit.org/test.html", "http://not_webkit.org/not_test.html"), { });
+    testRequest(ifTopURL, subResourceRequest("http://webkit.org/test.html", "http://not_webkit.org/test.html"), { });
+    testRequest(ifTopURL, subResourceRequest("http://webkit.org/test.html", "http://webkit.org/test.html"), blockLoad);
+    testRequest(ifTopURL, subResourceRequest("http://webkit.org/test.html", "http://example.com/#http://webkit.org/test.html"), { });
+    testRequest(ifTopURL, subResourceRequest("http://example.com/#http://webkit.org/test.html", "http://webkit.org/test.html"), blockLoad);
+
+    auto unlessTopURL = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"unless-top-url\":[\"^http://web.*kit.org\"]}}]");
+    testRequest(unlessTopURL, mainDocumentRequest("http://webkit.org/test.html"), { });
+    testRequest(unlessTopURL, mainDocumentRequest("http://WEBKIT.org/test.html"), { });
+    testRequest(unlessTopURL, mainDocumentRequest("http://webkit.org/TEST.html"), { });
+    testRequest(unlessTopURL, mainDocumentRequest("http://webkit.org/test.not_html"), { });
+    testRequest(unlessTopURL, mainDocumentRequest("http://web__kit.org/test.html"), { });
+    testRequest(unlessTopURL, mainDocumentRequest("http://webk__it.org/test.html"), blockLoad);
+    testRequest(unlessTopURL, mainDocumentRequest("http://web__kit.org/test.not_html"), { });
+    testRequest(unlessTopURL, mainDocumentRequest("http://not_webkit.org/test.html"), blockLoad);
+    testRequest(unlessTopURL, subResourceRequest("http://not_webkit.org/test.html", "http://webkit.org/not_test.html"), { });
+    testRequest(unlessTopURL, subResourceRequest("http://not_webkit.org/test.html", "http://not_webkit.org/not_test.html"), blockLoad);
+    testRequest(unlessTopURL, subResourceRequest("http://webkit.org/test.html", "http://not_webkit.org/not_test.html"), blockLoad);
+    testRequest(unlessTopURL, subResourceRequest("http://webkit.org/test.html", "http://not_webkit.org/test.html"), blockLoad);
+    testRequest(unlessTopURL, subResourceRequest("http://webkit.org/test.html", "http://webkit.org/test.html"), { });
+    testRequest(unlessTopURL, subResourceRequest("http://webkit.org/test.html", "http://example.com/#http://webkit.org/test.html"), blockLoad);
+    testRequest(unlessTopURL, subResourceRequest("http://example.com/#http://webkit.org/test.html", "http://webkit.org/test.html"), { });
+
+    auto ifTopURLMatchesEverything = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"if-top-url\":[\".*\"]}}]");
+    testRequest(ifTopURLMatchesEverything, mainDocumentRequest("http://webkit.org/test.html"), blockLoad);
+    testRequest(ifTopURLMatchesEverything, mainDocumentRequest("http://webkit.org/test.not_html"), { });
+    testRequest(ifTopURLMatchesEverything, mainDocumentRequest("http://not_webkit.org/test.html"), blockLoad);
+    
+    auto unlessTopURLMatchesEverything = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"unless-top-url\":[\".*\"]}}]");
+    testRequest(unlessTopURLMatchesEverything, mainDocumentRequest("http://webkit.org/test.html"), { });
+    testRequest(unlessTopURLMatchesEverything, mainDocumentRequest("http://webkit.org/test.not_html"), { });
+    testRequest(unlessTopURLMatchesEverything, mainDocumentRequest("http://not_webkit.org/test.html"), { });
+    
+    auto mixedConditions = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"if-top-url\":[\"^http://web.*kit.org\"]}},"
+        "{\"action\":{\"type\":\"block-cookies\"},\"trigger\":{\"resource-type\":[\"document\"], \"url-filter\":\"test\\\\.html\", \"unless-top-url\":[\"^http://web.*kit.org\"]}}]");
+    testRequest(mixedConditions, mainDocumentRequest("http://webkit.org/test.html"), blockLoad);
+    testRequest(mixedConditions, mainDocumentRequest("http://not_webkit.org/test.html"), blockCookies);
+    testRequest(mixedConditions, subResourceRequest("http://webkit.org/test.html", "http://not_webkit.org", ResourceType::Document), blockCookies);
+    testRequest(mixedConditions, subResourceRequest("http://webkit.org/test.html", "http://not_webkit.org", ResourceType::Image), { });
+
+    auto caseSensitive = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"if-top-url\":[\"^http://web.*kit.org/test\"], \"top-url-filter-is-case-sensitive\":true}}]");
+    testRequest(caseSensitive, mainDocumentRequest("http://webkit.org/test.html"), blockLoad);
+    testRequest(caseSensitive, mainDocumentRequest("http://WEBKIT.org/test.html"), blockLoad); // domains are canonicalized before running regexes.
+    testRequest(caseSensitive, mainDocumentRequest("http://webkit.org/TEST.html"), { });
+
+    auto caseInsensitive = makeBackend("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"if-top-url\":[\"^http://web.*kit.org/test\"]}}]");
+    testRequest(caseInsensitive, mainDocumentRequest("http://webkit.org/test.html"), blockLoad);
+    testRequest(caseInsensitive, mainDocumentRequest("http://webkit.org/TEST.html"), blockLoad);
 }
 
 TEST_F(ContentExtensionTest, MultipleExtensions)
@@ -1364,6 +1433,17 @@
     checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*\",\"unless-domain\":[\"a\"]}}]", { });
     checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"[\"}}]",
         ContentExtensions::ContentExtensionError::JSONInvalidRegex);
+
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-domain\":[\"a\"],\"unless-domain\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::JSONMultipleConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-top-url\":[\"a\"],\"unless-top-url\":[]}}]", ContentExtensions::ContentExtensionError::JSONMultipleConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-top-url\":[\"a\"],\"unless-top-url\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::JSONMultipleConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-domain\":[\"a\"],\"if-top-url\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::JSONMultipleConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-top-url\":[],\"unless-domain\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::JSONMultipleConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-top-url\":[\"a\"],\"if-domain\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::JSONMultipleConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-top-url\":[\"a\"]}}, {\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-domain\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::JSONTopURLAndDomainConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"if-top-url\":[\"a\"]}}, {\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"unless-domain\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::JSONTopURLAndDomainConditions);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"test\\\\.html\", \"unless-top-url\":[\"[\"]}}]", ContentExtensions::ContentExtensionError::JSONInvalidRegex);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\", \"unexpected-identifier-should-be-ignored\":5}}]", { });
 }
 
 TEST_F(ContentExtensionTest, StrictPrefixSeparatedMachines1)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to