Index: lib/matcher.js
===================================================================
--- a/lib/matcher.js
+++ b/lib/matcher.js
@@ -235,30 +235,50 @@
     {
       let map = this._filterMapsByType.get(type);
       if (!map)
         this._filterMapsByType.set(type, map = new Map());
 
       addFilterByKeyword(filter, keyword, map);
     }
 
+    let {domains} = filter;
+
     let filtersByDomain = this._filterDomainMapsByKeyword.get(keyword);
     if (!filtersByDomain)
-      this._filterDomainMapsByKeyword.set(keyword, filtersByDomain = new Map());
+    {
+      // In most cases, there is only one pure generic filter to a keyword.
+      // Instead of Map { "foo" => Map { "" => Map { filter => true } } }, we
+      // can just reduce it to Map { "foo" => filter } and save a lot of
+      // memory.
+      if (!domains)
+      {
+        this._filterDomainMapsByKeyword.set(keyword, filter);
+        return;
+      }
 
-    for (let [domain, include] of filter.domains || defaultDomains)
+      filtersByDomain = new Map();
+      this._filterDomainMapsByKeyword.set(keyword, filtersByDomain);
+    }
+    else if (!(filtersByDomain instanceof Map))
+    {
+      filtersByDomain = new Map([["", filtersByDomain]]);
+      this._filterDomainMapsByKeyword.set(keyword, filtersByDomain);
+    }
+
+    for (let [domain, include] of domains || defaultDomains)
     {
       if (!include && domain == "")
         continue;
 
       let map = filtersByDomain.get(domain);
       if (!map)
       {
         filtersByDomain.set(domain, include ? filter :
-                              map = new Map([[filter, false]]));
+                                      map = new Map([[filter, false]]));
       }
       else if (map.size == 1 && !(map instanceof Map))
       {
         if (filter != map)
         {
           filtersByDomain.set(domain, new Map([[map, true],
                                                [filter, include]]));
         }
@@ -296,35 +316,74 @@
       let map = this._filterMapsByType.get(type);
       if (map)
         removeFilterByKeyword(filter, keyword, map);
     }
 
     let filtersByDomain = this._filterDomainMapsByKeyword.get(keyword);
     if (filtersByDomain)
     {
+      // Because of the memory optimization in the add function, most of the
+      // time this will be a filter rather than a map.
+      if (!(filtersByDomain instanceof Map))
+      {
+        this._filterDomainMapsByKeyword.delete(keyword);
+        return;
+      }
+
       let domains = filter.domains || defaultDomains;
       for (let domain of domains.keys())
       {
         let map = filtersByDomain.get(domain);
         if (map)
         {
           if (map.size > 1 || map instanceof Map)
           {
             map.delete(filter);
 
             if (map.size == 0)
+            {
               filtersByDomain.delete(domain);
+            }
+            else if (map.size == 1)
+            {
+              for (let [lastFilter, include] of map)
+              {
+                // Reduce Map { "example.com" => Map { filter => true } } to
+                // Map { "example.com" => filter }
+                if (include)
+                  filtersByDomain.set(domain, lastFilter);
+
+                break;
+              }
+            }
           }
           else if (filter == map)
           {
             filtersByDomain.delete(domain);
           }
         }
       }
+
+      if (filtersByDomain.size == 0)
+      {
+        this._filterDomainMapsByKeyword.delete(keyword);
+      }
+      else if (filtersByDomain.size == 1)
+      {
+        for (let [lastDomain, map] of filtersByDomain)
+        {
+          // Reduce Map { "foo" => Map { "" => filter } } to
+          // Map { "foo" => filter }
+          if (lastDomain == "" && !(map instanceof Map))
+            this._filterDomainMapsByKeyword.set(keyword, map);
+
+          break;
+        }
+      }
     }
   }
 
   /**
    * Chooses a keyword to be associated with the filter
    * @param {Filter} filter
    * @returns {string} keyword or an empty string if no keyword could be found
    * @protected
@@ -419,18 +478,31 @@
   }
 
   _checkEntryMatchByDomain(keyword, location, typeMask, docDomain, thirdParty,
                            sitekey, specificOnly, collection)
   {
     let filtersByDomain = this._filterDomainMapsByKeyword.get(keyword);
     if (filtersByDomain)
     {
-      // The code in this block is similar to the generateStyleSheetForDomain
-      // function in lib/elemHide.js.
+      // Because of the memory optimization in the add function, most of the
+      // time this will be a filter rather than a map.
+      if (!(filtersByDomain instanceof Map))
+      {
+        if (filtersByDomain.matchesWithoutDomain(location, typeMask,
+                                                 thirdParty, sitekey))
+        {
+          if (!collection)
+            return filtersByDomain;
+
+          collection.push(filtersByDomain);
+        }
+
+        return null;
+      }
 
       if (docDomain)
       {
         if (docDomain[docDomain.length - 1] == ".")
           docDomain = docDomain.replace(/\.+$/, "");
 
         docDomain = docDomain.toLowerCase();
       }
Index: test/matcher.js
===================================================================
--- a/test/matcher.js
+++ b/test/matcher.js
@@ -348,25 +348,96 @@
 
   test.done();
 };
 
 exports.testAddRemoveByKeyword = function(test)
 {
   let matcher = new CombinedMatcher();
 
-  matcher.add(Filter.fromText("||example.com/foo/bar/image.jpg"));
+  matcher.add(Filter.fromText("||example.com/foo/bar/image.jpg^"));
 
   // Add the same filter a second time to make sure it doesn't get added again
   // by a different keyword.
-  matcher.add(Filter.fromText("||example.com/foo/bar/image.jpg"));
+  matcher.add(Filter.fromText("||example.com/foo/bar/image.jpg^"));
 
   test.ok(!!matcher.matchesAny("https://example.com/foo/bar/image.jpg",
                                RegExpFilter.typeMap.IMAGE));
 
-  matcher.remove(Filter.fromText("||example.com/foo/bar/image.jpg"));
+  matcher.remove(Filter.fromText("||example.com/foo/bar/image.jpg^"));
 
   // Make sure the filter got removed so there is no match.
   test.ok(!matcher.matchesAny("https://example.com/foo/bar/image.jpg",
                               RegExpFilter.typeMap.IMAGE));
 
+
+  // Map { "example" => { text: "||example.com^$~third-party" } }
+  matcher.add(Filter.fromText("||example.com^$~third-party"));
+
+  test.equal(matcher._blacklist._filterDomainMapsByKeyword.size, 1);
+
+  for (let [key, value] of matcher._blacklist._filterDomainMapsByKeyword)
+  {
+    test.equal(key, "example");
+    test.deepEqual(value, Filter.fromText("||example.com^$~third-party"));
+    break;
+  }
+
+  test.ok(!!matcher.matchesAny("https://example.com/example/image.jpg",
+                               RegExpFilter.typeMap.IMAGE, "example.com",
+                               false));
+
+  // Map {
+  //   "example" => Map {
+  //     "" => Map {
+  //       { text: "||example.com^$~third-party" } => true,
+  //       { text: "/example/*$~third-party" } => true
+  //     }
+  //   }
+  // }
+  matcher.add(Filter.fromText("/example/*$~third-party"));
+
+  test.equal(matcher._blacklist._filterDomainMapsByKeyword.size, 1);
+
+  for (let [key, value] of matcher._blacklist._filterDomainMapsByKeyword)
+  {
+    test.equal(key, "example");
+    test.equal(value.size, 1);
+
+    let map = value.get("");
+    test.equal(map.size, 2);
+    test.equal(map.get(Filter.fromText("||example.com^$~third-party")), true);
+    test.equal(map.get(Filter.fromText("/example/*$~third-party")), true);
+
+    break;
+  }
+
+  test.ok(!!matcher.matchesAny("https://example.com/example/image.jpg",
+                               RegExpFilter.typeMap.IMAGE, "example.com",
+                               false));
+
+  // Map { "example" => { text: "/example/*$~third-party" } }
+  matcher.remove(Filter.fromText("||example.com^$~third-party"));
+
+  test.equal(matcher._blacklist._filterDomainMapsByKeyword.size, 1);
+
+  for (let [key, value] of matcher._blacklist._filterDomainMapsByKeyword)
+  {
+    test.equal(key, "example");
+    test.deepEqual(value, Filter.fromText("/example/*$~third-party"));
+    break;
+  }
+
+  test.ok(!!matcher.matchesAny("https://example.com/example/image.jpg",
+                               RegExpFilter.typeMap.IMAGE, "example.com",
+                               false));
+
+  // Map {}
+  matcher.remove(Filter.fromText("/example/*$~third-party"));
+
+  test.equal(matcher._blacklist._filterDomainMapsByKeyword.size, 0);
+
+  test.ok(!matcher.matchesAny("https://example.com/example/image.jpg",
+                              RegExpFilter.typeMap.IMAGE, "example.com",
+                              false));
+
   test.done();
 };
