[Content Extensions] Correctly handle regular expressions matching everything
authorachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 31 Mar 2015 00:48:30 +0000 (00:48 +0000)
committerachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 31 Mar 2015 00:48:30 +0000 (00:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=143235

Reviewed by Benjamin Poulain.

Source/WebCore:

Test: http/tests/contentextensions/whitelist.html

* contentextensions/CompiledContentExtension.cpp:
(WebCore::ContentExtensions::CompiledContentExtension::globalDisplayNoneSelectors):
Global actions can have non-css actions.  Only put the selectors into the list of selectors.
* contentextensions/ContentExtensionCompiler.cpp:
(WebCore::ContentExtensions::serializeSelector):
(WebCore::ContentExtensions::serializeActions):
Merge sequential css selectors with identical triggers (usually .*) into one action to reduce the number of actions.
(WebCore::ContentExtensions::compileRuleList):
Fail if a regular expression matches everything after ignore-previous-rules.
* contentextensions/ContentExtensionError.cpp:
(WebCore::ContentExtensions::contentExtensionErrorCategory):
* contentextensions/ContentExtensionError.h:
Add more failure cases.
* contentextensions/ContentExtensionRule.h:
(WebCore::ContentExtensions::Trigger::operator==):
Allow comparing of Triggers to determine if sequential triggers are equal.
* contentextensions/ContentExtensionsBackend.cpp:
(WebCore::ContentExtensions::ContentExtensionsBackend::actionsForResourceLoad):
Put non-css actions that match everything into the list of actions if ignore-previous-rules was not hit.
These actions will be out of order, but order only matters when determining if ignore-previous-rules, and this case is handled correctly.
* contentextensions/DFABytecodeInterpreter.cpp:
(WebCore::ContentExtensions::DFABytecodeInterpreter::actionsFromDFARoot):
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpret):
Added an assertion that all actions that match everything should be in the first DFA root.
We should catch them all with URLFilterParser::MatchesEverything.

Tools:

* TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
(TestWebKitAPI::checkCompilerError):
(TestWebKitAPI::TEST_F):
Test ContentExtensionErrors.

LayoutTests:

* http/tests/contentextensions/css-display-none.html:
* http/tests/contentextensions/css-display-none.html.json:
Test multiple selectors with triggers that match everything.
* http/tests/contentextensions/whitelist-expected.txt: Added.
* http/tests/contentextensions/whitelist.html: Added.
* http/tests/contentextensions/whitelist.html.json: Added.

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

16 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/contentextensions/css-display-none.html
LayoutTests/http/tests/contentextensions/css-display-none.html.json
LayoutTests/http/tests/contentextensions/whitelist-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/contentextensions/whitelist.html [new file with mode: 0644]
LayoutTests/http/tests/contentextensions/whitelist.html.json [new file with mode: 0644]
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/ContentExtensionRule.h
Source/WebCore/contentextensions/ContentExtensionsBackend.cpp
Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp

index 339b9e9..1b6b4d9 100644 (file)
@@ -1,3 +1,17 @@
+2015-03-30  Alex Christensen  <achristensen@webkit.org>
+
+        [Content Extensions] Correctly handle regular expressions matching everything
+        https://bugs.webkit.org/show_bug.cgi?id=143235
+
+        Reviewed by Benjamin Poulain.
+
+        * http/tests/contentextensions/css-display-none.html:
+        * http/tests/contentextensions/css-display-none.html.json:
+        Test multiple selectors with triggers that match everything.
+        * http/tests/contentextensions/whitelist-expected.txt: Added.
+        * http/tests/contentextensions/whitelist.html: Added.
+        * http/tests/contentextensions/whitelist.html.json: Added.
+
 2015-03-30  Mark Lam  <mark.lam@apple.com>
 
         REGRESSION (r181993): inspector-protocol/debugger/setBreakpoint-dfg-and-modify-local.html crashes.
index 9757238..565c65c 100644 (file)
@@ -2,7 +2,8 @@
 <meta charset="UTF-8"></meta>
 </head>
 <body>
-<p class="hidden_global">This text should not be visible once the global css selector is applied.</p>
+<p class="hidden_global1">This text should not be visible once the global css selector is applied.</p>
+<p class="hidden_global2">This text should not be visible once the global css selector is applied.</p>
 <p class="hidden">This text should not be visible once the particular css selector is applied.</p>
 <p class="hidden_Ž">This text should not be visible once the particular css selector with non-ascii characters is applied.</p>
 <p class="not_hidden">This text should be visible.</p>
index 187dda7..8748117 100644 (file)
     {
         "action": {
             "type": "css-display-none",
-            "selector": ".hidden_global"
+            "selector": ".hidden_global1"
         },
         "trigger": {
             "url-filter": ".*"
         }
+    },
+    {
+        "action": {
+            "type": "css-display-none",
+            "selector": ".hidden_global2"
+        },
+        "trigger": {
+            "url-filter": ".*"
+        }
+    },
+    {
+        "action": {
+            "type": "ignore-previous-rules"
+        },
+        "trigger": {
+            "url-filter": "never_used"
+        }
     }
 ]
diff --git a/LayoutTests/http/tests/contentextensions/whitelist-expected.txt b/LayoutTests/http/tests/contentextensions/whitelist-expected.txt
new file mode 100644 (file)
index 0000000..9867c23
--- /dev/null
@@ -0,0 +1,7 @@
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderText {#text} at (0,0) size 192x18
+        text run at (0,0) width 192: "This should load successfully."
diff --git a/LayoutTests/http/tests/contentextensions/whitelist.html b/LayoutTests/http/tests/contentextensions/whitelist.html
new file mode 100644 (file)
index 0000000..31574bd
--- /dev/null
@@ -0,0 +1 @@
+This should load successfully.
diff --git a/LayoutTests/http/tests/contentextensions/whitelist.html.json b/LayoutTests/http/tests/contentextensions/whitelist.html.json
new file mode 100644 (file)
index 0000000..d9a31fd
--- /dev/null
@@ -0,0 +1,18 @@
+[
+    {
+        "action": {
+            "type": "block"
+        },
+        "trigger": {
+            "url-filter": ".*"
+        }
+    },
+    {
+        "action": {
+            "type": "ignore-previous-rules"
+        },
+        "trigger": {
+            "url-filter": "whitelist"
+        }
+    }
+]
index 0abd25c..f031a25 100644 (file)
@@ -1,3 +1,38 @@
+2015-03-30  Alex Christensen  <achristensen@webkit.org>
+
+        [Content Extensions] Correctly handle regular expressions matching everything
+        https://bugs.webkit.org/show_bug.cgi?id=143235
+
+        Reviewed by Benjamin Poulain.
+
+        Test: http/tests/contentextensions/whitelist.html
+
+        * contentextensions/CompiledContentExtension.cpp:
+        (WebCore::ContentExtensions::CompiledContentExtension::globalDisplayNoneSelectors):
+        Global actions can have non-css actions.  Only put the selectors into the list of selectors.
+        * contentextensions/ContentExtensionCompiler.cpp:
+        (WebCore::ContentExtensions::serializeSelector):
+        (WebCore::ContentExtensions::serializeActions):
+        Merge sequential css selectors with identical triggers (usually .*) into one action to reduce the number of actions.
+        (WebCore::ContentExtensions::compileRuleList):
+        Fail if a regular expression matches everything after ignore-previous-rules.
+        * contentextensions/ContentExtensionError.cpp:
+        (WebCore::ContentExtensions::contentExtensionErrorCategory):
+        * contentextensions/ContentExtensionError.h:
+        Add more failure cases.
+        * contentextensions/ContentExtensionRule.h:
+        (WebCore::ContentExtensions::Trigger::operator==):
+        Allow comparing of Triggers to determine if sequential triggers are equal.
+        * contentextensions/ContentExtensionsBackend.cpp:
+        (WebCore::ContentExtensions::ContentExtensionsBackend::actionsForResourceLoad):
+        Put non-css actions that match everything into the list of actions if ignore-previous-rules was not hit.
+        These actions will be out of order, but order only matters when determining if ignore-previous-rules, and this case is handled correctly.
+        * contentextensions/DFABytecodeInterpreter.cpp:
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::actionsFromDFARoot):
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpret):
+        Added an assertion that all actions that match everything should be in the first DFA root.
+        We should catch them all with URLFilterParser::MatchesEverything.
+
 2015-03-30  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Allow building on Windows without Cygwin
index 15bb911..2002cbd 100644 (file)
@@ -47,8 +47,7 @@ Vector<String> CompiledContentExtension::globalDisplayNoneSelectors()
     
     Vector<String> selectors;
     for (Action& action : globalActions) {
-        ASSERT(action.type() == ActionType::CSSDisplayNoneSelector);
-        if (action.stringArgument().length())
+        if (action.type() == ActionType::CSSDisplayNoneSelector)
             selectors.append(action.stringArgument());
     }
     
index 6bc09f5..38fdda7 100644 (file)
 #include <wtf/CurrentTime.h>
 #include <wtf/DataLog.h>
 #include <wtf/text/CString.h>
+#include <wtf/text/StringBuilder.h>
 
 namespace WebCore {
 namespace ContentExtensions {
 
+static void serializeSelector(Vector<SerializedActionByte>& actions, const String& selector)
+{
+    // Append action type (1 byte).
+    actions.append(static_cast<SerializedActionByte>(ActionType::CSSDisplayNoneSelector));
+    // Append Selector length (4 bytes).
+    unsigned selectorLength = selector.length();
+    actions.resize(actions.size() + sizeof(unsigned));
+    *reinterpret_cast<unsigned*>(&actions[actions.size() - sizeof(unsigned)]) = selectorLength;
+    bool wideCharacters = !selector.is8Bit();
+    actions.append(wideCharacters);
+    // Append Selector.
+    if (wideCharacters) {
+        unsigned startIndex = actions.size();
+        actions.resize(actions.size() + sizeof(UChar) * selectorLength);
+        for (unsigned i = 0; i < selectorLength; ++i)
+            *reinterpret_cast<UChar*>(&actions[startIndex + i * sizeof(UChar)]) = selector[i];
+    } else {
+        for (unsigned i = 0; i < selectorLength; ++i)
+            actions.append(selector[i]);
+    }
+}
+    
 static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& ruleList, Vector<SerializedActionByte>& actions)
 {
     ASSERT(!actions.size());
@@ -54,14 +77,33 @@ static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& rul
     for (unsigned ruleIndex = 0; ruleIndex < ruleList.size(); ++ruleIndex) {
         const ContentExtensionRule& rule = ruleList[ruleIndex];
         
+        // Consolidate css selectors with identical triggers.
+        if (rule.action().type() == ActionType::CSSDisplayNoneSelector) {
+            StringBuilder selector;
+            selector.append(rule.action().stringArgument());
+            actionLocations.append(actions.size());
+            for (unsigned i = ruleIndex + 1; i < ruleList.size(); i++) {
+                if (rule.trigger() == ruleList[i].trigger() && ruleList[i].action().type() == ActionType::CSSDisplayNoneSelector) {
+                    actionLocations.append(actions.size());
+                    ruleIndex++;
+                    selector.append(',');
+                    selector.append(ruleList[i].action().stringArgument());
+                } else
+                    break;
+            }
+            serializeSelector(actions, selector.toString());
+            continue;
+        }
+        
         // Identical sequential actions should not be rewritten.
         if (ruleIndex && rule.action() == ruleList[ruleIndex - 1].action()) {
             actionLocations.append(actionLocations[ruleIndex - 1]);
             continue;
         }
+
         actionLocations.append(actions.size());
-        
         switch (rule.action().type()) {
+        case ActionType::CSSDisplayNoneSelector:
         case ActionType::CSSDisplayNoneStyleSheet:
         case ActionType::InvalidAction:
             RELEASE_ASSERT_NOT_REACHED();
@@ -71,29 +113,6 @@ static Vector<unsigned> serializeActions(const Vector<ContentExtensionRule>& rul
         case ActionType::IgnorePreviousRules:
             actions.append(static_cast<SerializedActionByte>(rule.action().type()));
             break;
-
-        case ActionType::CSSDisplayNoneSelector: {
-            const String& selector = rule.action().stringArgument();
-            // Append action type (1 byte).
-            actions.append(static_cast<SerializedActionByte>(ActionType::CSSDisplayNoneSelector));
-            // Append Selector length (4 bytes).
-            unsigned selectorLength = selector.length();
-            actions.resize(actions.size() + sizeof(unsigned));
-            *reinterpret_cast<unsigned*>(&actions[actions.size() - sizeof(unsigned)]) = selectorLength;
-            bool wideCharacters = !selector.is8Bit();
-            actions.append(wideCharacters);
-            // Append Selector.
-            if (wideCharacters) {
-                for (unsigned i = 0; i < selectorLength; i++) {
-                    actions.resize(actions.size() + sizeof(UChar));
-                    *reinterpret_cast<UChar*>(&actions[actions.size() - sizeof(UChar)]) = selector[i];
-                }
-            } else {
-                for (unsigned i = 0; i < selectorLength; i++)
-                    actions.append(selector[i]);
-            }
-            break;
-        }
         }
     }
     return actionLocations;
@@ -117,7 +136,7 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, const
 
     Vector<NFA> nfas;
     nfas.append(NFA());
-    bool nonUniversalActionSeen = false;
+    bool ignorePreviousRulesSeen = false;
     for (unsigned ruleIndex = 0; ruleIndex < parsedRuleList.size(); ++ruleIndex) {
 
         // FIXME: Tune this better and adjust ContentExtensionTest.MultiDFA accordingly.
@@ -135,16 +154,18 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, const
         URLFilterParser::ParseStatus status = urlFilterParser.addPattern(trigger.urlFilter, trigger.urlFilterIsCaseSensitive, actionLocationAndFlags);
 
         if (status == URLFilterParser::MatchesEverything) {
-            if (nonUniversalActionSeen)
-                dataLogF("Trigger matching everything found not at beginning.  This may cause incorrect behavior with ignore-previous-rules");
+            if (ignorePreviousRulesSeen)
+                return ContentExtensionError::RegexMatchesEverythingAfterIgnorePreviousRules;
             universalActionLocations.append(actionLocationAndFlags);
-        } else
-            nonUniversalActionSeen = true;
+        }
         
         if (status != URLFilterParser::Ok && status != URLFilterParser::MatchesEverything) {
             dataLogF("Error while parsing %s: %s\n", trigger.urlFilter.utf8().data(), URLFilterParser::statusString(status).utf8().data());
-            continue;
+            return ContentExtensionError::JSONInvalidRegex;
         }
+        
+        if (contentExtensionRule.action().type() == ActionType::IgnorePreviousRules)
+            ignorePreviousRulesSeen = true;
     }
 
 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
@@ -172,6 +193,7 @@ std::error_code compileRuleList(ContentExtensionCompilationClient& client, const
         dfa.debugPrintDot();
 #endif
 
+        ASSERT_WITH_MESSAGE(!dfa.nodeAt(dfa.root()).actions.size(), "All actions on the DFA root should come from regular expressions that match everything.");
         if (!i) {
             // Put all the universal actions on the first DFA.
             for (uint64_t actionLocation : universalActionLocations)
index a31e7b2..b0b780a 100644 (file)
@@ -73,6 +73,10 @@ const std::error_category& contentExtensionErrorCategory()
                 return "Invalid action type.";
             case ContentExtensionError::JSONInvalidCSSDisplayNoneActionType:
                 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.";
             }
 
             return std::string();
index ede119d..b6c6489 100644 (file)
@@ -53,6 +53,9 @@ enum class ContentExtensionError {
     JSONInvalidAction,
     JSONInvalidActionType,
     JSONInvalidCSSDisplayNoneActionType,
+    JSONInvalidRegex,
+    
+    RegexMatchesEverythingAfterIgnorePreviousRules,
 };
 
 const std::error_category& contentExtensionErrorCategory();
index d6c6cc2..d482386 100644 (file)
@@ -45,6 +45,12 @@ struct Trigger {
     String urlFilter;
     bool urlFilterIsCaseSensitive { false };
     ResourceFlags flags { 0 };
+    bool operator==(const Trigger& other) const
+    {
+        return urlFilter == other.urlFilter
+            && urlFilterIsCaseSensitive == other.urlFilterIsCaseSensitive
+            && flags == other.flags;
+    }
 };
     
 struct Action {
index 470ba67..01ae272 100644 (file)
@@ -104,8 +104,17 @@ Vector<Action> ContentExtensionsBackend::actionsForResourceLoad(const ResourceLo
                 finalActions.append(action);
             }
         }
-        if (!sawIgnorePreviousRules)
+        if (!sawIgnorePreviousRules) {
+            DFABytecodeInterpreter::Actions universalActions = interpreter.actionsFromDFARoot();
+            for (auto actionLocation : universalActions) {
+                Action action = Action::deserialize(actions, actionsLength, actionLocation);
+                
+                // 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()));
+        }
     }
     return finalActions;
 }
index 6c2696e..a476be3 100644 (file)
@@ -47,6 +47,7 @@ DFABytecodeInterpreter::Actions DFABytecodeInterpreter::actionsFromDFARoot()
 
     // Skip first DFA header. All universal actions are in the first DFA root.
     unsigned programCounter = sizeof(unsigned);
+
     while (static_cast<DFABytecodeInstruction>(m_bytecode[programCounter]) == DFABytecodeInstruction::AppendAction) {
         universalActionLocations.add(static_cast<uint64_t>(getBits<unsigned>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecode))));
         programCounter += instructionSizeWithArguments(DFABytecodeInstruction::AppendAction);
@@ -69,10 +70,11 @@ DFABytecodeInterpreter::Actions DFABytecodeInterpreter::interpret(const CString&
         unsigned dfaBytecodeLength = getBits<unsigned>(m_bytecode, m_bytecodeLength, programCounter);
         programCounter += sizeof(unsigned);
 
-        // Skip the universal actions.
-        // FIXME: Replace AppendAction with AppendActions to make this just one jump and make sure there aren't universal actions with flags.
-        while (static_cast<DFABytecodeInstruction>(m_bytecode[programCounter]) == DFABytecodeInstruction::AppendAction)
+        // Skip the actions on the DFA root. These are accessed via actionsFromDFARoot.
+        while (static_cast<DFABytecodeInstruction>(m_bytecode[programCounter]) == DFABytecodeInstruction::AppendAction) {
+            ASSERT_WITH_MESSAGE(!dfaStart, "Triggers that match everything should only be in the first DFA.");
             programCounter += instructionSizeWithArguments(DFABytecodeInstruction::AppendAction);
+        }
         
         // Interpret the bytecode from this DFA.
         // This should always terminate if interpreting correctly compiled bytecode.
index bb3e2e2..64bc481 100644 (file)
@@ -1,3 +1,15 @@
+2015-03-30  Alex Christensen  <achristensen@webkit.org>
+
+        [Content Extensions] Correctly handle regular expressions matching everything
+        https://bugs.webkit.org/show_bug.cgi?id=143235
+
+        Reviewed by Benjamin Poulain.
+
+        * TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
+        (TestWebKitAPI::checkCompilerError):
+        (TestWebKitAPI::TEST_F):
+        Test ContentExtensionErrors.
+
 2015-03-30  Alexey Proskuryakov  <ap@apple.com>
 
         DumpRenderTree should set NSWindowDisplayWithRunLoopObserver
index de67d6b..197655d 100644 (file)
@@ -28,6 +28,7 @@
 #include "PlatformUtilities.h"
 #include <JavaScriptCore/InitializeThreading.h>
 #include <WebCore/ContentExtensionCompiler.h>
+#include <WebCore/ContentExtensionError.h>
 #include <WebCore/ContentExtensionsBackend.h>
 #include <WebCore/NFA.h>
 #include <WebCore/ResourceLoadInfo.h>
@@ -573,6 +574,53 @@ TEST_F(ContentExtensionTest, MultiDFA)
     testRequest(backend, mainDocumentRequest("http://webkit.org/"), { });
 }
 
+void checkCompilerError(const char* json, ContentExtensions::ContentExtensionError expectedError)
+{
+    WebCore::ContentExtensions::CompiledContentExtensionData extensionData;
+    InMemoryContentExtensionCompilationClient client(extensionData);
+    std::error_code compilerError = ContentExtensions::compileRuleList(client, json);
+    EXPECT_EQ(compilerError.value(), static_cast<int>(expectedError));
+}
+
+TEST_F(ContentExtensionTest, InvalidJSON)
+{
+    checkCompilerError("[", ContentExtensions::ContentExtensionError::JSONInvalid);
+    checkCompilerError("123", ContentExtensions::ContentExtensionError::JSONTopLevelStructureNotAnObject);
+    checkCompilerError("{}", ContentExtensions::ContentExtensionError::JSONTopLevelStructureNotAnArray);
+    // FIXME: Add unit test for JSONInvalidRule if that is possible to hit.
+    checkCompilerError("[]", ContentExtensions::ContentExtensionError::JSONContainsNoRules);
+
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":5}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidTrigger);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"\"}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidURLFilterInTrigger);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":{}}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidURLFilterInTrigger);
+
+    // FIXME: Add unit test for JSONInvalidObjectInTriggerFlagsArray if that is possible to hit.
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"load-type\":{}}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidTriggerFlagsArray);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"load-type\":[\"invalid\"]}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidStringInTriggerFlagsArray);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"resource-type\":{}}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidTriggerFlagsArray);
+    checkCompilerError("[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"webkit.org\",\"resource-type\":[\"invalid\"]}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidStringInTriggerFlagsArray);
+
+    checkCompilerError("[{\"action\":5,\"trigger\":{\"url-filter\":\"webkit.org\"}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidAction);
+    checkCompilerError("[{\"action\":{\"type\":\"invalid\"},\"trigger\":{\"url-filter\":\"webkit.org\"}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidActionType);
+    checkCompilerError("[{\"action\":{\"type\":\"css-display-none\"},\"trigger\":{\"url-filter\":\"webkit.org\"}}]",
+        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\":\"[\"}}]",
+        ContentExtensions::ContentExtensionError::JSONInvalidRegex);
+}
+
 static void testPatternStatus(String pattern, ContentExtensions::URLFilterParser::ParseStatus status)
 {
     ContentExtensions::NFA nfa;