Index: lib/elemHide.js
===================================================================
--- a/lib/elemHide.js
+++ b/lib/elemHide.js
@@ -51,16 +51,23 @@
  * This array caches the keys of filterBySelector table (selectors
  * which unconditionally apply on all domains). It will be null if the
  * cache needs to be rebuilt.
  * @type {?string[]}
  */
 let unconditionalSelectors = null;
 
 /**
+ * The default style sheet that applies on all domains. This is based on the
+ * value of <code>{@link unconditionalSelectors}</code>.
+ * @type {?string}
+ */
+let defaultStyleSheet = null;
+
+/**
  * Map to be used instead when a filter has a blank domains property.
  * @type {Map.<string,boolean>}
  * @const
  */
 let defaultDomains = new Map([["", true]]);
 
 /**
  * Set containing known element hiding filters
@@ -95,26 +102,94 @@
 function getUnconditionalSelectors()
 {
   if (!unconditionalSelectors)
     unconditionalSelectors = [...filterBySelector.keys()];
 
   return unconditionalSelectors;
 }
 
+/**
+ * Returns the list of selectors that apply on a given domain from the subset
+ * of filters that do not apply unconditionally on all domains.
+ *
+ * @param {string} domain The domain.
+ * @param {boolean} [specificOnly=false] Whether selectors from generic filters
+ *   should be included.
+ *
+ * @returns {Array.<string>} The list of selectors.
+ */
+function getConditionalSelectorsForDomain(domain, specificOnly = false)
+{
+  let selectors = [];
+
+  let excluded = new Set();
+  let currentDomain = domain ? domain.replace(/\.+$/, "").toLowerCase() : "";
+
+  // This code is a performance hot-spot, which is why we've made certain
+  // micro-optimisations. Please be careful before making changes.
+  while (true)
+  {
+    if (specificOnly && currentDomain == "")
+      break;
+
+    let filters = filtersByDomain.get(currentDomain);
+    if (filters)
+    {
+      for (let [filter, isIncluded] of filters)
+      {
+        if (!isIncluded)
+        {
+          excluded.add(filter);
+        }
+        else
+        {
+          let {selector} = filter;
+          if ((excluded.size == 0 || !excluded.has(filter)) &&
+              !ElemHideExceptions.getException(selector, domain))
+          {
+            selectors.push(selector);
+          }
+        }
+      }
+    }
+
+    if (currentDomain == "")
+      break;
+
+    let nextDot = currentDomain.indexOf(".");
+    currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
+  }
+
+  return selectors;
+}
+
+/**
+ * Returns the default style sheet that applies on all domains.
+ * @returns {string}
+ */
+function getDefaultStyleSheet()
+{
+  if (!defaultStyleSheet)
+    defaultStyleSheet = createStyleSheet(getUnconditionalSelectors());
+
+  return defaultStyleSheet;
+}
+
 ElemHideExceptions.on("added", ({selector}) =>
 {
   // If this is the first exception for a previously unconditionally applied
   // element hiding selector we need to take care to update the lookups.
   let unconditionalFilterForSelector = filterBySelector.get(selector);
   if (unconditionalFilterForSelector)
   {
     addToFiltersByDomain(unconditionalFilterForSelector);
     filterBySelector.delete(selector);
     unconditionalSelectors = null;
+    defaultStyleSheet = null;
   }
 });
 
 /**
  * Container for element hiding filters
  * @class
  */
 exports.ElemHide = {
@@ -122,16 +197,18 @@
    * Removes all known filters
    */
   clear()
   {
     for (let collection of [filtersByDomain, filterBySelector, knownFilters])
       collection.clear();
 
     unconditionalSelectors = null;
+    defaultStyleSheet = null;
+
     filterNotifier.emit("elemhideupdate");
   },
 
   /**
    * Add a new element hiding filter
    * @param {ElemHideFilter} filter
    */
   add(filter)
@@ -141,16 +218,17 @@
 
     let {selector} = filter;
 
     if (!(filter.domains || ElemHideExceptions.hasExceptions(selector)))
     {
       // The new filter's selector is unconditionally applied to all domains
       filterBySelector.set(selector, filter);
       unconditionalSelectors = null;
+      defaultStyleSheet = null;
     }
     else
     {
       // The new filter's selector only applies to some domains
       addToFiltersByDomain(filter);
     }
 
     knownFilters.add(filter);
@@ -168,16 +246,17 @@
 
     let {selector} = filter;
 
     // Unconditially applied element hiding filters
     if (filterBySelector.get(selector) == filter)
     {
       filterBySelector.delete(selector);
       unconditionalSelectors = null;
+      defaultStyleSheet = null;
     }
     // Conditionally applied element hiding filters
     else
     {
       let domains = filter.domains || defaultDomains;
       for (let domain of domains.keys())
       {
         let filters = filtersByDomain.get(domain);
@@ -191,68 +270,42 @@
       }
     }
 
     knownFilters.delete(filter);
     filterNotifier.emit("elemhideupdate");
   },
 
   /**
-   * Determines from the current filter list which selectors should be applied
-   * on a particular host name.
-   * @param {string} domain
-   * @param {boolean} [specificOnly] true if generic filters should not apply.
-   * @returns {string[]} List of selectors.
+   * @typedef {object} ElemHideStyleSheet
+   * @property {string} code CSS code.
+   * @property {Array.<string>} selectors List of selectors.
    */
-  getSelectorsForDomain(domain, specificOnly = false)
-  {
-    let selectors = [];
-
-    let excluded = new Set();
-    let currentDomain = domain ? domain.replace(/\.+$/, "").toLowerCase() : "";
-
-    // This code is a performance hot-spot, which is why we've made certain
-    // micro-optimisations. Please be careful before making changes.
-    while (true)
-    {
-      if (specificOnly && currentDomain == "")
-        break;
 
-      let filters = filtersByDomain.get(currentDomain);
-      if (filters)
-      {
-        for (let [filter, isIncluded] of filters)
-        {
-          if (!isIncluded)
-          {
-            excluded.add(filter);
-          }
-          else
-          {
-            let {selector} = filter;
-            if ((excluded.size == 0 || !excluded.has(filter)) &&
-                !ElemHideExceptions.getException(selector, domain))
-            {
-              selectors.push(selector);
-            }
-          }
-        }
-      }
-
-      if (currentDomain == "")
-        break;
-
-      let nextDot = currentDomain.indexOf(".");
-      currentDomain = nextDot == -1 ? "" : currentDomain.substr(nextDot + 1);
-    }
+  /**
+   * Generates a style sheet for a given domain based on the current set of
+   * filters.
+   *
+   * @param {string} domain The domain.
+   * @param {boolean} [specificOnly=false] Whether selectors from generic
+   *   filters should be included.
+   *
+   * @returns {ElemHideStyleSheet} An object containing the CSS code and the
+   *   list of selectors.
+   */
+  generateStyleSheetForDomain(domain, specificOnly = false)
+  {
+    let selectors = getConditionalSelectorsForDomain(domain, specificOnly);
+    let code = specificOnly ? createStyleSheet(selectors) :
+                 (getDefaultStyleSheet() + createStyleSheet(selectors));
 
     if (!specificOnly)
       selectors = getUnconditionalSelectors().concat(selectors);
 
-    return selectors;
+    return {code, selectors};
   }
 };
 
 /**
  * Splits a list of selectors into groups determined by the value of
  * <code>{@link selectorGroupSize}</code>.
  *
  * @param {Array.<string>} selectors
