[Pkg-mozext-commits] [requestpolicy] 139/280: introducing EnvironmentManager, fixing many bugs

David Prévot taffit at moszumanska.debian.org
Sat May 2 20:30:13 UTC 2015


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository requestpolicy.

commit 75aca763efa379a7a51d229246bf928ce79aa67a
Author: Martin Kimmerle <dev at 256k.de>
Date:   Thu Jan 15 16:41:17 2015 +0100

    introducing EnvironmentManager, fixing many bugs
    
    - The EnvironmentManager has been introduced. Documentation can
    be found in the Development Wiki.
    
    - Many bugs in respect of RP's shutdown (and startup) have been
    solved.
    
    - bootstrap.js now starts up the EnvironmentManager instead of
    ProcessEnvironment. This means that ProcessEnvironment is no
    longer responsible for essential tasks on startup/shutdown.
    
    - The Environment class now supports "Levels": Essential,
    Backend, Interface and UI. All startup and shutdown functions
    are assigned to one of those levels. For both the startup and
    the shutdown process the levels are processed in the right
    order.
    
    - Two new managers have been added, helping with startup and
    shutdown:
    - ManagerForMessageListeners
    - ManagerForEventListeners
    Both of them add/remove listeners on startup/shutdown; Every
    instance of those classes is bound to an environment.
---
 src/bootstrap.js                                  |  36 +--
 src/content/lib/content-policy.jsm                |  22 +-
 src/content/lib/environment.jsm                   | 282 +++++++++++++------
 src/content/lib/logger.jsm                        |   4 +-
 src/content/lib/manager-for-event-listeners.jsm   | 143 ++++++++++
 src/content/lib/manager-for-message-listeners.jsm | 159 +++++++++++
 src/content/lib/observer-manager.jsm              |  13 +-
 src/content/lib/policy-manager.jsm                |   5 +-
 src/content/lib/prefs.jsm                         |   8 +-
 src/content/lib/process-environment.jsm           | 147 ++--------
 src/content/lib/request-processor.compat.js       |  25 +-
 src/content/lib/request-processor.jsm             |  11 +-
 src/content/lib/request-processor.redirects.js    |   6 +
 src/content/lib/script-loader.jsm                 |  48 +++-
 src/content/lib/utils.jsm                         |   8 +-
 src/content/lib/utils/constants.jsm               |   1 +
 src/content/lib/utils/files.jsm                   |   4 +
 src/content/lib/utils/windows.jsm                 |   7 +-
 src/content/lib/utils/xul.jsm                     |   7 +-
 src/content/main/about-uri.jsm                    |  30 ++-
 src/content/main/environment-manager-child.js     | 116 ++++++++
 src/content/main/environment-manager-parent.js    | 117 ++++++++
 src/content/main/environment-manager.jsm          | 144 ++++++++++
 src/content/main/pref-manager.jsm                 |  12 +-
 src/content/main/requestpolicy-service.jsm        |  12 +-
 src/content/main/window-manager-toolbarbutton.js  |  17 +-
 src/content/main/window-manager.jsm               |  67 +++--
 src/content/settings/advancedprefs.js             |  58 ++--
 src/content/settings/basicprefs.js                |  32 ++-
 src/content/settings/common.js                    |   8 +-
 src/content/settings/defaultpolicy.js             |  20 +-
 src/content/settings/subscriptions.js             |   2 +-
 src/content/settings/yourpolicy.js                |   2 +-
 src/content/ui/frame.blocked-content.js           |  14 +-
 src/content/ui/frame.doc-manager.js               |  59 +++-
 src/content/ui/frame.dom-content-loaded.js        | 104 +++++--
 src/content/ui/frame.js                           | 240 ++++++++++++-----
 src/content/ui/overlay.js                         | 315 ++++++++++++----------
 src/content/ui/request-log.filtering.js           |   1 +
 src/content/ui/request-log.js                     |  27 +-
 40 files changed, 1680 insertions(+), 653 deletions(-)

diff --git a/src/bootstrap.js b/src/bootstrap.js
index d7f4978..f7d8533 100644
--- a/src/bootstrap.js
+++ b/src/bootstrap.js
@@ -25,34 +25,26 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
-const procEnvURI = "chrome://requestpolicy/content/lib/" +
-    "process-environment.jsm";
+const envManURI = "chrome://requestpolicy/content/main/" +
+    "environment-manager.jsm";
 
 /**
  * If any Exception gets into bootstrap.js, it will be a severe error.
  * The Logger can't be used, as it might not be available.
  */
-function logSevereError(msg, stack) {
-  dump("[RequestPolicy] [SEVERE] [ERROR] " + msg +
-       (stack ? ", stack was: " + stack : "") + "\n");
+function logSevereError(msg, e) {
+  dump("[RequestPolicy] [SEVERE] [ERROR] " + msg + " " + e +
+       (e.stack ? ", stack was: " + e.stack : "") + "\n");
+  Cu.reportError(e);
 }
 
 function startup(data, reason) {
-  // if the Browser Toolbox is open when enabling RP, stop here.
-  // uncomment to enable this functionality.
-  // see also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger
-  //debugger;
-
   try {
-    // Import the ProcessEnvironment and call its startup() function.
-    // Note: It is IMPORTANT that ProcessEnvironment is the FIRST module to be
-    //       imported! The reason is that many modules call
-    //       `ProcessEnvironment.enqueueStartupFunction()` at *load-time*, so
-    //       ProcessEnvironment has to be available.
-    Cu.import(procEnvURI);
-    ProcessEnvironment.startup(data, reason);
+    // Import the EnvironmentManager and call its startup() function.
+    Cu.import(envManURI);
+    EnvironmentManager.startup(data, reason);
   } catch(e) {
-    logSevereError("startup() failed! " + e, e.stack);
+    logSevereError("startup() failed!", e);
   }
 }
 
@@ -63,11 +55,11 @@ function shutdown(data, reason) {
 
   try {
     // shutdown, unset and unload.
-    ProcessEnvironment.shutdown(data, reason);
-    ProcessEnvironment = null;
-    Cu.unload(procEnvURI);
+    EnvironmentManager.shutdown(data, reason);
+    EnvironmentManager = null;
+    Cu.unload(envManURI);
   } catch(e) {
-    logSevereError("shutdown() failed! " + e, e.stack);
+    logSevereError("shutdown() failed!", e);
   }
 }
 
diff --git a/src/content/lib/content-policy.jsm b/src/content/lib/content-policy.jsm
index aec0c92..c1108a2 100644
--- a/src/content/lib/content-policy.jsm
+++ b/src/content/lib/content-policy.jsm
@@ -56,13 +56,13 @@ let PolicyImplementation = (function() {
   /**
    * Registers the content policy on startup.
    */
-  ProcessEnvironment.enqueueStartupFunction(function() {
+  function register() {
     Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
                       .registerFactory(self.classID, self.classDescription,
                                        self.contractID, self);
 
     let catMan = Utils.categoryManager;
-    for each (let category in xpcom_categories) {
+    for (let category of xpcom_categories) {
       catMan.addCategoryEntry(category, self.contractID, self.contractID, false,
           true);
     }
@@ -74,14 +74,22 @@ let PolicyImplementation = (function() {
           Cc['@mozilla.org/uriloader/external-helper-app-service;1']
           .getService(Ci.nsIMIMEService);
     }
-  });
+  }
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                        register);
 
 
-  ProcessEnvironment.pushShutdownFunction(function() {
+  function unregister() {
+    Logger.dump("shutting down PolicyImplementation...");
+
+    // Ensure that RP stops blocking requests even in case of an error in the
+    // shutdown process of PolicyImplementation.
+    self.shouldLoad = (() => C.CP_OK);
+
     let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
     let catMan = Utils.categoryManager;
 
-    for (let category in xpcom_categories) {
+    for (let category of xpcom_categories) {
       catMan.deleteCategoryEntry(category, self.contractID, false);
     }
 
@@ -89,7 +97,9 @@ let PolicyImplementation = (function() {
     Utils.runAsync(function() {
       registrar.unregisterFactory(self.classID, self);
     });
-  });
+  }
+  ProcessEnvironment.addShutdownFunction(Environment.LEVELS.INTERFACE,
+                                         unregister);
 
   //
   // nsISupports interface implementation
diff --git a/src/content/lib/environment.jsm b/src/content/lib/environment.jsm
index 530f552..28a6884 100644
--- a/src/content/lib/environment.jsm
+++ b/src/content/lib/environment.jsm
@@ -31,53 +31,69 @@ let globalScope = this;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(globalScope, "ScriptLoader",
-    "chrome://requestpolicy/content/lib/script-loader.jsm");
-XPCOMUtils.defineLazyGetter(globalScope, "ObserverManager", function() {
-  return ScriptLoader.importModule("lib/observer-manager").ObserverManager;
-});
+Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+//Cu.import("resource://gre/modules/devtools/Console.jsm");
+ScriptLoader.defineLazyModuleGetters({
+  "lib/manager-for-event-listeners": ["ManagerForEventListeners"],
+  "main/environment-manager": ["EnvironmentManager"],
+  "lib/observer-manager": ["ObserverManager"]
+}, globalScope);
 
 
 
+let ENV_STATES = {
+  "NOT_STARTED": 0,
+  "STARTING_UP": 1,
+  "STARTUP_DONE": 2,
+  "SHUTTING_DOWN": 3,
+  "SHUT_DOWN": 4
+};
+
+let LEVELS = {
+  // Essential functions do tasks that must be run first on startup and last
+  // on shutdown, that is they do tasks that are requirements for the Backend.
+  "ESSENTIAL": 1,
+  // Backend functions start up/shut down main parts of RequestPolicy, but
+  // they do not enable RequestPolicy at all.
+  "BACKEND": 2,
+  // Interface functions enable/disable external as well as internal interfaces,
+  // e.g. Event Listeners, Message Listeners, Observers, Factories.
+  "INTERFACE": 3,
+  // UI functions will enable/disable UI elements such as the menu.
+  "UI": 4
+};
+
+// a level can be entered, being processed, or finished being processed.
+let LEVEL_STATES = {
+  "NOT_ENTERED": 0,
+  "PROCESSING": 1,
+  "FINISHED_PROCESSING": 2
+};
+
+
 /**
  * The `Environment` class can take care of the "startup" (=initialization) and
  * "shutdown" of any environment.
  *
- * Example implementations for this class are:
- *   - "process" environments -- created e.g. by bootstrap.js
- *   - "window" environments -- used in frame scripts and on pref web pages
+ * To each `Environment` instance, `startup` and `shutdown` functions can be
+ * added. As soon as the Environment's startup() function is called, all of
+ * those functions will be called; same for shutdown().
  *
- * The Environment contains
- *   - one *queue* for startup functions
- *   - one *stack* for shutdown functions
- * Those functions will be called when startup() and shutdown(), respectively,
- * are called.
- *
- * ### Implementation of the Queue and the Stack:
- * The Queue is implemented in a FIFO [3] sense, the Stack in a LIFO [4] sense.
- *   => for the queue, Array.push() and Array.shift() are used.
- *   => for the stack, Array.push() and Array.pop() are used.
- *
- * ### Reason for queue <--> stack distinction:
- * The most important files will add their startup & shutdown functions
- * before the less important functions will do.
- *   => Startup: the important functions should be called first
- *   => Shutdown: the important functions should be called last
- *
- * ### more information on FIFO/LIFO and Queues/Stacks:
- *   [1] https://en.wikipedia.org/wiki/Queue_%28abstract_data_type%29
- *   [2] https://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29
- *   [3] https://en.wikipedia.org/wiki/FIFO
- *   [4] https://en.wikipedia.org/wiki/LIFO_%28computing%29
+ * Both startup and shutdown functions will have Levels assigned. The levels
+ * of the functions determine in which sequence they are called
  */
-function Environment() {
+function Environment(aName) {
   let self = this;
 
-  self.state = Environment.STATE_SHUT_DOWN;
+  // the Environment's name is only needed for debugging
+  self.name = aName || "anonymous";
+
+  self.envState = ENV_STATES.NOT_STARTED;
 
-  // The function queues
-  self.startupFnQueue = [];
-  self.shutdownFnStack = [];
+  self.startupLevels = generateLevelObjects();
+  self.shutdownLevels = generateLevelObjects();
+
+  EnvironmentManager.registerEnvironment(self);
 
   // Define a Lazy Getter to get an ObserverManager for this Environment.
   // Using that Getter is more convenient than doing it manually, as the
@@ -85,83 +101,197 @@ function Environment() {
   XPCOMUtils.defineLazyGetter(self, "obMan", function() {
     return new ObserverManager(self);
   });
+
+  // Define a Lazy Getter to get an instance of `ManagerForEventListeners` for
+  // this Environment.
+  XPCOMUtils.defineLazyGetter(self, "elManager", function() {
+    return new ManagerForEventListeners(self);
+  });
+
+  // generate an unique ID for debugging purposes
+  XPCOMUtils.defineLazyGetter(self, "uid", function() {
+    return Math.random().toString(36).substr(2, 5);
+  });
+
+  //console.debug('[RPC] created new Environment "'+self.name+'"');
 }
 
-Environment.STATE_SHUT_DOWN = 0;
-Environment.STATE_STARTING_UP = 1;
-Environment.STATE_STARTUP_DONE = 2;
-Environment.STATE_SHUTTING_DOWN = 3;
+Environment.ENV_STATES = ENV_STATES;
+Environment.LEVELS = LEVELS;
+
+
+/**
+ * This function creates one "Level Object" for each level. Those objects
+ * mainly will hold the startup- or shutdown-functions of the corresponding
+ * level. All of the Level Objects are put together in another object which
+ * is then returned.
+ */
+function generateLevelObjects() {
+  let obj = {};
+  for (let levelName in Environment.LEVELS) {
+    let level = Environment.LEVELS[levelName];
+    obj[level] = {"functions": [], "levelState": LEVEL_STATES.NOT_ENTERED};
+  }
+  return obj;
+}
+
+Environment.startupSequence = [
+  LEVELS.ESSENTIAL,
+  LEVELS.BACKEND,
+  LEVELS.INTERFACE,
+  LEVELS.UI
+];
+Environment.shutdownSequence = [
+  LEVELS.UI,
+  LEVELS.INTERFACE,
+  LEVELS.BACKEND,
+  LEVELS.ESSENTIAL
+];
 
 
 /**
  * This set of functions can be used for adding startup/shutdown functions.
  */
-Environment.prototype.enqueueStartupFunction = function(f) {
+Environment.prototype.addStartupFunction = function(aLevel, f) {
   let self = this;
-  switch (self.state) {
-    case Environment.STATE_SHUTTING_DOWN:
-      // When the shutdown is currently in progress we simply ignore the
-      // startup() function, as it makes no sense.
-      break;
-
-    case Environment.STATE_STARTUP_DONE:
-      // If the environment already finished starting up, the function is added
-      // to the stack and the stack is processed immediately.
-      // Note: Calling `callFunctions` is on purpose, as the function `f` might
-      //       add more startup functions as well!
-      self.startupFnQueue.push(f);
-      callFunctions(self.startupFnQueue, arguments);
-      break;
-
-    default:
-      // In any other case, add the function to the stack.
-      self.startupFnQueue.push(f);
-      break;
+  if (self.envState >= ENV_STATES.SHUTTING_DOWN) {
+    // the environment is shutting down or already shut down.
+    return;
+  }
+  if (self.startupLevels[aLevel].levelState >= LEVEL_STATES.PROCESSING) {
+    // Either the startup functions of the same level as `aLevel` have
+    //        already been processed
+    //    OR  they are currently being processed.
+    //
+    // ==> call the function immediately.
+    f();
+  } else {
+    // the startup process did not reach the function's level yet.
+    //
+    // ==> remember the function.
+    self.startupLevels[aLevel].functions.push(f);
   }
 };
-Environment.prototype.pushShutdownFunction = function(f) {
+
+Environment.prototype.addShutdownFunction = function(aLevel, f) {
   let self = this;
-  self.shutdownFnStack.push(f);
+  if (self.shutdownLevels[aLevel].levelState >= LEVEL_STATES.PROCESSING) {
+    // Either the shutdown functions of the same level as `aLevel` have
+    //        already been processed
+    //    OR  they are currently being processed.
+    //
+    // ==> call the function immediately.
+    f();
+    //console.debug('[RPC] calling shutdown function immediately: "' +
+    //              (f.name || "anonymous") + '" (' + self.name + ')');
+  } else {
+    // The opposite, i.e. the startup process did not reach the function's
+    // level yet.
+    //
+    // ==> remember the function.
+    self.shutdownLevels[aLevel].functions.push(f);
+  }
 };
 
 
+
 /**
  * This function calls all functions of a function queue.
  */
-function callFunctions(fnArray, sequence, fnArgsToApply) {
-  // `sequence` decides whether LIFO or FIFO is used
-  let popOrShift = sequence === "lifo" ? "pop" : "shift";
-
+function callFunctions(fnArray, fnArgsToApply) {
   // process the Array as long as it contains elements
   while (fnArray.length > 0) {
     // The following is either `fnArray.pop()` or `fnArray.shift()`
     // depending on `sequence`.
-    let f = fnArray[popOrShift]();
+    let f = fnArray.pop();
 
     // call the function
-    f.apply(this, fnArgsToApply);
+    f.apply(null, fnArgsToApply);
+  }
+};
+
+function processLevel(aLevelObj, fnArgsToApply) {
+  aLevelObj.levelState = LEVEL_STATES.PROCESSING;
+  callFunctions(aLevelObj.functions, fnArgsToApply);
+  aLevelObj.levelState = LEVEL_STATES.FINISHED_PROCESSING;
+}
+
+// not used for now
+//Environment.prototype.callStartupFunctions = function(aLevel, fnArgsToApply) {
+//  let self = this;
+//  processLevel(self.startupLevels[aLevel], fnArgsToApply);
+//};
+
+Environment.prototype.callShutdownFunctions = function(aLevel, fnArgsToApply) {
+  let self = this;
+  processLevel(self.shutdownLevels[aLevel], fnArgsToApply);
+};
+
+
+Environment.processSequence = function(aSequence, aCallback) {
+  for (let i = 0, len = aSequence.length; i < len; ++i) {
+    // Note: It's necessary to use for(;;) instead of  for(..of..)
+    //       because the order/sequence must be exactly the same as in the
+    //       array.  for(..of..) doesn't guarantee that the elements are
+    //       called in order.
+
+    let level = aSequence[i];
+    aCallback(level);
   }
 };
 
 
+
+
 Environment.prototype.startup = function() {
   let self = this;
-  self.state = Environment.STATE_STARTING_UP;
-  callFunctions(self.startupFnQueue, arguments);
-  self.state = Environment.STATE_STARTUP_DONE;
+  let fnArgsToApply = arguments;
+
+  if (self.envState == ENV_STATES.NOT_STARTED) {
+    //console.log('[RPC] starting up Environment "'+self.name+'"...');
+    self.envState = ENV_STATES.STARTING_UP;
+
+    Environment.processSequence(
+        Environment.startupSequence,
+        function (level) {
+          let levelObj = self.startupLevels[level];
+          processLevel(levelObj, fnArgsToApply);
+        });
+
+    self.envState = ENV_STATES.STARTUP_DONE;
+  }
 };
 
 Environment.prototype.shutdown = function() {
   let self = this;
-  self.state = Environment.STATE_SHUTTING_DOWN;
-  callFunctions(self.shutdownFnStack, arguments);
-  self.state = Environment.STATE_SHUT_DOWN;
-};
+  let fnArgsToApply = arguments;
+
+  if (self.envState === ENV_STATES.STARTUP_DONE) {
+    //console.log('[RPC] shutting down Environment "'+self.name+'"...');
+    self.envState = ENV_STATES.SHUTTING_DOWN;
 
+    EnvironmentManager.unregisterEnvironment(self);
 
-Environment.prototype.shutdownOnWindowUnload = function(aWindow) {
+    Environment.processSequence(
+        Environment.shutdownSequence,
+        function (level) {
+          let levelObj = self.shutdownLevels[level];
+          processLevel(levelObj, fnArgsToApply);
+        });
+
+    self.envState = ENV_STATES.SHUT_DOWN;
+  }
+};
+
+/**
+ * Helper function: shuts down the environment when an EventTarget's "unload"
+ * event occurres.
+ */
+Environment.prototype.shutdownOnUnload = function(aEventTarget) {
   let self = this;
-  aWindow.addEventListener("unload", function() {
+  self.elManager.addListener(aEventTarget, "unload", function() {
+    //console.log("[RPC] an EventTarget's `unload` function has been called. " +
+    //            'Going to shut down Environment "'+self.name+'"');
     self.shutdown();
   });
 };
diff --git a/src/content/lib/logger.jsm b/src/content/lib/logger.jsm
index d66cd3e..28d47c5 100644
--- a/src/content/lib/logger.jsm
+++ b/src/content/lib/logger.jsm
@@ -28,6 +28,7 @@ const Cu = Components.utils;
 let EXPORTED_SYMBOLS = ["Logger"];
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/devtools/Console.jsm");
 
 Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
 ScriptLoader.importModules([
@@ -121,7 +122,7 @@ let Logger = (function() {
     initialized = true;
   }
 
-  ProcessEnvironment.enqueueStartupFunction(init);
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.ESSENTIAL, init);
 
 
 
@@ -147,6 +148,7 @@ let Logger = (function() {
       if (aError) {
         // if an error was provided, report it to the browser console
         Cu.reportError(aError);
+        console.trace();
       }
 
       // TODO: remove the following after finishing e10s
diff --git a/src/content/lib/manager-for-event-listeners.jsm b/src/content/lib/manager-for-event-listeners.jsm
new file mode 100644
index 0000000..a0bb897
--- /dev/null
+++ b/src/content/lib/manager-for-event-listeners.jsm
@@ -0,0 +1,143 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008-2012 Justin Samuel
+ * Copyright (c) 2014-2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+let EXPORTED_SYMBOLS = ["ManagerForEventListeners"];
+
+let globalScope = this;
+
+Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ScriptLoader.importModules([
+  "main/environment-manager",
+  "lib/environment",
+  "lib/logger"
+], globalScope);
+
+
+/**
+ * This class provides an interface to multiple "Event Targets" which takes
+ * care of adding/removing event listeners at startup/shutdown.
+ * Every instance of this class is bound to an environment.
+ */
+function ManagerForEventListeners(aEnv) {
+  let self = this;
+
+  /**
+   * an object holding all listeners for removing them when unloading the page
+   */
+  self.listeners = [];
+
+  /**
+   * This variable tells if the listener handed over to `addListener` should
+   * be added immediately or not. It is set to true when the startup function
+   * is called.
+   */
+  self.addNewListenersImmediately = false;
+
+  self.environment = aEnv;
+
+  // Note: the startup functions have to be defined *last*, as they might get
+  //       called immediately.
+  if (!!aEnv) {
+    self.environment.addStartupFunction(
+        Environment.LEVELS.INTERFACE,
+        function() {
+          self.addNewListenersImmediately = true;
+          self.addAllListeners();
+        });
+    self.environment.addShutdownFunction(
+        Environment.LEVELS.INTERFACE,
+        function() {
+          // clean up when the environment shuts down
+          self.removeAllListeners();
+        });
+  } else {
+    // aEnv is not defined! Try to report an error.
+    if (!!Logger) {
+      Logger.warning(Logger.TYPE_INTERNAL, "No Environment was specified for " +
+                     "a new ManagerForEventListeners! This means that the " +
+                     "listeners won't be removed!");
+    }
+  }
+}
+
+
+function addEvLis(listener) {
+  listener.target.addEventListener(listener.eventType, listener.callback,
+                                   listener.useCapture);
+  listener.listening = true;
+};
+
+
+ManagerForEventListeners.prototype.addListener = function(aEventTarget,
+                                                          aEventType,
+                                                          aCallback,
+                                                          aUseCapture) {
+  let self = this;
+  if (typeof aCallback !== 'function') {
+    Logger.warning(Logger.TYPE_ERROR, "The callback for an event listener" +
+                   'must be a function! Event type was "' + aEventType + '"');
+    return;
+  }
+  let listener = {
+    target: aEventTarget,
+    eventType: aEventType,
+    callback: aCallback,
+    useCapture: !!aUseCapture,
+    listening: false
+  };
+  if (self.addNewListenersImmediately) {
+    addEvLis(listener);
+  }
+  self.listeners.push(listener);
+};
+
+
+
+/**
+ * The function will add all listeners already in the list.
+ */
+ManagerForEventListeners.prototype.addAllListeners = function() {
+  let self = this;
+  for (let listener of self.listeners) {
+    if (listener.listening === false) {
+      addEvLis(listener);
+    }
+  }
+};
+
+/**
+ * The function will remove all listeners.
+ */
+ManagerForEventListeners.prototype.removeAllListeners = function() {
+  let self = this;
+  while (self.listeners.length > 0) {
+    let listener = self.listeners.pop();
+    Logger.dump('Removing event listener for "' + listener.eventType + '"');
+    listener.target.removeEventListener(listener.eventType, listener.callback,
+                                        listener.useCapture);
+  }
+};
diff --git a/src/content/lib/manager-for-message-listeners.jsm b/src/content/lib/manager-for-message-listeners.jsm
new file mode 100644
index 0000000..46ae22b
--- /dev/null
+++ b/src/content/lib/manager-for-message-listeners.jsm
@@ -0,0 +1,159 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008-2012 Justin Samuel
+ * Copyright (c) 2014-2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+let EXPORTED_SYMBOLS = ["ManagerForMessageListeners"];
+
+let globalScope = this;
+
+Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ScriptLoader.importModules([
+  "main/environment-manager",
+  "lib/environment",
+  "lib/logger",
+  "lib/utils/constants"
+], globalScope);
+
+
+/**
+ * This class provides an interface to a "Message Manager" which takes
+ * care of adding/removing message listeners at startup/shutdown.
+ * Every instance of this class is bound to an environment and a MessageManager.
+ */
+function ManagerForMessageListeners(aEnv, aMM) {
+  let self = this;
+
+  /**
+   * an object holding all listeners for removing them when unloading the page
+   */
+  self.listeners = [];
+
+  /**
+   * This variable tells if the listener handed over to `addListener` should
+   * be added immediately or not. It is set to true when the startup function
+   * is called.
+   */
+  self.addNewListenersImmediately = false;
+
+  self.environment = aEnv;
+
+  // Note: the startup functions have to be defined *last*, as they might get
+  //       called immediately.
+  if (!!aEnv) {
+    self.environment.addStartupFunction(
+        Environment.LEVELS.INTERFACE,
+        function() {
+          self.addNewListenersImmediately = true;
+          self.addAllListeners();
+        });
+    self.environment.addShutdownFunction(
+        Environment.LEVELS.INTERFACE,
+        function() {
+          // clean up when the environment shuts down
+          self.removeAllListeners();
+        });
+  } else {
+    // aEnv is not defined! Try to report an error.
+    if (!!Logger) {
+      Logger.warning(Logger.TYPE_INTERNAL, "No Environment was specified for " +
+                     "a new ManagerForMessageListeners! This means that the listeners " +
+                     "won't be unregistered!");
+    }
+  }
+
+  self.mm = aMM;
+
+  if (!self.mm) {
+    if (!!Logger) {
+      Logger.warning(Logger.TYPE_INTERNAL, "No Message Manager was specified " +
+                     "for a new ManagerForMessageListeners!");
+    }
+  }
+}
+
+
+ManagerForMessageListeners.prototype.addListener = function(aMessageName,
+                                                            aCallback) {
+  let self = this;
+  if (typeof aCallback !== 'function') {
+    Logger.warning(Logger.TYPE_ERROR, "The callback for a message listener" +
+                   'must be a function! The message name was "' + aMessageName +
+                   '"');
+    return;
+  }
+  let listener = {
+    messageName: aMessageName,
+    messageID: C.MM_PREFIX + aMessageName,
+    callback: aCallback,
+    listening: false
+  };
+  if (self.addNewListenersImmediately) {
+    self.mm.addMessageListener(listener.messageID, listener.callback);
+    listener.listening = true;
+  }
+  self.listeners.push(listener);
+};
+
+
+
+/**
+ * The function will add all listeners already in the list.
+ */
+ManagerForMessageListeners.prototype.addAllListeners = function() {
+  let self = this;
+  for (let listener of self.listeners) {
+    if (listener.listening === false) {
+      self.mm.addMessageListener(listener.messageID, listener.callback);
+      listener.listening = true;
+    }
+  }
+};
+
+/**
+ * The function will remove all listeners.
+ */
+ManagerForMessageListeners.prototype.removeAllListeners = function() {
+  let self = this;
+  while (self.listeners.length > 0) {
+    let listener = self.listeners.pop();
+    //if (typeof listener.callback == 'undefined') {
+    //  Logger.warning(Logger.TYPE_ERROR, "Can't remove message listener '" +
+    //                 'for "' + listener.messageName + '", the callback ' +
+    //                 'is undefined!');
+    //  continue;
+    //}
+    Logger.dump('Removing message listener for "' + listener.messageName +
+                '".');
+    //try {
+    self.mm.removeMessageListener(listener.messageID, listener.callback);
+    //} catch (e) {
+    //  Logger.warning(Logger.TYPE_ERROR, 'Failed to remove message listener ' +
+    //                 'for "' + listener.messageName + '". ' +
+    //                 'Env "' + self.environment.uid + '" (' +
+    //                 self.environment.name + '). Error was: ' + e, e);
+    //}
+  }
+};
diff --git a/src/content/lib/observer-manager.jsm b/src/content/lib/observer-manager.jsm
index 7029aff..1f5c7ce 100644
--- a/src/content/lib/observer-manager.jsm
+++ b/src/content/lib/observer-manager.jsm
@@ -44,7 +44,7 @@ ScriptLoader.defineLazyModuleGetters({
 // Load the Logger at startup-time, not at load-time!
 // ( On load-time Logger might be null. )
 let Logger;
-ProcessEnvironment.enqueueStartupFunction(function() {
+ProcessEnvironment.addStartupFunction(Environment.LEVELS.BACKEND, function() {
   Logger = ScriptLoader.importModule("lib/logger").Logger;
 });
 
@@ -62,10 +62,12 @@ function ObserverManager(aEnv) {
   self.environment = aEnv;
 
   if (!!aEnv) {
-    self.environment.pushShutdownFunction(function() {
-      // unregister when the environment shuts down
-      self.unregisterAllObservers();
-    });
+    self.environment.addShutdownFunction(
+        Environment.LEVELS.INTERFACE,
+        function() {
+          // unregister when the environment shuts down
+          self.unregisterAllObservers();
+        });
   } else {
     // aEnv is not defined! Try to report an error.
     if (!!Logger) {
@@ -185,7 +187,6 @@ ObserverManager.prototype.unregisterAllObservers = function() {
   let self = this;
   while (self.observers.length > 0) {
     let observer = self.observers.pop();
-    Logger.dump("Unregistering observer for topic " + observer.topic);
     observer.unregister();
   }
 };
diff --git a/src/content/lib/policy-manager.jsm b/src/content/lib/policy-manager.jsm
index 0035a41..3dcf0d3 100644
--- a/src/content/lib/policy-manager.jsm
+++ b/src/content/lib/policy-manager.jsm
@@ -348,5 +348,6 @@ let PolicyManager = (function(self) {
 }(PolicyManager || {}));
 
 
-Services.scriptloader.loadSubScript(
-    "chrome://requestpolicy/content/lib/policy-manager.alias-functions.js");
+Services.scriptloader.loadSubScriptWithOptions(
+    "chrome://requestpolicy/content/lib/policy-manager.alias-functions.js",
+    {/*ignoreCache: true*/});
diff --git a/src/content/lib/prefs.jsm b/src/content/lib/prefs.jsm
index 06b8c85..512abe2 100644
--- a/src/content/lib/prefs.jsm
+++ b/src/content/lib/prefs.jsm
@@ -90,7 +90,7 @@ let Prefs = (function() {
 
         // define the pref's getter function to `self`
         self[getterName] = function() {
-          return cachedPrefs[getterName];
+          return cachedPrefs[prefID];
         };
 
         // define the pref's update() function to `cachedPrefList`
@@ -174,7 +174,7 @@ let Prefs = (function() {
     }
   };
 
-  ProcessEnvironment.enqueueStartupFunction(function() {
+  function registerPrefObserver() {
     ProcessEnvironment.obMan.observeRPPref({
       "": observePref
     });
@@ -182,7 +182,9 @@ let Prefs = (function() {
       "network.prefetch-next": observePref,
       "network.dns.disablePrefetch": observePref
     });
-  });
+  }
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                        registerPrefObserver);
 
   return self;
 }());
diff --git a/src/content/lib/process-environment.jsm b/src/content/lib/process-environment.jsm
index 3c37d50..f6ce784 100644
--- a/src/content/lib/process-environment.jsm
+++ b/src/content/lib/process-environment.jsm
@@ -25,142 +25,29 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
-let EXPORTED_SYMBOLS = ["ProcessEnvironment", "Environment"];
+let EXPORTED_SYMBOLS = [
+  "ProcessEnvironment",
+  // Not only ProcessEnvironment is exported, but also `Environment`. This
+  // might be helpful somewhere.
+  "Environment"
+];
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 let globalScope = this;
-let scriptLoaderURI = "chrome://requestpolicy/content/lib/script-loader.jsm";
 
+Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ScriptLoader.importModules([
+  "main/environment-manager",
+  "lib/environment"
+], globalScope);
 
-// In the main process this module is the first one to be loaded and the last to
-// be unloded. So this file defines what is done on the extension's startup.
-// These are the steps for the main process:
-//  1. the script loader is loaded *manually* (!)
-//  2. the module containing the Environment class is loaded
-//  3. create a new ProcessEnvironment (the Main Process Environment)
-//  4. define the startup function
-//     --> It loads all essential modules.
-//         This implicitely and recursively loads all other modules.
-//  5. define the shutdown function
-//     5.1. As the ScriptLoader must not load any of RP's modules, its
-//          Main Process shutdown function will be called from here.
-//     5.2. As this the ScriptLoader has been loaded manually, it has to be
-//          unloded manually as well!
+let envName = EnvironmentManager.isMainProcess ?
+    "Parent ProcEnv" : "Child ProcEnv";
 
+// create a new Environment
+let ProcessEnvironment = new Environment(envName);
 
-// =======================================
-// Step 1: Manually load the ScriptLoader.
-// ---------------------------------------
-// ( If this is the main process, it has to be unaloded manually as well! The
-//   shutdown function is defined below. )
-Cu.import(scriptLoaderURI, globalScope);
-// =======================================
 
-
-// =======================================
-// Step 2: load the Environment class
-// ----------------------------------
-ScriptLoader.importModule("lib/environment", globalScope);
-// =======================================
-
-
-// =======================================
-// Step 3: create a new Environment
-// --------------------------------
-let ProcessEnvironment = new Environment();
-// =======================================
-
-
-// determine if this is the main process
-ProcessEnvironment.isMainProcess = (function isMainProcess() {
-  let xulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
-  // The "default" type means that we're on the main process, the chrome process.
-  // This is relevant for multiprocessor firefox aka Electrolysis (e10s).
-  return xulRuntime.processType === xulRuntime.PROCESS_TYPE_DEFAULT;
-}());
-
-
-
-if (ProcessEnvironment.isMainProcess) {
-  // The following startup functions should be defined only in the main process.
-
-  // =======================================
-  // Step 4: define the startup function
-  // -----------------------------------
-  /**
-   * Import main modules on startup. This will be the first function that will
-   * be called. It imports essential modules which depend recursively on other
-   * modules. This means that when the second startup function is being called,
-   * all modules will already be loaded.
-   */
-  ProcessEnvironment.enqueueStartupFunction(function() {
-    // Import essential modules. Dependencies will be imported as well.
-    //
-    // IMPORTANT note:
-    //     Those modules have to be imported in a startup-function and NOT at
-    //     the time the `ProcessEnvironment` module itself is being loaded.
-    // In detail:
-    //     `ProcessEnvironment.enqueueStartupFunction()` is called by many
-    //     modules at load-time. If those modules would be loaded when
-    //     `process-environment.jsm` wasn't already loaded completely, the
-    //     `ProcessEnvironment` wouldn't be available. This would be an
-    //     "import()-loop".
-    // Illustration:
-    //     bootstrap.js  calls  load(ProcessEnvironment)
-    //         ProcessEnvironment  calls  load(moduleXY)
-    //             moduleXY  calls  load(ProcessEnvironment)
-    //                 ProcessEnvironment says "You can't load me, I didn't
-    //                 finish yet!"
-    {
-      // =======================================================================
-      // The following section is not optimal – read on…
-      // -----------------------------------------------
-
-      // load init PrefManager before anything else is loaded!
-      // the reason is that the Logger expects the prefs to be initialized
-      // and available already.
-      let {PrefManager} = ScriptLoader.importModule("main/pref-manager");
-      PrefManager.init();
-
-      // TODO: use the Browser Console for logging, see #563.
-      //       *Then* it's no longer necessary to load and init PrefManager
-      //       first. PrefManager will then be loaded and initialized when all
-      //       other back end modules are loaded / initialized.
-
-      // import the Logger as the first module so that its startup-function
-      // will be called after this one
-      ScriptLoader.importModule("lib/logger", globalScope);
-      // =======================================================================
-
-      // import main modules:
-      ScriptLoader.importModules([
-        "main/requestpolicy-service",
-        "lib/content-policy",
-        "main/window-manager",
-        "main/about-uri"
-      ], globalScope);
-    }
-  });
-  // =======================================
-
-
-  // =======================================
-  // Step 5: define the shutdown function
-  // ------------------------------------
-  ProcessEnvironment.pushShutdownFunction(function() {
-    // HACK WARNING: The Addon Manager does not properly clear all addon
-    //               related caches on update; in order to fully update
-    //               images and locales, their caches need clearing here.
-    Services.obs.notifyObservers(null, "chrome-flush-caches", null);
-
-
-    // Step 5.1: call ScriptLoader's Main Process shutdown functions
-    ScriptLoader.unloadAllLibraries();
-    ScriptLoader.unloadAllModules();
-
-    // Step 5.2: manually unload the ScriptLoader
-    Cu.unload(scriptLoaderURI);
-  });
-  // =======================================
-}
+// set whether this is the main process
+ProcessEnvironment.isMainProcess = EnvironmentManager.isMainProcess;
diff --git a/src/content/lib/request-processor.compat.js b/src/content/lib/request-processor.compat.js
index 1cb0e15..d111f37 100644
--- a/src/content/lib/request-processor.compat.js
+++ b/src/content/lib/request-processor.compat.js
@@ -40,28 +40,29 @@ let RequestProcessor = (function(self) {
   let compatibilityRules = [];
   let topLevelDocTranslationRules = {};
 
-  ProcessEnvironment.enqueueStartupFunction(function() {
+  // TODO: update compatibility rules etc. when addons are enabled/disabled
+  let addonListener = {
+    onDisabling : function(addon, needsRestart) {},
+    onUninstalling : function(addon, needsRestart) {},
+    onOperationCancelled : function(addon, needsRestart) {}
+  };
+
+  function init() {
     // Detect other installed extensions and the current application and do
     // what is needed to allow their requests.
     initializeExtensionCompatibility();
     initializeApplicationCompatibility();
 
     AddonManager.addAddonListener(addonListener);
-  });
+  }
 
   // stop observers / listeners
-  ProcessEnvironment.pushShutdownFunction(function() {
+  function cleanup() {
     AddonManager.removeAddonListener(addonListener);
-  });
-
-  // TODO: update compatibility rules etc. when addons are enabled/disabled
-  let addonListener = {
-    onDisabling : function(addon, needsRestart) {},
-    onUninstalling : function(addon, needsRestart) {},
-    onOperationCancelled : function(addon, needsRestart) {}
-  };
-
+  }
 
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.BACKEND, init);
+  ProcessEnvironment.addShutdownFunction(Environment.LEVELS.BACKEND, cleanup);
 
 
   function initializeExtensionCompatibility() {
diff --git a/src/content/lib/request-processor.jsm b/src/content/lib/request-processor.jsm
index 97aae85..ff564cd 100644
--- a/src/content/lib/request-processor.jsm
+++ b/src/content/lib/request-processor.jsm
@@ -1080,7 +1080,10 @@ let RequestProcessor = (function(self) {
   return self;
 }(RequestProcessor || {}));
 
-Services.scriptloader.loadSubScript('chrome://requestpolicy/content/lib/' +
-                                    'request-processor.redirects.js');
-Services.scriptloader.loadSubScript('chrome://requestpolicy/content/lib/' +
-                                    'request-processor.compat.js');
+
+Services.scriptloader.loadSubScriptWithOptions(
+    'chrome://requestpolicy/content/lib/request-processor.redirects.js',
+    {/*ignoreCache: true*/});
+Services.scriptloader.loadSubScriptWithOptions(
+    'chrome://requestpolicy/content/lib/request-processor.compat.js',
+    {/*ignoreCache: true*/});
diff --git a/src/content/lib/request-processor.redirects.js b/src/content/lib/request-processor.redirects.js
index 0163fff..1463b23 100644
--- a/src/content/lib/request-processor.redirects.js
+++ b/src/content/lib/request-processor.redirects.js
@@ -45,6 +45,12 @@ let RequestProcessor = (function(self) {
   let internal = Utils.moduleInternal(self);
 
 
+  const Ci = Components.interfaces;
+  const Cc = Components.classes;
+  const Cr = Components.results;
+  const Cu = Components.utils;
+
+
   /**
    * These are redirects that the user allowed when presented with a redirect
    * notification.
diff --git a/src/content/lib/script-loader.jsm b/src/content/lib/script-loader.jsm
index 39f396c..4bc66f4 100644
--- a/src/content/lib/script-loader.jsm
+++ b/src/content/lib/script-loader.jsm
@@ -34,6 +34,7 @@ let EXPORTED_SYMBOLS = ["ScriptLoader"];
 //         when the module to be imported wants to import ScriptLoader.
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/devtools/Console.jsm");
 
 
 const rpChromeContentURI = 'chrome://requestpolicy/content/';
@@ -45,9 +46,9 @@ function getModuleURI(id) {
 /**
  * If the ScriptLoader catches an Exception, it will be a severe error.
  */
-function logSevereError(msg, stack) {
-  dump("[RequestPolicy] [SEVERE] [ERROR] " + msg +
-       (stack ? ", stack was: " + stack : "") + "\n");
+function logSevereError(msg, e) {
+  dump("[RequestPolicy] [SEVERE] [ERROR] " + msg + " " + e +
+       (e.stack ? ", stack was: " + e.stack : "") + "\n");
 }
 
 
@@ -56,6 +57,14 @@ let ScriptLoader = (function() {
 
   let importedModuleURIs = {};
 
+  // URIs in that variable will not be unloaded
+  let moduleUnloadExceptions = {};
+  // a module shouldn't unload itself
+  moduleUnloadExceptions[getModuleURI("lib/script-loader")] = true;
+  // EnvironmentManager has to be unloaded even later than ScriptLoader
+  moduleUnloadExceptions[getModuleURI("main/environment-manager")] = true;
+
+
   // contains the module IDs that are currently being imported initially and
   // have not finished importing yet.
   let modulesCurrentlyBeingImported = {};
@@ -65,19 +74,32 @@ let ScriptLoader = (function() {
     /**
      * Unload all modules that have been imported.
      * See https://developer.mozilla.org/en-US/docs/Components.utils.unload
-     * The Process Environment of the main process takes care of calling this
-     * function.
      */
     unloadAllModules: function() {
       for (let uri in importedModuleURIs) {
-        if (importedModuleURIs.hasOwnProperty(uri)) {
-          Cu.unload(uri);
+        if (importedModuleURIs.hasOwnProperty(uri) &&
+            moduleUnloadExceptions.hasOwnProperty(uri) === false) {
+          //console.debug("[RPC] Unloading module "+uri);
+          try {
+            Cu.unload(uri);
+          } catch(e) {
+            console.error("[RPC] Failed to unload module "+uri);
+            Components.utils.reportError(e);
+          }
           delete importedModuleURIs[uri];
         }
       }
     },
 
     /**
+     * Function called by EnvironmentManager before ScriptLoader is being
+     * unloaded.
+     */
+    doShutdownTasks: function() {
+      self.unloadAllModules();
+    },
+
+    /**
      * @param {Array} moduleID
      *        the moduleID of the module to import
      * @param {Object} scope
@@ -93,11 +115,14 @@ let ScriptLoader = (function() {
         return scope;
       }
 
+      //console.debug("[RPC] `importModule` called for "+moduleID);
+
       let uri = getModuleURI(moduleID);
       try {
         if (!(uri in importedModuleURIs)) {
           // the module hasn't been imported yet
           modulesCurrentlyBeingImported[moduleID] = true;
+          //console.debug("[RPC] importing " + moduleID);
         }
 
         Cu.import(uri, scope);
@@ -107,11 +132,11 @@ let ScriptLoader = (function() {
           delete modulesCurrentlyBeingImported[moduleID];
         }
       } catch (e if e.result === Cr.NS_ERROR_FILE_NOT_FOUND) {
-        logSevereError("Failed to import module with ID \"" + moduleID + "\", the " +
-                       "file was not found! ", e.stack);
+        logSevereError('Failed to import module with ID "' + moduleID +
+                       '", the file was not found!', e);
       } catch (e) {
-        logSevereError("Failed to import module with ID \"" + moduleID + "\": " + e,
-                       e.stack);
+        logSevereError('Failed to import module with ID "' + moduleID +
+                       '".', e);
       }
       return scope;
     },
@@ -147,6 +172,7 @@ let ScriptLoader = (function() {
     defineLazyModuleGetter: function(moduleID, names, scope) {
       scope = scope || {};
 
+      //console.debug("[RPC] defining lazy module getter(s) for " + moduleID);
       let uri = getModuleURI(moduleID);
       for (let i in names) {
         let name = names[i];
diff --git a/src/content/lib/utils.jsm b/src/content/lib/utils.jsm
index 9a44c01..9dd45ad 100644
--- a/src/content/lib/utils.jsm
+++ b/src/content/lib/utils.jsm
@@ -30,6 +30,7 @@ let EXPORTED_SYMBOLS = ["Utils"];
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
+//Cu.import("resource://gre/modules/devtools/Console.jsm");
 
 Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
 ScriptLoader.importModules([
@@ -54,6 +55,8 @@ let Utils = (function() {
    * @param {Object} thisPtr
    */
   self.runAsync = function(callback, thisPtr) {
+    //console.log("registering async execution. Caller is "+
+    //            Components.stack.caller.filename);
     let params = Array.prototype.slice.call(arguments, 2);
     let runnable = {
       run: function() {
@@ -126,10 +129,11 @@ let Utils = (function() {
    */
   self.moduleInternal = function(aModuleScope) {
     aModuleScope.internal = aModuleScope.internal || {};
-    let sealInternal = function() {
+    function sealInternal() {
       delete aModuleScope.internal;
     };
-    ProcessEnvironment.enqueueStartupFunction(sealInternal);
+    ProcessEnvironment.addStartupFunction(Environment.LEVELS.ESSENTIAL,
+                                          sealInternal);
     return aModuleScope.internal;
   };
 
diff --git a/src/content/lib/utils/constants.jsm b/src/content/lib/utils/constants.jsm
index b9b6486..22b2aa2 100644
--- a/src/content/lib/utils/constants.jsm
+++ b/src/content/lib/utils/constants.jsm
@@ -32,6 +32,7 @@ let C = {};
 C.EXTENSION_ID = "requestpolicy at requestpolicy.com";
 C.FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
 C.MMID = C.EXTENSION_ID; // message manager ID
+C.MM_PREFIX = C.MMID + ":";
 
 // reason constants for startup(), shutdown(), install() and uninstall()
 // see https://developer.mozilla.org/en-US/Add-ons/Bootstrapped_extensions#Reason_constants
diff --git a/src/content/lib/utils/files.jsm b/src/content/lib/utils/files.jsm
index 0a249bf..c9b5934 100644
--- a/src/content/lib/utils/files.jsm
+++ b/src/content/lib/utils/files.jsm
@@ -76,6 +76,10 @@ var FileUtil = {
    * @param {nsIFile} file
    */
   fileToString : function(file) {
+    if (file.exists() === false) {
+      // prevent NS_ERROR_FILE_NOT_FOUND
+      return "";
+    }
     var stream = Cc["@mozilla.org/network/file-input-stream;1"]
         .createInstance(Ci.nsIFileInputStream);
     stream.init(file, 0x01, octal444, 0);
diff --git a/src/content/lib/utils/windows.jsm b/src/content/lib/utils/windows.jsm
index ce38ef9..02f664f 100644
--- a/src/content/lib/utils/windows.jsm
+++ b/src/content/lib/utils/windows.jsm
@@ -103,14 +103,17 @@ let WindowUtils = (function() {
                                         aCallback) {
     let scope = aScope || {};
     let document = aWindow.document;
-    aWindow.addEventListener("load", function() {
+    let callback = function() {
+      aWindow.removeEventListener("load", callback);
+
       for (let elementName in aElementIDs) {
         scope[elementName] = document.getElementById(aElementIDs[elementName]);
       }
       if (aCallback) {
         aCallback();
       }
-    });
+    };
+    aWindow.addEventListener("load", callback);
     return scope;
   };
 
diff --git a/src/content/lib/utils/xul.jsm b/src/content/lib/utils/xul.jsm
index 01f3fde..d14b053 100644
--- a/src/content/lib/utils/xul.jsm
+++ b/src/content/lib/utils/xul.jsm
@@ -35,9 +35,10 @@ let EXPORTED_SYMBOLS = ["XULUtils"];
 let XULUtils = {};
 
 let xulTrees = XULUtils.xulTrees = {};
-// TODO: convert to JSON
-Services.scriptloader.loadSubScript(
-    'chrome://requestpolicy/content/ui/xul-trees.js', xulTrees);
+
+Services.scriptloader.loadSubScriptWithOptions(
+    'chrome://requestpolicy/content/ui/xul-trees.js',
+    {target: xulTrees/*, ignoreCache: true*/});
 
 
 function getParentElement(doc, element) {
diff --git a/src/content/main/about-uri.jsm b/src/content/main/about-uri.jsm
index 5a5e459..461dd90 100644
--- a/src/content/main/about-uri.jsm
+++ b/src/content/main/about-uri.jsm
@@ -25,14 +25,17 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
+let EXPORTED_SYMBOLS = ["AboutRequestPolicy"];
+
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+let globalScope = this;
 
-let EXPORTED_SYMBOLS = ["AboutRequestPolicy"];
 
 Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
-ScriptLoader.importModule("lib/process-environment", this);
+ScriptLoader.importModule("lib/process-environment", globalScope);
+
 
 var filenames = {
   "basicprefs": "basicprefs.html",
@@ -46,10 +49,8 @@ var filenames = {
 function getURI(aURI) {
   let id;
   let index = aURI.path.indexOf("?");
-  dump("path: "+aURI.path+"\n");
   if (index >= 0 && aURI.path.length > index) {
     id = aURI.path.substr(index+1);
-    dump("id: "+id+"\n");
   }
   if (!id || !(id in filenames)) {
     id = "basicprefs";
@@ -90,21 +91,26 @@ let AboutRequestPolicy = (function() {
   };
 
 
-
-  ProcessEnvironment.enqueueStartupFunction(function() {
+  function registerFactory() {
     Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
-        .registerFactory(self.classID, self.classDescription, self.contractID,
-            self);
-  });
+        .registerFactory(self.classID, self.classDescription,
+                         self.contractID, self);
+  }
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                        registerFactory);
 
-  ProcessEnvironment.pushShutdownFunction(function() {
+  function unregisterFactory() {
     let registrar = Components.manager
         .QueryInterface(Ci.nsIComponentRegistrar);
-    // This needs to run asynchronously, see bug 753687
+    let {Utils} = ScriptLoader.importModule("lib/utils");
+
+    // This needs to run asynchronously, see Mozilla bug 753687
     Utils.runAsync(function() {
       registrar.unregisterFactory(self.classID, self);
     });
-  });
+  }
+  ProcessEnvironment.addShutdownFunction(Environment.LEVELS.INTERFACE,
+                                         unregisterFactory);
 
   return self;
 }());
diff --git a/src/content/main/environment-manager-child.js b/src/content/main/environment-manager-child.js
new file mode 100644
index 0000000..7fbb689
--- /dev/null
+++ b/src/content/main/environment-manager-child.js
@@ -0,0 +1,116 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008-2012 Justin Samuel
+ * Copyright (c) 2014-2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+//Cu.import("resource://gre/modules/devtools/Console.jsm");
+
+
+let EnvironmentManager = (function(self) {
+  // Manually import the scriptloader without waiting for the startup.
+  let mod = {};
+  let scriptLoaderURI = "chrome://requestpolicy/content/lib/script-loader.jsm";
+  Cu.import(scriptLoaderURI, mod);
+  mod.ScriptLoader.defineLazyModuleGetters({
+    "lib/utils/constants": ["C"]
+  }, mod);
+
+
+  // ================================
+  // variables set by `registerFramescript`:
+  // ---------------------------------------
+  // the content frame's message manager
+  let mm = null;
+
+  // The framescript's URI will be checked against the URI in the
+  // message to ensure there is no conflics between old and new framescripts
+  // in case the addon is disabled and enabled in quick succession.
+  // For details see:
+  // https://palant.de/2014/11/19/unloading-frame-scripts-in-restartless-extensions
+  let framescriptURI = null;
+  // ================================
+
+
+  let shutdownMessageName = mod.C.MM_PREFIX + "shutdown";
+
+  function shutDownEnvMan(message) {
+    if (message.data.uri == framescriptURI) {
+      //console.log("[RPC] Child EnvironmentManager received `shutdown` " +
+      //            'message. Going to shut down all environments.');
+
+      /**
+       * cleanup
+       */
+      {
+        mm.removeMessageListener(shutdownMessageName, shutDownEnvMan);
+        framescriptURI = null;
+        // remove the reference to the message manager
+        mm = null;
+      }
+
+      /**
+       * shut down all environments
+       */
+      {
+        self.shutdown();
+        // if shutdown() arguments would be needed, the following could be used:
+        //self.shutdown.apply(self, message.data.arguments);
+      }
+    }
+  };
+
+
+  /**
+   * Each framescript instance registers itself to EnvironmentManager. The
+   * purpose is that Environment Managers in child process listen to the
+   * "shutdown" message. When that message is received, the child's
+   * EnvironmentManager shuts down.
+   *
+   * Note: framescripts might also be in the *main* process, but then it's not
+   *       necessary to listen for "shutdown" as each environment in the
+   *       framescript's has direct access to the *main* EnvironmentManager.
+   */
+  self.registerFramescript = function(aContentFrameMessageManager) {
+    // ensure that `registerFramescript` is called only once
+    if (mm === null) {
+      //console.log("a framescript has registered");
+      // remember the MM
+      mm = aContentFrameMessageManager;
+      // set the framescriptURI
+      framescriptURI = Components.stack.caller.filename;
+      // manually add a Message Listener (without a ManagerForMessageListeners)
+      mm.addMessageListener(shutdownMessageName, shutDownEnvMan);
+    }
+  };
+
+
+
+  self.doShutdownTasks = function() {
+    // "shutdown" and unload the mod.ScriptLoader *manually*
+    mod.ScriptLoader.doShutdownTasks();
+    mod = {};
+    Cu.unload(scriptLoaderURI);
+  }
+
+
+  return self;
+}(EnvironmentManager || {}));
diff --git a/src/content/main/environment-manager-parent.js b/src/content/main/environment-manager-parent.js
new file mode 100644
index 0000000..60826f7
--- /dev/null
+++ b/src/content/main/environment-manager-parent.js
@@ -0,0 +1,117 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008-2012 Justin Samuel
+ * Copyright (c) 2014-2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+
+let globalScope = globalScope || this;
+
+
+let EnvironmentManager = (function(self) {
+
+  // Object for holding imported modules, especilly the mod.ScriptLoader.
+  let mod = {};
+
+  let scriptLoaderURI = "chrome://requestpolicy/content/lib/script-loader.jsm";
+
+  let ScriptLoader = null;
+
+
+  /**
+   * Ignore any calls to `registerFramescript` completely; the
+   * `ContentFrameMessageManager` handed over is needed only in child proceses.
+   */
+  self.registerFramescript = function(aContentFrameMessageManager) {
+    // do nothing.
+  };
+
+
+
+  /**
+   * This is the first function to be called by the EnvironmentManager's
+   * startup() function.
+   *
+   * This function imports essential modules which depend recursively on
+   * other modules, so that finally, after this function has finished,
+   * all modules will be available to be imported by any other module.
+   */
+  self.doStartupTasks = function() {
+    // =======================================
+    // Manually load the ScriptLoader.
+    // ---------------------------------------
+    // ( It has to be unloaded manually as well! The shutdown function is
+    //   defined below. )
+    Cu.import(scriptLoaderURI, mod);
+    // =======================================
+
+
+    // Create a dummy scope for modules that have to be imported but not
+    // remembered by EnvironmentManager. As the scope is a local variable,
+    // it will be removed after the function has finished.
+    // However, the main modules register their startup and shutdown functions
+    // anyway.
+    let dummyScope = {};
+
+
+    /**
+     * The following section is not optimal – read on…
+     */
+    {
+      // Load and init PrefManager before anything else is loaded!
+      // The reason is that the Logger, which is imported by many modules,
+      // expects the prefs to be initialized and available already.
+      let {PrefManager} = mod.ScriptLoader.importModule("main/pref-manager");
+      PrefManager.init();
+
+      // TODO: use the Browser Console for logging, see #563.
+      //       *Then* it's no longer necessary to load and init PrefManager
+      //       first. PrefManager will then be loaded and initialized when all
+      //       other back end modules are loaded / initialized.
+    }
+
+    // import main modules:
+    mod.ScriptLoader.importModules([
+      "main/requestpolicy-service",
+      "lib/content-policy",
+      "main/window-manager",
+      "main/about-uri"
+    ], dummyScope);
+  }
+
+
+
+  /**
+   * On shutdown, this function is the last one to be called.
+   */
+  self.doShutdownTasks = function() {
+    // HACK WARNING: The Addon Manager does not properly clear all addon
+    //               related caches on update; in order to fully update
+    //               images and locales, their caches need clearing here.
+    Services.obs.notifyObservers(null, "chrome-flush-caches", null);
+
+    // "shutdown" and unload the mod.ScriptLoader *manually*
+    mod.ScriptLoader.doShutdownTasks();
+    mod = {};
+    Cu.unload(scriptLoaderURI);
+  }
+
+  return self;
+}(EnvironmentManager || {}));
diff --git a/src/content/main/environment-manager.jsm b/src/content/main/environment-manager.jsm
new file mode 100644
index 0000000..2429928
--- /dev/null
+++ b/src/content/main/environment-manager.jsm
@@ -0,0 +1,144 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008-2012 Justin Samuel
+ * Copyright (c) 2014-2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+let EXPORTED_SYMBOLS = ["EnvironmentManager"];
+
+let globalScope = this;
+
+Cu.import("resource://gre/modules/Services.jsm");
+//Cu.import("resource://gre/modules/devtools/Console.jsm");
+
+
+/**
+ * EnvironmentManager is one of the central modules relevant for the
+ * extension's bootstrapping process. However, this is documented elsewhere,
+ * possibly in the developer wiki.
+ */
+let EnvironmentManager = (function(self) {
+
+  // determine if this is the main process
+  self.isMainProcess = (function isMainProcess() {
+    let xulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+    // The "default" type means that we're on the main process, the chrome process.
+    // This is relevant for multiprocessor firefox aka Electrolysis (e10s).
+    return xulRuntime.processType === xulRuntime.PROCESS_TYPE_DEFAULT;
+  }());
+
+  let procString = (self.isMainProcess ? "parent" : "child") + " process";
+
+  //console.debug("[RPC] creating new EnvironmentManager (" + procString + ")");
+
+
+  self.environments = new Set();
+
+  self.registerEnvironment = function(aEnv) {
+    self.environments.add(aEnv);
+  };
+  self.unregisterEnvironment = function(aEnv) {
+    self.environments.delete(aEnv);
+  };
+
+
+  // Ensure that `doStartupTasks` and `doShutdownTasks` exist. They can be
+  // overwritten.
+  self.doStartupTasks = self.doStartupTasks || function() {};
+  self.doShutdownTasks = self.doShutdownTasks || function() {};
+
+
+  /**
+   */
+  self.startup = function() {
+    //console.debug("[RPC] EnvironmentManager ("+procString+") is going to " +
+    //              "start up the Process Environment...");
+    self.doStartupTasks();
+
+    // on startup, only the Process Environment is started.
+    Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+    ScriptLoader.importModule("lib/process-environment")
+        .ProcessEnvironment.startup();
+  };
+
+  self.shutdown = function(data, reason) {
+    // remove the comments in this function for debugging.
+    //console.debug("[RPC] EnvironmentManager ("+procString+") is going to " +
+    //              "shut down all registered Environments...");
+    //if (self.isMainProcess) {
+    //  // only group in the main process -- some loggings of the child process
+    //  // might not reach the parent. In case the `groupEnd()` does not reach
+    //  // the parent, the Group will live for the whole browser session!
+    //  console.group();
+    //}
+
+    Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+    let {Environment} = ScriptLoader.importModule("lib/environment");
+    let sequence = Environment.shutdownSequence;
+
+    let fnArgsToApply = [data, reason];
+
+    // prepare shutdown
+    for (let env of self.environments) {
+      env.envState = Environment.SHUTTING_DOWN;
+    }
+    // shut down
+    Environment.processSequence(
+        Environment.shutdownSequence,
+        function (level) {
+          //console.debug("[RPC] reaching level "+level+" ...");
+          for (let env of self.environments) {
+            env.callShutdownFunctions(level, fnArgsToApply);
+          }
+        });
+    // finishing tasks
+    for (let env of self.environments) {
+      env.envState = Environment.SHUT_DOWN;
+      self.unregisterEnvironment(env);
+    }
+
+    // final tasks
+    //console.debug("[RPC] reaching level 0 ...");
+    self.doShutdownTasks();
+
+    // remove the references to all environments
+    self.environments = new Set();
+
+    //console.debug("[RPC] EnvironmentManager ("+procString+") finished " +
+    //              "shutting down all registered Environments.");
+    //if (self.isMainProcess) {
+    //  console.groupEnd();
+    //}
+  };
+
+  return self;
+}(EnvironmentManager || {}));
+
+
+// load parent- or child-specific parts
+let subScriptURI = EnvironmentManager.isMainProcess === true ?
+    "chrome://requestpolicy/content/main/environment-manager-parent.js" :
+    "chrome://requestpolicy/content/main/environment-manager-child.js";
+Services.scriptloader.loadSubScriptWithOptions(subScriptURI,
+                                               {/*ignoreCache: true*/});
diff --git a/src/content/main/pref-manager.jsm b/src/content/main/pref-manager.jsm
index dfd554f..84aabf6 100644
--- a/src/content/main/pref-manager.jsm
+++ b/src/content/main/pref-manager.jsm
@@ -57,7 +57,6 @@ let PrefManager = (function() {
 
   // TODO: move to bootstrap.js
   function handleUninstallOrDisable() {
-    console.info("Performing 'disable' operations.");
     var resetLinkPrefetch = rpPrefBranch.getBoolPref(
         "prefetch.link.restoreDefaultOnUninstall");
     var resetDNSPrefetch = rpPrefBranch.getBoolPref(
@@ -85,8 +84,9 @@ let PrefManager = (function() {
     // to handle their default preferences manually, see Mozilla Bug 564675:
     // https://bugzilla.mozilla.org/show_bug.cgi?id=564675
     // The scope of that script doesn't need to be remembered.
-    Services.scriptloader.loadSubScript("chrome://requestpolicy/content/main/" +
-                                        "default-pref-handler.js", {});
+    Services.scriptloader.loadSubScriptWithOptions(
+        "chrome://requestpolicy/content/main/default-pref-handler.js",
+        {target: {}/*, ignoreCache: true*/});
 
 
     // ================================
@@ -131,13 +131,15 @@ let PrefManager = (function() {
   };
 
 
-  ProcessEnvironment.pushShutdownFunction(function(data, reason) {
+  function eventuallyHandleUninstallOrDisable(data, reason) {
     if (reason == C.ADDON_DISABLE || reason == C.ADDON_UNINSTALL) {
       // TODO: Handle uninstallation in bootstrap.js, not here, RP might be
       //       disabled when being uninstalled.
       handleUninstallOrDisable();
     }
-  });
+  }
+  ProcessEnvironment.addShutdownFunction(Environment.LEVELS.BACKEND,
+                                         eventuallyHandleUninstallOrDisable);
 
   return self;
 }());
diff --git a/src/content/main/requestpolicy-service.jsm b/src/content/main/requestpolicy-service.jsm
index b90c49d..fa33322 100644
--- a/src/content/main/requestpolicy-service.jsm
+++ b/src/content/main/requestpolicy-service.jsm
@@ -127,12 +127,10 @@ let rpService = (function() {
   // /////////////////////////////////////////////////////////////////////////
 
   // prepare back-end
-  ProcessEnvironment.enqueueStartupFunction(function() {
-    loadConfigAndRules();
-  });
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.BACKEND,
+                                        loadConfigAndRules);
 
-  // start observers / listeners
-  ProcessEnvironment.enqueueStartupFunction(function() {
+  function registerObservers() {
     ProcessEnvironment.obMan.observe({
       "sessionstore-windows-restored": self.observe,
       SUBSCRIPTION_UPDATED_TOPIC: self.observe,
@@ -144,7 +142,9 @@ let rpService = (function() {
       //       see https://github.com/RequestPolicyContinued/requestpolicy/issues/533#issuecomment-68851396
       "private-browsing": self.observe
     });
-  });
+  }
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                        registerObservers);
 
 
 
diff --git a/src/content/main/window-manager-toolbarbutton.js b/src/content/main/window-manager-toolbarbutton.js
index 984a0ce..e8c4210 100644
--- a/src/content/main/window-manager-toolbarbutton.js
+++ b/src/content/main/window-manager-toolbarbutton.js
@@ -42,18 +42,19 @@ let rpWindowManager = (function(self) {
   let isAustralis = Utils.info.isAustralis;
 
   //
-  // Case 1: Australis (Gecko >= 29)
+  // Case 1: Australis (Firefox >= 29)
   //
 
   if (isAustralis) {
-    ProcessEnvironment.enqueueStartupFunction(function() {
-      addToolbarButtonToAustralis();
-    });
+    ProcessEnvironment.addStartupFunction(Environment.LEVELS.UI,
+                                          addToolbarButtonToAustralis);
+    ProcessEnvironment.addShutdownFunction(Environment.LEVELS.UI,
+                                           removeToolbarButtonFromAustralis);
+  }
 
-    ProcessEnvironment.pushShutdownFunction(function() {
-      let tbb = XULUtils.xulTrees.toolbarbutton[0];
-      CustomizableUI.destroyWidget(tbb.id);
-    });
+  function removeToolbarButtonFromAustralis() {
+    let tbb = XULUtils.xulTrees.toolbarbutton[0];
+    CustomizableUI.destroyWidget(tbb.id);
   }
 
   function addToolbarButtonToAustralis() {
diff --git a/src/content/main/window-manager.jsm b/src/content/main/window-manager.jsm
index dc31bc2..59d25e2 100644
--- a/src/content/main/window-manager.jsm
+++ b/src/content/main/window-manager.jsm
@@ -44,15 +44,18 @@ let rpWindowManager = (function(self) {
   ], globalScope);
 
   // import the WindowListener
-  Services.scriptloader.loadSubScript("chrome://requestpolicy/content/main/" +
-                                      "window-manager.listener.js",
-                                      globalScope);
+  Services.scriptloader.loadSubScriptWithOptions(
+      "chrome://requestpolicy/content/main/window-manager.listener.js",
+      {target: globalScope/*, ignoreCache: true*/});
 
   let styleSheets = [
     "chrome://requestpolicy/skin/requestpolicy.css",
     "chrome://requestpolicy/skin/toolbarbutton.css"
   ];
 
+  let frameScriptURI = "chrome://requestpolicy/content/ui/frame.js?" +
+      Math.random();
+
 
 
   function loadIntoWindow(window) {
@@ -77,15 +80,18 @@ let rpWindowManager = (function(self) {
     // # 3 : load the overlay's and menu's javascript
     // ----------------------------------------------
     try {
-      Services.scriptloader.loadSubScript(
-          "chrome://requestpolicy/content/ui/overlay.js", window);
-      Services.scriptloader.loadSubScript(
-          "chrome://requestpolicy/content/ui/menu.js", window);
-      Services.scriptloader.loadSubScript(
-          "chrome://requestpolicy/content/ui/classicmenu.js", window);
+      Services.scriptloader.loadSubScriptWithOptions(
+          "chrome://requestpolicy/content/ui/overlay.js",
+          {target: window/*, ignoreCache: true*/});
+      Services.scriptloader.loadSubScriptWithOptions(
+          "chrome://requestpolicy/content/ui/menu.js",
+          {target: window/*, ignoreCache: true*/});
+      Services.scriptloader.loadSubScriptWithOptions(
+          "chrome://requestpolicy/content/ui/classicmenu.js",
+          {target: window/*, ignoreCache: true*/});
     } catch (e) {
       Logger.warning(Logger.TYPE_ERROR,
-                     "Error loading subscripts for window: "+e, e);
+                     "Error loading subscripts for window: " + e, e);
     }
 
 
@@ -116,8 +122,7 @@ let rpWindowManager = (function(self) {
     // ==================================
     // # 6 : load frame scripts
     try {
-      window.messageManager.loadFrameScript(
-          "chrome://requestpolicy/content/ui/frame.js", true);
+      window.messageManager.loadFrameScript(frameScriptURI, true);
     } catch (e) {
       Logger.warning(Logger.TYPE_ERROR, "Error loading the frame script: "+e,e);
     }
@@ -126,7 +131,10 @@ let rpWindowManager = (function(self) {
   function unloadFromWindow(window) {
     // # 6 : unload frame scripts
     // --------------------------
-    // not needed
+    let mm = window.messageManager;
+    mm.removeDelayedFrameScript(frameScriptURI);
+    Logger.dump("broadcasting `shutdown` message to framescripts!");
+    mm.broadcastAsyncMessage(C.MM_PREFIX + "shutdown", {uri: frameScriptURI});
 
 
     // # 5 : "shutdown" the overlay
@@ -156,21 +164,26 @@ let rpWindowManager = (function(self) {
 
 
 
-  ProcessEnvironment.enqueueStartupFunction(function(data, reason) {
-    forEachOpenWindow(loadIntoWindow);
-    WindowListener.setLoadFunction(loadIntoWindow);
-    WindowListener.setUnloadFunction(unloadFromWindow);
-    WindowListener.startListening();
+  ProcessEnvironment.addStartupFunction(
+      Environment.LEVELS.INTERFACE,
+      function(data, reason) {
+        forEachOpenWindow(loadIntoWindow);
+        WindowListener.setLoadFunction(loadIntoWindow);
+        WindowListener.setUnloadFunction(unloadFromWindow);
+        WindowListener.startListening();
+      });
 
-    loadStyleSheets();
-  });
+  ProcessEnvironment.addStartupFunction(Environment.LEVELS.UI, loadStyleSheets);
 
-  ProcessEnvironment.pushShutdownFunction(function() {
-    forEachOpenWindow(unloadFromWindow);
-    WindowListener.stopListening();
+  ProcessEnvironment.addShutdownFunction(
+      Environment.LEVELS.INTERFACE,
+      function() {
+        forEachOpenWindow(unloadFromWindow);
+        WindowListener.stopListening();
+      });
 
-    unloadStyleSheets();
-  });
+  ProcessEnvironment.addShutdownFunction(Environment.LEVELS.UI,
+                                         unloadStyleSheets);
 
 
 
@@ -215,6 +228,6 @@ let rpWindowManager = (function(self) {
 
 
 // extend rpWindowManager
-Services.scriptloader.loadSubScript(
+Services.scriptloader.loadSubScriptWithOptions(
     "chrome://requestpolicy/content/main/window-manager-toolbarbutton.js",
-    globalScope);
+    {target: globalScope/*, ignoreCache: true*/});
diff --git a/src/content/settings/advancedprefs.js b/src/content/settings/advancedprefs.js
index 2721c3e..e8ac116 100644
--- a/src/content/settings/advancedprefs.js
+++ b/src/content/settings/advancedprefs.js
@@ -73,64 +73,60 @@ function onload() {
   updateDisplay();
 
   // Link prefetch.
-  $id('pref-linkPrefetch').addEventListener('change',
-      function (event) {
-        rootPrefBranch.setBoolPref('network.prefetch-next', event.target.checked);
-        Services.prefs.savePrefFile(null);
-      }
-  );
+  elManager.addListener($id('pref-linkPrefetch'), 'change', function (event) {
+    rootPrefBranch.setBoolPref('network.prefetch-next', event.target.checked);
+    Services.prefs.savePrefFile(null);
+  });
 
-  $id('pref-prefetch.link.disableOnStartup').addEventListener('change',
+  elManager.addListener(
+      $id('pref-prefetch.link.disableOnStartup'), 'change',
       function (event) {
         rpPrefBranch.setBoolPref('prefetch.link.disableOnStartup',
-            event.target.checked);
+                                 event.target.checked);
         Services.prefs.savePrefFile(null);
-      }
-  );
+      });
 
-  $id('pref-prefetch.link.restoreDefaultOnUninstall').addEventListener('change',
+  elManager.addListener(
+      $id('pref-prefetch.link.restoreDefaultOnUninstall'), 'change',
       function (event) {
         rpPrefBranch.setBoolPref('prefetch.link.restoreDefaultOnUninstall', event.target.checked);
         Services.prefs.savePrefFile(null);
-      }
-  );
+      });
 
   // DNS prefetch.
-  $id('pref-dnsPrefetch').addEventListener('change',
-      function (event) {
-        rootPrefBranch.setBoolPref('network.dns.disablePrefetch', !event.target.checked);
-        Services.prefs.savePrefFile(null);
-      }
-  );
+  elManager.addListener($id('pref-dnsPrefetch'), 'change', function (event) {
+    rootPrefBranch.setBoolPref('network.dns.disablePrefetch', !event.target.checked);
+    Services.prefs.savePrefFile(null);
+  });
 
-  $id('pref-prefetch.dns.disableOnStartup').addEventListener('change',
+  elManager.addListener(
+      $id('pref-prefetch.dns.disableOnStartup'), 'change',
       function (event) {
         rpPrefBranch.setBoolPref('prefetch.dns.disableOnStartup', event.target.checked);
         Services.prefs.savePrefFile(null);
-      }
-  );
+      });
 
-  $id('pref-prefetch.dns.restoreDefaultOnUninstall').addEventListener('change',
+  elManager.addListener(
+      $id('pref-prefetch.dns.restoreDefaultOnUninstall'), 'change',
       function (event) {
         rpPrefBranch.setBoolPref('prefetch.dns.restoreDefaultOnUninstall', event.target.checked);
         Services.prefs.savePrefFile(null);
-      }
-  );
+      });
 
   var sortingListener = function (event) {
     rpPrefBranch.setCharPref('menu.sorting', event.target.value);
     Services.prefs.savePrefFile(null);
   };
-  $id('sortByNumRequests').addEventListener('change', sortingListener);
-  $id('sortByDestName').addEventListener('change', sortingListener);
-  $id('noSorting').addEventListener('change', sortingListener);
+  elManager.addListener($id('sortByNumRequests'), 'change', sortingListener);
+  elManager.addListener($id('sortByDestName'), 'change', sortingListener);
+  elManager.addListener($id('noSorting'), 'change', sortingListener);
 
-  $id('menu.info.showNumRequests').addEventListener('change',
+  elManager.addListener(
+      $id('menu.info.showNumRequests'), 'change',
       function (event) {
         rpPrefBranch.setBoolPref('menu.info.showNumRequests', event.target.checked);
         Services.prefs.savePrefFile(null);
-      }
-  );
+      });
 
   // call updateDisplay() every time a preference gets changed
   WinEnv.obMan.observePrefChanges(updateDisplay);
diff --git a/src/content/settings/basicprefs.js b/src/content/settings/basicprefs.js
index f84c5d6..34011d9 100644
--- a/src/content/settings/basicprefs.js
+++ b/src/content/settings/basicprefs.js
@@ -42,39 +42,37 @@ function updateDisplay() {
 function onload() {
   updateDisplay();
 
-  $id('pref-indicateBlockedObjects').addEventListener('change',
+  elManager.addListener(
+      $id('pref-indicateBlockedObjects'), 'change',
       function (event) {
         rpPrefBranch.setBoolPref('indicateBlockedObjects', event.target.checked);
         Services.prefs.savePrefFile(null);
         updateDisplay();
-      }
-  );
+      });
 
-  $id('pref-dontIndicateBlacklistedObjects').addEventListener('change',
+  elManager.addListener(
+      $id('pref-dontIndicateBlacklistedObjects'), 'change',
       function (event) {
         rpPrefBranch.setBoolPref('indicateBlacklistedObjects',
                                  !event.target.checked);
         Services.prefs.savePrefFile(null);
         updateDisplay();
-      }
-  );
+      });
 
-  $id('pref-autoReload').addEventListener('change',
-    function(event) {
-      rpPrefBranch.setBoolPref('autoReload', event.target.checked);
-      Services.prefs.savePrefFile(null);
-      updateDisplay();
-    }
-  );
+  elManager.addListener($id('pref-autoReload'), 'change', function(event) {
+    rpPrefBranch.setBoolPref('autoReload', event.target.checked);
+    Services.prefs.savePrefFile(null);
+    updateDisplay();
+  });
 
-  $id('pref-privateBrowsingPermanentWhitelisting')
-      .addEventListener('change', function (event) {
+  elManager.addListener(
+      $id('pref-privateBrowsingPermanentWhitelisting'), 'change',
+      function (event) {
         rpPrefBranch.setBoolPref('privateBrowsingPermanentWhitelisting',
                                  event.target.checked);
         Services.prefs.savePrefFile(null);
         updateDisplay();
-      }
-  );
+      });
 
   // call updateDisplay() every time a preference gets changed
   WinEnv.obMan.observePrefChanges(updateDisplay);
diff --git a/src/content/settings/common.js b/src/content/settings/common.js
index 0bfc50f..c14cda0 100644
--- a/src/content/settings/common.js
+++ b/src/content/settings/common.js
@@ -19,8 +19,12 @@ ScriptLoader.importModules([
 ], this);
 
 // create a new Environment for this window
-var WinEnv = new Environment();
-WinEnv.shutdownOnWindowUnload(content);
+var WinEnv = new Environment("WinEnv");
+// The Environment has to be shut down when the content window gets unloaded.
+WinEnv.shutdownOnUnload(content);
+// start up right now, as there won't be any startup functions
+WinEnv.startup();
+var elManager = WinEnv.elManager;
 
 
 var $id = content.document.getElementById.bind(content.document);
diff --git a/src/content/settings/defaultpolicy.js b/src/content/settings/defaultpolicy.js
index 0e966bc..b8c7fdd 100644
--- a/src/content/settings/defaultpolicy.js
+++ b/src/content/settings/defaultpolicy.js
@@ -39,7 +39,8 @@ function showManageSubscriptionsLink() {
 function onload() {
   updateDisplay();
 
-  $id('defaultallow').addEventListener('change',
+  elManager.addListener(
+      $id('defaultallow'), 'change',
       function (event) {
         var allow = event.target.checked;
         rpPrefBranch.setBoolPref('defaultPolicy.allow', allow);
@@ -49,9 +50,10 @@ function onload() {
         common.switchSubscriptionPolicies();
         updateDisplay();
         showManageSubscriptionsLink();
-      }
-  );
-  $id('defaultdeny').addEventListener('change',
+      });
+
+  elManager.addListener(
+      $id('defaultdeny'), 'change',
       function (event) {
         var deny = event.target.checked;
         rpPrefBranch.setBoolPref('defaultPolicy.allow', !deny);
@@ -61,16 +63,16 @@ function onload() {
         common.switchSubscriptionPolicies();
         updateDisplay();
         showManageSubscriptionsLink();
-      }
-  );
-  $id('allowsamedomain').addEventListener('change',
+      });
+
+  elManager.addListener(
+      $id('allowsamedomain'), 'change',
       function (event) {
         var allowSameDomain = event.target.checked;
         rpPrefBranch.setBoolPref('defaultPolicy.allowSameDomain',
             allowSameDomain);
         Services.prefs.savePrefFile(null);
-      }
-  );
+      });
 
   // call updateDisplay() every time a preference gets changed
   WinEnv.obMan.observePrefChanges(updateDisplay);
diff --git a/src/content/settings/subscriptions.js b/src/content/settings/subscriptions.js
index 99d3306..2aabbc5 100644
--- a/src/content/settings/subscriptions.js
+++ b/src/content/settings/subscriptions.js
@@ -121,7 +121,7 @@ function onload() {
       Logger.dump('Skipping unexpected official subName: ' + subName);
       continue;
     }
-    el.addEventListener('change', handleSubscriptionCheckboxChange);
+    elManager.addListener(el, 'change', handleSubscriptionCheckboxChange);
   }
 
   var selector = '[data-defaultpolicy=' +
diff --git a/src/content/settings/yourpolicy.js b/src/content/settings/yourpolicy.js
index b7556ca..4ae4c71 100644
--- a/src/content/settings/yourpolicy.js
+++ b/src/content/settings/yourpolicy.js
@@ -216,7 +216,7 @@ function addRuleHelper() {
 
 function onload() {
   var search = $id('rulesearch');
-  search.addEventListener('keyup', function (event) {
+  elManager.addListener(search, 'keyup', function (event) {
     if (searchTimeoutId != null) {
       clearTimeout(searchTimeoutId);
     }
diff --git a/src/content/ui/frame.blocked-content.js b/src/content/ui/frame.blocked-content.js
index 2b523ce..fed427c 100644
--- a/src/content/ui/frame.blocked-content.js
+++ b/src/content/ui/frame.blocked-content.js
@@ -46,13 +46,13 @@ let ManagerForBlockedContent = (function() {
   let transparentImageDataUri = "data:image/gif;base64,R0lGODlhAQABAIAAA"
       + "AAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
 
-  function indicateBlockedVisibleObjects(message) {
+  let indicateBlockedVisibleObjects = function(message) {
     let {blockedURIs, docID} = message.data;
-    let document = DocManager.getDocument(docID);
-    if (!document) {
+    let doc = DocManager.getDocument(docID);
+    if (!doc) {
       return;
     }
-    let images = document.getElementsByTagName("img");
+    let images = doc.getElementsByTagName("img");
 
     // Ideally, want the image to be a broken image so that the alt text
     // shows. By default, the blocked image will just not show up at all.
@@ -90,10 +90,10 @@ let ManagerForBlockedContent = (function() {
         img.src = transparentImageDataUri;
       }
     }
-  }
+  };
 
-  addMessageListener(C.MMID + ":indicateBlockedVisibleObjects",
-                     indicateBlockedVisibleObjects);
+  mlManager.addListener("indicateBlockedVisibleObjects",
+                        indicateBlockedVisibleObjects);
 
   return self;
 }());
diff --git a/src/content/ui/frame.doc-manager.js b/src/content/ui/frame.doc-manager.js
index 8d77325..acf0afc 100644
--- a/src/content/ui/frame.doc-manager.js
+++ b/src/content/ui/frame.doc-manager.js
@@ -24,29 +24,78 @@
  * This singleton module can be used for having a reference to documents
  * (whether top level or frame documents). This is necessary when chrome code
  * needs to call functions on specific documents.
+ *
  */
 let DocManager = (function() {
   let self = {};
 
   let nextDocID = 0;
-  let documents = [];
+  let documents = new Map();
+
+  // the DocManager is enabled until it is shut down.
+  let enabled = true;
+
+
+  function cleanUpDoc(docID) {
+    let {doc, unloadCallback} = documents.get(docID);
+    // clean up listeners
+
+    doc.removeEventListener("unload", unloadCallback);
+    // no longer remember the document
+    documents.delete(docID);
+  }
+
+  // TODO: Create a `getDocEnv` function. The Environment can then also be
+  //       used to call `cleanUpDoc`.
+  //self.getDocEnv = function(doc) {};
+
 
   self.generateDocID = function(doc) {
+    if (!enabled) {
+      return null;
+    }
+
     let docID = nextDocID++;
-    documents[docID] = doc;
+
+    function cleanUpThisDoc() {
+      cleanUpDoc(docID);
+    };
 
     // Destructor function:
     // As soon as the document is unloaded, delete the reference.
     // The unload event is called when the document's location changes.
-    content.addEventListener("unload", function() {
-      delete documents[docID];
+    doc.addEventListener("unload", cleanUpThisDoc);
+
+    documents.set(docID, {
+      doc: doc,
+      unloadCallback: cleanUpThisDoc
     });
 
     return docID;
   };
 
+  function cleanUpAllDocs() {
+    // call `cleanUpAllDoc` for all docs
+    for (let [docID] of documents) {
+      // Note to the loop's head:
+      //     Destructuring assignment (ECMAScript 6) is used, that is, only
+      //     the "key" of `documents` is used, the "value" is ignored.
+      cleanUpDoc(docID);
+    }
+  }
+
+  function shutdownDocManager() {
+    enabled = false;
+    cleanUpAllDocs();
+  }
+  FrameScriptEnv.addShutdownFunction(Environment.LEVELS.BACKEND,
+                                     shutdownDocManager);
+
   self.getDocument = function(docID) {
-    return documents[docID] || null;
+    if (documents.has(docID)) {
+      return documents.get(docID).doc;
+    }
+    return null;
   };
 
   return self;
diff --git a/src/content/ui/frame.dom-content-loaded.js b/src/content/ui/frame.dom-content-loaded.js
index b327955..5c409fa 100644
--- a/src/content/ui/frame.dom-content-loaded.js
+++ b/src/content/ui/frame.dom-content-loaded.js
@@ -42,9 +42,9 @@ let ManagerForDOMContentLoaded = (function() {
     // Notify the main thread that a link has been clicked.
     // Note: The <a> element is `currentTarget`! See:
     // https://developer.mozilla.org/en-US/docs/Web/API/Event.currentTarget
-    sendSyncMessage(C.MMID + ":notifyLinkClicked",
-                    {origin: event.currentTarget.ownerDocument.URL,
-                     dest: event.currentTarget.href});
+    mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                       {origin: event.currentTarget.ownerDocument.URL,
+                        dest: event.currentTarget.href});
   }
 
 
@@ -77,12 +77,12 @@ let ManagerForDOMContentLoaded = (function() {
 
     onDocumentLoaded(doc);
     let docID = DocManager.generateDocID(doc);
-    sendAsyncMessage(C.MMID + ":notifyDocumentLoaded",
+    mm.sendAsyncMessage(C.MM_PREFIX + "notifyDocumentLoaded",
                      {docID: docID, documentURI: doc.documentURI});
 
 
     if (isActiveTopLevelDocument(doc)) {
-      sendAsyncMessage(C.MMID + ":notifyTopLevelDocumentLoaded");
+      mm.sendAsyncMessage(C.MM_PREFIX + "notifyTopLevelDocumentLoaded");
     }
   }
 
@@ -108,7 +108,7 @@ let ManagerForDOMContentLoaded = (function() {
     // other origins every time an iframe is loaded. Maybe, then, this should
     // use a timeout like observerBlockedRequests does.
     if (isActiveTopLevelDocument(iframe.ownerDocument)) {
-      sendAsyncMessage(C.MMID + ":notifyDOMFrameContentLoaded");
+      mm.sendAsyncMessage(C.MM_PREFIX + "notifyDOMFrameContentLoaded");
       /*
       // This has an advantage over just relying on the
       // observeBlockedRequest() call in that this will clear a blocked
@@ -121,19 +121,30 @@ let ManagerForDOMContentLoaded = (function() {
   }
 
 
+
+
   /**
    * Perform the actions required once the DOM is loaded. This may be being
    * called for more than just the page content DOM. It seems to work for now.
    *
    * @param {Event} event
    */
-  function onDocumentLoaded(document) {
-    let documentURI = document.documentURI;
+  function onDocumentLoaded(doc) {
+    // Create a new Environment for this Document and shut it down when
+    // the document is unloaded.
+    let DocEnv = new Environment("DocEnv");
+    DocEnv.shutdownOnUnload(doc.defaultView);
+    // start up the Environment immediately, as it won't have any startup
+    // functions.
+    DocEnv.startup();
+
+
+    let documentURI = doc.documentURI;
 
     let metaRefreshes = [];
 
     // Find all meta redirects.
-    var metaTags = document.getElementsByTagName("meta");
+    var metaTags = doc.getElementsByTagName("meta");
     for (var i = 0; i < metaTags.length; i++) {
       let metaTag = metaTags[i];
       if (!metaTag.httpEquiv || metaTag.httpEquiv.toLowerCase() != "refresh") {
@@ -153,7 +164,7 @@ let ManagerForDOMContentLoaded = (function() {
       // If destURI isn't a valid uri, assume it's a relative uri.
       if (!DomainUtil.isValidUri(destURI)) {
         originalDestURI = destURI;
-        destURI = document.documentURIObject.resolve(destURI);
+        destURI = doc.documentURIObject.resolve(destURI);
       }
 
       metaRefreshes.push({delay: delay, destURI: destURI,
@@ -163,7 +174,11 @@ let ManagerForDOMContentLoaded = (function() {
     if (metaRefreshes.length > 0) {
       // meta refreshes have been found.
 
-      var docShell = document.defaultView
+      Logger.info(Logger.TYPE_META_REFRESH,
+                  "Number of meta refreshes found: " + metaRefreshes.length);
+
+
+      var docShell = doc.defaultView
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIDocShell);
@@ -172,7 +187,7 @@ let ManagerForDOMContentLoaded = (function() {
             "Another extension disabled docShell.allowMetaRedirects.");
       }
 
-      sendAsyncMessage(C.MMID + ":handleMetaRefreshes",
+      mm.sendAsyncMessage(C.MM_PREFIX + "handleMetaRefreshes",
           {documentURI: documentURI, metaRefreshes: metaRefreshes});
     }
 
@@ -188,12 +203,22 @@ let ManagerForDOMContentLoaded = (function() {
     // but I just don't know what it is. For now, there remains the risk of
     // dynamically added links whose target of the click event isn't the anchor
     // tag.
-    var anchorTags = document.getElementsByTagName("a");
-    for (var i = 0; i < anchorTags.length; i++) {
-      anchorTags[i].addEventListener("click", htmlAnchorTagClicked, false);
+    // TODO: is it possible to implement this differently?
+    var anchorTags = doc.getElementsByTagName("a");
+    for (let anchorTag of anchorTags) {
+      anchorTag.addEventListener("click", htmlAnchorTagClicked, false);
     }
+    DocEnv.addShutdownFunction(Environment.LEVELS.INTERFACE, function() {
+      for (let anchorTag of anchorTags) {
+        anchorTag.removeEventListener("click", htmlAnchorTagClicked, false);
+      }
+    });
 
-    wrapWindowFunctions(document.defaultView);
+    // maybe not needed?
+    //wrapWindowFunctions(doc.defaultView);
+    //DocEnv.addShutdownFunction(Environment.LEVELS.INTERFACE, function() {
+    //  unwrapWindowFunctions(doc.defaultView);
+    //});
   }
 
   /**
@@ -206,8 +231,9 @@ let ManagerForDOMContentLoaded = (function() {
    * @param {Function} aNewFunction
    */
   function wrapWindowFunction(aWindow, aFunctionName, aNewFunction) {
-    let originals = aWindow.rpOriginalFunctions
-        = aWindow.rpOriginalFunctions || {};
+    aWindow.rpOriginalFunctions = aWindow.rpOriginalFunctions || {};
+    let originals = aWindow.rpOriginalFunctions;
+
     if (!(aFunctionName in originals)) {
       originals[aFunctionName] = aWindow[aFunctionName];
       aWindow[aFunctionName] = function() {
@@ -216,6 +242,18 @@ let ManagerForDOMContentLoaded = (function() {
       }
     }
   }
+  function unwrapWindowFunction(aWindow, aFunctionName) {
+    if (typeof aWindow.rpOriginalFunctions !== 'object') {
+      return;
+    }
+    let originals = aWindow.rpOriginalFunctions;
+
+    if (aFunctionName in originals) {
+      aWindow[aFunctionName] =
+          originals[aFunctionName];
+      delete originals[aFunctionName];
+    }
+  }
 
   /**
    * Wraps the window's open() and openDialog() methods so that RequestPolicy
@@ -233,7 +271,7 @@ let ManagerForDOMContentLoaded = (function() {
   function wrapWindowFunctions(aWindow) {
     wrapWindowFunction(aWindow, "open",
         function(url, windowName, windowFeatures) {
-          sendSyncMessage(C.MMID + ":notifyLinkClicked",
+          mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
                           {origin: aWindow.document.documentURI,
                            dest: url});
         });
@@ -241,18 +279,40 @@ let ManagerForDOMContentLoaded = (function() {
     wrapWindowFunction(aWindow, "openDialog",
         function() {
           // openDialog(url, name, features, arg1, arg2, ...)
-          sendSyncMessage(C.MMID + ":notifyLinkClicked",
+          mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
                           {origin: aWindow.document.documentURI,
                            dest: arguments[0]});
         });
   }
+  function unwrapWindowFunctions(aWindow) {
+    unwrapWindowFunction(aWindow, "open");
+    unwrapWindowFunction(aWindow, "openDialog");
+    delete aWindow.rpOriginalFunctions;
+  }
 
 
-  addEventListener("DOMContentLoaded", onDOMContentLoaded, true);
+  FrameScriptEnv.elManager.addListener(mm, "DOMContentLoaded",
+                                       onDOMContentLoaded, true);
 
   // DOMFrameContentLoaded is same DOMContentLoaded but also fires for
   // enclosed frames.
-  addEventListener("DOMFrameContentLoaded", onDOMFrameContentLoaded, true);
+  FrameScriptEnv.elManager.addListener(mm, "DOMFrameContentLoaded",
+                                       onDOMFrameContentLoaded, true);
+
+  //mm.addEventListener("DOMContentLoaded", onDOMContentLoaded, true);
+  //
+  //// DOMFrameContentLoaded is same DOMContentLoaded but also fires for
+  //// enclosed frames.
+  //mm.addEventListener("DOMFrameContentLoaded", onDOMFrameContentLoaded, true);
+  //
+  //// clean up on shutdown
+  //FrameScriptEnv.addShutdownFunction(Environment.LEVELS.INTERFACE, function() {
+  //  Logger.dump('removing listeners for "DOMContentLoaded" and ' +
+  //              '"DOMFrameContentLoaded"');
+  //  mm.removeEventListener("DOMContentLoaded", onDOMContentLoaded, true);
+  //  mm.removeEventListener("DOMFrameContentLoaded", onDOMFrameContentLoaded,
+  //                         true);
+  //});
 
   return self;
 }());
diff --git a/src/content/ui/frame.js b/src/content/ui/frame.js
index 1dcf9fe..ef4d3c4 100644
--- a/src/content/ui/frame.js
+++ b/src/content/ui/frame.js
@@ -21,59 +21,187 @@
  */
 
 Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+//Components.utils.import("resource://gre/modules/devtools/Console.jsm");
 
-ScriptLoader.importModule("lib/utils/constants", this);
+/**
+ * This anonymous function is needed because of Mozilla Bug 673569, fixed in
+ * Firefox 29 / Gecko 29.
+ * The bug means that all frame scripts run in the same shared scope. The
+ * anonymous function ensures that the framescripts do not overwrite
+ * one another.
+ */
+(function () {
+  //console.debug('[RPC] new framescript loading...');
+
+  // the ContentFrameMessageManager of this framescript
+  let mm = this;
+
+  // Create a new scope that can be removed easily when the framescript has to
+  // be unloaded.
+  var FrameScriptScope = {
+    mm: mm,
+    content: mm.content,
+    Components: mm.Components,
+
+    Ci: mm.Components.interfaces,
+    Cc: mm.Components.classes,
+    Cu: mm.Components.utils
+  };
+
+  const Cu = Components.utils;
+  Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm",
+            FrameScriptScope);
+  let ScriptLoader = FrameScriptScope.ScriptLoader;
+
+  ScriptLoader.importModules([
+    "lib/utils/constants",
+    "lib/environment",
+    "main/environment-manager"
+  ], FrameScriptScope);
+  let C = FrameScriptScope.C;
+  let Environment = FrameScriptScope.Environment;
+  let EnvironmentManager = FrameScriptScope.EnvironmentManager;
+
+
+  /**
+   * This function gets a Environment variable that has the same lifespan as
+   * the framescript. , i.e. the Environment's shutdown() function will be called
+   * when the Tab is  is unloaded.
+   *
+   * There are two cases:
+   *
+   * If this is the main process:
+   *     A new Environment is created.
+   *
+   * If this is *not* the main process:
+   *     `ProcessEnvironment` will be used. This ensures that this script will
+   *     have the same Environment as the modules that will be loaded.
+   */
+  FrameScriptScope.FrameScriptEnv = (function getFrameScriptEnv() {
+    // Check if this is the main process.
+    if (EnvironmentManager.isMainProcess === true) {
+      //console.debug('[RPC] the framescript is in the main process. ' +
+      //              'Creating a new environment...');
+      // This is the main process. The `ProcessEnvironment` can't be used as the
+      // content window's Environment, so a new Environment has to be created.
+      let {Environment} = ScriptLoader.importModules(["lib/environment"]);
+      return new Environment("FrameScriptEnv (main process)");
+    } else {
+      //console.debug('[RPC] the framescript is in a child process. ' +
+      //              "Going to use the child's ProcEnv...");
+      // This is a child process. The `ProcessEnvironment` can be used for this
+      // window's Environment.
+      return ScriptLoader.importModule("lib/process-environment")
+                         .ProcessEnvironment;
+    }
+  }());
 
+  let FrameScriptEnv = FrameScriptScope.FrameScriptEnv;
 
+  FrameScriptEnv.addShutdownFunction(Environment.LEVELS.ESSENTIAL, function() {
+    //console.debug("removing FrameScriptScope");
+    FrameScriptScope = null;
+  });
 
-/**
- * This function gets a Environment variable that has the same lifespan like
- * the content window, i.e. the Environment's shutdown() function will be called
- * when the content window is unloaded.
- *
- * There are two cases:
- *
- * If this is the main process:
- *     A new Environment is created.
- *
- * If this is *not* the main process:
- *     `ProcessEnvironment` will be used. This ensures that this script will
- *     have the same Environment as the modules that will be loaded.
- */
-var WinEnv = (function getWindowEnvironment() {
-  var {ProcessEnvironment} = ScriptLoader.importModule("lib/process-environment");
-
-  let env;
-
-  // Check if this is the main process.
-  if (ProcessEnvironment.isMainProcess === true) {
-    // This is the main process. The `ProcessEnvironment` can't be used as the
-    // content window's Environment, so a new Environment has to be created.
-    let {Environment} = ScriptLoader.importModules(["lib/environment"]);
-    env = new Environment();
-  } else {
-    // This is a child process. The `ProcessEnvironment` can be used for this
-    // window's Environment.
-    env = ProcessEnvironment;
+
+  let {ManagerForMessageListeners} = ScriptLoader.importModule(
+      "lib/manager-for-message-listeners");
+  FrameScriptScope.mlManager = new ManagerForMessageListeners(
+      FrameScriptEnv, mm);
+  let mlManager = FrameScriptScope.mlManager;
+
+
+
+  /**
+   * Ensure that the framescript is „shut down“ when the addon gets disabled.
+   *
+   * TODO: use the Child Message Manager instead!
+   *       https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/The_message_manager#Process_Message_Managers
+   */
+  {
+    // Hand over the ContentFrameMessageManager of this framescript to the
+    // EnvironmentManager. The EnvironmentManager then shuts down all environments
+    // in the child process when the "shutdown" message is received. If this
+    // framescript is in the *chrome* process (aka. main process / parent process)
+    // then EnvironmentManager will simply ignore this function call.
+    EnvironmentManager.registerFramescript(mm);
   }
 
-  // Tell the Environment to shut down when the content window is unloaded.
-  // Note that this is necessary in any of the above cases.
-  env.shutdownOnWindowUnload(content);
+  /**
+   * Ensure that the framescript is „shut down“ when the correspondung Tab gets
+   * closed.
+   */
+  {
+    FrameScriptEnv.elManager.addListener(mm, "unload", function() {
+      FrameScriptEnv.shutdown();
+    }, false);
+  }
+
+  /**
+   * Ensure that EnvironmentManager is unloaded in a child process. If it
+   * wouldn't be unloaded, the EnvironmentManager might be the same one the
+   * next time the addon gets enabled.
+   *
+   * The unload can't be done from EnvironmentManager itself, as due to Mozilla
+   * Bug 769253 a module cannot unload itself. This is also the reason why
+   * the unload is done async -- the shutdown functions are called by
+   * EnvironmentManager.
+   */
+  function unloadEnvMan() {
+    // The runnable (nsIRunnable) that will be executed asynchronously.
+    let runnableForUnloadingEnvMan = {
+      run: function() {
+        Components.utils.unload("chrome://requestpolicy/content/" +
+                                "main/environment-manager.jsm");
+      }
+    };
+
+    // tell the current thread to run the `unloadEnvMan` runnable async.
+    Components.classes["@mozilla.org/thread-manager;1"]
+        .getService(Components.interfaces.nsIThreadManager)
+        .currentThread
+        .dispatch(runnableForUnloadingEnvMan,
+                  Components.interfaces.nsIEventTarget.DISPATCH_NORMAL);
+  }
+  if (EnvironmentManager.isMainProcess === false) {
+    FrameScriptEnv.addShutdownFunction(Environment.LEVELS.ESSENTIAL,
+                                       unloadEnvMan);
+  }
 
-  return env;
-}());
+
+  function reloadDocument() {
+    content.document.location.reload(false);
+  }
+  mlManager.addListener("reload", reloadDocument);
+
+  function setLocation(aUri) {
+    content.document.location.href = aUri;
+  }
+  mlManager.addListener("setLocation", function (message) {
+    setLocation(message.data.uri);
+  });
+
+  function loadSubScripts() {
+    Services.scriptloader.loadSubScriptWithOptions(
+        'chrome://requestpolicy/content/ui/frame.blocked-content.js',
+        {target: FrameScriptScope/*, ignoreCache: true*/});
+    Services.scriptloader.loadSubScriptWithOptions(
+        'chrome://requestpolicy/content/ui/frame.dom-content-loaded.js',
+        {target: FrameScriptScope/*, ignoreCache: true*/});
+    Services.scriptloader.loadSubScriptWithOptions(
+        'chrome://requestpolicy/content/ui/frame.doc-manager.js',
+        {target: FrameScriptScope/*, ignoreCache: true*/});
+  }
+  FrameScriptEnv.addStartupFunction(Environment.LEVELS.BACKEND,
+                                    loadSubScripts);
+
+  FrameScriptEnv.startup();
 
 
 
-// fixme: It's unclear whether it's necessary to listen for *any* click in
-//        the window. Originally the following code has been part of
-//        overlay.onLoad and has been moved here in order to support e10s.
-/*
   // Listen for click events so that we can allow requests that result from
   // user-initiated link clicks and form submissions.
-  addEventListener("click", function(event) {
+  function mouseClicked(event) {
     // If mozInputSource is undefined or zero, then this was a javascript-generated event.
     // If there is a way to forge mozInputSource from javascript, then that could be used
     // to bypass RequestPolicy.
@@ -91,7 +219,7 @@ var WinEnv = (function getWindowEnvironment() {
     // I believe an empty href always gets filled in with the current URL so
     // it will never actually be empty. However, I don't know this for certain.
     if (event.target.nodeName.toLowerCase() == "a" && event.target.href) {
-      sendSyncMessage(C.MMID + ":notifyLinkClicked",
+      sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
                       {origin: event.target.ownerDocument.URL,
                        dest: event.target.href});
       return;
@@ -102,31 +230,13 @@ var WinEnv = (function getWindowEnvironment() {
     if (event.target.nodeName.toLowerCase() == "input" &&
         event.target.type.toLowerCase() == "submit" &&
         event.target.form && event.target.form.action) {
-      sendSyncMessage(C.MMID + ":registerFormSubmitted",
+      sendSyncMessage(C.MM_PREFIX + "registerFormSubmitted",
                       {origin: event.target.ownerDocument.URL,
                        dest: event.target.form.action});
       return;
     }
-  }, true);*/
-
-WinEnv.enqueueStartupFunction(function() {
-  addMessageListener(C.MMID + ":reload", function() {
-    content.document.location.reload(false);
-  });
-
-  addMessageListener(C.MMID + ":setLocation", function(message) {
-    content.document.location.href = message.data.uri;
+  };
+  FrameScriptEnv.addStartupFunction(Environment.LEVELS.INTERFACE, function() {
+    FrameScriptEnv.elManager.addListener(mm, "click", mouseClicked, true);
   });
-});
-
-
-WinEnv.enqueueStartupFunction(function() {
-  Services.scriptloader.loadSubScript(
-      'chrome://requestpolicy/content/ui/frame.blocked-content.js');
-  Services.scriptloader.loadSubScript(
-      'chrome://requestpolicy/content/ui/frame.dom-content-loaded.js');
-  Services.scriptloader.loadSubScript(
-      'chrome://requestpolicy/content/ui/frame.doc-manager.js');
-});
-
-WinEnv.startup();
+}());
diff --git a/src/content/ui/overlay.js b/src/content/ui/overlay.js
index 1c5b938..255b19a 100644
--- a/src/content/ui/overlay.js
+++ b/src/content/ui/overlay.js
@@ -42,6 +42,8 @@ requestpolicy.overlay = (function() {
   }
   // iMod: Alias for ScriptLoader.importModule
   let iMod = ScriptLoader.importModule;
+  let {Environment} = iMod("lib/environment");
+  let {ManagerForMessageListeners} = iMod("lib/manager-for-message-listeners");
   let {Logger} = iMod("lib/logger");
   let {rpPrefBranch, Prefs} = iMod("lib/prefs");
   let {RequestProcessor} = iMod("lib/request-processor");
@@ -59,6 +61,13 @@ requestpolicy.overlay = (function() {
   //let _prefetchDisablingInstructionsUri = "http://www.requestpolicy.com/help/prefetch.html#disable";
 
 
+  // create an environment for this overlay.
+  let OverlayEnvironment = new Environment("OverlayEnv");
+  // manage this overlay's message listeners:
+  let mlManager = new ManagerForMessageListeners(OverlayEnvironment,
+                                                 window.messageManager);
+
+
   let initialized = false;
 
   let toolbarButtonId = "requestpolicyToolbarButton";
@@ -162,160 +171,178 @@ requestpolicy.overlay = (function() {
    *          event
    */
   self.onWindowLoad = function() {
-    //try {
-      // Info on detecting page load at:
-      // http://developer.mozilla.org/En/Code_snippets/On_page_load
-      var appcontent = $id("appcontent"); // browser
-      const requestpolicyOverlay = this;
-      if (appcontent) {
-        if (isFennec) {
-          appcontent.addEventListener("TabSelect", function(event) {
-            requestpolicyOverlay.tabChanged();
-          }, false);
-        }
+    OverlayEnvironment.startup();
+  };
+
+  function addAppcontentTabSelectListener() {
+    // Info on detecting page load at:
+    // http://developer.mozilla.org/En/Code_snippets/On_page_load
+    var appcontent = $id("appcontent"); // browser
+    if (appcontent) {
+      if (isFennec) {
+        OverlayEnvironment.elManager.addListener(appcontent, "TabSelect",
+                                                 self.tabChanged, false);
       }
+    }
+  }
+  OverlayEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                        addAppcontentTabSelectListener);
 
+  /**
+   * Add an event listener for when the contentAreaContextMenu (generally
+   * the right-click menu within the document) is shown.
+   */
+  function addContextMenuListener() {
+    var contextMenu = $id("contentAreaContextMenu");
+    if (contextMenu) {
+      OverlayEnvironment.elManager.addListener(contextMenu, "popupshowing",
+                                               self._contextMenuOnPopupShowing,
+                                               false);
+    }
+  }
+  OverlayEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                        addContextMenuListener);
+
+  function addTabContainerTabSelectListener() {
+    // We consider the default place for the popup to be attached to the
+    // context menu, so attach it there.
+    //self._attachPopupToContextMenu();
+
+    // Listen for the user changing tab so we can update any notification or
+    // indication of blocked requests.
+    if (!isFennec) {
+      var container = gBrowser.tabContainer;
+
+      let tabSelectCallback = function(event) {
+        self.tabChanged();
+      };
+
+      OverlayEnvironment.elManager.addListener(container, "TabSelect",
+                                               tabSelectCallback, false);
+
+      // maybe it's not necessary anymore to wrap the tab. if it's needed,
+      // the wrapping needs to be undone at shutdown!
+      //self._wrapAddTab();
+      self._addLocationObserver();
+      self._addHistoryObserver();
+    }
+  }
+  OverlayEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                        addTabContainerTabSelectListener);
 
 
-      messageManager.addMessageListener(
-          C.MMID + ":notifyDocumentLoaded",
-          function(message) {
-            dump("notifyDocumentLoaded\n\n");
-            let {docID, documentURI} = message.data;
-
-            // the <browser> element of the corresponding tab.
-            let browser = message.target;
-
-            if (rpPrefBranch.getBoolPref("indicateBlockedObjects")) {
-              var indicateBlacklisted = rpPrefBranch
-                  .getBoolPref("indicateBlacklistedObjects");
-
-              var rejectedRequests = RequestProcessor._rejectedRequests
-                  .getOriginUri(documentURI);
-              let blockedURIs = {};
-              for (var destBase in rejectedRequests) {
-                for (var destIdent in rejectedRequests[destBase]) {
-                  for (var destUri in rejectedRequests[destBase][destIdent]) {
-                    // case 1: indicateBlacklisted == true
-                    //         ==> indicate the object has been blocked
-                    //
-                    // case 2: indicateBlacklisted == false
-                    // case 2a: all requests have been blocked because of a blacklist
-                    //          ==> do *not* indicate
-                    //
-                    // case 2b: at least one of the blocked (identical) requests has been
-                    //          blocked by a rule *other than* the blacklist
-                    //          ==> *do* indicate
-                    let requests = rejectedRequests[destBase][destIdent][destUri];
-                    if (indicateBlacklisted ||
-                        requestpolicyOverlay._containsNonBlacklistedRequests(
-                            requests)) {
-                      blockedURIs[destUri] = blockedURIs[destUri] ||
-                          {identifier: DomainUtil.getIdentifier(destUri)};
-                    }
-                  }
-                }
-              }
-              message.target.messageManager.sendAsyncMessage(
-                  C.MMID + ":indicateBlockedVisibleObjects",
-                  {blockedURIs: blockedURIs, docID: docID});
-            }
 
-            if ("requestpolicy" in browser &&
-                documentURI in browser.requestpolicy.blockedRedirects) {
-              // bad smell: do not save blocked requests in the <browser> obj
-              var dest = browser.requestpolicy.blockedRedirects[documentURI];
-              Logger.warning(Logger.TYPE_HEADER_REDIRECT,
-                  "Showing notification for blocked redirect. To <" + dest +
-                  "> " + "from <" + documentURI + ">");
-              self._showRedirectNotification(browser, dest);
 
-              delete browser.requestpolicy.blockedRedirects[documentURI];
+  mlManager.addListener("notifyDocumentLoaded", function(message) {
+    let {docID, documentURI} = message.data;
+
+    // the <browser> element of the corresponding tab.
+    let browser = message.target;
+
+    if (rpPrefBranch.getBoolPref("indicateBlockedObjects")) {
+      var indicateBlacklisted = rpPrefBranch
+          .getBoolPref("indicateBlacklistedObjects");
+
+      var rejectedRequests = RequestProcessor._rejectedRequests
+          .getOriginUri(documentURI);
+      let blockedURIs = {};
+      for (var destBase in rejectedRequests) {
+        for (var destIdent in rejectedRequests[destBase]) {
+          for (var destUri in rejectedRequests[destBase][destIdent]) {
+            // case 1: indicateBlacklisted == true
+            //         ==> indicate the object has been blocked
+            //
+            // case 2: indicateBlacklisted == false
+            // case 2a: all requests have been blocked because of a blacklist
+            //          ==> do *not* indicate
+            //
+            // case 2b: at least one of the blocked (identical) requests has been
+            //          blocked by a rule *other than* the blacklist
+            //          ==> *do* indicate
+            let requests = rejectedRequests[destBase][destIdent][destUri];
+            if (indicateBlacklisted ||
+                self._containsNonBlacklistedRequests(requests)) {
+              blockedURIs[destUri] = blockedURIs[destUri] ||
+                  {identifier: DomainUtil.getIdentifier(destUri)};
             }
-          });
-
-      messageManager.addMessageListener(
-          C.MMID + ":notifyTopLevelDocumentLoaded",
-          function (message) {
-            // Clear any notifications that may have been present.
-            self._setContentBlockedState(false);
-            // We don't do this immediately anymore because slow systems might have
-            // this slow down the loading of the page, which is noticable
-            // especially with CSS loading delays (it's not unlikely that slow
-            // webservers have a hand in this, too).
-            // Note that the change to _updateBlockedContentStateAfterTimeout seems to have
-            // added a bug where opening a blank tab and then quickly switching back
-            // to the original tab can cause the original tab's blocked content
-            // notification to be cleared. A simple compensation was to decrease
-            // the timeout from 1000ms to 250ms, making it much less likely the tab
-            // switch can be done in time for a blank opened tab. This isn't a real
-            // solution, though.
-            self._updateBlockedContentStateAfterTimeout();
-          });
-
-      messageManager.addMessageListener(
-          C.MMID + ":notifyDOMFrameContentLoaded",
-          function (message) {
-            // This has an advantage over just relying on the
-            // observeBlockedRequest() call in that this will clear a blocked
-            // content notification if there no longer blocked content. Another way
-            // to solve this would be to observe allowed requests as well as blocked
-            // requests.
-            blockedContentCheckLastTime = (new Date()).getTime();
-            self._stopBlockedContentCheckTimeout();
-            self._updateBlockedContentState(message.target);
-          });
-
-      messageManager.addMessageListener(C.MMID + ":handleMetaRefreshes",
-                                        self.handleMetaRefreshes);
-
-      messageManager.addMessageListener(
-          C.MMID + ":notifyLinkClicked", function (message) {
-              RequestProcessor.registerLinkClicked(message.data.origin,
-                                                   message.data.dest);
-          });
-
-      messageManager.addMessageListener(
-          C.MMID + ":notifyFormSubmitted", function (message) {
-              RequestProcessor.registerFormSubmitted(message.data.origin,
-                                                     message.data.dest);
-          });
-
-
-
-
-      // Add an event listener for when the contentAreaContextMenu (generally
-      // the right-click menu within the document) is shown.
-      var contextMenu = $id("contentAreaContextMenu");
-      if (contextMenu) {
-        contextMenu.addEventListener("popupshowing",
-            self._contextMenuOnPopupShowing, false);
+          }
+        }
       }
+      message.target.messageManager.sendAsyncMessage(
+          C.MM_PREFIX + "indicateBlockedVisibleObjects",
+          {blockedURIs: blockedURIs, docID: docID});
+    }
+
+    if ("requestpolicy" in browser &&
+        documentURI in browser.requestpolicy.blockedRedirects) {
+      // bad smell: do not save blocked requests in the <browser> obj
+      var dest = browser.requestpolicy.blockedRedirects[documentURI];
+      Logger.warning(Logger.TYPE_HEADER_REDIRECT,
+          "Showing notification for blocked redirect. To <" + dest +
+          "> " + "from <" + documentURI + ">");
+      self._showRedirectNotification(browser, dest);
+
+      delete browser.requestpolicy.blockedRedirects[documentURI];
+    }
+  });
+
+
+
+  mlManager.addListener("notifyTopLevelDocumentLoaded", function (message) {
+    // Clear any notifications that may have been present.
+    self._setContentBlockedState(false);
+    // We don't do this immediately anymore because slow systems might have
+    // this slow down the loading of the page, which is noticable
+    // especially with CSS loading delays (it's not unlikely that slow
+    // webservers have a hand in this, too).
+    // Note that the change to _updateBlockedContentStateAfterTimeout seems to have
+    // added a bug where opening a blank tab and then quickly switching back
+    // to the original tab can cause the original tab's blocked content
+    // notification to be cleared. A simple compensation was to decrease
+    // the timeout from 1000ms to 250ms, making it much less likely the tab
+    // switch can be done in time for a blank opened tab. This isn't a real
+    // solution, though.
+    self._updateBlockedContentStateAfterTimeout();
+  });
+
+
+
+  mlManager.addListener("notifyDOMFrameContentLoaded", function (message) {
+    // This has an advantage over just relying on the
+    // observeBlockedRequest() call in that this will clear a blocked
+    // content notification if there no longer blocked content. Another way
+    // to solve this would be to observe allowed requests as well as blocked
+    // requests.
+    blockedContentCheckLastTime = (new Date()).getTime();
+    self._stopBlockedContentCheckTimeout();
+    self._updateBlockedContentState(message.target);
+  });
+
+
+
+  mlManager.addListener("handleMetaRefreshes", function(message) {
+    self.handleMetaRefreshes(message);
+  });
+
+
+
+  mlManager.addListener("notifyLinkClicked", function (message) {
+    RequestProcessor.registerLinkClicked(message.data.origin,
+                                         message.data.dest);
+  });
+
+
+
+  mlManager.addListener("notifyFormSubmitted", function (message) {
+    RequestProcessor.registerFormSubmitted(message.data.origin,
+                                           message.data.dest);
+  });
 
-      // We consider the default place for the popup to be attached to the
-      // context menu, so attach it there.
-      //self._attachPopupToContextMenu();
-
-      // Listen for the user changing tab so we can update any notification or
-      // indication of blocked requests.
-      if (!isFennec) {
-        var container = gBrowser.tabContainer;
-        container.addEventListener("TabSelect", function(event) {
-          requestpolicyOverlay.tabChanged();
-        }, false);
-        self._wrapAddTab();
-        self._addLocationObserver();
-        self._addHistoryObserver();
-      }
 
-    //} catch (e) {
-    //  Logger.severeError("Fatal Error, " + e, e);
-    //  Logger.severeError(
-    //      "Unable to complete requestpolicy.overlay.onWindowLoad actions.");
-    //}
-  };
 
   self.handleMetaRefreshes = function(message) {
+    Logger.dump("Handling meta refreshes...");
+
     let {documentURI, metaRefreshes} = message.data;
     let browser = message.target;
 
@@ -526,7 +553,7 @@ requestpolicy.overlay = (function() {
             RequestProcessor.registerAllowedRedirect(
                 browser.documentURI.specIgnoringRef, redirectTargetUri);
 
-            browser.messageManager.sendAsyncMessage(C.MMID + ":setLocation",
+            browser.messageManager.sendAsyncMessage(C.MM_PREFIX + "setLocation",
                 {uri: redirectTargetUri});
           }
         },
@@ -990,7 +1017,7 @@ requestpolicy.overlay = (function() {
     if (rulesChanged || self._needsReloadOnMenuClose) {
       if (rpPrefBranch.getBoolPref("autoReload")) {
         let mm = gBrowser.selectedBrowser.messageManager;
-        mm.sendAsyncMessage(C.MMID + ":reload");
+        mm.sendAsyncMessage(C.MM_PREFIX + "reload");
       }
     }
     self._needsReloadOnMenuClose = false;
diff --git a/src/content/ui/request-log.filtering.js b/src/content/ui/request-log.filtering.js
index 4ed3ba8..30fb09c 100644
--- a/src/content/ui/request-log.filtering.js
+++ b/src/content/ui/request-log.filtering.js
@@ -40,6 +40,7 @@ window.requestpolicy.requestLog = (function (self) {
 
   let filterText = null;
 
+  // TODO: use the Window Environment instead
   let elements = WindowUtils.getElementsByIdOnLoad(window, {
         filterTextbox: "requestpolicy-requestLog-requestFilter",
         clearFilterButton: "requestpolicy-requestLog-clearFilter"
diff --git a/src/content/ui/request-log.js b/src/content/ui/request-log.js
index b04e38b..fb66f24 100644
--- a/src/content/ui/request-log.js
+++ b/src/content/ui/request-log.js
@@ -38,6 +38,15 @@ window.requestpolicy.requestLog = (function (self) {
   let {StringUtils} = ScriptLoader.importModule("lib/utils/strings");
   let {WindowUtils} = ScriptLoader.importModule("lib/utils/windows");
 
+  // create a new Environment for this window
+  var WinEnv = new Environment("WinEnv");
+  // The Environment has to be shut down when the content window gets unloaded.
+  WinEnv.shutdownOnUnload(window);
+  // start up right now, as there won't be any startup functions
+  WinEnv.startup();
+
+  let $id = window.document.getElementById.bind(window.document);
+
 
   self.isEmptyMessageDisplayed = true;
   self.rows = [];
@@ -45,8 +54,9 @@ window.requestpolicy.requestLog = (function (self) {
 
 
 
-  let init = function() {
-    // callback function – gets called when the tree is available.
+  function init() {
+    self.tree = $id("requestpolicy-requestLog-tree")
+
     self.tree.view = self.treeView;
 
     showLogIsEmptyMessage();
@@ -54,20 +64,15 @@ window.requestpolicy.requestLog = (function (self) {
     // Give the requestpolicy overlay direct access to the the request log.
     window.parent.requestpolicy.overlay.requestLog = self;
   }
-
-  WindowUtils.getElementsByIdOnLoad(window, {
-        tree: "requestpolicy-requestLog-tree"
-      }, self, init);
-
-
-
-
   function showLogIsEmptyMessage() {
     var message = StringUtils.$str("requestLogIsEmpty");
     var directions = StringUtils.$str("requestLogDirections");
     self.visibleRows.push([message, directions, false, ""]);
     self.treebox.rowCountChanged(0, 1);
-  };
+  }
+
+  // call init() on the window's "load" event
+  WinEnv.elManager.addListener(window, "load", init, false);
 
 
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/requestpolicy.git



More information about the Pkg-mozext-commits mailing list