[Content Extensions] Relax restrictions on triggers that match everything.
authorachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 May 2015 17:56:17 +0000 (17:56 +0000)
committerachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 May 2015 17:56:17 +0000 (17:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=145069

Reviewed by Benjamin Poulain.

Source/WebCore:

Added API tests that cover the new functionality and test for correctness in behavior.

* contentextensions/CompiledContentExtension.cpp:
(WebCore::ContentExtensions::CompiledContentExtension::globalDisplayNoneSelectors):
* contentextensions/ContentExtensionCompiler.cpp:
(WebCore::ContentExtensions::addUniversalActionsToDFA):
(WebCore::ContentExtensions::compileRuleList):
Put universalActionsWithoutDomains into the DFA from filtersWithoutDomains and
put universalActionsWithDomains into the DFA from filtersWithDomains.
* contentextensions/ContentExtensionError.cpp:
(WebCore::ContentExtensions::contentExtensionErrorCategory):
* contentextensions/ContentExtensionError.h:
Remove error codes for errors that are not errors any more.
* contentextensions/ContentExtensionsBackend.cpp:
(WebCore::ContentExtensions::ContentExtensionsBackend::actionsForResourceLoad):
* contentextensions/ContentExtensionsBackend.h:
* contentextensions/DFABytecode.h:
(WebCore::ContentExtensions::instructionSizeWithArguments):
* contentextensions/DFABytecodeCompiler.cpp:
(WebCore::ContentExtensions::set32Bits):
(WebCore::ContentExtensions::DFABytecodeCompiler::emitAppendAction):
(WebCore::ContentExtensions::DFABytecodeCompiler::emitJump):
(WebCore::ContentExtensions::DFABytecodeCompiler::emitCheckValue):
(WebCore::ContentExtensions::DFABytecodeCompiler::emitCheckValueRange):
(WebCore::ContentExtensions::DFABytecodeCompiler::compileNode):
(WebCore::ContentExtensions::DFABytecodeCompiler::compile):
(WebCore::ContentExtensions::DFABytecodeCompiler::emitTestFlagsAndAppendAction): Deleted.
* contentextensions/DFABytecodeCompiler.h:
* contentextensions/DFABytecodeInterpreter.cpp:
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpretAppendAction):
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpretTestFlagsAndAppendAction):
(WebCore::ContentExtensions::DFABytecodeInterpreter::actionsForDefaultStylesheetFromDFARoot):
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpret):
(WebCore::ContentExtensions::DFABytecodeInterpreter::actionsFromDFARoot): Deleted.
* contentextensions/DFABytecodeInterpreter.h:
Add a new bytecode AppendActionDefaultStylesheet to mark actions that are css-display-none
that need to be put in the default stylesheet to be ignored or not as a whole.
css-display-none actions with flags or domain rules and css-display-none actions
after ignore-previous-rules actions are not to be in this precompiled stylesheet, but
they will be applied as needed per page.  The precompiled stylesheet is already applied
if no ignore-previous-rules action is triggered.
* loader/ResourceLoadInfo.h:

Source/WebKit2:

* UIProcess/API/APIUserContentExtensionStore.h:
Increment version number to reflect changes in DFABytecode.

Tools:

* TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
(TestWebKitAPI::TEST_F):
Update and add tests for new possibilities with .*

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

17 files changed:
Source/WebCore/ChangeLog
Source/WebCore/contentextensions/CompiledContentExtension.cpp
Source/WebCore/contentextensions/ContentExtensionCompiler.cpp
Source/WebCore/contentextensions/ContentExtensionError.cpp
Source/WebCore/contentextensions/ContentExtensionError.h
Source/WebCore/contentextensions/ContentExtensionsBackend.cpp
Source/WebCore/contentextensions/ContentExtensionsBackend.h
Source/WebCore/contentextensions/DFABytecode.h
Source/WebCore/contentextensions/DFABytecodeCompiler.cpp
Source/WebCore/contentextensions/DFABytecodeCompiler.h
Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp
Source/WebCore/contentextensions/DFABytecodeInterpreter.h
Source/WebCore/loader/ResourceLoadInfo.h
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/APIUserContentExtensionStore.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp

index d69c32c..4ac8979 100644 (file)
@@ -1,3 +1,53 @@
+2015-05-20  Alex Christensen  <achristensen@webkit.org>
+
+        [Content Extensions] Relax restrictions on triggers that match everything.
+        https://bugs.webkit.org/show_bug.cgi?id=145069
+
+        Reviewed by Benjamin Poulain.
+
+        Added API tests that cover the new functionality and test for correctness in behavior.
+
+        * contentextensions/CompiledContentExtension.cpp:
+        (WebCore::ContentExtensions::CompiledContentExtension::globalDisplayNoneSelectors):
+        * contentextensions/ContentExtensionCompiler.cpp:
+        (WebCore::ContentExtensions::addUniversalActionsToDFA):
+        (WebCore::ContentExtensions::compileRuleList):
+        Put universalActionsWithoutDomains into the DFA from filtersWithoutDomains and
+        put universalActionsWithDomains into the DFA from filtersWithDomains.        
+        * contentextensions/ContentExtensionError.cpp:
+        (WebCore::ContentExtensions::contentExtensionErrorCategory):
+        * contentextensions/ContentExtensionError.h:
+        Remove error codes for errors that are not errors any more.
+        * contentextensions/ContentExtensionsBackend.cpp:
+        (WebCore::ContentExtensions::ContentExtensionsBackend::actionsForResourceLoad):
+        * contentextensions/ContentExtensionsBackend.h:
+        * contentextensions/DFABytecode.h:
+        (WebCore::ContentExtensions::instructionSizeWithArguments):
+        * contentextensions/DFABytecodeCompiler.cpp:
+        (WebCore::ContentExtensions::set32Bits):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::emitAppendAction):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::emitJump):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::emitCheckValue):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::emitCheckValueRange):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::compileNode):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::compile):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::emitTestFlagsAndAppendAction): Deleted.
+        * contentextensions/DFABytecodeCompiler.h:
+        * contentextensions/DFABytecodeInterpreter.cpp:
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpretAppendAction):
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpretTestFlagsAndAppendAction):
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::actionsForDefaultStylesheetFromDFARoot):
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpret):
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::actionsFromDFARoot): Deleted.
+        * contentextensions/DFABytecodeInterpreter.h:
+        Add a new bytecode AppendActionDefaultStylesheet to mark actions that are css-display-none
+        that need to be put in the default stylesheet to be ignored or not as a whole.
+        css-display-none actions with flags or domain rules and css-display-none actions 
+        after ignore-previous-rules actions are not to be in this precompiled stylesheet, but
+        they will be applied as needed per page.  The precompiled stylesheet is already applied
+        if no ignore-previous-rules action is triggered.
+        * loader/ResourceLoadInfo.h:
+
 2015-05-20  Youenn Fablet  <youenn.fablet@crf.canon.fr>
 
         JS binding generator should create a member variable for each Promise attribute of an interface
index 7f45173..ec22b2a 100644 (file)
@@ -39,7 +39,7 @@ CompiledContentExtension::~CompiledContentExtension()
 Vector<String> CompiledContentExtension::globalDisplayNoneSelectors(Vector<bool>& pagesUsed)
 {
     DFABytecodeInterpreter interpreter(filtersWithoutDomainsBytecode(), filtersWithoutDomainsBytecodeLength(), pagesUsed);
-    DFABytecodeInterpreter::Actions actionLocations = interpreter.actionsFromDFARoot();
+    DFABytecodeInterpreter::Actions actionLocations = interpreter.actionsForDefaultStylesheetFromDFARoot();
     
     Vector<Action> globalActions;
     for (uint64_t actionLocation : actionLocations)
index 525bf5a..94d06e4 100644 (file)
@@ -121,21 +121,24 @@ static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& rul
     return actionLocations;
 }
 
-typedef HashSet<uint64_t, DefaultHash<uint64_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<uint64_t>> UniversalActionLocationsSet;
+typedef HashSet<uint64_t, DefaultHash<uint64_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<uint64_t>> UniversalActionSet;
 
-static void addUniversalActionsToDFA(DFA& dfa, const UniversalActionLocationsSet& universalActionLocations)
+static void addUniversalActionsToDFA(DFA& dfa, const UniversalActionSet& universalActions)
 {
-    if (universalActionLocations.isEmpty())
+    if (universalActions.isEmpty())
         return;
 
+    DFANode& root = dfa.nodes[dfa.root];
+    ASSERT(!root.actionsLength());
     unsigned actionsStart = dfa.actions.size();
-    dfa.actions.reserveCapacity(dfa.actions.size() + universalActionLocations.size());
-    for (uint64_t actionLocation : universalActionLocations)
-        dfa.actions.uncheckedAppend(actionLocation);
+    dfa.actions.reserveCapacity(dfa.actions.size() + universalActions.size());
+    for (uint64_t action : universalActions)
+        dfa.actions.uncheckedAppend(action);
     unsigned actionsEnd = dfa.actions.size();
+
     unsigned actionsLength = actionsEnd - actionsStart;
     RELEASE_ASSERT_WITH_MESSAGE(actionsLength < std::numeric_limits<uint16_t>::max(), "Too many uncombined actions that match everything");
-    dfa.nodes[dfa.root].setActions(actionsStart, static_cast<uint16_t>(actionsLength));
+    root.setActions(actionsStart, static_cast<uint16_t>(actionsLength));
 }
 
 std::error_code compileRuleList(ContentExtensionCompilationClient& client, String&& ruleList)
@@ -156,7 +159,8 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
     LOG_LARGE_STRUCTURES(actions, actions.capacity() * sizeof(SerializedActionByte));
     actions.clear();
 
-    UniversalActionLocationsSet universalActionLocations;
+    UniversalActionSet universalActionsWithoutDomains;
+    UniversalActionSet universalActionsWithDomains;
 
     // FIXME: These don't all need to be in memory at the same time.
     CombinedURLFilters filtersWithoutDomains;
@@ -169,6 +173,7 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
     for (unsigned ruleIndex = 0; ruleIndex < parsedRuleList.size(); ++ruleIndex) {
         const ContentExtensionRule& contentExtensionRule = parsedRuleList[ruleIndex];
         const Trigger& trigger = contentExtensionRule.trigger();
+        const Action& action = contentExtensionRule.action();
         ASSERT(trigger.urlFilter.length());
 
         // High bits are used for flags. This should match how they are used in DFABytecodeCompiler::compileNode.
@@ -178,9 +183,12 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
             ASSERT(trigger.domainCondition == Trigger::DomainCondition::None);
             status = filtersWithoutDomainParser.addPattern(trigger.urlFilter, trigger.urlFilterIsCaseSensitive, actionLocationAndFlags);
             if (status == URLFilterParser::MatchesEverything) {
-                if (ignorePreviousRulesSeen)
-                    return ContentExtensionError::RegexMatchesEverythingAfterIgnorePreviousRules;
-                universalActionLocations.add(actionLocationAndFlags);
+                if (!ignorePreviousRulesSeen
+                    && trigger.domainCondition == Trigger::DomainCondition::None
+                    && action.type() == ActionType::CSSDisplayNoneSelector
+                    && !trigger.flags)
+                    actionLocationAndFlags |= DisplayNoneStyleSheetFlag;
+                universalActionsWithoutDomains.add(actionLocationAndFlags);
                 status = URLFilterParser::Ok;
             }
             if (status != URLFilterParser::Ok) {
@@ -196,8 +204,10 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
             }
             
             status = filtersWithDomainParser.addPattern(trigger.urlFilter, trigger.urlFilterIsCaseSensitive, actionLocationAndFlags);
-            if (status == URLFilterParser::MatchesEverything)
-                return ContentExtensionError::RegexMatchesEverythingWithDomains;
+            if (status == URLFilterParser::MatchesEverything) {
+                universalActionsWithDomains.add(actionLocationAndFlags);
+                status = URLFilterParser::Ok;
+            }
             if (status != URLFilterParser::Ok) {
                 dataLogF("Error while parsing %s: %s\n", trigger.urlFilter.utf8().data(), URLFilterParser::statusString(status).utf8().data());
                 return ContentExtensionError::JSONInvalidRegex;
@@ -207,7 +217,7 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
         }
         ASSERT(status == URLFilterParser::Ok);
         
-        if (contentExtensionRule.action().type() == ActionType::IgnorePreviousRules)
+        if (action.type() == ActionType::IgnorePreviousRules)
             ignorePreviousRulesSeen = true;
     }
     LOG_LARGE_STRUCTURES(parsedRuleList, parsedRuleList.capacity() * sizeof(ContentExtensionRule)); // Doesn't include strings.
@@ -231,7 +241,7 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
     // FIXME: This can be tuned. More NFAs take longer to interpret, fewer use more memory and time to compile.
     const unsigned maxNFASize = 50000;
     
-    bool firstNFASeen = false;
+    bool firstNFAWithoutDomainsSeen = false;
     // FIXME: Combine small NFAs to reduce the number of NFAs.
     filtersWithoutDomains.processNFAs(maxNFASize, [&](NFA&& nfa) {
 #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
@@ -252,9 +262,9 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
 #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) {
+        if (!firstNFAWithoutDomainsSeen) {
             // Put all the universal actions on the first DFA.
-            addUniversalActionsToDFA(dfa, universalActionLocations);
+            addUniversalActionsToDFA(dfa, universalActionsWithoutDomains);
         }
 
         Vector<DFABytecode> bytecode;
@@ -263,18 +273,18 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
         LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
         client.writeFiltersWithoutDomainsBytecode(WTF::move(bytecode));
 
-        firstNFASeen = true;
+        firstNFAWithoutDomainsSeen = true;
     });
     ASSERT(filtersWithoutDomains.isEmpty());
 
-    if (!firstNFASeen) {
+    if (!firstNFAWithoutDomainsSeen) {
         // 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.
 
         NFA dummyNFA;
         DFA dummyDFA = NFAToDFA::convert(dummyNFA);
 
-        addUniversalActionsToDFA(dummyDFA, universalActionLocations);
+        addUniversalActionsToDFA(dummyDFA, universalActionsWithoutDomains);
 
         Vector<DFABytecode> bytecode;
         DFABytecodeCompiler compiler(dummyDFA, bytecode);
@@ -282,9 +292,10 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
         LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
         client.writeFiltersWithoutDomainsBytecode(WTF::move(bytecode));
     }
-    LOG_LARGE_STRUCTURES(universalActionLocations, universalActionLocations.capacity() * sizeof(unsigned));
-    universalActionLocations.clear();
+    LOG_LARGE_STRUCTURES(universalAction, universalAction.capacity() * sizeof(unsigned));
+    universalActionsWithoutDomains.clear();
     
+    bool firstNFAWithDomainsSeen = false;
     filtersWithDomains.processNFAs(maxNFASize, [&](NFA&& nfa) {
 #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
         dataLogF("filtersWithDomains NFA\n");
@@ -304,13 +315,38 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, Strin
 #endif
         ASSERT_WITH_MESSAGE(!dfa.nodes[dfa.root].hasActions(), "Filters with domains that match everything are not allowed right now.");
         
+        if (!firstNFAWithDomainsSeen) {
+            // Put all the universal actions on the first DFA.
+            addUniversalActionsToDFA(dfa, universalActionsWithDomains);
+        }
+        
         Vector<DFABytecode> bytecode;
         DFABytecodeCompiler compiler(dfa, bytecode);
         compiler.compile();
         LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
         client.writeFiltersWithDomainsBytecode(WTF::move(bytecode));
+        
+        firstNFAWithDomainsSeen = true;
     });
     ASSERT(filtersWithDomains.isEmpty());
+    
+    if (!firstNFAWithDomainsSeen) {
+        // 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.
+        
+        NFA dummyNFA;
+        DFA dummyDFA = NFAToDFA::convert(dummyNFA);
+        
+        addUniversalActionsToDFA(dummyDFA, universalActionsWithDomains);
+        
+        Vector<DFABytecode> bytecode;
+        DFABytecodeCompiler compiler(dummyDFA, bytecode);
+        compiler.compile();
+        LOG_LARGE_STRUCTURES(bytecode, bytecode.capacity() * sizeof(uint8_t));
+        client.writeFiltersWithDomainsBytecode(WTF::move(bytecode));
+    }
+    LOG_LARGE_STRUCTURES(universalActionsWithDomains, universalActionsWithDomains.capacity() * sizeof(unsigned));
+    universalActionsWithDomains.clear();
 
     domainFilters.processNFAs(maxNFASize, [&](NFA&& nfa) {
 #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
index 13314eb..63122b4 100644 (file)
@@ -75,10 +75,6 @@ const std::error_category& contentExtensionErrorCategory()
                 return "Invalid css-display-none action type. Requires a selector.";
             case ContentExtensionError::JSONInvalidRegex:
                 return "Invalid or unsupported regular expression.";
-            case ContentExtensionError::RegexMatchesEverythingAfterIgnorePreviousRules:
-                return "Regular expressions that match everything are only allowed before the first ignore-previous-rules.";
-            case ContentExtensionError::RegexMatchesEverythingWithDomains:
-                return "Regular expressions that match everything are not allowed with unless- or if-domain.";
             case ContentExtensionError::JSONInvalidDomainList:
                 return "Invalid domain list.";
             case ContentExtensionError::JSONDomainNotLowerCaseASCII:
index 0e10a7c..1d2558e 100644 (file)
@@ -57,9 +57,6 @@ enum class ContentExtensionError {
     JSONInvalidActionType,
     JSONInvalidCSSDisplayNoneActionType,
     JSONInvalidRegex,
-    
-    RegexMatchesEverythingAfterIgnorePreviousRules,
-    RegexMatchesEverythingWithDomains,
 };
 
 WEBCORE_EXPORT const std::error_category& contentExtensionErrorCategory();
index 9db8839..dfa7938 100644 (file)
@@ -146,16 +146,6 @@ Vector<Action> ContentExtensionsBackend::actionsForResourceLoad(const ResourceLo
             }
         }
         if (!sawIgnorePreviousRules) {
-            DFABytecodeInterpreter::Actions universalActions = withoutDomainsInterpreter.actionsFromDFARoot();
-            for (uint64_t actionLocation : universalActions) {
-                // FIXME: We shouldn't deserialize an action all the way if it is a css-display-none selector.
-                Action action = Action::deserialize(actions, actionsLength, static_cast<unsigned>(actionLocation));
-                action.setExtensionIdentifier(contentExtension->identifier());
-
-                // CSS selectors were already compiled into a stylesheet using globalDisplayNoneSelectors.
-                if (action.type() != ActionType::CSSDisplayNoneSelector)
-                    finalActions.append(action);
-            }
             finalActions.append(Action(ActionType::CSSDisplayNoneStyleSheet, contentExtension->identifier()));
             finalActions.last().setExtensionIdentifier(contentExtension->identifier());
         }
index 0202729..3aa4bb8 100644 (file)
@@ -63,7 +63,7 @@ public:
 
     // - Internal WebCore Interface.
     WEBCORE_EXPORT Vector<Action> actionsForResourceLoad(const ResourceLoadInfo&) const;
-    StyleSheetContents* globalDisplayNoneStyleSheet(const String& identifier) const;
+    WEBCORE_EXPORT StyleSheetContents* globalDisplayNoneStyleSheet(const String& identifier) const;
 
     void processContentExtensionRulesForLoad(ResourceRequest&, ResourceType, DocumentLoader& initiatingDocumentLoader);
 
index ae26c61..d0a00e0 100644 (file)
@@ -55,6 +55,7 @@ enum class DFABytecodeInstruction : uint8_t {
     // AppendAction has one argument:
     // The action to append (4 bytes).
     AppendAction,
+    AppendActionDefaultStylesheet,
     AppendActionWithIfDomain,
     
     // TestFlagsAndAppendAction has two arguments:
@@ -76,20 +77,21 @@ static inline size_t instructionSizeWithArguments(DFABytecodeInstruction instruc
     switch (instruction) {
     case DFABytecodeInstruction::CheckValueCaseSensitive:
     case DFABytecodeInstruction::CheckValueCaseInsensitive:
-        return sizeof(DFABytecodeInstruction) + sizeof(uint8_t) + sizeof(unsigned);
+        return sizeof(DFABytecodeInstruction) + sizeof(uint8_t) + sizeof(uint32_t);
     case DFABytecodeInstruction::CheckValueRangeCaseSensitive:
     case DFABytecodeInstruction::CheckValueRangeCaseInsensitive:
-        return sizeof(DFABytecodeInstruction) + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(unsigned);
+        return sizeof(DFABytecodeInstruction) + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t);
     case DFABytecodeInstruction::AppendAction:
+    case DFABytecodeInstruction::AppendActionDefaultStylesheet:
     case DFABytecodeInstruction::AppendActionWithIfDomain:
-        return sizeof(DFABytecodeInstruction) + sizeof(unsigned);
+        return sizeof(DFABytecodeInstruction) + sizeof(uint32_t);
     case DFABytecodeInstruction::TestFlagsAndAppendAction:
     case DFABytecodeInstruction::TestFlagsAndAppendActionWithIfDomain:
-        return sizeof(DFABytecodeInstruction) + sizeof(uint16_t) + sizeof(unsigned);
+        return sizeof(DFABytecodeInstruction) + sizeof(uint16_t) + sizeof(uint32_t);
     case DFABytecodeInstruction::Terminate:
         return sizeof(DFABytecodeInstruction);
     case DFABytecodeInstruction::Jump:
-        return sizeof(DFABytecodeInstruction) + sizeof(unsigned);
+        return sizeof(DFABytecodeInstruction) + sizeof(uint32_t);
     }
 }
     
index f211e77..b89457e 100644 (file)
@@ -43,47 +43,50 @@ inline void append(Vector<DFABytecode>& bytecode, IntType value)
     *reinterpret_cast<IntType*>(&bytecode[bytecode.size() - sizeof(IntType)]) = value;
 }
 
-inline void set32Bits(Vector<DFABytecode>& bytecode, unsigned index, unsigned value)
+inline void set32Bits(Vector<DFABytecode>& bytecode, uint32_t index, uint32_t value)
 {
-    *reinterpret_cast<unsigned*>(&bytecode[index]) = value;
+    *reinterpret_cast<uint32_t*>(&bytecode[index]) = value;
 }
 
-void DFABytecodeCompiler::emitAppendAction(unsigned action, bool ifDomain)
+void DFABytecodeCompiler::emitAppendAction(uint64_t action)
 {
-    if (ifDomain)
-        append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::AppendActionWithIfDomain);
-    else
-        append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::AppendAction);
-    append<unsigned>(m_bytecode, action);
-}
-
-void DFABytecodeCompiler::emitTestFlagsAndAppendAction(uint16_t flags, unsigned action, bool ifDomain)
-{
-    ASSERT(flags);
-    if (ifDomain)
-        append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::TestFlagsAndAppendActionWithIfDomain);
-    else
-        append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::TestFlagsAndAppendAction);
-    append<uint16_t>(m_bytecode, flags);
-    append<unsigned>(m_bytecode, action);
+    // High bits are used to store flags. See compileRuleList.
+    if (action & 0xFFFF00000000) {
+        ASSERT(!(action & DisplayNoneStyleSheetFlag));
+        if (action & IfDomainFlag)
+            append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::TestFlagsAndAppendActionWithIfDomain);
+        else
+            append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::TestFlagsAndAppendAction);
+        append<uint16_t>(m_bytecode, static_cast<uint16_t>(action >> 32));
+        append<uint32_t>(m_bytecode, static_cast<uint32_t>(action));
+    } else {
+        if (action & DisplayNoneStyleSheetFlag) {
+            RELEASE_ASSERT(!(action & IfDomainFlag));
+            append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::AppendActionDefaultStylesheet);
+        } else if (action & IfDomainFlag)
+            append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::AppendActionWithIfDomain);
+        else
+            append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::AppendAction);
+        append<uint32_t>(m_bytecode, static_cast<uint32_t>(action));
+    }
 }
 
-void DFABytecodeCompiler::emitJump(unsigned destinationNodeIndex)
+void DFABytecodeCompiler::emitJump(uint32_t destinationNodeIndex)
 {
     append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::Jump);
     m_linkRecords.append(std::make_pair(m_bytecode.size(), destinationNodeIndex));
-    append<unsigned>(m_bytecode, 0); // This value will be set when linking.
+    append<uint32_t>(m_bytecode, 0); // This value will be set when linking.
 }
 
-void DFABytecodeCompiler::emitCheckValue(uint8_t value, unsigned destinationNodeIndex, bool caseSensitive)
+void DFABytecodeCompiler::emitCheckValue(uint8_t value, uint32_t destinationNodeIndex, bool caseSensitive)
 {
     append<DFABytecodeInstruction>(m_bytecode, caseSensitive ? DFABytecodeInstruction::CheckValueCaseSensitive : DFABytecodeInstruction::CheckValueCaseInsensitive);
     append<uint8_t>(m_bytecode, value);
     m_linkRecords.append(std::make_pair(m_bytecode.size(), destinationNodeIndex));
-    append<unsigned>(m_bytecode, 0); // This value will be set when linking.
+    append<uint32_t>(m_bytecode, 0); // This value will be set when linking.
 }
 
-void DFABytecodeCompiler::emitCheckValueRange(uint8_t lowValue, uint8_t highValue, unsigned destinationNodeIndex, bool caseSensitive)
+void DFABytecodeCompiler::emitCheckValueRange(uint8_t lowValue, uint8_t highValue, uint32_t destinationNodeIndex, bool caseSensitive)
 {
     ASSERT_WITH_MESSAGE(lowValue < highValue, "The instruction semantic impose lowValue is strictly less than highValue.");
 
@@ -91,7 +94,7 @@ void DFABytecodeCompiler::emitCheckValueRange(uint8_t lowValue, uint8_t highValu
     append<uint8_t>(m_bytecode, lowValue);
     append<uint8_t>(m_bytecode, highValue);
     m_linkRecords.append(std::make_pair(m_bytecode.size(), destinationNodeIndex));
-    append<unsigned>(m_bytecode, 0); // This value will be set when linking.
+    append<uint32_t>(m_bytecode, 0); // This value will be set when linking.
 }
 
 void DFABytecodeCompiler::emitTerminate()
@@ -99,11 +102,11 @@ void DFABytecodeCompiler::emitTerminate()
     append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::Terminate);
 }
 
-void DFABytecodeCompiler::compileNode(unsigned index, bool root)
+void DFABytecodeCompiler::compileNode(uint32_t index, bool root)
 {
     const DFANode& node = m_dfa.nodes[index];
     if (node.isKilled()) {
-        m_nodeStartOffsets[index] = std::numeric_limits<unsigned>::max();
+        m_nodeStartOffsets[index] = std::numeric_limits<uint32_t>::max();
         return;
     }
 
@@ -111,13 +114,8 @@ void DFABytecodeCompiler::compileNode(unsigned index, bool root)
     if (!root)
         m_nodeStartOffsets[index] = m_bytecode.size();
 
-    for (uint64_t action : node.actions(m_dfa)) {
-        // High bits are used to store flags. A boolean is stored in the 48th bit. See compileRuleList.
-        if (action & 0xFFFF00000000)
-            emitTestFlagsAndAppendAction(static_cast<uint16_t>(action >> 32), static_cast<unsigned>(action), action & IfDomainFlag);
-        else
-            emitAppendAction(static_cast<unsigned>(action), action & IfDomainFlag);
-    }
+    for (uint64_t action : node.actions(m_dfa))
+        emitAppendAction(action);
     
     // If we jump to the root, we don't want to re-add its actions to a HashSet.
     // We know we have already added them because the root is always compiled first and we always start interpreting at the beginning.
@@ -212,24 +210,24 @@ void DFABytecodeCompiler::compileCheckForRange(const Range& range)
 void DFABytecodeCompiler::compile()
 {
     // DFA header.
-    unsigned startLocation = m_bytecode.size();
-    append<unsigned>(m_bytecode, 0);
+    uint32_t startLocation = m_bytecode.size();
+    append<uint32_t>(m_bytecode, 0);
     m_nodeStartOffsets.resize(m_dfa.nodes.size());
     
     // Make sure the root is always at the beginning of the bytecode.
     compileNode(m_dfa.root, true);
-    for (unsigned i = 0; i < m_dfa.nodes.size(); i++) {
+    for (uint32_t i = 0; i < m_dfa.nodes.size(); i++) {
         if (i != m_dfa.root)
             compileNode(i, false);
     }
 
     // Link.
     for (const auto& linkRecord : m_linkRecords) {
-        unsigned offset = linkRecord.first;
-        ASSERT(!(*reinterpret_cast<unsigned*>(&m_bytecode[offset])));
+        uint32_t offset = linkRecord.first;
+        ASSERT(!(*reinterpret_cast<uint32_t*>(&m_bytecode[offset])));
 
-        unsigned target = m_nodeStartOffsets[linkRecord.second];
-        RELEASE_ASSERT(target != std::numeric_limits<unsigned>::max());
+        uint32_t target = m_nodeStartOffsets[linkRecord.second];
+        RELEASE_ASSERT(target != std::numeric_limits<uint32_t>::max());
         set32Bits(m_bytecode, offset, m_nodeStartOffsets[linkRecord.second]);
     }
     
index 26244a2..63319b2 100644 (file)
@@ -50,7 +50,7 @@ public:
 
 private:
     struct Range {
-        Range(uint8_t min, uint8_t max, unsigned destination, bool caseSensitive)
+        Range(uint8_t min, uint8_t max, uint32_t destination, bool caseSensitive)
             : min(min)
             , max(max)
             , destination(destination)
@@ -59,28 +59,27 @@ private:
         }
         uint8_t min;
         uint8_t max;
-        unsigned destination;
+        uint32_t destination;
         bool caseSensitive;
     };
-    void compileNode(unsigned, bool root);
+    void compileNode(uint32_t, bool root);
     void compileNodeTransitions(const DFANode&);
     void compileCheckForRange(const Range&);
 
-    void emitAppendAction(unsigned, bool ifDomain);
-    void emitTestFlagsAndAppendAction(uint16_t flags, unsigned, bool ifDomain);
-    void emitJump(unsigned destinationNodeIndex);
-    void emitCheckValue(uint8_t value, unsigned destinationNodeIndex, bool caseSensitive);
-    void emitCheckValueRange(uint8_t lowValue, uint8_t highValue, unsigned destinationNodeIndex, bool caseSensitive);
+    void emitAppendAction(uint64_t);
+    void emitJump(uint32_t destinationNodeIndex);
+    void emitCheckValue(uint8_t value, uint32_t destinationNodeIndex, bool caseSensitive);
+    void emitCheckValueRange(uint8_t lowValue, uint8_t highValue, uint32_t destinationNodeIndex, bool caseSensitive);
     void emitTerminate();
 
     Vector<DFABytecode>& m_bytecode;
     const DFA& m_dfa;
     
-    Vector<unsigned> m_nodeStartOffsets;
+    Vector<uint32_t> m_nodeStartOffsets;
     
     // The first value is the index in the bytecode buffer where the jump is to be written.
     // The second value is the index of the node to jump to.
-    Vector<std::pair<unsigned, unsigned>> m_linkRecords;
+    Vector<std::pair<uint32_t, uint32_t>> m_linkRecords;
 };
 
 } // namespace ContentExtensions
index 6643524..3adb30f 100644 (file)
@@ -53,9 +53,12 @@ static inline IntType getBits(const DFABytecode* bytecode, unsigned bytecodeLeng
 void DFABytecodeInterpreter::interpretAppendAction(unsigned& programCounter, Actions& actions, bool ifDomain)
 {
     ASSERT(getBits<DFABytecodeInstruction>(m_bytecode, m_bytecodeLength, programCounter, m_pagesUsed) == DFABytecodeInstruction::AppendAction
-        || getBits<DFABytecodeInstruction>(m_bytecode, m_bytecodeLength, programCounter, m_pagesUsed) == DFABytecodeInstruction::AppendActionWithIfDomain);
+        || getBits<DFABytecodeInstruction>(m_bytecode, m_bytecodeLength, programCounter, m_pagesUsed) == DFABytecodeInstruction::AppendActionWithIfDomain
+        || getBits<DFABytecodeInstruction>(m_bytecode, m_bytecodeLength, programCounter, m_pagesUsed) == DFABytecodeInstruction::AppendActionDefaultStylesheet);
     actions.add((ifDomain ? IfDomainFlag : 0) | static_cast<uint64_t>(getBits<unsigned>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecode), m_pagesUsed)));
     programCounter += instructionSizeWithArguments(DFABytecodeInstruction::AppendAction);
+    ASSERT(instructionSizeWithArguments(DFABytecodeInstruction::AppendAction) == instructionSizeWithArguments(DFABytecodeInstruction::AppendActionWithIfDomain));
+    ASSERT(instructionSizeWithArguments(DFABytecodeInstruction::AppendAction) == instructionSizeWithArguments(DFABytecodeInstruction::AppendActionDefaultStylesheet));
 }
 
 void DFABytecodeInterpreter::interpretTestFlagsAndAppendAction(unsigned& programCounter, uint16_t flags, Actions& actions, bool ifDomain)
@@ -65,9 +68,10 @@ void DFABytecodeInterpreter::interpretTestFlagsAndAppendAction(unsigned& program
     if (flags & getBits<uint16_t>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecode), m_pagesUsed))
         actions.add((ifDomain ? IfDomainFlag : 0) | static_cast<uint64_t>(getBits<unsigned>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecode) + sizeof(uint16_t), m_pagesUsed)));
     programCounter += instructionSizeWithArguments(DFABytecodeInstruction::TestFlagsAndAppendAction);
+    ASSERT(instructionSizeWithArguments(DFABytecodeInstruction::TestFlagsAndAppendAction) == instructionSizeWithArguments(DFABytecodeInstruction::TestFlagsAndAppendActionWithIfDomain));
 }
 
-DFABytecodeInterpreter::Actions DFABytecodeInterpreter::actionsFromDFARoot()
+DFABytecodeInterpreter::Actions DFABytecodeInterpreter::actionsForDefaultStylesheetFromDFARoot()
 {
     Actions actions;
 
@@ -77,12 +81,19 @@ DFABytecodeInterpreter::Actions DFABytecodeInterpreter::actionsFromDFARoot()
 
     while (programCounter < dfaBytecodeLength) {
         DFABytecodeInstruction instruction = static_cast<DFABytecodeInstruction>(m_bytecode[programCounter]);
-        if (instruction == DFABytecodeInstruction::AppendAction)
+        if (instruction == DFABytecodeInstruction::AppendActionDefaultStylesheet)
             interpretAppendAction(programCounter, actions, false);
+        else if (instruction == DFABytecodeInstruction::AppendAction)
+            programCounter += instructionSizeWithArguments(DFABytecodeInstruction::AppendAction);
         else if (instruction == DFABytecodeInstruction::TestFlagsAndAppendAction)
             programCounter += instructionSizeWithArguments(DFABytecodeInstruction::TestFlagsAndAppendAction);
-        else
+        else {
+            // actionsForDefaultStylesheetFromDFARoot should only be called on the DFA without domains,
+            // which should never have any actions with if-domain.
+            ASSERT(instruction != DFABytecodeInstruction::TestFlagsAndAppendActionWithIfDomain);
+            ASSERT(instruction != DFABytecodeInstruction::AppendActionWithIfDomain);
             break;
+        }
     }
     return actions;
 }
@@ -102,12 +113,14 @@ DFABytecodeInterpreter::Actions DFABytecodeInterpreter::interpret(const CString&
         unsigned dfaBytecodeLength = getBits<unsigned>(m_bytecode, m_bytecodeLength, programCounter, m_pagesUsed);
         programCounter += sizeof(unsigned);
 
-        // Skip the actions on the DFA root. These are accessed via actionsFromDFARoot.
+        // Skip the default stylesheet actions on the DFA root. These are accessed via actionsForDefaultStylesheetFromDFARoot.
         if (!dfaStart) {
             while (programCounter < dfaBytecodeLength) {
                 DFABytecodeInstruction instruction = static_cast<DFABytecodeInstruction>(m_bytecode[programCounter]);
-                if (instruction == DFABytecodeInstruction::AppendAction)
-                    programCounter += instructionSizeWithArguments(DFABytecodeInstruction::AppendAction);
+                if (instruction == DFABytecodeInstruction::AppendActionDefaultStylesheet)
+                    programCounter += instructionSizeWithArguments(DFABytecodeInstruction::AppendActionDefaultStylesheet);
+                else if (instruction == DFABytecodeInstruction::AppendAction)
+                    interpretAppendAction(programCounter, actions, false);
                 else if (instruction == DFABytecodeInstruction::TestFlagsAndAppendAction)
                     interpretTestFlagsAndAppendAction(programCounter, flags, actions, false);
                 else
index f07ec5c..1405fab 100644 (file)
@@ -60,7 +60,7 @@ public:
     typedef HashSet<uint64_t, DefaultHash<uint64_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<uint64_t>> Actions;
     
     Actions interpret(const CString&, uint16_t flags);
-    Actions actionsFromDFARoot();
+    Actions actionsForDefaultStylesheetFromDFARoot();
 
 private:
     void interpretAppendAction(unsigned& programCounter, Actions&, bool ifDomain);
index 939ff5c..3e48158 100644 (file)
@@ -56,8 +56,10 @@ typedef uint16_t ResourceFlags;
 // The first 32 bits of a uint64_t action are used for the action location.
 // The next 16 bits are used for the flags (ResourceType and LoadType).
 // The next bit is used to mark actions that are from a rule with an if-domain condition.
+// The next bit is used to mark actions that in the default stylesheet.
 // The values -1 and -2 are used for removed and empty values in HashTables.
 const uint64_t IfDomainFlag = 0x0001000000000000;
+const uint64_t DisplayNoneStyleSheetFlag = 0x0002000000000000;
 
 ResourceType toResourceType(CachedResource::Type);
 uint16_t readResourceType(const String&);
index 1e51405..a78810e 100644 (file)
@@ -1,3 +1,13 @@
+2015-05-20  Alex Christensen  <achristensen@webkit.org>
+
+        [Content Extensions] Relax restrictions on triggers that match everything.
+        https://bugs.webkit.org/show_bug.cgi?id=145069
+
+        Reviewed by Benjamin Poulain.
+
+        * UIProcess/API/APIUserContentExtensionStore.h:
+        Increment version number to reflect changes in DFABytecode.
+
 2015-05-20  Sungmann Cho  <sungmann.cho@navercorp.com>
 
         Minor cleanups to PluginProcessProxy.
index cc760b2..bf68c45 100644 (file)
@@ -51,7 +51,7 @@ public:
     
     // 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 = 2;
+    const static uint32_t CurrentContentExtensionFileVersion = 3;
 
     static UserContentExtensionStore& defaultStore();
 
index df49064..5bac7ca 100644 (file)
@@ -1,3 +1,14 @@
+2015-05-20  Alex Christensen  <achristensen@webkit.org>
+
+        [Content Extensions] Relax restrictions on triggers that match everything.
+        https://bugs.webkit.org/show_bug.cgi?id=145069
+
+        Reviewed by Benjamin Poulain.
+
+        * TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
+        (TestWebKitAPI::TEST_F):
+        Update and add tests for new possibilities with .*
+
 2015-05-20  Rohit Kumar  <kumar.rohit@samsung.com>
 
         [EFL][Minibrowser] Hide URL bar on fullscreen mode for HTML elements and browser window in WebKit EFL Minibrowser
index 35f62cd..b2b819c 100644 (file)
@@ -743,6 +743,37 @@ void checkCompilerError(const char* json, std::error_code expectedError)
         EXPECT_STREQ(compilerError.category().name(), expectedError.category().name());
 }
 
+TEST_F(ContentExtensionTest, MatchesEverything)
+{
+    // Only css-display-none rules with triggers that match everything, no domain rules, and no flags
+    // should go in the global display:none stylesheet. css-display-none rules with domain rules or flags
+    // are applied separately on pages where they apply.
+    auto backend1 = makeBackend("[{\"action\":{\"type\":\"css-display-none\",\"selector\":\".hidden\"},\"trigger\":{\"url-filter\":\".*\"}}]");
+    EXPECT_TRUE(nullptr != backend1.globalDisplayNoneStyleSheet(ASCIILiteral("testFilter")));
+    testRequest(backend1, mainDocumentRequest("http://webkit.org"), { }); // Selector is in global stylesheet.
+    
+    auto backend2 = makeBackend("[{\"action\":{\"type\":\"css-display-none\",\"selector\":\".hidden\"},\"trigger\":{\"url-filter\":\".*\",\"if-domain\":[\"webkit.org\"]}}]");
+    EXPECT_EQ(nullptr, backend2.globalDisplayNoneStyleSheet(ASCIILiteral("testFilter")));
+    testRequest(backend2, mainDocumentRequest("http://webkit.org"), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(backend2, mainDocumentRequest("http://w3c.org"), { });
+    
+    auto backend3 = makeBackend("[{\"action\":{\"type\":\"css-display-none\",\"selector\":\".hidden\"},\"trigger\":{\"url-filter\":\".*\",\"unless-domain\":[\"webkit.org\"]}}]");
+    EXPECT_EQ(nullptr, backend3.globalDisplayNoneStyleSheet(ASCIILiteral("testFilter")));
+    testRequest(backend3, mainDocumentRequest("http://webkit.org"), { });
+    testRequest(backend3, mainDocumentRequest("http://w3c.org"), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    
+    auto backend4 = makeBackend("[{\"action\":{\"type\":\"css-display-none\",\"selector\":\".hidden\"},\"trigger\":{\"url-filter\":\".*\",\"load-type\":[\"third-party\"]}}]");
+    EXPECT_EQ(nullptr, backend4.globalDisplayNoneStyleSheet(ASCIILiteral("testFilter")));
+    testRequest(backend4, mainDocumentRequest("http://webkit.org"), { });
+    testRequest(backend4, subResourceRequest("http://not_webkit.org", "http://webkit.org"), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+
+    // css-display-none rules after ignore-previous-rules should not be put in the default stylesheet.
+    auto backend5 = makeBackend("[{\"action\":{\"type\":\"ignore-previous-rules\"},\"trigger\":{\"url-filter\":\".*\"}},"
+        "{\"action\":{\"type\":\"css-display-none\",\"selector\":\".hidden\"},\"trigger\":{\"url-filter\":\".*\"}}]");
+    EXPECT_EQ(nullptr, backend5.globalDisplayNoneStyleSheet(ASCIILiteral("testFilter")));
+    testRequest(backend5, mainDocumentRequest("http://webkit.org"), { ContentExtensions::ActionType::CSSDisplayNoneSelector }, true);
+}
+    
 TEST_F(ContentExtensionTest, InvalidJSON)
 {
     checkCompilerError("[", ContentExtensions::ContentExtensionError::JSONInvalid);
@@ -803,10 +834,9 @@ TEST_F(ContentExtensionTest, InvalidJSON)
         ContentExtensions::ContentExtensionError::JSONInvalidCSSDisplayNoneActionType);
 
     checkCompilerError("[{\"action\":{\"type\":\"ignore-previous-rules\"},\"trigger\":{\"url-filter\":\"webkit.org\"}},"
-        "{\"action\":{\"type\":\"css-display-none\",\"selector\":\".hidden\"},\"trigger\":{\"url-filter\":\".*\"}}]",
-        ContentExtensions::ContentExtensionError::RegexMatchesEverythingAfterIgnorePreviousRules);
-    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*\",\"if-domain\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::RegexMatchesEverythingWithDomains);
-    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*\",\"unless-domain\":[\"a\"]}}]", ContentExtensions::ContentExtensionError::RegexMatchesEverythingWithDomains);
+        "{\"action\":{\"type\":\"css-display-none\",\"selector\":\".hidden\"},\"trigger\":{\"url-filter\":\".*\"}}]", { });
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*\",\"if-domain\":[\"a\"]}}]", { });
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*\",\"unless-domain\":[\"a\"]}}]", { });
     checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"[\"}}]",
         ContentExtensions::ContentExtensionError::JSONInvalidRegex);
 }