Index: lib/matcher.js
===================================================================
--- a/lib/matcher.js
+++ b/lib/matcher.js
@@ -17,31 +17,41 @@
 
 "use strict";
 
 /**
  * @fileOverview Matcher class implementing matching addresses against
  *               a list of filters.
  */
 
-const {WhitelistFilter} = require("./filterClasses");
+const {RegExpFilter, WhitelistFilter} = require("./filterClasses");
 
 /**
  * Regular expression for matching a keyword in a filter.
  * @type {RegExp}
  */
 const keywordRegExp = /[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/;
 
 /**
  * Regular expression for matching all keywords in a filter.
  * @type {RegExp}
  */
 const allKeywordsRegExp = new RegExp(keywordRegExp, "g");
 
 /**
+ * Bitmask for "types" that are for exception rules only, like
+ * <code>$document</code>, <code>$elemhide</code>, and so on.
+ * @type {number}
+ */
+const WHITELIST_ONLY_TYPES = RegExpFilter.typeMap.DOCUMENT |
+                             RegExpFilter.typeMap.ELEMHIDE |
+                             RegExpFilter.typeMap.GENERICHIDE |
+                             RegExpFilter.typeMap.GENERICBLOCK;
+
+/**
  * Checks whether a particular filter is slow.
  * @param {RegExpFilter} filter
  * @returns {boolean}
  */
 function isSlowFilter(filter)
 {
   return !filter.pattern || !keywordRegExp.test(filter.pattern);
 }
@@ -315,34 +325,45 @@
   matchesAnyInternal(location, typeMask, docDomain, thirdParty, sitekey,
                      specificOnly)
   {
     let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
     if (candidates === null)
       candidates = [];
     candidates.push("");
 
+    let whitelistHit = null;
     let blacklistHit = null;
-    for (let i = 0, l = candidates.length; i < l; i++)
+
+    // If the type mask includes no types other than whitelist-only types, we
+    // can skip the blacklist.
+    if ((typeMask & ~WHITELIST_ONLY_TYPES) != 0)
     {
-      let substr = candidates[i];
-      let result = this.whitelist._checkEntryMatch(
-        substr, location, typeMask, docDomain, thirdParty, sitekey
-      );
-      if (result)
-        return result;
-      if (blacklistHit === null)
+      for (let i = 0, l = candidates.length; !blacklistHit && i < l; i++)
       {
-        blacklistHit = this.blacklist._checkEntryMatch(
-          substr, location, typeMask, docDomain, thirdParty, sitekey,
-          specificOnly
-        );
+        blacklistHit = this.blacklist._checkEntryMatch(candidates[i], location,
+                                                       typeMask, docDomain,
+                                                       thirdParty, sitekey,
+                                                       specificOnly);
       }
     }
-    return blacklistHit;
+
+    // If the type mask includes any whitelist-only types, we need to check the
+    // whitelist.
+    if (blacklistHit || (typeMask & WHITELIST_ONLY_TYPES) != 0)
+    {
+      for (let i = 0, l = candidates.length; !whitelistHit && i < l; i++)
+      {
+        whitelistHit = this.whitelist._checkEntryMatch(candidates[i], location,
+                                                       typeMask, docDomain,
+                                                       thirdParty, sitekey);
+      }
+    }
+
+    return whitelistHit || blacklistHit;
   }
 
   /**
    * @see Matcher#matchesAny
    * @inheritdoc
    */
   matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
   {
