Index: lib/common.js
===================================================================
--- a/lib/common.js
+++ b/lib/common.js
@@ -96,8 +96,27 @@
     }
   }
 
   selectors.push(selector.substring(start));
   return selectors;
 }
 
 exports.splitSelector = splitSelector;
+
+/**
+ * Yields subdomains of a domain
+ * @param {string} domain
+ * @yields {string}
+ */
+function* subdomains(domain)
+{
+  while (true)
+  {
+    let index = domain.indexOf(".");
+    if (index == -1)
+      break;
+
+    yield domain = domain.substring(index + 1);
+  }
+}
+
+exports.subdomains = subdomains;
Index: lib/elemHideEmulation.js
===================================================================
--- a/lib/elemHideEmulation.js
+++ b/lib/elemHideEmulation.js
@@ -17,64 +17,135 @@
 
 "use strict";
 
 /**
  * @fileOverview Element hiding emulation implementation.
  */
 
 const {ElemHide} = require("./elemHide");
-const {Filter} = require("./filterClasses");
+const {subdomains} = require("./common");
+
+/**
+ * Map of element hiding emulation filters by domain
+ * @type {Map.<string,Map.<ElemHideEmulationFilter,boolean>>}
+ */
+let filtersByDomain = new Map();
+
+/**
+ * Set containing known element hiding emulation filters
+ * @type {Set.<ElemHideEmulation>}
+ */
+let knownFilters = new Set();
 
-let filters = new Set();
+/**
+ * Yields the domains associated with a filter, along with whether the filter
+ * should be included on the domain and the set of filters associated with the
+ * domain
+ * @param {ElemHideEmulationFilter} filter
+ * @yields {Array.<string,boolean,?Set.<ElemHideEmulationFilter>>}
+ */
+function* filterDomains(filter)
+{
+  for (let [domain, include] of filter.domains || [])
+  {
+    if (domain != "")
+      yield [domain, include, filtersByDomain.get(domain)];
+  }
+}
+
+/**
+ * Yields the filters for a domain and its subdomains, along with whether the
+ * filter should be included on its corresponding domain
+ * @param {string} domain
+ * @yields {Array.<ElemHideEmulationFilter,boolean>}
+ */
+function* filtersForDomain(domain)
+{
+  yield* filtersByDomain.get(domain) || [];
+
+  for (let subdomain of subdomains(domain))
+    yield* filtersByDomain.get(subdomain) || [];
+}
 
 /**
  * Container for element hiding emulation filters
  * @class
  */
 let ElemHideEmulation = {
   /**
    * Removes all known filters
    */
   clear()
   {
-    filters.clear();
+    filtersByDomain.clear();
   },
 
   /**
    * Add a new element hiding emulation filter
    * @param {ElemHideEmulationFilter} filter
    */
   add(filter)
   {
-    filters.add(filter.text);
+    if (knownFilters.has(filter))
+      return;
+
+    for (let [domain, include, filters] of filterDomains(filter))
+    {
+      if (filters)
+        filters.set(filter, include);
+      else
+        filtersByDomain.set(domain, new Map([[filter, include]]));
+    }
+
+    knownFilters.add(filter);
   },
 
   /**
    * Removes an element hiding emulation filter
    * @param {ElemHideEmulationFilter} filter
    */
   remove(filter)
   {
-    filters.delete(filter.text);
+    if (!knownFilters.has(filter))
+      return;
+
+    for (let [domain, , filters] of filterDomains(filter))
+    {
+      if (filters)
+      {
+        filters.delete(filter);
+
+        if (filters.size == 0)
+          filtersByDomain.delete(domain);
+      }
+    }
+
+    knownFilters.delete(filter);
   },
 
   /**
    * Returns a list of all rules active on a particular domain
    * @param {string} domain
    * @return {ElemHideEmulationFilter[]}
    */
   getRulesForDomain(domain)
   {
     let result = [];
-    for (let text of filters.values())
+
+    let excludeSet = new Set();
+    for (let [filter, include] of filtersForDomain(domain.toUpperCase()))
     {
-      let filter = Filter.fromText(text);
-      if (filter.isActiveOnDomain(domain) &&
-          !ElemHide.getException(filter, domain))
+      if (!include)
+      {
+        excludeSet.add(filter);
+      }
+      else if ((excludeSet.size == 0 || !excludeSet.has(filter)) &&
+               !ElemHide.getException(filter, domain))
       {
         result.push(filter);
       }
     }
+
     return result;
   }
 };
 exports.ElemHideEmulation = ElemHideEmulation;
Index: test/filterListener.js
===================================================================
--- a/test/filterListener.js
+++ b/test/filterListener.js
@@ -27,17 +27,17 @@
 let SpecialSubscription = null;
 let ElemHideException = null;
 
 exports.setUp = function(callback)
 {
   sandboxedRequire = createSandbox({
     extraExports: {
       elemHide: ["knownFilters"],
-      elemHideEmulation: ["filters"]
+      elemHideEmulation: ["knownFilters"]
     }
   });
 
   // We need to require the filterListener module so that filter changes will be
   // noticed, even though we don't directly use the module here.
   sandboxedRequire("../lib/filterListener");
 
   (
@@ -86,17 +86,17 @@
     if (filter instanceof ElemHideException)
       result.elemhideexception.push(filter.text);
     else
       result.elemhide.push(filter.text);
   }
 
   let elemHideEmulation = sandboxedRequire("../lib/elemHideEmulation");
   result.elemhideemulation = [];
-  for (let filterText of elemHideEmulation.filters)
+  for (let {text: filterText} of elemHideEmulation.knownFilters)
     result.elemhideemulation.push(filterText);
 
   let types = ["blacklist", "whitelist", "elemhide", "elemhideexception",
                "elemhideemulation"];
   for (let type of types)
   {
     if (!(type in expected))
       expected[type] = [];
