Index: dependencies
===================================================================
--- a/dependencies
+++ b/dependencies
@@ -2,6 +2,6 @@
 _self = buildtools/ensure_dependencies.py
 buildtools = buildtools hg:cf407fe581d0 git:91d1e3a
 adblockplus = adblockplus hg:1e8c64115e5b git:4815245
-adblockpluscore = adblockpluscore hg:3b518ca1bfcc git:adb6d08
-adblockplusui = adblockplusui hg:49b08709f3d2 git:5989747
+adblockpluscore = adblockpluscore hg:a44326ccd660 git:b5eab6d
+adblockplusui = adblockplusui hg:9b91cdef871c git:e78579a
 adblockplustests = adblockplustests hg:95fa66491d7d git:b4902cd
Index: lib/devtools.js
===================================================================
--- a/lib/devtools.js
+++ b/lib/devtools.js
@@ -257,35 +257,8 @@
   }
 }
 
-function onFilterChange(action, arg)
+function updateFilters(filters, added)
 {
-  let added, filters;
-  switch (action)
-  {
-    case "filter.added":
-      added = true;
-      filters = [arg];
-      break;
-
-    case "filter.removed":
-      added = false;
-      filters = [arg];
-      break;
-
-    // When there haven't ever been any user filters before, the subscription is
-    // added, triggering a "subscription.added" instead of a "filter.added" event.
-    case "subscription.added":
-      if (arg instanceof SpecialSubscription)
-      {
-        added = true;
-        filters = arg.filters;
-        break;
-      }
-
-    default:
-      return;
-  }
-
   for (let tabId in panels)
   {
     let panel = panels[tabId];
@@ -344,6 +317,22 @@
   }
 }
 
+function onFilterAdded(filter)
+{
+  updateFilters([filter], true);
+}
+
+function onFilterRemoved(filter)
+{
+  updateFilters([filter], false);
+}
+
+function onSubscriptionAdded(subscription)
+{
+  if (subscription instanceof SpecialSubscription)
+    updateFilters(subscription.filters, true);
+}
+
 chrome.runtime.onConnect.addListener(port =>
 {
   let match = port.name.match(/^devtools-(\d+)$/);
@@ -365,7 +354,9 @@
   if (!hasPanels())
   {
     ext.pages.onLoading.addListener(onLoading);
-    FilterNotifier.addListener(onFilterChange);
+    FilterNotifier.on("filter.added", onFilterAdded);
+    FilterNotifier.on("filter.removed", onFilterRemoved);
+    FilterNotifier.on("subscription.added", onSubscriptionAdded);
   }
 
   port.onDisconnect.addListener(() =>
@@ -375,8 +366,10 @@
 
     if (!hasPanels())
     {
-      FilterNotifier.removeListener(onFilterChange);
       ext.pages.onLoading.removeListener(onLoading);
+      FilterNotifier.off("filter.added", onFilterAdded);
+      FilterNotifier.off("filter.removed", onFilterRemoved);
+      FilterNotifier.off("subscription.added", onSubscriptionAdded);
     }
   });
 
Index: lib/events.js
===================================================================
deleted file mode 100644
--- a/lib/events.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * This file is part of Adblock Plus <https://adblockplus.org/>,
- * Copyright (C) 2006-2016 Eyeo GmbH
- *
- * Adblock Plus is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * Adblock Plus is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-/** @module events */
-
-"use strict";
-
-/**
- * Registers and emits names events.
- *
- * @constructor
- */
-exports.EventEmitter = function()
-{
-  this._listeners = Object.create(null);
-};
-
-exports.EventEmitter.prototype = {
-  /**
-   * Adds a listener for the specified event name.
-   *
-   * @param {string}   name
-   * @param {function} listener
-   */
-  on: function(name, listener)
-  {
-    if (name in this._listeners)
-      this._listeners[name].push(listener);
-    else
-      this._listeners[name] = [listener];
-  },
-
-  /**
-   * Removes a listener for the specified event name.
-   *
-   * @param {string}   name
-   * @param {function} listener
-   */
-  off: function(name, listener)
-  {
-    let listeners = this._listeners[name];
-    if (listeners)
-    {
-      let idx = listeners.indexOf(listener);
-      if (idx != -1)
-        listeners.splice(idx, 1);
-    }
-  },
-
-  /**
-   * Calls all previously added listeners for the given event name.
-   *
-   * @param {string} name
-   * @param {...*}   [arg]
-   */
-  emit: function(name)
-  {
-    let listeners = this._listeners[name];
-    if (listeners)
-    {
-      let args = [];
-      for (let i = 1; i < arguments.length; i++)
-        args.push(arguments[i]);
-
-      let currentListeners = listeners.slice();
-      for (let listener of currentListeners)
-        listener.apply(null, args);
-    }
-  }
-};
Index: lib/filterComposer.js
===================================================================
--- a/lib/filterComposer.js
+++ b/lib/filterComposer.js
@@ -172,11 +172,7 @@
     page.contextMenus.create(contextMenuItem);
 }
 
-FilterNotifier.addListener((action, page, filter) =>
-{
-  if (action == "page.WhitelistingStateRevalidate")
-    updateContextMenu(page, filter);
-});
+FilterNotifier.on("page.WhitelistingStateRevalidate", updateContextMenu);
 
 Prefs.on("shouldShowBlockElementMenu", () =>
 {
Index: lib/icon.js
===================================================================
--- a/lib/icon.js
+++ b/lib/icon.js
@@ -72,14 +72,11 @@
   }
 }
 
-FilterNotifier.addListener((action, page, filter) =>
+FilterNotifier.on("page.WhitelistingStateRevalidate", (page, filter) =>
 {
-  if (action == "page.WhitelistingStateRevalidate")
-  {
-    whitelistedState.set(page, !!filter);
-    if (canUpdateIcon)
-      setIcon(page);
-  }
+  whitelistedState.set(page, !!filter);
+  if (canUpdateIcon)
+    setIcon(page);
 });
 
 function renderFrames(notificationType)
Index: lib/messaging.js
===================================================================
--- a/lib/messaging.js
+++ b/lib/messaging.js
@@ -37,29 +37,26 @@
   _onMessage: function(message, sender, sendResponse)
   {
     let async = false;
-    let callbacks = this._eventEmitter._listeners[message.type];
+    let callbacks = this._eventEmitter.listeners(message.type);
 
-    if (callbacks)
+    for (let callback of callbacks)
     {
-      for (let callback of callbacks)
+      let response = callback(message, sender);
+
+      if (response && typeof response.then == "function")
       {
-        let response = callback(message, sender);
-
-        if (response && typeof response.then == "function")
-        {
-          response.then(
-            sendResponse,
-            reason => {
-              console.error(reason);
-              sendResponse(undefined);
-            }
-          );
-          async = true;
-        }
-        else if (typeof response != "undefined")
-        {
-          sendResponse(response);
-        }
+        response.then(
+          sendResponse,
+          reason => {
+            console.error(reason);
+            sendResponse(undefined);
+          }
+        );
+        async = true;
+      }
+      else if (typeof response != "undefined")
+      {
+        sendResponse(response);
       }
     }
 
Index: lib/requestBlocker.js
===================================================================
--- a/lib/requestBlocker.js
+++ b/lib/requestBlocker.js
@@ -19,7 +19,8 @@
 
 "use strict";
 
-let {RegExpFilter, BlockingFilter} = require("filterClasses");
+let {Filter, RegExpFilter, BlockingFilter} = require("filterClasses");
+let {Subscription} = require("subscriptionClasses");
 let {defaultMatcher} = require("matcher");
 let {FilterNotifier} = require("filterNotifier");
 let {Prefs} = require("prefs");
@@ -39,7 +40,7 @@
                               specificOnly, filter)
 {
   if (filter)
-    FilterNotifier.triggerListeners("filter.hitCount", filter, 0, 0, page);
+    FilterNotifier.emit("filter.hitCount", filter, 0, 0, page);
 
   if (devtools)
     devtools.logRequest(
@@ -113,56 +114,43 @@
 });
 
 let ignoreFilterNotifications = false;
-FilterNotifier.addListener((action, arg) =>
+
+function onFilterChange(arg, isDisabledAction)
 {
   // Avoid triggering filters.behaviorChanged multiple times
   // when multiple filter hanges happen at the same time.
   if (ignoreFilterNotifications)
     return;
 
-  if (action != "load")
-  {
-    let parts = action.split(".");
-    let [category, event] = parts;
-    if (category == "subscription")
-    {
-      if (event != "added"  &&
-          event != "removed" &&
-          event != "updated" &&
-          event != "disabled")
-        return;
+  // Ignore disabled subscriptions and filters, unless they just got
+  // disabled, otherwise they have no effect on the handler behavior.
+  if (arg && arg.disabled && !isDisabledAction)
+    return;
 
-      // Ignore empty subscriptions. This includes subscriptions
-      // that have just been added, but not downloaded yet.
-      if (arg.filters.length == 0)
-        return;
-    }
-    else if (category == "filter")
-    {
-      if (event != "added" &&
-          event != "removed" &&
-          event != "disabled")
-        return;
+  // Ignore empty subscriptions. This includes subscriptions
+  // that have just been added, but not downloaded yet.
+  if (arg instanceof Subscription && arg.filters.length == 0)
+    return;
 
-      // Ignore all types of filters but request filters,
-      // only these have an effect on the handler behavior.
-      if (!(arg instanceof RegExpFilter))
-        return;
-    }
-    else
-      return;
-
-    // Ignore disabled subscriptions and filters, unless they just got
-    // disabled, otherwise they have no effect on the handler behavior.
-    if (arg.disabled && event != "disabled")
-      return;
-  }
+  // Ignore all types of filters but request filters,
+  // only these have an effect on the handler behavior.
+  if (arg instanceof Filter && !(arg instanceof RegExpFilter))
+    return;
 
   ignoreFilterNotifications = true;
   setTimeout(() =>
   {
     ignoreFilterNotifications = false;
     ext.webRequest.handlerBehaviorChanged();
-    FilterNotifier.triggerListeners("filter.behaviorChanged");
+    FilterNotifier.emit("filter.behaviorChanged");
   });
-});
+}
+
+FilterNotifier.on("subscription.added", onFilterChange)
+FilterNotifier.on("subscription.removed", onFilterChange);
+FilterNotifier.on("subscription.updated", onFilterChange);
+FilterNotifier.on("subscription.disabled", arg => onFilterChange(arg, true));
+FilterNotifier.on("filter.added", onFilterChange);
+FilterNotifier.on("filter.removed", onFilterChange);
+FilterNotifier.on("filter.disabled", arg => onFilterChange(arg, true));
+FilterNotifier.on("load", onFilterChange);
Index: lib/stats.js
===================================================================
--- a/lib/stats.js
+++ b/lib/stats.js
@@ -35,12 +35,9 @@
   return blockedPerPage.get(page) || 0;
 };
 
-FilterNotifier.addListener(function(action, item, newValue, oldValue, page)
+FilterNotifier.on("filter.hitCount", (filter, newValue, oldValue, page) =>
 {
-  if (action != "filter.hitCount" || !page)
-    return;
-
-  if (!(item instanceof BlockingFilter))
+  if (!(filter instanceof BlockingFilter) || !page)
     return;
 
   Prefs.blocked_total++;
@@ -58,7 +55,7 @@
   }
 });
 
-Prefs.on("show_statsinicon", function()
+Prefs.on("show_statsinicon", () =>
 {
   ext.pages.query({}, function(pages)
   {
Index: lib/subscriptionInit.js
===================================================================
--- a/lib/subscriptionInit.js
+++ b/lib/subscriptionInit.js
@@ -31,19 +31,6 @@
 let firstRun;
 let subscriptionsCallback = null;
 
-let filtersLoaded = new Promise(resolve =>
-{
-  function onFilterAction(action)
-  {
-    if (action == "load")
-    {
-      FilterNotifier.removeListener(onFilterAction);
-      resolve();
-    }
-  }
-  FilterNotifier.addListener(onFilterAction);
-});
-
 /**
  * If there aren't any filters, the default subscriptions are added.
  * However, if patterns.ini already did exist and/or any preference
@@ -172,9 +159,10 @@
   initNotifications();
 }
 
-Promise.all([filtersLoaded, Prefs.untilLoaded]).then(detectFirstRun)
-                                            .then(getSubscriptions)
-                                            .then(finishInitialization);
+Promise.all([FilterNotifier.once("load"),
+             Prefs.untilLoaded]).then(detectFirstRun)
+                                .then(getSubscriptions)
+                                .then(finishInitialization);
 
 /**
  * Indicates whether the default filter subscriptions have been added
Index: lib/whitelisting.js
===================================================================
--- a/lib/whitelisting.js
+++ b/lib/whitelisting.js
@@ -91,20 +91,19 @@
 
 function revalidateWhitelistingState(page)
 {
-  FilterNotifier.triggerListeners(
+  FilterNotifier.emit(
     "page.WhitelistingStateRevalidate",
     page, checkWhitelisted(page)
   );
 }
 
-FilterNotifier.addListener(action =>
+FilterNotifier.on("filter.behaviorChanged", () =>
 {
-  if (action == "filter.behaviorChanged")
-    ext.pages.query({}, pages =>
-    {
-      for (let page of pages)
-        revalidateWhitelistingState(page);
-    });
+  ext.pages.query({}, pages =>
+  {
+    for (let page of pages)
+      revalidateWhitelistingState(page);
+  });
 });
 
 ext.pages.onLoading.addListener(revalidateWhitelistingState);
Index: metadata.common
===================================================================
--- a/metadata.common
+++ b/metadata.common
@@ -70,7 +70,7 @@
 qunit/qunit.css = adblockplustests/chrome/content/qunit.css
 
 [convert_js]
-lib/adblockplus.js = lib/events.js
+lib/adblockplus.js = adblockpluscore/lib/events.js
   lib/prefs.js
   lib/utils.js
   lib/io.js
Index: options.js
===================================================================
--- a/options.js
+++ b/options.js
@@ -67,7 +67,17 @@
   $("#removeCustomFilter").click(removeSelectedFilters);
   $("#rawFiltersButton").click(toggleFiltersInRawFormat);
   $("#importRawFilters").click(importRawFiltersText);
-  FilterNotifier.addListener(onFilterChange);
+
+  FilterNotifier.on("load", reloadFilters);
+  FilterNotifier.on("subscription.title", onSubscriptionChange);
+  FilterNotifier.on("subscription.disabled", onSubscriptionChange);
+  FilterNotifier.on("subscription.homepage", onSubscriptionChange);
+  FilterNotifier.on("subscription.lastDownload", onSubscriptionChange);
+  FilterNotifier.on("subscription.downloadStatus", onSubscriptionChange);
+  FilterNotifier.on("subscription.added", onSubscriptionAdded);
+  FilterNotifier.on("subscription.removed", onSubscriptionRemoved);
+  FilterNotifier.on("filter.added", onFilterAdded);
+  FilterNotifier.on("filter.removed", onFilterRemoved);
 
   // Display jQuery UI elements
   $("#tabs").tabs();
@@ -175,7 +185,16 @@
 // Cleans up when the options window is closed
 function unloadOptions()
 {
-  FilterNotifier.removeListener(onFilterChange);
+  FilterNotifier.off("load", reloadFilters);
+  FilterNotifier.off("subscription.title", onSubscriptionChange);
+  FilterNotifier.off("subscription.disabled", onSubscriptionChange);
+  FilterNotifier.off("subscription.homepage", onSubscriptionChange);
+  FilterNotifier.off("subscription.lastDownload", onSubscriptionChange);
+  FilterNotifier.off("subscription.downloadStatus", onSubscriptionChange);
+  FilterNotifier.off("subscription.added", onSubscriptionAdded);
+  FilterNotifier.off("subscription.removed", onSubscriptionRemoved);
+  FilterNotifier.off("filter.added", onFilterAdded);
+  FilterNotifier.off("filter.removed", onFilterRemoved);
 }
 
 function initCheckbox(id, descriptor)
@@ -421,61 +440,59 @@
   }
 }
 
-function onFilterChange(action, item, param1, param2)
+function onSubscriptionChange(subscription)
 {
-  switch (action)
+  var element = findSubscriptionElement(subscription);
+  if (element)
+    updateSubscriptionInfo(element);
+}
+
+function onSubscriptionAdded(subscription)
+{
+  if (subscription instanceof SpecialSubscription)
   {
-    case "load":
-      reloadFilters();
-      break;
-    case "subscription.title":
-    case "subscription.disabled":
-    case "subscription.homepage":
-    case "subscription.lastDownload":
-    case "subscription.downloadStatus":
-      var element = findSubscriptionElement(item);
-      if (element)
-        updateSubscriptionInfo(element);
-      break;
-    case "subscription.added":
-      if (item instanceof SpecialSubscription)
-      {
-        for (var i = 0; i < item.filters.length; i++)
-          onFilterChange("filter.added", item.filters[i]);
-      }
-      else if (item.url == Prefs.subscriptions_exceptionsurl)
-        $("#acceptableAds").prop("checked", true);
-      else if (!findSubscriptionElement(item))
-        addSubscriptionEntry(item);
-      break;
-    case "subscription.removed":
-      if (item instanceof SpecialSubscription)
-      {
-        for (var i = 0; i < item.filters.length; i++)
-          onFilterChange("filter.removed", item.filters[i]);
-      }
-      else if (item.url == Prefs.subscriptions_exceptionsurl)
-        $("#acceptableAds").prop("checked", false);
-      else
-      {
-        var element = findSubscriptionElement(item);
-        if (element)
-          element.parentNode.removeChild(element);
-      }
-      break;
-    case "filter.added":
-      if (item instanceof WhitelistFilter && /^@@\|\|([^\/:]+)\^\$document$/.test(item.text))
-        appendToListBox("excludedDomainsBox", RegExp.$1);
-      else
-        appendToListBox("userFiltersBox", item.text);
-      break;
-    case "filter.removed":
-      if (item instanceof WhitelistFilter && /^@@\|\|([^\/:]+)\^\$document$/.test(item.text))
-        removeFromListBox("excludedDomainsBox", RegExp.$1);
-      else
-        removeFromListBox("userFiltersBox", item.text);
-      break;
+    for (var i = 0; i < subscription.filters.length; i++)
+      onFilterChange("filter.added", subscription.filters[i]);
   }
+  else if (subscription.url == Prefs.subscriptions_exceptionsurl)
+    $("#acceptableAds").prop("checked", true);
+  else if (!findSubscriptionElement(subscription))
+    addSubscriptionEntry(subscription);
+}
+
+function onSubscriptionRemoved(subscription)
+{
+  if (subscription instanceof SpecialSubscription)
+  {
+    for (var i = 0; i < subscription.filters.length; i++)
+      onFilterChange("filter.removed", subscription.filters[i]);
+  }
+  else if (subscription.url == Prefs.subscriptions_exceptionsurl)
+    $("#acceptableAds").prop("checked", false);
+  else
+  {
+    var element = findSubscriptionElement(subscription);
+    if (element)
+      element.parentNode.removeChild(element);
+  }
+}
+
+function onFilterAdded(filter)
+{
+  if (filter instanceof WhitelistFilter &&
+      /^@@\|\|([^\/:]+)\^\$document$/.test(filter.text))
+    appendToListBox("excludedDomainsBox", RegExp.$1);
+  else
+    appendToListBox("userFiltersBox", filter.text);
+}
+
+function onFilterRemoved(filter)
+{
+  if (filter instanceof WhitelistFilter &&
+      /^@@\|\|([^\/:]+)\^\$document$/.test(filter.text))
+    removeFromListBox("excludedDomainsBox", RegExp.$1);
+  else
+    removeFromListBox("userFiltersBox", filter.text);
 }
 
 // Populates a list box with a number of entries
Index: stats.js
===================================================================
--- a/stats.js
+++ b/stats.js
@@ -81,7 +81,7 @@
       currentPage = pages[0];
       updateStats();
 
-      FilterNotifier.addListener(onNotify);
+      FilterNotifier.on("filter.hitCount", updateStats);
 
       document.getElementById("stats-container").removeAttribute("hidden");
     });
@@ -89,13 +89,7 @@
   
   function onUnload()
   {
-    FilterNotifier.removeListener(onNotify);
-  }
-  
-  function onNotify(action, item)
-  {
-    if (action == "filter.hitCount")
-      updateStats();
+    FilterNotifier.off("filter.hitCount", updateStats);
   }
   
   function updateStats()
