Index: ext/background.js
===================================================================
--- a/ext/background.js
+++ b/ext/background.js
@@ -10,84 +10,92 @@
  * 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/>.
  */
 
+/* global internal, defineNamespace */
+
 "use strict";
 
 {
   let nonEmptyPageMaps = new Set();
 
   let PageMap = ext.PageMap = function()
   {
-    this._map = new Map();
+    defineNamespace(this, internal);
+
+    this[internal].map = new Map();
   };
   PageMap.prototype = {
-    _delete(id)
-    {
-      this._map.delete(id);
-
-      if (this._map.size == 0)
-        nonEmptyPageMaps.delete(this);
-    },
     keys()
     {
-      return Array.from(this._map.keys()).map(ext.getPage);
+      return Array.from(this[internal].map.keys()).map(ext.getPage);
     },
     get(page)
     {
-      return this._map.get(page.id);
+      return this[internal].map.get(page.id);
     },
     set(page, value)
     {
-      this._map.set(page.id, value);
+      this[internal].map.set(page.id, value);
       nonEmptyPageMaps.add(this);
     },
     has(page)
     {
-      return this._map.has(page.id);
+      return this[internal].map.has(page.id);
     },
     clear()
     {
-      this._map.clear();
+      this[internal].map.clear();
       nonEmptyPageMaps.delete(this);
     },
     delete(page)
     {
-      this._delete(page.id);
+      deletePage(this, page.id);
     }
   };
 
-  ext._removeFromAllPageMaps = pageId =>
+  function deletePage(pageMap, pageId)
+  {
+    pageMap[internal].map.delete(pageId);
+
+    if (pageMap[internal].map.size == 0)
+      nonEmptyPageMaps.delete(pageMap);
+  }
+
+  ext[internal].removeFromAllPageMaps = pageId =>
   {
     for (let pageMap of nonEmptyPageMaps)
-      pageMap._delete(pageId);
+      deletePage(pageMap, pageId);
   };
 
   /* Pages */
 
   let Page = ext.Page = function(tab)
   {
+    defineNamespace(this, internal);
+
     this.id = tab.id;
-    this._url = tab.url && new URL(tab.url);
+
+    this[internal].url = tab.url && new URL(tab.url);
 
     this.browserAction = new BrowserAction(tab.id);
     this.contextMenus = new ContextMenus(this);
   };
   Page.prototype = {
     get url()
     {
       // usually our Page objects are created from Chrome's Tab objects, which
       // provide the url. So we can return the url given in the constructor.
-      if (this._url)
-        return this._url;
+      if (this[internal].url)
+        return this[internal].url;
 
       // but sometimes we only have the tab id when we create a Page object.
       // In that case we get the url from top frame of the tab, recorded by
       // the onBeforeRequest handler.
       let frames = framesOfTabs.get(this.id);
       if (frames)
       {
         let frame = frames.get(0);
@@ -115,25 +123,25 @@
           callback(new Page(openedTab));
         }
       };
       browser.tabs.onUpdated.addListener(onUpdated);
     };
   }
 
   ext.pages = {
-    onLoading: new ext._EventTarget(),
-    onActivated: new ext._EventTarget(),
-    onRemoved: new ext._EventTarget()
+    onLoading: new ext[internal].EventTarget(),
+    onActivated: new ext[internal].EventTarget(),
+    onRemoved: new ext[internal].EventTarget()
   };
 
   browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) =>
   {
     if (changeInfo.status == "loading")
-      ext.pages.onLoading._dispatch(new Page(tab));
+      ext[internal].dispatchEvent(ext.pages.onLoading, new Page(tab));
   });
 
   function createFrame(tabId, frameId)
   {
     let frames = framesOfTabs.get(tabId);
     if (!frames)
     {
       frames = new Map();
@@ -151,28 +159,28 @@
   }
 
   function updatePageFrameStructure(frameId, tabId, url, parentFrameId)
   {
     if (frameId == 0)
     {
       let page = new Page({id: tabId, url});
 
-      ext._removeFromAllPageMaps(tabId);
+      ext[internal].removeFromAllPageMaps(tabId);
 
       browser.tabs.get(tabId, () =>
       {
         // If the tab is prerendered, browser.tabs.get() sets
         // browser.runtime.lastError and we have to dispatch the onLoading
         // event, since the onUpdated event isn't dispatched for prerendered
         // tabs. However, we have to keep relying on the onUpdated event for
         // tabs that are already visible. Otherwise browser action changes get
         // overridden when Chrome automatically resets them on navigation.
         if (browser.runtime.lastError)
-          ext.pages.onLoading._dispatch(page);
+          ext[internal].dispatchEvent(ext.pages.onLoading, page);
       });
     }
 
     // Update frame URL and parent in frame structure
     let frame = createFrame(tabId, frameId);
     frame.url = new URL(url);
 
     let parentFrame = framesOfTabs.get(tabId).get(parentFrameId);
@@ -272,160 +280,172 @@
     {
       updatePageFrameStructure(details.frameId, details.tabId, url,
                                details.parentFrameId);
     }
   });
 
   function forgetTab(tabId)
   {
-    ext.pages.onRemoved._dispatch(tabId);
+    ext[internal].dispatchEvent(ext.pages.onRemoved, tabId);
 
-    ext._removeFromAllPageMaps(tabId);
+    ext[internal].removeFromAllPageMaps(tabId);
     framesOfTabs.delete(tabId);
   }
 
   browser.tabs.onReplaced.addListener((addedTabId, removedTabId) =>
   {
     forgetTab(removedTabId);
   });
 
   browser.tabs.onRemoved.addListener(forgetTab);
 
   browser.tabs.onActivated.addListener(details =>
   {
-    ext.pages.onActivated._dispatch(new Page({id: details.tabId}));
+    ext[internal].dispatchEvent(ext.pages.onActivated,
+                                new Page({id: details.tabId}));
   });
 
 
   /* Browser actions */
 
   let BrowserAction = function(tabId)
   {
-    this._tabId = tabId;
-    this._changes = null;
+    defineNamespace(this, internal);
+
+    this[internal].tabId = tabId;
+    this[internal].changes = null;
   };
   BrowserAction.prototype = {
-    _applyChanges()
-    {
-      if ("iconPath" in this._changes)
-      {
-        // Firefox for Android displays the browser action not as an icon but
-        // as a menu item. There is no icon, but such an option may be added in
-        // the future.
-        // https://bugzilla.mozilla.org/show_bug.cgi?id=1331746
-        if ("setIcon" in browser.browserAction)
-        {
-          let path = {
-            16: this._changes.iconPath.replace("$size", "16"),
-            19: this._changes.iconPath.replace("$size", "19"),
-            20: this._changes.iconPath.replace("$size", "20"),
-            32: this._changes.iconPath.replace("$size", "32"),
-            38: this._changes.iconPath.replace("$size", "38"),
-            40: this._changes.iconPath.replace("$size", "40")
-          };
-          try
-          {
-            browser.browserAction.setIcon({tabId: this._tabId, path});
-          }
-          catch (e)
-          {
-            // Edge throws if passed icon sizes different than 19,20,38,40px.
-            delete path[16];
-            delete path[32];
-            browser.browserAction.setIcon({tabId: this._tabId, path});
-          }
-        }
-      }
-
-      if ("badgeText" in this._changes)
-      {
-        // There is no badge on Firefox for Android; the browser action is
-        // simply a menu item.
-        if ("setBadgeText" in browser.browserAction)
-        {
-          browser.browserAction.setBadgeText({
-            tabId: this._tabId,
-            text: this._changes.badgeText
-          });
-        }
-      }
-
-      if ("badgeColor" in this._changes)
-      {
-        // There is no badge on Firefox for Android; the browser action is
-        // simply a menu item.
-        if ("setBadgeBackgroundColor" in browser.browserAction)
-        {
-          browser.browserAction.setBadgeBackgroundColor({
-            tabId: this._tabId,
-            color: this._changes.badgeColor
-          });
-        }
-      }
-
-      this._changes = null;
-    },
-    _queueChanges()
-    {
-      browser.tabs.get(this._tabId, () =>
-      {
-        // If the tab is prerendered, browser.tabs.get() sets
-        // browser.runtime.lastError and we have to delay our changes
-        // until the currently visible tab is replaced with the
-        // prerendered tab. Otherwise browser.browserAction.set* fails.
-        if (browser.runtime.lastError)
-        {
-          let onReplaced = (addedTabId, removedTabId) =>
-          {
-            if (addedTabId == this._tabId)
-            {
-              browser.tabs.onReplaced.removeListener(onReplaced);
-              this._applyChanges();
-            }
-          };
-          browser.tabs.onReplaced.addListener(onReplaced);
-        }
-        else
-        {
-          this._applyChanges();
-        }
-      });
-    },
-    _addChange(name, value)
-    {
-      if (!this._changes)
-      {
-        this._changes = {};
-        this._queueChanges();
-      }
-
-      this._changes[name] = value;
-    },
     setIcon(path)
     {
-      this._addChange("iconPath", path);
+      addBrowserActionChange(this, "iconPath", path);
     },
     setBadge(badge)
     {
       if (!badge)
       {
-        this._addChange("badgeText", "");
+        addBrowserActionChange(this, "badgeText", "");
       }
       else
       {
         if ("number" in badge)
-          this._addChange("badgeText", badge.number.toString());
+          addBrowserActionChange(this, "badgeText", badge.number.toString());
 
         if ("color" in badge)
-          this._addChange("badgeColor", badge.color);
+          addBrowserActionChange(this, "badgeColor", badge.color);
       }
     }
   };
 
+  function queueBrowserActionChanges(browserAction)
+  {
+    browser.tabs.get(browserAction[internal].tabId, () =>
+    {
+      // If the tab is prerendered, browser.tabs.get() sets
+      // browser.runtime.lastError and we have to delay our changes
+      // until the currently visible tab is replaced with the
+      // prerendered tab. Otherwise browser.browserAction.set* fails.
+      if (browser.runtime.lastError)
+      {
+        let onReplaced = (addedTabId, removedTabId) =>
+        {
+          if (addedTabId == browserAction[internal].tabId)
+          {
+            browser.tabs.onReplaced.removeListener(onReplaced);
+            applyBrowserActionChanges(browserAction);
+          }
+        };
+        browser.tabs.onReplaced.addListener(onReplaced);
+      }
+      else
+      {
+        applyBrowserActionChanges(browserAction);
+      }
+    });
+  }
+
+  function applyBrowserActionChanges(browserAction)
+  {
+    if ("iconPath" in browserAction[internal].changes)
+    {
+      // Firefox for Android displays the browser action not as an icon but
+      // as a menu item. There is no icon, but such an option may be added in
+      // the future.
+      // https://bugzilla.mozilla.org/show_bug.cgi?id=1331746
+      if ("setIcon" in browser.browserAction)
+      {
+        let path = {
+          16: browserAction[internal].changes.iconPath.replace("$size", "16"),
+          19: browserAction[internal].changes.iconPath.replace("$size", "19"),
+          20: browserAction[internal].changes.iconPath.replace("$size", "20"),
+          32: browserAction[internal].changes.iconPath.replace("$size", "32"),
+          38: browserAction[internal].changes.iconPath.replace("$size", "38"),
+          40: browserAction[internal].changes.iconPath.replace("$size", "40")
+        };
+        try
+        {
+          browser.browserAction.setIcon({
+            tabId: browserAction[internal].tabId,
+            path
+          });
+        }
+        catch (e)
+        {
+          // Edge throws if passed icon sizes different than 19,20,38,40px.
+          delete path[16];
+          delete path[32];
+          browser.browserAction.setIcon({
+            tabId: browserAction[internal].tabId,
+            path
+          });
+        }
+      }
+    }
+
+    if ("badgeText" in browserAction[internal].changes)
+    {
+      // There is no badge on Firefox for Android; the browser action is
+      // simply a menu item.
+      if ("setBadgeText" in browser.browserAction)
+      {
+        browser.browserAction.setBadgeText({
+          tabId: browserAction[internal].tabId,
+          text: browserAction[internal].changes.badgeText
+        });
+      }
+    }
+
+    if ("badgeColor" in browserAction[internal].changes)
+    {
+      // There is no badge on Firefox for Android; the browser action is
+      // simply a menu item.
+      if ("setBadgeBackgroundColor" in browser.browserAction)
+      {
+        browser.browserAction.setBadgeBackgroundColor({
+          tabId: browserAction[internal].tabId,
+          color: browserAction[internal].changes.badgeColor
+        });
+      }
+    }
+
+    browserAction[internal].changes = null;
+  }
+
+  function addBrowserActionChange(browserAction, name, value)
+  {
+    if (!browserAction[internal].changes)
+    {
+      browserAction[internal].changes = {};
+      queueBrowserActionChanges(browserAction);
+    }
+
+    browserAction[internal].changes[name] = value;
+  }
+
 
   /* Context menus */
 
   let contextMenuItems = new ext.PageMap();
   let contextMenuUpdating = false;
 
   let updateContextMenu = () =>
   {
@@ -462,31 +482,33 @@
           });
         });
       });
     });
   };
 
   let ContextMenus = function(page)
   {
-    this._page = page;
+    defineNamespace(this, internal);
+
+    this[internal].page = page;
   };
   ContextMenus.prototype = {
     create(item)
     {
-      let items = contextMenuItems.get(this._page);
+      let items = contextMenuItems.get(this[internal].page);
       if (!items)
-        contextMenuItems.set(this._page, items = []);
+        contextMenuItems.set(this[internal].page, items = []);
 
       items.push(item);
       updateContextMenu();
     },
     remove(item)
     {
-      let items = contextMenuItems.get(this._page);
+      let items = contextMenuItems.get(this[internal].page);
       if (items)
       {
         let index = items.indexOf(item);
         if (index != -1)
         {
           items.splice(index, 1);
           updateContextMenu();
         }
@@ -532,17 +554,17 @@
       browser.webRequest.handlerBehaviorChanged();
 
       handlerBehaviorChangedQuota--;
       setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000);
     }
   }
 
   ext.webRequest = {
-    onBeforeRequest: new ext._EventTarget(),
+    onBeforeRequest: new ext[internal].EventTarget(),
     handlerBehaviorChanged()
     {
       // Defer handlerBehaviorChanged() until navigation occurs.
       // There wouldn't be any visible effect when calling it earlier,
       // but it's an expensive operation and that way we avoid to call
       // it multiple times, if multiple filters are added/removed.
       let {onBeforeNavigate} = browser.webNavigation;
       if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange))
@@ -617,19 +639,21 @@
     let frame = null;
     let page = null;
     if (details.tabId != -1)
     {
       frame = ext.getFrame(details.tabId, frameId);
       page = new Page({id: details.tabId});
     }
 
-    if (ext.webRequest.onBeforeRequest._dispatch(
-        url, type, page, frame).includes(false))
+    if (ext[internal].dispatchEvent(ext.webRequest.onBeforeRequest,
+                                    url, type, page, frame).includes(false))
+    {
       return {cancel: true};
+    }
   }, {urls: ["<all_urls>"]}, ["blocking"]);
 
 
   /* Message passing */
 
   browser.runtime.onMessage.addListener((message, rawSender, sendResponse) =>
   {
     let sender = {};
@@ -655,17 +679,18 @@
           if (frame)
             return frame.parent || null;
 
           return frames.get(0) || null;
         }
       };
     }
 
-    return ext.onMessage._dispatch(
+    return ext[internal].dispatchEvent(
+      ext.onMessage,
       message, sender, sendResponse
     ).includes(true);
   });
 
 
   /* Storage */
 
   ext.storage = {
