[Pkg-mozext-commits] [requestpolicy] 272/280: Imported Upstream version 1.0.0~beta9.1+dfsg

David Prévot taffit at moszumanska.debian.org
Sat May 2 20:30:38 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 0e84f398a39a8f415cdad40b44bd7715f52eaed7
Merge: eeb28da c295bff
Author: David Prévot <taffit at debian.org>
Date:   Sat May 2 14:57:42 2015 -0400

    Imported Upstream version 1.0.0~beta9.1+dfsg

 LICENSE                                            |  526 +++++-
 META-INF/manifest.mf                               |  702 +++++++-
 META-INF/zigbert.rsa                               |  Bin 3293 -> 3293 bytes
 META-INF/zigbert.sf                                |  702 +++++++-
 README                                             |    2 +-
 bootstrap.js                                       |   78 +
 chrome.manifest                                    |   58 +-
 chrome/requestpolicy.jar!/content/menu.js          | 1208 --------------
 chrome/requestpolicy.jar!/content/overlay.js       | 1750 --------------------
 chrome/requestpolicy.jar!/content/overlay.xul      |  155 --
 chrome/requestpolicy.jar!/content/requestLog.js    |   99 --
 .../content/requestLogTreeView.js                  |  249 ---
 .../content/settings/advancedprefs.js              |  141 --
 .../content/settings/basicprefs.js                 |   84 -
 .../content/settings/defaultpolicy.js              |   82 -
 .../requestpolicy.jar!/locale/de/requestpolicy.dtd |   20 -
 .../locale/de/requestpolicy.properties             |  109 --
 .../locale/en-US/requestpolicy.dtd                 |   20 -
 .../requestpolicy.jar!/locale/eo/requestpolicy.dtd |   20 -
 .../locale/es-MX/requestpolicy.dtd                 |   20 -
 .../requestpolicy.jar!/locale/eu/requestpolicy.dtd |   20 -
 .../requestpolicy.jar!/locale/fr/requestpolicy.dtd |   20 -
 .../requestpolicy.jar!/locale/it/requestpolicy.dtd |   20 -
 .../requestpolicy.jar!/locale/ja/requestpolicy.dtd |   20 -
 .../locale/ja/requestpolicy.properties             |  109 --
 .../locale/ko-KR/requestpolicy.dtd                 |   20 -
 .../locale/lv-LV/requestpolicy.dtd                 |   20 -
 .../requestpolicy.jar!/locale/nl/requestpolicy.dtd |   20 -
 .../locale/pt-BR/requestpolicy.dtd                 |   20 -
 .../locale/ru-RU/requestpolicy.dtd                 |   20 -
 .../locale/sk-SK/requestpolicy.dtd                 |   20 -
 .../locale/sv-SE/requestpolicy.dtd                 |   20 -
 .../requestpolicy.jar!/locale/tr/requestpolicy.dtd |   20 -
 .../locale/uk-UA/requestpolicy.dtd                 |   20 -
 .../locale/zh-CN/requestpolicy.dtd                 |   20 -
 .../locale/zh-TW/requestpolicy.dtd                 |   20 -
 components/requestpolicyService.js                 | 1137 -------------
 .../lib/default-preferences.js                     |    2 +
 content/lib/environment.jsm                        |  517 ++++++
 content/lib/environment.process.js                 |  217 +++
 .../lib/framescript-to-overlay-communication.jsm   |  166 ++
 .../lib/gui-location.jsm                           |   36 +-
 content/lib/http-response.jsm                      |  182 ++
 content/lib/logger.jsm                             |  191 +++
 content/lib/manager-for-event-listeners.jsm        |  153 ++
 content/lib/manager-for-message-listeners.jsm      |  187 +++
 content/lib/observer-manager.jsm                   |  184 ++
 content/lib/policy-manager.alias-functions.js      |   99 ++
 .../lib/policy-manager.jsm                         |  251 +--
 content/lib/prefs.jsm                              |  150 ++
 content/lib/request-processor.compat.js            |  321 ++++
 content/lib/request-processor.jsm                  | 1076 ++++++++++++
 content/lib/request-processor.redirects.js         |  414 +++++
 .../lib/request-result.jsm                         |   10 +-
 content/lib/request-set.jsm                        |  242 +++
 modules/Request.jsm => content/lib/request.jsm     |  191 +--
 .../lib/ruleset-storage.jsm                        |   42 +-
 modules/Ruleset.jsm => content/lib/ruleset.jsm     |  186 ++-
 content/lib/script-loader.jsm                      |  210 +++
 .../lib/subscription.jsm                           |   48 +-
 content/lib/utils.jsm                              |  193 +++
 content/lib/utils/constants.jsm                    |   55 +
 content/lib/utils/dom.jsm                          |   51 +
 .../lib/utils/domains.jsm                          |  157 +-
 .../FileUtil.jsm => content/lib/utils/files.jsm    |  104 +-
 content/lib/utils/observers.jsm                    |   93 ++
 content/lib/utils/strings.jsm                      |   75 +
 content/lib/utils/windows.jsm                      |  148 ++
 content/lib/utils/xul.jsm                          |  176 ++
 content/main/about-uri.jsm                         |  130 ++
 content/main/content-policy.jsm                    |  188 +++
 content/main/default-pref-handler.js               |  115 ++
 content/main/pref-manager.jsm                      |  146 ++
 content/main/requestpolicy-service.jsm             |  234 +++
 content/main/window-manager-toolbarbutton.js       |  153 ++
 content/main/window-manager.jsm                    |  238 +++
 content/main/window-manager.listener.js            |  151 ++
 .../settings/advancedprefs.html                    |   45 +-
 content/settings/advancedprefs.js                  |  134 ++
 .../content => content}/settings/basicprefs.html   |   27 +-
 content/settings/basicprefs.js                     |   80 +
 .../content => content}/settings/common.js         |  100 +-
 .../settings/defaultpolicy.html                    |   42 +-
 content/settings/defaultpolicy.js                  |   80 +
 .../content => content}/settings/oldrules.html     |   29 +-
 .../content => content}/settings/oldrules.js       |   39 +-
 .../content => content}/settings/settings.css      |    0
 .../content => content}/settings/setup.css         |    0
 .../content => content}/settings/setup.html        |   15 +-
 .../content => content}/settings/setup.js          |   74 +-
 .../settings/subscriptions.html                    |   43 +-
 .../content => content}/settings/subscriptions.js  |   40 +-
 .../content => content}/settings/yourpolicy.html   |   39 +-
 .../content => content}/settings/yourpolicy.js     |   80 +-
 .../content => content/ui}/classicmenu.js          |  105 +-
 content/ui/frame.blocked-content.js                |   97 ++
 content/ui/frame.dom-content-loaded.js             |  316 ++++
 content/ui/frame.js                                |  165 ++
 content/ui/menu.js                                 | 1216 ++++++++++++++
 content/ui/overlay.js                              | 1225 ++++++++++++++
 content/ui/request-log.filtering.js                |  111 ++
 content/ui/request-log.interface.js                |  135 ++
 content/ui/request-log.js                          |   82 +
 content/ui/request-log.tree-view.js                |  186 +++
 .../requestLog.xul => content/ui/request-log.xul   |   44 +-
 content/ui/xul-trees.js                            |  186 +++
 install.rdf                                        |   17 +-
 locale/de/requestpolicy.dtd                        |   12 +
 locale/de/requestpolicy.properties                 |  124 ++
 locale/en-US/requestpolicy.dtd                     |   12 +
 .../en-US/requestpolicy.properties                 |   17 +-
 locale/eo/requestpolicy.dtd                        |   12 +
 .../locale => locale}/eo/requestpolicy.properties  |   17 +-
 locale/es-MX/requestpolicy.dtd                     |   12 +
 .../es-MX/requestpolicy.properties                 |   17 +-
 locale/eu/requestpolicy.dtd                        |   12 +
 .../locale => locale}/eu/requestpolicy.properties  |   17 +-
 locale/fr/requestpolicy.dtd                        |   12 +
 .../locale => locale}/fr/requestpolicy.properties  |   17 +-
 locale/it/requestpolicy.dtd                        |   12 +
 .../locale => locale}/it/requestpolicy.properties  |   17 +-
 locale/ja/requestpolicy.dtd                        |   12 +
 locale/ja/requestpolicy.properties                 |  124 ++
 locale/ko-KR/requestpolicy.dtd                     |   12 +
 .../ko-KR/requestpolicy.properties                 |   17 +-
 locale/lv-LV/requestpolicy.dtd                     |   12 +
 .../lv-LV/requestpolicy.properties                 |   17 +-
 locale/nl/requestpolicy.dtd                        |   12 +
 .../locale => locale}/nl/requestpolicy.properties  |   17 +-
 locale/pt-BR/requestpolicy.dtd                     |   12 +
 .../pt-BR/requestpolicy.properties                 |   17 +-
 locale/ru-RU/requestpolicy.dtd                     |   12 +
 .../ru-RU/requestpolicy.properties                 |   17 +-
 locale/sk-SK/requestpolicy.dtd                     |   12 +
 .../sk-SK/requestpolicy.properties                 |   17 +-
 locale/sv-SE/requestpolicy.dtd                     |   12 +
 .../sv-SE/requestpolicy.properties                 |   17 +-
 locale/tr/requestpolicy.dtd                        |   12 +
 .../locale => locale}/tr/requestpolicy.properties  |   17 +-
 locale/uk-UA/requestpolicy.dtd                     |   12 +
 .../uk-UA/requestpolicy.properties                 |   17 +-
 locale/zh-CN/requestpolicy.dtd                     |   12 +
 .../zh-CN/requestpolicy.properties                 |   17 +-
 locale/zh-TW/requestpolicy.dtd                     |   12 +
 .../zh-TW/requestpolicy.properties                 |   17 +-
 modules/JSON.jsm                                   |   81 -
 modules/Logger.jsm                                 |  117 --
 modules/Prompter.jsm                               |   33 -
 modules/RequestProcessor.jsm                       | 1209 --------------
 modules/RequestUtil.jsm                            |  426 -----
 modules/Services.jsm                               |   31 -
 modules/Util.jsm                                   |   66 -
 {chrome/requestpolicy.jar!/skin => skin}/close.png |  Bin
 {chrome/requestpolicy.jar!/skin => skin}/dot.png   |  Bin
 .../initialSetup.css => skin/initial-setup.css     |    0
 .../skin => skin}/menu-allowed.svg                 |    0
 .../skin => skin}/menu-blocked.svg                 |    0
 .../skin => skin}/menu-default.svg                 |    0
 .../skin => skin}/menu-other-origins.png           |  Bin
 .../skin/requestLog.css => skin/request-log.css    |    0
 .../requestpolicy-icon-24-allowed.png              |  Bin
 .../requestpolicy-icon-24-blocked.png              |  Bin
 .../requestpolicy-icon-24-disabled.png             |  Bin
 .../requestpolicy-icon-32-allowed.png              |  Bin
 .../requestpolicy-icon-32-blocked.png              |  Bin
 .../requestpolicy-icon-32-disabled.png             |  Bin
 .../skin => skin}/requestpolicy-icon-32.png        |  Bin
 .../skin => skin}/requestpolicy-icon-allowed.png   |  Bin
 .../skin => skin}/requestpolicy-icon-blocked.png   |  Bin
 .../skin => skin}/requestpolicy-icon-disabled.png  |  Bin
 .../requestpolicy-statusbar-allowed.png            |  Bin
 .../requestpolicy-statusbar-blocked.png            |  Bin
 .../requestpolicy-statusbar-disabled.png           |  Bin
 .../skin => skin}/requestpolicy.css                |   98 --
 skin/toolbarbutton-seamonkey.css                   |   64 +
 skin/toolbarbutton.css                             |  164 ++
 176 files changed, 14258 insertions(+), 8601 deletions(-)

diff --cc META-INF/manifest.mf
index b8545ef,0000000..18d3eff
mode 100644,000000..100644
--- a/META-INF/manifest.mf
+++ b/META-INF/manifest.mf
@@@ -1,118 -1,0 +1,688 @@@
 +Manifest-Version: 1.0
 +Created-By: Signtool (signtool 3.17.2 Basic ECC)
 +Comments: PLEASE DO NOT EDIT THIS FILE. YOU WILL BREAK IT.
 +
- Name: chrome/requestpolicy.jar
++Name: content/main/pref-manager.jsm
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: aTjT/ygvKwjupz4DwBpKmg==
- SHA1-Digest: wYEM74kZPp0rX4AN6RZ0OcQg9yE=
++MD5-Digest: qaEcU+XGZ+q5kKuX49xaMA==
++SHA1-Digest: FZ93kmh7wZrNYKjHVo3sWZ6xriU=
 +
- Name: defaults/preferences/defaults.js
++Name: content/main/about-uri.jsm
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: IBsOABUBNIaD8y4JGVVSRg==
- SHA1-Digest: lgTc31x9iqv30DnEghQhjhkjF+I=
++MD5-Digest: XvCAmgDFnHjenEV3Nx3Cgg==
++SHA1-Digest: 7Q+avDVT3L6PCg59jtEw8IpYcvk=
 +
- Name: README
++Name: content/main/requestpolicy-service.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: RF+r7QMFgTjC5U9upJ65BA==
++SHA1-Digest: BIgNH0hOKfjqghGNB18C2qQOcSo=
++
++Name: content/main/window-manager.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: dwPnAYf9Eh+nQdqQZHx0ZQ==
++SHA1-Digest: qoGBZCC018L11tsM1KzF8vnThlE=
++
++Name: content/main/window-manager.listener.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: miZk3iC65O67HodkGln9Vg==
++SHA1-Digest: hkdB7YXsN0FHIrRt7SnuYxYb3qs=
++
++Name: content/main/window-manager-toolbarbutton.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: I7wn/EAyyp2XFXv0c1eZvQ==
++SHA1-Digest: ME2Iz/dCWuYyzAUjgwc2Ie+qsts=
++
++Name: content/main/default-pref-handler.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: nwbIgibWPf0+49hsvM6FqA==
++SHA1-Digest: hAW/iaKpN8SiDk2KBrTG3m03BoA=
++
++Name: content/main/content-policy.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: CHIIi6Hr5dRwcHDk+LQgnQ==
++SHA1-Digest: 0qsJngcTpb1Wqi+3KNWwxxZ6tJM=
++
++Name: content/ui/overlay.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: R5LvIWVe1mKHFdSwNpQkyQ==
++SHA1-Digest: 3a3l1l3gFRURVjlCUYGe2VpRK0Q=
++
++Name: content/ui/frame.dom-content-loaded.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: auYD1TFKZkLESRYL0T1RXA==
++SHA1-Digest: 3Jq0bJdyYr9gQBBlsOISwHfeKyA=
++
++Name: content/ui/frame.blocked-content.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: mYQw56+9+Xcn6q3tz5BdzA==
++SHA1-Digest: v5/10F+mJ8/7ReCsmI4HwQUpHlE=
++
++Name: content/ui/request-log.xul
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: LKlT8I1UQKG1m/OEI5EVXA==
++SHA1-Digest: f9HZWLQKD7hU3Cm58cNpPDHVnUg=
++
++Name: content/ui/request-log.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: dFovAT0NBeInAGl0sDdaFA==
++SHA1-Digest: f5b6+xa71Bfd66UqFSYU2RI47cI=
++
++Name: content/ui/frame.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: nCottjXtAmBym28s2OJpqw==
++SHA1-Digest: +7XgKrOytQLf0LkGoowr/xPz7FI=
++
++Name: content/ui/request-log.interface.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: tvMnvvM15vaFzApTEXCxvg==
++SHA1-Digest: v0+KmH7g21NmvbPVXKKAmHX3GtI=
++
++Name: content/ui/request-log.tree-view.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 52tyES9OLfBP9PLm1pPp7w==
++SHA1-Digest: ptMO8CJbInLJL+3tH6QbJjIBdWU=
++
++Name: content/ui/request-log.filtering.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: bKqtT7dxrjqI7FXp7NcUKw==
++SHA1-Digest: yZGcUhiNZVqOtTcETQat3qetQ9k=
++
++Name: content/ui/xul-trees.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: dV3uuyYdD0ioRxhApjoZOA==
++SHA1-Digest: IgjnLTJtQxqH5WASycaFzNaYIes=
++
++Name: content/ui/menu.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: QK8dd30y/iHBXC4isyelxQ==
++SHA1-Digest: wzdd05OS0pWdtPkOTTRf0ZLrZMI=
++
++Name: content/ui/classicmenu.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: JfmnE0g4lHLawWUAcgxJ9w==
++SHA1-Digest: lnsQnL0NQv3TIFB3W41eWMrYjKM=
++
++Name: content/lib/ruleset-storage.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: bYxnWXZ63ScmTMLg+VdRUg==
++SHA1-Digest: 7KrI6vQyggF+A/n3BRNAfGOdH84=
++
++Name: content/lib/policy-manager.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ZSlOd8PyR/MyzfOou36b3g==
++SHA1-Digest: CdHWzdGuqNSyUEOR3uWMDB4WBgQ=
++
++Name: content/lib/manager-for-message-listeners.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: qGZN/5ZR0u9Ln9gDjfSIOQ==
++SHA1-Digest: JGagsCy2RaszH+Kky6sLn+uENgI=
++
++Name: content/lib/ruleset.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: IRp67PHX/EHU3Er4wmT5Kw==
++SHA1-Digest: soabMCaR4JflvnjuhIiZ2SFb+DU=
++
++Name: content/lib/prefs.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: MOs1W9+Fq7oePGQEh3qIKQ==
++SHA1-Digest: m0vI08LULvIyY5ADKw/JUNkk2tU=
++
++Name: content/lib/gui-location.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: GoE2Qx0qZ0bnbRiF/UmB8Q==
++SHA1-Digest: r/s6Y+e6bbmfMQevFXDRKeWfdKU=
++
++Name: content/lib/request.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: WrrtzCGt/dIUnSt2/NSggA==
++SHA1-Digest: KBtV00utrf4cdhkDNf2SrPZQkws=
++
++Name: content/lib/observer-manager.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 31Z4XMCNt2ITBnTcMSWzew==
++SHA1-Digest: pugtpqvjFnET7MyCOUYl2TuuR38=
++
++Name: content/lib/environment.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: g7ZQgxO+CzvuIn0fYs7Tsw==
++SHA1-Digest: ykABX0ACQQ5Df40X92gtMvMeyew=
++
++Name: content/lib/request-processor.redirects.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ak/zQY7RiBSpjnjZUNghmA==
++SHA1-Digest: tGGQB0yFymekmKfHditBBiIRBCc=
++
++Name: content/lib/request-result.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: wsa4Pka+ZpXcBSOs991WiA==
++SHA1-Digest: Gn4bvK2HjzZoHMB1GmVH87INzc8=
++
++Name: content/lib/utils/domains.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: tQ5FvvSfz18WN4/B86KkSw==
++SHA1-Digest: sEQB7n7q3TvjSDJMRPvhF6Z2heo=
++
++Name: content/lib/utils/files.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: b1Fpp6Ctd55aqkmfvuSXyA==
++SHA1-Digest: XyB5e7Mp5Jvi0s1aeWHR7k87xcM=
++
++Name: content/lib/utils/windows.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 2zajnJKJA24SAR6pM8VtXA==
++SHA1-Digest: rZkPXzIz92e9osx+mxGMtayftGE=
++
++Name: content/lib/utils/constants.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: DmU+88GnN/GGvF5eh2SBUg==
++SHA1-Digest: LFOdeu1FJ7pSBps0FLHXf1H7urU=
++
++Name: content/lib/utils/xul.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: z/wimVHrka+9v8Kw5e7Dag==
++SHA1-Digest: feKYjMJQKooYIkYElbxObgeHU6A=
++
++Name: content/lib/utils/strings.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: K2HsdWVlSddV0vq2bIhr3Q==
++SHA1-Digest: UHSuTCKYTQBirABMdotoT6cYp40=
++
++Name: content/lib/utils/dom.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: U1y5YwWhwAV6LpVncxgvEw==
++SHA1-Digest: 3g5j8dfxLsZpB693dsEFAJo5pks=
++
++Name: content/lib/utils/observers.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: HGU7YL1Ba3qGkNJGxRrbzQ==
++SHA1-Digest: /C6Z4pvlQd3O1D7o6sQAwCxcToA=
++
++Name: content/lib/subscription.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: LSmZZOxgBlsu8/5GrLozFQ==
++SHA1-Digest: RDKTuPlinnnz3lAhQfBGA6gbwNU=
++
++Name: content/lib/utils.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: NvE0kWlmFhlruYDUuN1z1w==
++SHA1-Digest: ygjKZz/nrOsLaHWq/sAaelLb6JA=
++
++Name: content/lib/default-preferences.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: nHOfxoGV7+KfjKTbFQtAPQ==
++SHA1-Digest: Ysdpf+pJpQZPot5PEA+SS1lBXVw=
++
++Name: content/lib/request-set.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: TG5RHNLFyUjEQgF2GhSl7g==
++SHA1-Digest: SULFw69lrwgNPayh/jy89rbkUrE=
++
++Name: content/lib/policy-manager.alias-functions.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: jYjdlbe2edw/0/iiAYh+Bg==
++SHA1-Digest: /uzAHaEyttWJnSyo1uhPgh/1f6o=
++
++Name: content/lib/logger.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: dty/DgjMGBxZAdnXaDutiw==
++SHA1-Digest: cLyFnegiO1a6MH5oqOjheKRpJus=
++
++Name: content/lib/request-processor.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: pLlz2OE0QaRFM9lldVSOyg==
++SHA1-Digest: HI1FsWmpXqgLNo34kHngcX3F8n0=
++
++Name: content/lib/framescript-to-overlay-communication.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: mwIl823JXV0IcOq9sVzikQ==
++SHA1-Digest: 6AX7pKlJFHI3xfX2RbEITShD2/Y=
++
++Name: content/lib/http-response.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 6dCM5zSVDe1LdzZYGf19YA==
++SHA1-Digest: LzxtbXHypmXp9g2u/CdNHY8OBKs=
++
++Name: content/lib/manager-for-event-listeners.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Yl+HZU90wU6jw+IWU47NxA==
++SHA1-Digest: I0wFIcHFxBOYH+60mExIpeC0Wsg=
++
++Name: content/lib/environment.process.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: PKljIll8ECwkO/jpUHo+9Q==
++SHA1-Digest: 8OX+1R/c5+rPKPxEBImdcWZHfCE=
++
++Name: content/lib/request-processor.compat.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: I62EQRGd9jNr9yoUKDxOJw==
++SHA1-Digest: HMWgg2AS+0VXPP2hXnUVSPZ2aPs=
++
++Name: content/lib/script-loader.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 1JGpf8hIwthcilKJYatrhg==
++SHA1-Digest: 6pLxyMV6ce2KAuUpvhYGO9lzWZ0=
++
++Name: content/settings/subscriptions.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: vGoe2o430BB0u2Ga+1WCzA==
++SHA1-Digest: GbYfl99OCcwBR3m4iUHR8e5ZDew=
++
++Name: content/settings/setup.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 1B2M2Y8AsgTpgAmY7PhCfg==
++SHA1-Digest: 2jmj7l5rSw0yVb/vlWAYkK/YBwk=
++
++Name: content/settings/common.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 3wjE4FayeCO2voQNEne4Bg==
++SHA1-Digest: hEeOd3d8eeO41vYPI+BeGFt3DMU=
++
++Name: content/settings/advancedprefs.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: nUWJKjURjmD6fnbcEiSMJw==
++SHA1-Digest: AYBFGx2mvwq63YJkkJHVEegOG/0=
++
++Name: content/settings/setup.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: dXpITARU9yDW35mrGByoUg==
++SHA1-Digest: afKOofQJYc1FraRajCg1T8sZTt0=
++
++Name: content/settings/subscriptions.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 9O63R4/91LbGQom/6mIWXw==
++SHA1-Digest: 5tLA94rjwdm4TFmFrZx71tSgPrU=
++
++Name: content/settings/basicprefs.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: l239BOUiFMBYq+EeYu1cXQ==
++SHA1-Digest: dVzTM566MbEZp76tKu/hqNOYJWo=
++
++Name: content/settings/defaultpolicy.html
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: 32oxpYH4KoNUWGq0sY44MA==
- SHA1-Digest: g/E8iypzREFNMsM39YoBewJAR7s=
++MD5-Digest: DvlHUgo/AKfT2SoCzyRcIg==
++SHA1-Digest: SSLoqAau/54tJlNrxjfPrQTflvI=
 +
- Name: components/requestpolicyService.js
++Name: content/settings/yourpolicy.js
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: C5L6kAr21uXgQhknlmqAiw==
- SHA1-Digest: eNC9HC9SgsLMwKtynDoeYBtZMow=
++MD5-Digest: ZlaVoYyAYmO86w2lMy9LnA==
++SHA1-Digest: 53TpHzktsgmWjX7b71YtafKum4E=
++
++Name: content/settings/oldrules.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: lUM194TKHEmkOlOzEuJWnw==
++SHA1-Digest: 6tvQ5MCsHJHlMGxuB077F7yIC+0=
++
++Name: content/settings/jquery.min.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: TxNjnTNZXvJw9hFQkAhB6Q==
++SHA1-Digest: jacHesddtmkZTyT/NOy74s7jwoU=
++
++Name: content/settings/defaultpolicy.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: AeHp46dxnsGMDZ/2J1WG6A==
++SHA1-Digest: DNbKh1KCcZSxC5Lh9oyVw+QPUXE=
++
++Name: content/settings/yourpolicy.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Xc5ohNMbWioD1X2dUUFT8g==
++SHA1-Digest: JpO632zIPlqfLRMaiLXoihplPaY=
++
++Name: content/settings/oldrules.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: qtdsy09pWo7hn4Q+Hr28Ow==
++SHA1-Digest: RlomqWm7Qqd6s+Teb/Eb4KlvjUc=
++
++Name: content/settings/setup.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 1pnFL1mCkXyth1L9vWzQhA==
++SHA1-Digest: AADjy5Aswhd719uoH04roTd/E6w=
++
++Name: content/settings/settings.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: w6wNTVHjUVJzaH6EmPrItQ==
++SHA1-Digest: XFLmd5icAUDEtJ3jlHlpxgKdXj8=
++
++Name: content/settings/basicprefs.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: L5h06IQ3gqyKEhmXqPv8Uw==
++SHA1-Digest: C+7JP0vPPDoZsPo26eUcG98OaxI=
++
++Name: content/settings/advancedprefs.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: m8VBBIkukJXTHW/8wkpcDw==
++SHA1-Digest: aIht/dvxYtcePYG3szEF1rNYHNY=
++
++Name: README
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 6IfZq3sTTDTENL+Se1ZoIA==
++SHA1-Digest: iDJpM8K+fZKkpeqnpi0kVupmPDk=
 +
 +Name: install.rdf
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: D6D9vL7r++VRQX9iDpV1lQ==
- SHA1-Digest: qvEse8Guni/WQWfDykWt5BUYRno=
++MD5-Digest: TJ3paHwjO9WXVsvbrRmEVg==
++SHA1-Digest: RrEa4ypt0qXMifoqxm7PI5hqpMU=
 +
 +Name: LICENSE
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: /YHJAxMA510Jme5F86jn2A==
- SHA1-Digest: tITdPM3Z5CuWoHPmuuwzVwTlv6s=
++MD5-Digest: V+iUvQ8339+uK2nUxBt4oQ==
++SHA1-Digest: BKr7OaEqkHyN92EAoQgXBoUWHk4=
++
++Name: locale/tr/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: YHGevfSYcNwB5aR/DjrRLQ==
++SHA1-Digest: 7BKNoiJcmmRhC+PF2+2GaFOiNBI=
++
++Name: locale/tr/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 8VMm/0yULog/NuMgM4QcoQ==
++SHA1-Digest: ZQUBp7O5zZ0I4nXPlABNZObvDcc=
++
++Name: locale/uk-UA/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: oGWBoRlf+qVuz66wmB/J2Q==
++SHA1-Digest: Qucwrc3cqcj6CZhVG99sQWh+2LY=
++
++Name: locale/uk-UA/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Tk1CVFfpYGdF8G1C02R4qg==
++SHA1-Digest: KS2CiBLX1sQW+d0vLOKjwQaTlHM=
 +
- Name: modules/RequestResult.jsm
++Name: locale/eu/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: cIgpDI9lF3WpQl6fkW1vIg==
- SHA1-Digest: nRn1agGp8auUZNvf/qQMLNwKNIU=
++MD5-Digest: C4s7AtLXDSyI2Wy0OrJb2w==
++SHA1-Digest: U3N8NZX4Mz2Q6MrBnf4nxhRRo2c=
 +
- Name: modules/Logger.jsm
++Name: locale/eu/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: iqhFoovgBCDmaDeLCTHbpg==
- SHA1-Digest: +dZ6Od0P0gNDLQ4usTw0K+zacrQ=
++MD5-Digest: 1PUm3ipXh0Y904ADOEgd9w==
++SHA1-Digest: lf+1Vtt1UvoDC/CY7yyIj4Tr3wE=
 +
- Name: modules/Util.jsm
++Name: locale/sk-SK/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: qRTcXSephxnsSXdIhyRcDQ==
- SHA1-Digest: n3CDD2sUStZN0zQga02RQYmWM9Q=
++MD5-Digest: /HrV/tEV1jqBNtrqcBs1WQ==
++SHA1-Digest: 20a1yeDGOjjAu9a4QNJiYcz5SeU=
 +
- Name: modules/RulesetStorage.jsm
++Name: locale/sk-SK/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: SAPk/vJ359sK52M9cjfesg==
- SHA1-Digest: okJtAVdPR1guIDoVAmAjB5JtSE0=
++MD5-Digest: KV5OqgFa8lDCRK8E2rYm0Q==
++SHA1-Digest: 3rLXSU4m3cqZS1iOgs5bOVqb0z8=
 +
- Name: modules/FileUtil.jsm
++Name: locale/ru-RU/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: zwyHqryKl+/X583ZQ8Urnw==
- SHA1-Digest: F1BPgSOn4NubRuPy8lBJdt/g31c=
++MD5-Digest: CwU7WRvYODMiRHmWxWylcw==
++SHA1-Digest: zmeQL5LH3Q0zo+cf3I8yTAjMVpI=
 +
- Name: modules/Request.jsm
++Name: locale/ru-RU/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: lUR8ElEvzPH4RDQm8DPEDA==
- SHA1-Digest: 6yHySOrHqlMGLaP0hhmJSvtXyLI=
++MD5-Digest: ruzbqwxPm8ZqODwkS/EKAQ==
++SHA1-Digest: eaGKLWiLVSVWaIuvP4gVUuAQWUM=
 +
- Name: modules/Subscription.jsm
++Name: locale/it/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: lSdZx3x+99UbvDVla32m4g==
- SHA1-Digest: JLNZvzikKKygFH3qk77xw+Ab3nY=
++MD5-Digest: iuaTQG8IWWJHMEqm8/wzeg==
++SHA1-Digest: ADrzsCPiLkT1ClP6Xp+nKwsQu2M=
 +
- Name: modules/DomainUtil.jsm
++Name: locale/it/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: STOWK+IdQ1RCUryKAPa2OA==
- SHA1-Digest: VvjO11Hsj2IzKrApy4LPUkPjZM8=
++MD5-Digest: WZRvu0vmoB1wEvXhzfEREA==
++SHA1-Digest: QFSNR3Lf9mdkyrPuB5sWFAnb8jQ=
 +
- Name: modules/Services.jsm
++Name: locale/eo/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: wstD9vYNy1Ma2Wv0Si+01Q==
- SHA1-Digest: VsfqNm8IBiV5S+UFq4FIwWnE4Vk=
++MD5-Digest: cpw0czZp2ImP3XqSkcOLew==
++SHA1-Digest: 8Hof6vgM3HoPcg+NbWGct5A5f+c=
 +
- Name: modules/Ruleset.jsm
++Name: locale/eo/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: s0xfqgSshQVt3P9RX9RRbA==
- SHA1-Digest: 73wOZfnfe4Ya1Kj42SVmLp8hkc0=
++MD5-Digest: LSsj6eSm5ULIAzZCiWu2Mw==
++SHA1-Digest: hd7uYaOxFZML+MAIFd62Tm4P4Ko=
 +
- Name: modules/Prompter.jsm
++Name: locale/sv-SE/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: DiDa+VdsJm8FlBiTbIrO7w==
- SHA1-Digest: 5+E4XARzCJdwCKqDE2X2NtNsfc8=
++MD5-Digest: 9Oby/weV3RG1PoVJy+8Few==
++SHA1-Digest: B5ZC1vu7d2RT001lQ74d4lYDJ0A=
 +
- Name: modules/GUILocation.jsm
++Name: locale/sv-SE/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: BEuqSgMUbSguyEPajYayUQ==
- SHA1-Digest: pN47sUo+1KfzzvNz3Cn72Qz0Hdo=
++MD5-Digest: NMq0OcAJm7qt3SpuvPouPQ==
++SHA1-Digest: RRebH1TdHjwvcUtWDWhIo+WKyZE=
 +
- Name: modules/RequestUtil.jsm
++Name: locale/ja/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: b3yLMzYE2OTnqqX5Ubhs4Q==
- SHA1-Digest: qArZ6/7VXDAAUToo6/WKg/d1k8E=
++MD5-Digest: D1qyBtMKfDUCxQ78Xclzew==
++SHA1-Digest: RsE6DQpQPXNKipnGtRGrVuYxvrU=
 +
- Name: modules/RequestProcessor.jsm
++Name: locale/ja/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: utMjsmQuvMTK19RMHsbz2g==
- SHA1-Digest: iABr60ZwJOvsItcz39UE4DVDnQo=
++MD5-Digest: 2bdw7BU8M6kswpme/CDT4g==
++SHA1-Digest: rqFMKxiRqivkpHyq9YA7k5oGTeY=
 +
- Name: modules/PolicyManager.jsm
++Name: locale/nl/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: Eg7HOKz25baGb19Ui1oUYw==
- SHA1-Digest: yQUlzZoE5kPsIBnwjMGxElM6EHc=
++MD5-Digest: 8MG3dZttG264EPhnB/K3+Q==
++SHA1-Digest: ZeyaN7q1wX+om//sU36NpYYIU8M=
 +
- Name: modules/JSON.jsm
++Name: locale/nl/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: 66tLBkirDbNGVScWVmuT0Q==
- SHA1-Digest: la4ccHi5IO/UdzZM1Sg2XGw4Euw=
++MD5-Digest: 5D97loMSiGuz4oM2Ei/h8w==
++SHA1-Digest: pyuahhoPx0PyMPdc0nmromZxTE0=
++
++Name: locale/zh-CN/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: T5Bqf7eJXZxkvt1kRf2WTw==
++SHA1-Digest: rsMS9bomSXElul3g2FIISZAq0CA=
++
++Name: locale/zh-CN/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: nDKYvUOrd+NdLAYAfXPF3A==
++SHA1-Digest: HaAotqsCbstWEndCRgNc3MEcWrc=
++
++Name: locale/zh-TW/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: PfmqQTFvc1tNhrhtngY7tQ==
++SHA1-Digest: MejY4QleZFjtH1FQRmulTz+bMcM=
++
++Name: locale/zh-TW/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 5Rly3fQBebz6oOIrZKJ2+Q==
++SHA1-Digest: 6v011Y6ZxypDMnM6FYJcdqnB/HM=
++
++Name: locale/pt-BR/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ygevhxxjopSuy+jHRvTQ4w==
++SHA1-Digest: G9v9ugU1Ksv5F+OmOvg/xpxedQ4=
++
++Name: locale/pt-BR/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: k1fkORM2keBYpJ6kr6OTnQ==
++SHA1-Digest: GZ+YDVwukV68huPON54XjF6xDbQ=
++
++Name: locale/fr/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: cU13QRB7T8ukgCiSoJgkmw==
++SHA1-Digest: 8ASkBmqZ2Im5sU7XfRJmghOc+Yg=
++
++Name: locale/fr/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: iqn/g0UH6jQQ+Q7fyh8Iig==
++SHA1-Digest: wSHwbDvjWZFbeZOi5AeIblSIaf8=
++
++Name: locale/de/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: P3bt/Rvk7IFSK+FeLqrf2Q==
++SHA1-Digest: fi2ZzsQfYcSnMwVvzuMjKmj5ABA=
++
++Name: locale/de/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: MnDaGK7QCVcPaG9jbZV+gw==
++SHA1-Digest: WN71NIJjbJS5HFiuGPgxiSkNRAI=
++
++Name: locale/en-US/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: z9mL9ndqOIJDSki2OAliIA==
++SHA1-Digest: oI+05rIfs3LkbVj2A8/ZybgtDSQ=
++
++Name: locale/en-US/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: a3WLK0QWcnXjMu4aEQJ6qw==
++SHA1-Digest: NdKVZzdktdK8cLLAdJFFGfBVxTs=
++
++Name: locale/ko-KR/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: awJU2LVBkGH5531QM3WseQ==
++SHA1-Digest: 0CW1gSuTZoPHkUo8QaBSiEtPJ5Y=
++
++Name: locale/ko-KR/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: DPTq0986Db/Cp41y9d32UA==
++SHA1-Digest: Gggeop7zQ7maK20sRPpNCSCoJOc=
++
++Name: locale/lv-LV/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: F9pyEdjG26OezWX4uIvt3Q==
++SHA1-Digest: qU+7HD6MIJakGnFqLLJzRv6hPkA=
++
++Name: locale/lv-LV/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: WdvWgwuH1Gpu62pTq/u0PA==
++SHA1-Digest: Gjh9HSILzCMvAeHb8BUR4PDI/uU=
++
++Name: locale/es-MX/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: p4AB7nYZ/Kw6d9gfDi8IoQ==
++SHA1-Digest: zq7CXn+GYo7De4Xt91GOeOS3qZg=
++
++Name: locale/es-MX/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: bfkA7maKj2bJ3s8goBGSSw==
++SHA1-Digest: KcD1qZXKeSEhoESrlMsaKvejrMk=
++
++Name: skin/requestpolicy-statusbar-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: DLfnmMXcX11H83/x1l+PkA==
++SHA1-Digest: JwE7jJrdz9dY3SDzPDN5cXyuIPE=
++
++Name: skin/dot.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 6ZQiOl6YR5jZzvD/M9z8ig==
++SHA1-Digest: RDgRl3pigBseZY3o2FgGlA4lhe4=
++
++Name: skin/request-log.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 5WbDhFzsSSD+m7QrHUNnBQ==
++SHA1-Digest: vJPXdQg8S6qzNtqUS9wrTpThxas=
++
++Name: skin/requestpolicy-icon-32-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Azv0YBgCQQPEOYE1H5FA1Q==
++SHA1-Digest: r/saE8YuaR1GmoXPLNiEEcWDyCQ=
++
++Name: skin/requestpolicy-statusbar-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 0folj9+9/KhWd2DmfBs3Fg==
++SHA1-Digest: v7VT838WMXI1DwX2lrSW3YH9jow=
++
++Name: skin/close.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 7h607/eRKeau7XntOi2fCw==
++SHA1-Digest: iTy8yNjtkoRcODVHdACukPyUdmk=
++
++Name: skin/requestpolicy-icon-32-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ad8QX7maxSoOWvptubGk2w==
++SHA1-Digest: jJxLCde6TR+WUhbU+uADUvfaTe4=
++
++Name: skin/requestpolicy-icon-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: IbB/LJgfFQ0Bh//Ov/Pl1Q==
++SHA1-Digest: poh+kgwVdBewWFYWOXD6jMTnqUM=
++
++Name: skin/menu-other-origins.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: dUzdeO5c8zbWFl/zN6NwfQ==
++SHA1-Digest: wz+fvLxp7vdR4rq6/B3r6lZhVGM=
++
++Name: skin/requestpolicy-icon-24-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: zYXVJ1OS66hBNFm5yrFgkA==
++SHA1-Digest: C2IFm7adI+wabttSCm8NiLINjlo=
++
++Name: skin/toolbarbutton-seamonkey.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: M6P3PlhyQnPxYMH2Vu9jXg==
++SHA1-Digest: P5IZief2S7k8qxurgvu9pxCSh1M=
++
++Name: skin/menu-default.svg
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: YMrcua6M9KW8ha0v3Ye6rw==
++SHA1-Digest: UnW/mDqYtJjpsmaMDxseUmWZqbw=
++
++Name: skin/toolbarbutton.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: IoyZ/3BIN23q95ifOCWn4w==
++SHA1-Digest: 2/H3B53cKhiCaxLKA7onmCMh924=
++
++Name: skin/requestpolicy-icon-24-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Wic5nAdiHv1e+ncHnNZhxg==
++SHA1-Digest: GsJl5v6HO7CUOS/8lB/4MMRPCuw=
++
++Name: skin/initial-setup.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: VR2u7MSsSw6xOHlM5bcNRg==
++SHA1-Digest: m2P4gSrSfOSWKgipRM3jlN8oulg=
++
++Name: skin/menu-allowed.svg
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: nV5VrjIu5Xtzzx12iVediw==
++SHA1-Digest: Xa8xm1qix3i/VH+dvzVvDwImgC8=
++
++Name: skin/requestpolicy-icon-32-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: B2n1zStlybgvbtikAtcGyg==
++SHA1-Digest: HfF/LBsrRVTUF3vqN4fH4OVoyVI=
++
++Name: skin/requestpolicy-icon-32.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 7cfrRo4hVSZjIitHKQJd6A==
++SHA1-Digest: L8b37XGnevF0DKRos7lxvVq7sTs=
++
++Name: skin/requestpolicy-icon-24-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: g/xzYn+NcYrBALsvLvCygA==
++SHA1-Digest: HgHArB9Dfe7ljls1nIrJNK6MHTg=
++
++Name: skin/menu-blocked.svg
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: n3DEgJZDT6gUSnxPKSq26g==
++SHA1-Digest: DWmR3LX2AnSv2Ns92RkP037LKr0=
++
++Name: skin/requestpolicy-icon-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: T7JlJKlYh0xouf5n3nXoIw==
++SHA1-Digest: 9Xd8pFqrBNRfiJaii6PuSQNwGGg=
++
++Name: skin/requestpolicy.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 0ul9T4Lr/ojP7uIIUTFWGw==
++SHA1-Digest: SkZdfTxj9zJvPneRclU4sczEoyw=
++
++Name: skin/requestpolicy-icon-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: H2bEnhsko2AFOyB/J0CoXg==
++SHA1-Digest: GKvD/D6A6XHVoYfW9J5LtkpFQis=
++
++Name: skin/requestpolicy-statusbar-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: AbAK6+qCdyzo9q3OA5Qt9w==
++SHA1-Digest: ulSmlBGaY0+PeWUM//y8TQryZ44=
 +
 +Name: chrome.manifest
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: EBLrFrZ0vxrC2c5gJA9fcg==
- SHA1-Digest: 11N6xQYHdmHq7xMuOq8FJ8alu10=
++MD5-Digest: 3cwuJSYo5KfrSeqmUWFMVA==
++SHA1-Digest: FXr45mmpMkL+fQ8WD9r1Z6ueH3w=
++
++Name: bootstrap.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: uQ87I+QLRvqjM2s/YsHYjA==
++SHA1-Digest: sIxtY5PqnaILCUCWmkRDEBCdPgU=
diff --cc META-INF/zigbert.rsa
index c068a5a,0000000..b72b7b9
mode 100644,000000..100644
Binary files differ
diff --cc META-INF/zigbert.sf
index ec40692,0000000..980a395
mode 100644,000000..100644
--- a/META-INF/zigbert.sf
+++ b/META-INF/zigbert.sf
@@@ -1,121 -1,0 +1,691 @@@
 +Signature-Version: 1.0
 +Created-By: Signtool (signtool 3.17.2 Basic ECC)
 +Comments: PLEASE DO NOT EDIT THIS FILE. YOU WILL BREAK IT.
 +Digest-Algorithms: MD5 SHA1
 +MD5-Digest: Y542DeFadqxYpgFCAnJgFw==
 +SHA1-Digest: gmQgtQLLpa+Pi8q+8RUrZjcjaEk=
 +
- Name: chrome/requestpolicy.jar
++Name: content/main/pref-manager.jsm
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: 8fLUWC3/UA5+8Y1N7zNLUw==
- SHA1-Digest: 21n2gNs6kzOCuykqmbt1HRn25Iw=
++MD5-Digest: 4wQlO/3drh58AzIPIhir7g==
++SHA1-Digest: uvsvuYWOH6nXWMFO9OdEGC4DQBc=
 +
- Name: defaults/preferences/defaults.js
++Name: content/main/about-uri.jsm
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: iiRycr/IN1ty3+fmYwj0rw==
- SHA1-Digest: sRD08BZUoK7iF1WnAomdvsmQL5M=
++MD5-Digest: xcaPyAfXLrwGTX7RsJnpQw==
++SHA1-Digest: 8G3cntGSGgnCraSUYk6GlO2gbuU=
 +
- Name: README
++Name: content/main/requestpolicy-service.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: xl2R4jFMtwyre1/fGM89ug==
++SHA1-Digest: EorhyOSQVGKuVGfHQf9iCuVS0XU=
++
++Name: content/main/window-manager.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: qH2UwvProMYfc2T8FrbXNg==
++SHA1-Digest: /CeW/7yAxhaZ0q5rkVAYNatP8FU=
++
++Name: content/main/window-manager.listener.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: BTg+2p4f4U31k+rZcncqMw==
++SHA1-Digest: dzYbbgIMIIhvy1LbZz1j2YR8Wdw=
++
++Name: content/main/window-manager-toolbarbutton.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Ls/+XISBUAmW04/A1AipNA==
++SHA1-Digest: Ytk4f/gZlzFDIpMg0i7AvylcLBQ=
++
++Name: content/main/default-pref-handler.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: RQ1e3JDuXio/nTfIf40wqg==
++SHA1-Digest: BGkHxjM82nHb6/OV/rf2kw35N6U=
++
++Name: content/main/content-policy.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: YWBH6DyQRzkkEN1xRhomJw==
++SHA1-Digest: WyiayvkQ0PHlJs6+ZyXsMTWSzsM=
++
++Name: content/ui/overlay.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 6SmPqRD+JwagCs03Pelq0A==
++SHA1-Digest: orV8ZFgq+uZsVXFlZ0s7e7wzrec=
++
++Name: content/ui/frame.dom-content-loaded.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: /YM3h1/L8lxQZMwv31NVVQ==
++SHA1-Digest: KEFaD/a0RugV/Smn8iO5l7W2f2M=
++
++Name: content/ui/frame.blocked-content.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: fUJCr27uoPVkFF+UBnqVvw==
++SHA1-Digest: vZnCjRPjEemYptXaNQzo+xTmVos=
++
++Name: content/ui/request-log.xul
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: RdI6dKfsm4EUoC0Fe8xgWw==
++SHA1-Digest: UboElBQcsO+JXapPwJb+h8pyUh0=
++
++Name: content/ui/request-log.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: CIuzubfthWsYurE3Ca+GnQ==
++SHA1-Digest: WheXOhEblQc456jLqicL1Q+gSII=
++
++Name: content/ui/frame.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ohMQ/8alOh0cxvUuQ2c2Aw==
++SHA1-Digest: RpEwXYvKJUJvHjsogmRJKuxkiy4=
++
++Name: content/ui/request-log.interface.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: IpnjxFC8fNhbuyv4IkSdXQ==
++SHA1-Digest: Yb3H2uI7qmLYO/RGXipN+wZ2oXs=
++
++Name: content/ui/request-log.tree-view.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: yp9KA09Hp1fvmuTlrY+h/A==
++SHA1-Digest: AjguzG9oTtVUoE5z3lCTtWvNVhM=
++
++Name: content/ui/request-log.filtering.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: rwVwz9QOXpmH1IldyRsvWA==
++SHA1-Digest: UuOWIBWDbwiSwmbEoszC3oVa8eA=
++
++Name: content/ui/xul-trees.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: TBoK+zuHS2U/E8ErdEX/jg==
++SHA1-Digest: NZliW0mUdfyQDBZ7K3ACw5ehlBs=
++
++Name: content/ui/menu.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Tv6ygxux3qQW6CHNpISOqg==
++SHA1-Digest: kaGJPdl2sE/uwXlxYeJ1/Zl9LY4=
++
++Name: content/ui/classicmenu.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: aGkkX+nasqxd3P+d+FnaXg==
++SHA1-Digest: WO96DQg9xTP05jVCCwyRkugrJ+I=
++
++Name: content/lib/ruleset-storage.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: uDWdTZjGoJRNTzideoyXXw==
++SHA1-Digest: VamwXTBqdkbJKy8i4p1ozRAn52I=
++
++Name: content/lib/policy-manager.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: kRFXDCJmEdnRP6mmdncE+g==
++SHA1-Digest: llM5wsKJ3wACRALYVWOpo+4EF2Y=
++
++Name: content/lib/manager-for-message-listeners.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: gcCLrBbXaha8LHApPxquyw==
++SHA1-Digest: 2XRS02P1b6QqX1E5v6YnxkiyltM=
++
++Name: content/lib/ruleset.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: jcaRWtDMUvdgRvJToDC0YA==
++SHA1-Digest: AhoCIsULPrL5AI6yT1ohxn4HOp0=
++
++Name: content/lib/prefs.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: oggU6BoF8r6EG7UQuS/dpA==
++SHA1-Digest: Zh73svpbEawoeZ6q57+FfR7D43Q=
++
++Name: content/lib/gui-location.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 1yKkN1QNuxzNfSQNJFYAJw==
++SHA1-Digest: hgI+syo6HkByh5iBhgZvIAYfvWQ=
++
++Name: content/lib/request.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: BLDkn5ZBBI6DhdbflU+o6w==
++SHA1-Digest: YagHhlcdBOMOP0Z5vq7CnpmhrEc=
++
++Name: content/lib/observer-manager.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 7W9XF+z3UDWdRiYl6rRPJw==
++SHA1-Digest: iOM606s31SirOgLCsQK66nLU0W4=
++
++Name: content/lib/environment.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Xp1JNaDyuhIYK3VggBQBrA==
++SHA1-Digest: MG6roaq36h80oR6i6RfWCgwe9Bw=
++
++Name: content/lib/request-processor.redirects.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: QIF0xy3ZU2xgLEhV/scb1g==
++SHA1-Digest: Y81bgYb3Ps5zU+JegfdrdtfTvgs=
++
++Name: content/lib/request-result.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: +MWgmqGC8u9owqYXOQqbnA==
++SHA1-Digest: BGOMS3msgHW+3lnLND30tJ1nolg=
++
++Name: content/lib/utils/domains.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ZtmdMuVUWTRhe6PEK3iuOA==
++SHA1-Digest: nLfygM6U/ykxs1x4kcqkmpJkwHs=
++
++Name: content/lib/utils/files.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Y8Jbs2vtsJKlcBZ/naj0fw==
++SHA1-Digest: Q08vjPDDtekVgSVhe/jSJj8nwOo=
++
++Name: content/lib/utils/windows.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: EHhillCS27JpPJlT+R/B/Q==
++SHA1-Digest: EnY/BgDDd6PbVgYtsfeMQlkJvG0=
++
++Name: content/lib/utils/constants.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: WCgO+vTkkoOp/gPINZ5vCA==
++SHA1-Digest: IRFlPQijKpzeXXqq/I/MACcfREw=
++
++Name: content/lib/utils/xul.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: CfW1AbpJeS0NSdzMkFkhSg==
++SHA1-Digest: keTepkVBk7m+UP88JprggZYnYos=
++
++Name: content/lib/utils/strings.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: /m1YoiGU1zSplYCYkIJlyw==
++SHA1-Digest: A7TpJ3Fyc6lsCwhuCuKiKeyr9Qg=
++
++Name: content/lib/utils/dom.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 3vsBjO3mJohsD+fgsZiHDw==
++SHA1-Digest: nwpRiD9D/w2aXXWNFV1qGCF/k4o=
++
++Name: content/lib/utils/observers.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: AOjBGaE3MQZpcejWCNHWeQ==
++SHA1-Digest: xAYw8fPUALwiLdk8pWftdIk4+8A=
++
++Name: content/lib/subscription.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 5NW7uUtsBIk6gOjreskqfg==
++SHA1-Digest: D1+C7kvWFR+VQLPpOK+snFj12Rc=
++
++Name: content/lib/utils.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Ooajcj+l7+ecFq5QhPICQQ==
++SHA1-Digest: ORM3ElSUrxfiG0IIaEWlUExg7jc=
++
++Name: content/lib/default-preferences.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: cDKS/RobAypkph652OWNtA==
++SHA1-Digest: PyEWIiQw/2eCRldFkPNUT4BVlQg=
++
++Name: content/lib/request-set.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: y7fyUS6n11zqAi/+rczxog==
++SHA1-Digest: TEdLH9F7754mH8jmfOLgbkJhpiA=
++
++Name: content/lib/policy-manager.alias-functions.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ENNhUWFGUnTVwJ/+W/9Zyw==
++SHA1-Digest: d+sKGtodZPWTIUW9GCq6WujH+Us=
++
++Name: content/lib/logger.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: DSDyBumes2oEwrxkjYpf+g==
++SHA1-Digest: bRPiQAZUwDQwaThHDYnvbpTSfpk=
++
++Name: content/lib/request-processor.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: TUdViZd0P3+NlACJyRFyAQ==
++SHA1-Digest: vamjP/DfPg+iZ//rl/mZTUysXq4=
++
++Name: content/lib/framescript-to-overlay-communication.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: MKuwy/LMM+3CbazMxO0ajQ==
++SHA1-Digest: ra0mTuEb1wUkh6I1ybZx5vCCTk4=
++
++Name: content/lib/http-response.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: IZMuGSSLEZ3L34hxQSpJYg==
++SHA1-Digest: XN+3aCzZ8DOpmwT8DhS/XS0sspQ=
++
++Name: content/lib/manager-for-event-listeners.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: DqfZ6x1OsQCREg2RdoR5aA==
++SHA1-Digest: jrwTDeFdFjGCPytcsVE749XqnBg=
++
++Name: content/lib/environment.process.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: TnGyo7rXA20ODcCChs2QzQ==
++SHA1-Digest: PoierHXICptkybXNbqpR7PhuB60=
++
++Name: content/lib/request-processor.compat.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ukxqqFrk518y5DUGozjdOg==
++SHA1-Digest: kT3LpbjjRRfP5hqb6nvoCLJzrog=
++
++Name: content/lib/script-loader.jsm
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: wL11uTz60v8ctchex/I8LA==
++SHA1-Digest: +CgOyDC6KYwHX2gHhepURJ36UqA=
++
++Name: content/settings/subscriptions.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: YcWhcrIxuBIWZTVkHgmOrw==
++SHA1-Digest: eRSM9z8QzcSmlFhEu1yMtmbnqTc=
++
++Name: content/settings/setup.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: QoWEmx22j94AQUpObNsAEg==
++SHA1-Digest: xNrenrNUc7QEKSSzKTan1fBsNOE=
++
++Name: content/settings/common.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 94NfxdMeKZidYoVwzhAvaA==
++SHA1-Digest: 73+v4CQ9ZRA96SZ43xJXTZBzZnk=
++
++Name: content/settings/advancedprefs.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: AvRFDRcR9c8iUdu6gzTrNQ==
++SHA1-Digest: IiBxP63t1vdTyjmNNcvuGU895bo=
++
++Name: content/settings/setup.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: kzFvLO3H+zDrMI1cR+to1g==
++SHA1-Digest: 8uy+iBbRwx69cjuCh4xRy5QkgE8=
++
++Name: content/settings/subscriptions.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Q26Srm41597PDoMSwadPhQ==
++SHA1-Digest: qG9Ah3y3SulyUhUaJn0H/EnBHIk=
++
++Name: content/settings/basicprefs.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: vjYo9kJzdSfnS3fQVNWtXA==
++SHA1-Digest: Dk+OmN1+eY6FU7fN+ccnccftGOE=
++
++Name: content/settings/defaultpolicy.html
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: 4IF/FQ/CPR+dJ4KpixbQpA==
- SHA1-Digest: Y0R7rsNx4KaWYKrecWY+X/KVPJc=
++MD5-Digest: JJwj96RJnM0eOS3QtYA4Kg==
++SHA1-Digest: 9oMKkKuWhWmuw84mM4rwPY5yWGA=
 +
- Name: components/requestpolicyService.js
++Name: content/settings/yourpolicy.js
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: jzE5C1cGlEFZz/05esgRcg==
- SHA1-Digest: 5+4AixpMF5i1HYAjVO4AUz87Vxk=
++MD5-Digest: o1N5OFaK7YdxothTUvjO+Q==
++SHA1-Digest: QiaOHceJhp2DQBKmAG1/HMnXC8o=
++
++Name: content/settings/oldrules.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: SYh4JVTlMdtUauo9b6h5eA==
++SHA1-Digest: rKkRlajO35PzciV1R3THvdTr8Eo=
++
++Name: content/settings/jquery.min.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: pCi1Mlsw4okPUXKBP4wW8A==
++SHA1-Digest: vxN6G5Y9UzhA5TUYKjhaw/wJH74=
++
++Name: content/settings/defaultpolicy.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: SlyoXA8yCOxQ5fvMT6b2Rg==
++SHA1-Digest: zWN3s2oNW3rTyXchl2rl01jKmDg=
++
++Name: content/settings/yourpolicy.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 8ghuv1FRZkvDA7UVMcDQSg==
++SHA1-Digest: n1dlolToBHvO+424IlKzNM1AhUM=
++
++Name: content/settings/oldrules.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: bBOaFHWiTDCFsRCcHlmLLg==
++SHA1-Digest: ufeUuEamMZlMlyq4cPQVIZMGbW4=
++
++Name: content/settings/setup.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: zguwTJ/EUPtIraafhEprpg==
++SHA1-Digest: x+ieG7MD3Rx+Y077FMC0veP9/E8=
++
++Name: content/settings/settings.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: eOWZ/81MSLHmT+idCxjh7w==
++SHA1-Digest: t7VUu2R+hsw7zaUkJukPAXvdaAg=
++
++Name: content/settings/basicprefs.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 5u4dpCZYmtZVx6CBnksKEA==
++SHA1-Digest: U65psgfnNZHR8yLdIOIWdoyDWCc=
++
++Name: content/settings/advancedprefs.html
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: +YJhgHvT7b8qQfYxQt8b7Q==
++SHA1-Digest: 5fkbAd+onHReUQ5sFV3J4W7quu0=
++
++Name: README
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: TgYM0sWvnUeqz8pGEC26QQ==
++SHA1-Digest: GPu/AAhbBA3U6gizB3T1f0jsodw=
 +
 +Name: install.rdf
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: ufrQFCUKe4Rs6H7pLm5syQ==
- SHA1-Digest: IJTwN4o/0S0hud5FpboFagVpVww=
++MD5-Digest: UmK828c4wUyxz+X1ZP4c3w==
++SHA1-Digest: TrQqT1V605eAc9dplTu6+aN+gis=
 +
 +Name: LICENSE
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: rq48gHVxmUTeD2ju1xqW6Q==
- SHA1-Digest: rZMp//tjtjReS6q4HHErfYsR8VI=
++MD5-Digest: ehUwRgZtsc5jhoDdyK393w==
++SHA1-Digest: n1A9Ct7K+QhfyjdwgdxpLAXTOBc=
++
++Name: locale/tr/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Ex6SjuZC0vn1y4EeJ3vN9g==
++SHA1-Digest: ejuIKSlvLvr3q+0GKDSoi6nHq9k=
++
++Name: locale/tr/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: zX9TFP2qUEm41TqYVU4+4g==
++SHA1-Digest: ZiEyAQzsL6ejodw0hjFzNZOdf6s=
++
++Name: locale/uk-UA/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Vr6DOgp2+m/If0ZdlCdmQA==
++SHA1-Digest: xaPVOd+5iwJUiPXdpOKyGqFi/Ec=
++
++Name: locale/uk-UA/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: Pj8O7QDgc6MzAO6T+EFQ2g==
++SHA1-Digest: ZYEHC18plXZEvSJOYf689PXe5JQ=
 +
- Name: modules/RequestResult.jsm
++Name: locale/eu/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: zPG3K21DT5Fipia6uCNXmw==
- SHA1-Digest: Ojv8OJgnVHTOd115E6OXO2oP3ks=
++MD5-Digest: 1DovZOZyq0+/t+Zk/iJX8Q==
++SHA1-Digest: feYCbktaGbMvEGrRNrUXvoSevTE=
 +
- Name: modules/Logger.jsm
++Name: locale/eu/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: 8xUhks96ohRxgV1LazuevQ==
- SHA1-Digest: 8EYimHiaLlN5tgpU78sIdGeKbTk=
++MD5-Digest: 5LeYpJXucoW4s2KkQyuM3w==
++SHA1-Digest: ORwJSlunt/FCjaozqBf0IQFjd3w=
 +
- Name: modules/Util.jsm
++Name: locale/sk-SK/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: IX1NlqFneJBOzTX7yBMoYQ==
- SHA1-Digest: eu7ac11oMFd3/NkchfRjz1zv3k0=
++MD5-Digest: 6fM57lPnruOjY71DVdPJDg==
++SHA1-Digest: 760uHEeNhO0Ou12RNTBwT4NUqQQ=
 +
- Name: modules/RulesetStorage.jsm
++Name: locale/sk-SK/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: M2cI6O4DI7BVgHFVvB/Jyw==
- SHA1-Digest: P10qEuHE051TdLVbFvnzIhiOKZQ=
++MD5-Digest: Y3THL1PcRJufZbQATPCTcA==
++SHA1-Digest: 2iwI2bQczTRcxBnzJGh15aovmJc=
 +
- Name: modules/FileUtil.jsm
++Name: locale/ru-RU/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: Qo70/CuFJnFgvYIwAh9ysQ==
- SHA1-Digest: m1+BpUPw+k59bw8exrpCv09ygDM=
++MD5-Digest: IRMfvQ8wLsvdQDPk+U8iGw==
++SHA1-Digest: qFqWxONJMdM7QPNMgw/86Z/1G80=
 +
- Name: modules/Request.jsm
++Name: locale/ru-RU/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: p/tSX2dZSZFjaZBt37wcWA==
- SHA1-Digest: CEtA3qsSF5113brBS9/zadGbgg8=
++MD5-Digest: Dj/ZZW1BRfzGr2fe8VsVkA==
++SHA1-Digest: trQgVpSuYUtmZbni4hzRgyybKao=
 +
- Name: modules/Subscription.jsm
++Name: locale/it/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: RWdO528ZfssMIn1is/5H+A==
- SHA1-Digest: uoVYQlOUzVyj40zZZwrAL3msAGI=
++MD5-Digest: smeGdTrCgnGmuYRKycDrsA==
++SHA1-Digest: +VnCaafzK9UbQfjOhRJstmL5NII=
 +
- Name: modules/DomainUtil.jsm
++Name: locale/it/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: zrKkx2OYshdkT7J8AdV7rg==
- SHA1-Digest: rr8mBm7BYmuGgmUKQoSCZ5uLstI=
++MD5-Digest: GlUkT12Pmr9vcDF79LXRjw==
++SHA1-Digest: ngA7E5MqtwNu02AF9jqVs6nQkXM=
 +
- Name: modules/Services.jsm
++Name: locale/eo/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: lOmstAWKhmx/5L+TxMBUgg==
- SHA1-Digest: YpacGBejz84Hl1lCf7pf9q/fx7o=
++MD5-Digest: xtxyDw0AbV5L7ILcgrDlRA==
++SHA1-Digest: NiIYvlZ9GINT+3p6tCsfaDRCS7s=
 +
- Name: modules/Ruleset.jsm
++Name: locale/eo/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: V/CYr7g9nhNP4QAIV8/DSg==
- SHA1-Digest: RHQIHFF+ZcM9qBQ9e+opADVhtPA=
++MD5-Digest: AsQstb26+U66VJC9EmC4Ew==
++SHA1-Digest: yYuubNoGIEU4WtI3hM6xZ16LRdA=
 +
- Name: modules/Prompter.jsm
++Name: locale/sv-SE/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: UV6JMdUK9TDsEEzaPHvtNQ==
- SHA1-Digest: A8+pdVdDMTKj8YQ0welKqOM8u04=
++MD5-Digest: DH/LFssK2rU7Y3R9MyqcbQ==
++SHA1-Digest: LSN6BCEu1qlutqvunIJqo07z30I=
 +
- Name: modules/GUILocation.jsm
++Name: locale/sv-SE/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: ByIRIkIsJbFQdbzBxjqLkw==
- SHA1-Digest: uxehxf3Nod+JeHi+u7nX1VHlYQg=
++MD5-Digest: QXkaOSZgQLFl5Q6UZ2PYTQ==
++SHA1-Digest: tflXANCOB+Qnxhfe3hvMRBAYNvk=
 +
- Name: modules/RequestUtil.jsm
++Name: locale/ja/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: nOXO5TEAY5caW5WcpXAnng==
- SHA1-Digest: dvcRbnz2yeVwAxCffoLzslImcQE=
++MD5-Digest: aQoxdQCtoBwKIyG5lkJc7A==
++SHA1-Digest: pWyBgC+oxIW12WEOq9ShjVbo+Pc=
 +
- Name: modules/RequestProcessor.jsm
++Name: locale/ja/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: GvnmMN0+LCt50VCDSTOQKg==
- SHA1-Digest: fy1TpePqyvfCGD66VAQtGpnvPh4=
++MD5-Digest: K18WbxcQTHtY+OIWGnklpw==
++SHA1-Digest: PdA3iU+ienZIC/F5pw8/wTMvDlI=
 +
- Name: modules/PolicyManager.jsm
++Name: locale/nl/requestpolicy.dtd
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: v4WKj0QJ7+zSkJSB6mCO2A==
- SHA1-Digest: 3sQiDxAz1USZ6jb/UdwXuRJwc0A=
++MD5-Digest: kR8X6CFrYZlpc3bfG8btuQ==
++SHA1-Digest: 2mbW+7boAZR0dO9GwuxWmqzKKXs=
 +
- Name: modules/JSON.jsm
++Name: locale/nl/requestpolicy.properties
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: 40J/UCMpeBexf0V7W2tPcA==
- SHA1-Digest: 5re99XCChGRNoWNYKpVnXsIxIqo=
++MD5-Digest: f10oLQjBYIklpIDUzBmO5Q==
++SHA1-Digest: i7NmSrOrJx2RXlzsVmPdSxsBJHk=
++
++Name: locale/zh-CN/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: XQPotTYcAVzsizvLFyKGOg==
++SHA1-Digest: vMtNT+AaiIwiXu5KbyaGPUfSUow=
++
++Name: locale/zh-CN/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: gQ4THovj5TAx5sWijmZA1A==
++SHA1-Digest: 9roHnZWU37Z7PzxKFSAIeJn4vms=
++
++Name: locale/zh-TW/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: kdk4p0kngU9Y63aMWpXpkw==
++SHA1-Digest: ngeGprUXqKEIe5Iiwb9Pela2PsM=
++
++Name: locale/zh-TW/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 1+Cc11UEA9dN/9JkAhhAFA==
++SHA1-Digest: i1NxgmcZtO9odYPxDDb/N9lGfxM=
++
++Name: locale/pt-BR/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 409PEUWhES0hPUlL1qNHTA==
++SHA1-Digest: 9fPf3+2mx3nqsLUKjQa/gdf9Xo0=
++
++Name: locale/pt-BR/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: PQ9wHFpbxl4SCXKCLzSyfg==
++SHA1-Digest: gyiUVFsH4qDt7Y7bk3Sb0CAdFRo=
++
++Name: locale/fr/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: iqLFrcKFbnLmvTKMdxQoyA==
++SHA1-Digest: WWteCdobl3ziBYl41EfEPrIKwZM=
++
++Name: locale/fr/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ABHk4vpRL+Z0LR165WybFA==
++SHA1-Digest: QgFd14IHawakoI2FjyG2cp8JITk=
++
++Name: locale/de/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: T07jj7gAigGrTKHW2sX3BA==
++SHA1-Digest: S0tEYPakQiJFAFNVptALTdYeKN4=
++
++Name: locale/de/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 3D0j61Ox3XLEeaZjdU2/tA==
++SHA1-Digest: A+bx2c7s9ZBP5x84eMKG2chm9PQ=
++
++Name: locale/en-US/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ExX48pvPFLYKMmLCcUFI2w==
++SHA1-Digest: hqsmBUSTCzWLviPTpLPlc2I8Ae8=
++
++Name: locale/en-US/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 9pyu7TtJlwyk9fzsi8vqHQ==
++SHA1-Digest: EBntLMUb2a38WhUi49YVZAioOzQ=
++
++Name: locale/ko-KR/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: sKrxqRKDzFjz8X29haDgVw==
++SHA1-Digest: eXZ3IrLxNF5eZXVtk9LFMD8uqlE=
++
++Name: locale/ko-KR/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 18K0GuQOozavVuyjMSgFvA==
++SHA1-Digest: 9koZjtWxYV6ac+aKwf8NwJheFQw=
++
++Name: locale/lv-LV/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: /FDRMcRFdwsRebTy7VgtEQ==
++SHA1-Digest: d5jCb62Qc2+ZwoxZBUClEKSjXqs=
++
++Name: locale/lv-LV/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: j3x+DlqlyEloB9uU/mzDRg==
++SHA1-Digest: XEwD8FM8Ynzlpy0hzPCzHRe+3WI=
++
++Name: locale/es-MX/requestpolicy.dtd
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 7IkGkmGEiuS5aABRztcYbA==
++SHA1-Digest: 3sCeWdo6tYJ8oAJlEE3vTHNHngk=
++
++Name: locale/es-MX/requestpolicy.properties
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: NauvaJVJWEHrIuYjTd2CSg==
++SHA1-Digest: Fu/siar4lAQks6+gFfg6OoQ41XU=
++
++Name: skin/requestpolicy-statusbar-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: FRT3wgqPekWPzNGkY1BHuQ==
++SHA1-Digest: 9rGBl3oNOcl5nAT/+ka0PQw39Sc=
++
++Name: skin/dot.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ZVQUzeOwErCtpS613LI4Sw==
++SHA1-Digest: D1ht/xn+OstSN5ZX1cVjoXJvOqU=
++
++Name: skin/request-log.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: J7G+gU2P16zfC6sUwfSK8Q==
++SHA1-Digest: JjS/LOhkM3gKHgLhufhAFeWIT3Q=
++
++Name: skin/requestpolicy-icon-32-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: f/Eg+9mmWPKitMH+7Yc1zA==
++SHA1-Digest: EkKGAQF7x4ajt++teFD7C28kvNE=
++
++Name: skin/requestpolicy-statusbar-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: xEe1GaeU14Qn9vecTpVN6g==
++SHA1-Digest: HApwwIGPGPvPeDmIYF8cY7NXV1Q=
++
++Name: skin/close.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ULn22sAvNhzEfLZ1IrrGNA==
++SHA1-Digest: nrun822JA28GZ1qSuAVyg/psrFY=
++
++Name: skin/requestpolicy-icon-32-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: xTzq3/ElD2dQD68SLztK4A==
++SHA1-Digest: SNsRmVfGKAmdCv0DFUYSt/QYMLM=
++
++Name: skin/requestpolicy-icon-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: bodpzwuJ86wtzqMDdSd9aw==
++SHA1-Digest: KaNPBDoyHkH1oFHOfX0PihGp3DU=
++
++Name: skin/menu-other-origins.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: laOkW8yr6GHe7ZN4vz3oog==
++SHA1-Digest: zkV/iXv120HBNR+lCcRIsA+7vY0=
++
++Name: skin/requestpolicy-icon-24-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: G7A7ajTea2Ks2oHchbzgsg==
++SHA1-Digest: yUcxP3wWa3Huu2k7MJYbjWzkrqY=
++
++Name: skin/toolbarbutton-seamonkey.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: QLs3wUB7vlSLbY/hgSPqLw==
++SHA1-Digest: 4ZCQAKM/WvnxIEzXt0SVYFtKFGU=
++
++Name: skin/menu-default.svg
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: FxDUStQr3wcxRg/vw8hBNg==
++SHA1-Digest: PVzmI4T9Y+NKP7FniXnv7/uiKcQ=
++
++Name: skin/toolbarbutton.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 6ADISm8YfcMSMGdN5cJwYA==
++SHA1-Digest: VA1+jBA79I7s0JQ3KGYC13nfo1k=
++
++Name: skin/requestpolicy-icon-24-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: glQdQUi6B6P4Ft7DqvJIGA==
++SHA1-Digest: tuRrmHqmIUHE+TYw3LjqYyAHiNg=
++
++Name: skin/initial-setup.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: ntHRarzJQpJEyBJetklfKA==
++SHA1-Digest: 0J6pGvfARhVYtHT56dDsHkcSbdk=
++
++Name: skin/menu-allowed.svg
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: mFSaK6rxV0vlor1Luv2fdw==
++SHA1-Digest: SXr2n9FDTimW5srNWc/ZpIsDujY=
++
++Name: skin/requestpolicy-icon-32-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: X8vCUb2sv5csVnyFCp547g==
++SHA1-Digest: qEwWDsn8/oV8bleeb+/UMC5vzgk=
++
++Name: skin/requestpolicy-icon-32.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: AFWBmzMO4ftSLLMUbzFFxA==
++SHA1-Digest: jVr+aWjHB7q82yeG+E0jkgm2d14=
++
++Name: skin/requestpolicy-icon-24-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: GQX1n7q3WAcCGMetDLsS5g==
++SHA1-Digest: 8ROPk1eycusSR517LGveT5V6e20=
++
++Name: skin/menu-blocked.svg
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 5Tcapu+ja7Ws3yq8IDmPKA==
++SHA1-Digest: LMrd4fhkOrajGx2RP6GnSnveXyo=
++
++Name: skin/requestpolicy-icon-allowed.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: WQWkoBcDTA1noDd7veYq7A==
++SHA1-Digest: CEqeyLzIojGigFA17s65FYjcaUo=
++
++Name: skin/requestpolicy.css
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 1S7DJ1iIFNRTHoyP77ZWIw==
++SHA1-Digest: tqAnt8pa+W0QYzrhhmIVDFmfRHQ=
++
++Name: skin/requestpolicy-icon-blocked.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: GjPJ45n0DrIKxNhfXMm7UQ==
++SHA1-Digest: qq0aQYfm9a8GGlkj0s0SwnGWcwE=
++
++Name: skin/requestpolicy-statusbar-disabled.png
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: 4Wkv8zWU2BRESXv0NKayJg==
++SHA1-Digest: ++eAfx6faAE4JcuuelIjr5wj+Ew=
 +
 +Name: chrome.manifest
 +Digest-Algorithms: MD5 SHA1
- MD5-Digest: jcHQF0iTVo0GPb5z0KMyYg==
- SHA1-Digest: U2QasCJhKrRFJA3dYOE7LU7toSQ=
++MD5-Digest: 8XvJuhmfJ9aWfCQEA0mU6Q==
++SHA1-Digest: udtOxdKZIUoybi7idl/s4dqWi3U=
++
++Name: bootstrap.js
++Digest-Algorithms: MD5 SHA1
++MD5-Digest: IiCmgt1HnfrsHRXo+QqKqA==
++SHA1-Digest: Z/TdmFzQUz+6u/sdv1IR6Sy8lFM=
diff --cc bootstrap.js
index 0000000,c22a6f9..d11d7b8
mode 000000,100644..100644
--- a/bootstrap.js
+++ b/bootstrap.js
@@@ -1,0 -1,77 +1,78 @@@
+ /*
+  * ***** 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;
+ 
+ var mod = {};
+ const envURI = "chrome://requestpolicy/content/lib/environment.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, e) {
+   dump("[RequestPolicy] [SEVERE] [ERROR] " + msg + " " + e +
+        (e.stack ? ", stack was: " + e.stack : "") + "\n");
+   Cu.reportError(e);
+ }
+ 
+ function startup(data, reason) {
+   try {
+     // Import the ProcessEnvironment and call its startup() function.
+     Cu.import(envURI, mod);
+     mod.ProcessEnvironment.startup(arguments);
+   } catch(e) {
+     logSevereError("startup() failed!", e);
+   }
+ }
+ 
+ function shutdown(data, reason) {
+ 
+   try {
+     // shutdown, unset and unload.
+     mod.ProcessEnvironment.shutdown(arguments);
+     mod = {};
+     Cu.unload(envURI);
+   } catch(e) {
+     logSevereError("shutdown() failed!", e);
+   }
+ }
+ 
+ function install(data, reason) {
+   // note: the addon might be not activated when this function gets called
+ 
+   // 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 are flushed.
+   // Note: Due to Bug 1144248 this has to be done in the
+   //       `install` function.
+   Components.utils.import("resource://gre/modules/Services.jsm");
+   Services.obs.notifyObservers(null, "chrome-flush-caches", null);
+ }
+ 
+ function uninstall(data, reason) {
+   // note: the addon might be not activated when this function gets called
+ }
++
diff --cc content/lib/default-preferences.js
index c218b7d,b347b32..b3241be
--- a/content/lib/default-preferences.js
+++ b/content/lib/default-preferences.js
@@@ -28,6 -28,10 +28,7 @@@ pref("extensions.requestpolicy.menu.inf
  pref("extensions.requestpolicy.lastVersion", "0.0");
  pref("extensions.requestpolicy.lastAppVersion", "0.0");
  
 -// #ifdef UNIT_TESTING
 -pref("extensions.requestpolicy.unitTesting.errorCount", 0);
 -// #endif
+ 
  // Old prefs that are no longer used.
  //pref("extensions.requestpolicy.allowedOrigins", "");
  //pref("extensions.requestpolicy.allowedDestinations", "");
@@@ -35,3 -39,3 +36,4 @@@
  //pref("extensions.requestpolicy.contextMenu", true);
  //pref("extensions.requestpolicy.statusbarIcon", "standard");
  //pref("extensions.requestpolicy.initialSetupDialogShown", false);
++
diff --cc content/lib/environment.jsm
index 0000000,963e8f2..b83d16f
mode 000000,100644..100644
--- a/content/lib/environment.jsm
+++ b/content/lib/environment.jsm
@@@ -1,0 -1,538 +1,517 @@@
+ /*
+  * ***** 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 EXPORTED_SYMBOLS = [
+   "Environment",
+   "FrameScriptEnvironment",
+   "ProcessEnvironment"
+ ];
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ let globalScope = this;
+ 
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ 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.defineLazyModuleGetters({
+   "lib/manager-for-event-listeners": ["ManagerForEventListeners"],
+   "lib/manager-for-message-listeners": ["ManagerForMessageListeners"],
+   "lib/utils/constants": ["C"],
+   "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
+ };
+ 
+ 
+ let BOOTSTRAP = {
+   "startup": {
+     levelSequence: [
+       LEVELS.ESSENTIAL,
+       LEVELS.BACKEND,
+       LEVELS.INTERFACE,
+       LEVELS.UI
+     ],
+     lastLevel: LEVELS.UI,
+     envStates: {
+       "beforeProcessing": ENV_STATES.NOT_STARTED,
+       "duringProcessing": ENV_STATES.STARTING_UP,
+       "afterProcessing": ENV_STATES.STARTUP_DONE
+     },
+     functions: {
+       "beforeProcessing": function() {
+         // "this" will be an environment
+         let self = this;
+         self.register();
+       },
+       "afterProcessing": function() {}
+     }
+   },
+   "shutdown": {
+     levelSequence: [
+       LEVELS.UI,
+       LEVELS.INTERFACE,
+       LEVELS.BACKEND,
+       LEVELS.ESSENTIAL
+     ],
+     lastLevel: LEVELS.ESSENTIAL,
+     envStates: {
+       "beforeProcessing": ENV_STATES.STARTUP_DONE,
+       "duringProcessing": ENV_STATES.SHUTTING_DOWN,
+       "afterProcessing": ENV_STATES.SHUT_DOWN
+     },
+     functions: {
+       "beforeProcessing": function() {},
+       "afterProcessing": function() {
+         // "this" will be an environment
+         let self = this;
+         self.innerEnvs.length = 0;
+         self.unregister();
+       }
+     }
+   }
+ };
+ function getBootstrapMetadata(startupOrShutdown) {
+   return BOOTSTRAP[startupOrShutdown];
+ }
+ 
+ 
+ /**
+  * The `Environment` class can take care of the "startup" (=initialization)
+  * and "shutdown" of any environment.
+  *
+  * To each `Environment` instance, `startup` and `shutdown` functions can be
+  * added. As soon as the Environment starts up, e.g. via its startup()
+  * function, all those functions will be called. Equally the shutdown
+  * functions are called on the environment's shutdown.
+  *
+  * Both startup and shutdown functions will have Levels assigned. The levels
+  * of the functions determine in which sequence they are called.
+  *
+  * @constructor
+  * @param {Environment=} aOuterEnv - the Environment to which this environment
+  *     will register itself. Inner environments shut down when its outer
+  *     environment shuts down.
+  * @param {string=} aName - the Environment's name; only needed for debugging.
+  */
+ function Environment(aOuterEnv, aName="anonymous") {
+   let self = this;
+ 
+   self.envState = ENV_STATES.NOT_STARTED;
+ 
+   self.name = aName;
+ 
+   self.outerEnv = aOuterEnv instanceof Environment ? aOuterEnv : null;
+   self.innerEnvs = new Set();
+ 
+   self.levels = {
+     "startup": generateLevelObjects(),
+     "shutdown": generateLevelObjects()
+   };
+ 
+   // Define a Lazy Getter to get an ObserverManager for this Environment.
+   // Using that Getter is more convenient than doing it manually, as the
+   // Environment has to be created *before* the ObserverManager.
+   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.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;
+ }
+ 
+ 
+ /**
+  * Registers the environment to its outer environment.
+  */
+ Environment.prototype.register = function() {
+   let self = this;
+   if (self.outerEnv) {
+     self.outerEnv.registerInnerEnvironment(self);
+   }
+ }
+ /**
+  * Unregisters the environment from its outer environment.
+  */
+ Environment.prototype.unregister = function() {
+   let self = this;
+   if (self.outerEnv) {
+     self.outerEnv.unregisterInnerEnvironment(self);
+   }
+ }
+ /**
+  * Function called by an inner environment when it starts up.
+  *
+  * @param {Environment} aEnv - the environment that wants to register itself.
+  */
+ Environment.prototype.registerInnerEnvironment = function(aEnv) {
+   let self = this;
+   if (self.envState === ENV_STATES.NOT_STARTED) {
+     console.warn("[RPC] registerInnerEnvironment() has been called, " +
+                  "but the outer environment hasn't started up yet. " +
+                  "Starting up now.");
+     self.startup();
+   }
+   //console.debug("[RPC] registering inner environment.");
+   self.innerEnvs.add(aEnv);
+ };
+ /**
+  * Function that is called each time an inner environment shuts down.
+  *
+  * @param {Environment} aEnv - the environment that is unregistering
+  */
+ Environment.prototype.unregisterInnerEnvironment = function(aEnv) {
+   let self = this;
+ 
+   if (self.innerEnvs.has(aEnv) === false) {
+     console.error("[RPC] it seems like an inner Environment " +
+                   "did not register.");
+   } else {
+     self.innerEnvs.delete(aEnv);
+   }
+ };
+ 
+ 
+ 
+ 
+ /**
+  * Add a startup function to the environment.
+  */
+ Environment.prototype.addStartupFunction = function(aLevel, f) {
+   let self = this;
+   if (self.envState >= ENV_STATES.SHUTTING_DOWN) {
+     // the environment is shutting down or already shut down.
+     return;
+   }
+   if (self.levels["startup"][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.levels["startup"][aLevel].functions.push(f);
+   }
+ };
+ 
+ /**
+  * Add a shutdown function to the environment.
+  */
+ Environment.prototype.addShutdownFunction = function(aLevel, f) {
+   let self = this;
+   if (self.levels["shutdown"][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.levels["shutdown"][aLevel].functions.push(f);
+   }
+ };
+ 
+ 
+ // have a scope/closure for private functions specific to
+ // startup() and shutdown().
+ (function createMethods_StartupAndShutdown(Environment) {
+   /**
+    * Iterates all levels of either the startup or the shutdown
+    * sequence and calls a function for each level.
+    *
+    * @param {string} aStartupOrShutdown
+    * @param {function()} aFn - the function to call
+    * @param {integer=} aUntilLevel - if specified, iteration stops
+    *     after that level.
+    */
+   function iterateLevels(aStartupOrShutdown, aFn, aUntilLevel=null) {
+     let sequence = BOOTSTRAP[aStartupOrShutdown].levelSequence;
+ 
+     for (let i = 0, len = sequence.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 = sequence[i];
+       aFn(level);
+ 
+       if (level === aUntilLevel) {
+         // Stop after aUntilLevel
+         break;
+       }
+     }
+   }
+ 
+ 
+   /**
+    * This function calls all functions in an array.
+    *
+    * @param {Array.<function()>} aFunctions
+    * @param {Array} aBootstrapArgs - the arguments to apply
+    */
+   function callFunctions(aFunctions, aBootstrapArgs) {
+     // process the Array as long as it contains elements
+     while (aFunctions.length > 0) {
+       // The following is either `fnArray.pop()` or `fnArray.shift()`
+       // depending on `sequence`.
+       let f = aFunctions.pop();
+ 
+       // call the function
+       f.apply(null, aBootstrapArgs);
+       //console.debug("[RPC] function called! (" + fnArray.length +
+       //              " functions left)");
+     }
+   };
+ 
+ 
+   /**
+    * Process a level independently of the environment's states and
+    * independently of the other levels' states.
+    *
+    * @this {Environment}
+    * @param {string} aStartupOrShutdown - either "startup" or "shutdown"
+    * @param {integer} aLevel
+    */
+   function processLevel(aStartupOrShutdown, aLevel, aBootstrapArgs) {
+     let self = this;
+ 
+     let levelObj = self.levels[aStartupOrShutdown][aLevel];
+ 
+     if (levelObj.levelState === LEVEL_STATES.NOT_ENTERED) {
+       levelObj.levelState = LEVEL_STATES.PROCESSING;
+ 
+       if (aStartupOrShutdown === "shutdown") {
+         // shut down all inner environments
+         self.innerEnvs.forEach(function(innerEnv) {
+           innerEnv.shutdown(aBootstrapArgs, aLevel);
+         });
+       }
+ 
+       callFunctions(levelObj.functions, aBootstrapArgs);
+ 
+       levelObj.levelState = LEVEL_STATES.FINISHED_PROCESSING;
+     }
+   }
+ 
+ 
+   /**
+    * Iterate levels and call processLevel() for each level.
+    *
+    * @this {Environment}
+    * @param {string} aStartupOrShutdown
+    * @param {Array} aBootstrapArgs
+    * @param {integer=} aUntilLevel
+    */
+   function processLevels(aStartupOrShutdown, aBootstrapArgs, aUntilLevel) {
+     let self = this;
+     iterateLevels(aStartupOrShutdown, function (level) {
+       processLevel.call(self, aStartupOrShutdown, level, aBootstrapArgs);
+     }, aUntilLevel);
+   }
+ 
+ 
+   /**
+    * Return some information about an environment.
+    *
+    * @param {Environment} env
+    * @return {string}
+    */
+   function getEnvInfo(env) {
+     return "'" + env.name + "' (" + env.uid + ")";
+   }
+ 
 -  // #ifdef LOG_ENVIRONMENT
 -  /**
 -   * Log some debug information on startup or shutdown.
 -   *
 -   * @this {Environment}
 -   * @param {string} aStartupOrShutdown
 -   */
 -  function logStartupOrShutdown(aStartupOrShutdown) {
 -    let self = this;
 -    console.log("[RPC] " + aStartupOrShutdown + ": " + getEnvInfo(self) + "." +
 -                (self.outerEnv ?
 -                 " OuterEnv is " + getEnvInfo(self.outerEnv) + "." :
 -                 " No OuterEnv."));
 -  }
 -  // #endif
+ 
+   /**
+    * Actual body of the functions startup() and shutdown().
+    *
+    * @this {Environment}
+    * @param {string} aStartupOrShutdown - either "startup" or "shutdown"
+    * @param {Array} aBootstrapArgs
+    * @param {integer=} aUntilLevel - The level after which the startup
+    *     (or shutdown) processing is stopped.
+    */
+   function bootstrap(aStartupOrShutdown,
+                      aBootstrapArgs,
+                      aUntilLevel=BOOTSTRAP[aStartupOrShutdown].lastLevel) {
+     let self = this;
+ 
+     let {
+       lastLevel,
+       envStates,
+       functions
+     } = getBootstrapMetadata(aStartupOrShutdown);
+ 
+     if (self.envState === envStates["beforeProcessing"]) {
 -      // #ifdef LOG_ENVIRONMENT
 -      logStartupOrShutdown.call(self, aStartupOrShutdown);
 -      // #endif
+       functions["beforeProcessing"].call(self);
+ 
+       self.envState = envStates["duringProcessing"];
+     }
+ 
+     if (self.envState === envStates["duringProcessing"]) {
+       processLevels.call(self, aStartupOrShutdown, aBootstrapArgs, aUntilLevel);
+ 
+       if (aUntilLevel === lastLevel) {
+         self.envState = envStates["afterProcessing"];
+         functions["afterProcessing"].call(self);
+       }
+     }
+   };
+ 
+   Environment.prototype.startup = function(aBootstrapArgs, aUntilLevel) {
+     let self = this;
+     bootstrap.call(self, "startup", aBootstrapArgs, aUntilLevel);
+   };
+ 
+   Environment.prototype.shutdown = function(aBootstrapArgs, aUntilLevel) {
+     let self = this;
+     bootstrap.call(self, "shutdown", aBootstrapArgs, aUntilLevel);
+   };
+ 
+ })(Environment);
+ 
+ /**
+  * Tell the Environment to shut down when an EventTarget's
+  * "unload" event occurres.
+  *
+  * @param {EventTarget} aEventTarget - an object having the functions
+  *     addEventListener() and removeEventListener().
+  */
+ Environment.prototype.shutdownOnUnload = function(aEventTarget) {
+   let self = this;
+   self.elManager.addListener(aEventTarget, "unload", function() {
 -    // #ifdef LOG_ENVIRONMENT
 -    console.log("[RPC] an EventTarget's `unload` function has been called. " +
 -                'Going to shut down Environment "' + self.name + '"');
 -    // #endif
+     self.shutdown();
+   });
+ };
+ 
+ 
+ /**
+  * @constructor
+  * @extends {Environment}
+  * @param {ContentFrameMessageManager} aMM
+  * @param {string=} aName - the environment's name, passed to the superclass.
+  */
+ function FrameScriptEnvironment(aMM, aName="frame script environment") {
+   let self = this;
+ 
+   // The outer Environment will be either ChildProcessEnvironment
+   // or ParentProcessEnvironment.
+   let _outerEnv = ProcessEnvironment;
+ 
+   Environment.call(self, _outerEnv, aName);
+ 
+   self.mm = aMM;
+ 
+   self.addStartupFunction(LEVELS.INTERFACE, function() {
+     // shut down the framescript on the message manager's
+     // `unload`. That event will occur when the browsing context
+     // (e.g. the tab) has been closed.
+     self.shutdownOnUnload(self.mm);
+   });
+ 
+   // a "MessageListener"-Manager for this environment
+   XPCOMUtils.defineLazyGetter(self, "mlManager", function() {
+     return new ManagerForMessageListeners(self, self.mm);
+   });
+ }
+ FrameScriptEnvironment.prototype = Object.create(Environment.prototype);
+ FrameScriptEnvironment.prototype.constructor = Environment;
+ 
+ 
+ // Load the "ProcessEnvironment"
+ Services.scriptloader.loadSubScript("chrome://requestpolicy/content/" +
+                                     "lib/environment.process.js");
++
diff --cc content/lib/environment.process.js
index 0000000,7333366..00b5a88
mode 000000,100644..100644
--- a/content/lib/environment.process.js
+++ b/content/lib/environment.process.js
@@@ -1,0 -1,216 +1,217 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ 
+ // ProcessEnvironment is either a ParentProcessEnvironment or
+ // a ChildProcessEnvironment.
+ let ProcessEnvironment = (function() {
+ 
+ 
+   // determine if this is the main process
+   let isMainProcess = (function isMainProcess() {
+     let xulRuntime = Cc["@mozilla.org/xre/app-info;1"]
+         .getService(Ci.nsIXULRuntime);
+     // The "default" type means that it's the main process,
+     // the chrome process. This is relevant for multiprocess
+     // firefox aka Electrolysis (e10s).
+     return xulRuntime.processType === xulRuntime.PROCESS_TYPE_DEFAULT;
+   }());
+ 
+ 
+   const shutdownMessage = C.MM_PREFIX + "shutdown";
+ 
+   /**
+    * @constructor
+    * @extends {Environment}
+    * @param {string=} aName - the environment's name, passed to the superclass.
+    */
+   function ProcessEnvironmentBase(aName="Process Environment") {
+     let self = this;
+ 
+     // Process environments are the outermost environment in each process.
+     let outerEnv = null;
+ 
+     Environment.call(self, outerEnv, aName);
+ 
+     self.isMainProcess = isMainProcess;
+   }
+   ProcessEnvironmentBase.prototype = Object.create(Environment.prototype);
+   ProcessEnvironmentBase.prototype.constructor = Environment;
+ 
+ 
+ 
+ 
+   /**
+    * @constructor
+    * @extends {ProcessEnvironmentBase}
+    * @param {string=} aName - the environment's name, passed to the superclass.
+    */
+   function ParentProcessEnvironment(aName="Parent Process Environment") {
+     let self = this;
+     ProcessEnvironmentBase.call(self, aName);
+ 
+ 
+     function sendShutdownMessageToChildren() {
+       let parentMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+           .getService(Ci.nsIMessageBroadcaster);
+       parentMM.broadcastAsyncMessage(shutdownMessage);
+     };
+ 
+     // Very important: The shutdown message must be sent *after*
+     //     calling `removeDelayedFrameScript`, which is done in
+     //     the LEVELS.INTERFACE level.
+     self.addShutdownFunction(Environment.LEVELS.BACKEND,
+                              sendShutdownMessageToChildren);
+   }
+   ParentProcessEnvironment.prototype = Object.create(ProcessEnvironmentBase.prototype);
+   ParentProcessEnvironment.prototype.constructor = ProcessEnvironmentBase;
+ 
+ 
+ 
+   /**
+    * @override
+    */
+   ParentProcessEnvironment.prototype.startup = function() {
+     let self = this;
+ 
+     // Create a dummy scope for modules that have to be imported
+     // but not remembered. 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} = 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:
+     ScriptLoader.importModules([
+       "main/requestpolicy-service",
+       "main/content-policy",
+       "main/window-manager",
+       "main/about-uri"
+     ], dummyScope);
+ 
+     ProcessEnvironmentBase.prototype.startup.apply(self, arguments);
+   };
+ 
+ 
+   /**
+    * @override
+    */
+   ParentProcessEnvironment.prototype.shutdown = function() {
+     let self = this;
+ 
+     ProcessEnvironmentBase.prototype.shutdown.apply(self, arguments);
+ 
+     ScriptLoader.doShutdownTasks();
+     Cu.unload("chrome://requestpolicy/content/lib/script-loader.jsm");
+   };
+ 
+ 
+ 
+ 
+   /**
+    * @constructor
+    * @extends {ProcessEnvironmentBase}
+    * @param {string=} aName - the environment's name, passed to the superclass.
+    */
+   function ChildProcessEnvironment(aName="Child Process Environment") {
+     let self = this;
+     ProcessEnvironmentBase.call(self, aName);
+ 
+     let childMM = Cc["@mozilla.org/childprocessmessagemanager;1"]
+         .getService(Ci.nsISyncMessageSender);
+ 
+     /**
+      * This function will be called when the paren process
+      * sends the shutdown message. After this function has
+      * finished, RequestPolicy has cleaned up itself from
+      * that child process.
+      */
+     function receiveShutdownMessage() {
+       childMM.removeMessageListener(shutdownMessage, receiveShutdownMessage);
+       self.shutdown();
+ 
+       // Unloading `environment.jsm` has to be the last task.
+       // After that task, any global object, such as
+       // `Environment` or `Cu` is not available anymore.
+       //console.debug("unloading environment.jsm");
+       Cu.unload("chrome://requestpolicy/content/lib/environment.jsm");
+     };
+ 
+     childMM.addMessageListener(shutdownMessage, receiveShutdownMessage);
+   }
+   ChildProcessEnvironment.prototype = Object.create(ProcessEnvironmentBase.prototype);
+   ChildProcessEnvironment.prototype.constructor = ProcessEnvironmentBase;
+ 
+ 
+   /**
+    * @override
+    */
+   ChildProcessEnvironment.prototype.shutdown = function() {
+     let self = this;
+ 
+     ProcessEnvironmentBase.prototype.shutdown.apply(self, arguments);
+ 
+     ScriptLoader.doShutdownTasks();
+     Cu.unload("chrome://requestpolicy/content/lib/script-loader.jsm");
+   };
+ 
+   ChildProcessEnvironment.prototype.registerInnerEnvironment = function(aEnv) {
+     let self = this;
+     if (self.envState === ENV_STATES.NOT_STARTED) {
+       // The child Process Environment needs to start up when
+       // the first framescript in that child is loading.
+       //console.debug("[RPC] Going to start up Child Process Environment.");
+       self.startup();
+     }
+     ProcessEnvironmentBase.prototype.registerInnerEnvironment.apply(self,
+                                                                     arguments);
+   };
+ 
+ 
+ 
+ 
+   if (isMainProcess === true) {
+     return new ParentProcessEnvironment();
+   } else {
+     return new ChildProcessEnvironment();
+   }
+ 
+ })();
++
diff --cc content/lib/framescript-to-overlay-communication.jsm
index 0000000,f1ba449..97f2264
mode 000000,100644..100644
--- a/content/lib/framescript-to-overlay-communication.jsm
+++ b/content/lib/framescript-to-overlay-communication.jsm
@@@ -1,0 -1,165 +1,166 @@@
+ /*
+  * ***** 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 = ["FramescriptToOverlayCommunication"];
+ 
+ let globalScope = this;
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/environment",
+   "lib/logger",
+   "lib/utils/constants"
+ ], globalScope);
+ 
+ 
+ /**
+  * The states of the communication channel to with the overlay.
+  * @enum {number}
+  */
+ let States = Object.freeze({
+   "WAITING": 0,
+   "RUNNING": 1,
+   "STOPPED": 2
+ });
+ 
+ 
+ /**
+  * Sometimes the framescript loads and starts up faster than the
+  * Overlay of the corresponding window. This is due to the async
+  * nature of framescripts. As a result, the overlay does not
+  * receive those early messages from the framescript.
+  *
+  * This class helps to ensure that any message to the overlay is
+  * actually being received. Instances take functions ("runnables")
+  * that will be called as soon as the overlay is ready. If the
+  * overlay is already in the "RUNNING" state, the function will be
+  * called immediately.
+  *
+  * @param {Environment} aEnv - The environment to which the
+  *     communication channel's lifetime will be bound to.
+  * @constructor
+  */
+ function FramescriptToOverlayCommunication(aEnv) {
+   let self = this;
+ 
+   /**
+    * A queue of runnables that wait for the overlay to be ready.
+    * As it's a queue, the functions `pop` and `shift` have to be
+    * used.
+    * @type  {Array.<function>}
+    */
+   self.waitingRunnables = [];
+ 
+   /**
+    * The state of the communication
+    * @type {States}
+    */
+   self.state = States.WAITING;
+ 
+   /**
+    * @type {Environment}
+    */
+   self.env = aEnv;
+ 
+   self.env.addStartupFunction(Environment.LEVELS.INTERFACE,
+                               startCommNowOrLater.bind(null, self));
+   self.env.addShutdownFunction(Environment.LEVELS.INTERFACE,
+                                stopCommunication.bind(null, self));
+ }
+ 
+ function dump(self, msg) {
+   Logger.dump(self.env.uid + ": " + msg);
+ }
+ 
+ /**
+  * Check whether the Overlay is ready. If it is, start the
+  * communication. If not, wait for the overlay to be ready.
+  *
+  * @param {FramescriptToOverlayCommunication} self
+  */
+ function startCommNowOrLater(self) {
+   let answers = self.env.mm.sendSyncMessage(C.MM_PREFIX + "isOverlayReady");
+   if (answers.length > 0 && answers[0] === true) {
+     startCommunication(self);
+   } else {
+     // The Overlay is not ready yet, so listen for the message.
+     // Add the listener immediately.
+     self.env.mlManager.addListener("overlayIsReady",
+                                    startCommunication.bind(null, self),
+                                    true);
+   }
+ }
+ 
+ /**
+  * The overlay is ready.
+  *
+  * @param {FramescriptToOverlayCommunication} self
+  */
+ function startCommunication(self) {
+   if (self.state === States.WAITING) {
+     //dump(self, "The Overlay is ready!");
+     self.state = States.RUNNING;
+ 
+     while (self.waitingRunnables.length !== 0) {
+       let runnable = self.waitingRunnables.shift();
+       //dump(self, "Lazily running function.");
+       runnable.call(null);
+     }
+   }
+ }
+ 
+ /**
+  * @param {FramescriptToOverlayCommunication} self
+  */
+ function stopCommunication(self) {
+   self.state = States.STOPPED;
+ }
+ 
+ /**
+  * Add a function that will be called now or later.
+  *
+  * @param {function} aRunnable
+  */
+ FramescriptToOverlayCommunication.prototype.run = function(aRunnable) {
+   let self = this;
+   switch (self.state) {
+     case States.RUNNING:
+       //dump(self, "Immediately running function.");
+       aRunnable.call(null);
+       break;
+ 
+     case States.WAITING:
+       //dump(self, "Remembering runnable.");
+       self.waitingRunnables.push(aRunnable);
+       break;
+ 
+     default:
+       //dump(self, "Ignoring runnable.");
+       break;
+   }
+ };
++
diff --cc content/lib/gui-location.jsm
index 59d01a8,ade6912..a1a9f97
--- a/content/lib/gui-location.jsm
+++ b/content/lib/gui-location.jsm
@@@ -263,3 -254,3 +254,4 @@@ GUILocationProperties.merge = function 
  
    return newObj;
  };
++
diff --cc content/lib/http-response.jsm
index 0000000,c10b59c..e38cc5d
mode 000000,100644..100644
--- a/content/lib/http-response.jsm
+++ b/content/lib/http-response.jsm
@@@ -1,0 -1,181 +1,182 @@@
+ /*
+  * ***** 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 = [
+   "HttpResponse"
+ ];
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/logger",
+   "lib/utils/domains",
+   "lib/utils/windows"
+ ], this);
+ 
+ 
+ 
+ function HttpResponse(aHttpChannel) {
+   this.httpChannel = aHttpChannel;
+ 
+   this.containsRedirection = undefined;
+   this.redirHeaderType = undefined;
+   this.redirHeaderValue = undefined;
+ 
+   // initialize
+   determineHttpHeader.call(this);
+   XPCOMUtils.defineLazyGetter(this, "rawDestString", getRawDestString);
+   XPCOMUtils.defineLazyGetter(this, "destURI", getDestURI);
+   XPCOMUtils.defineLazyGetter(this, "originURI", getOriginURI);
+ 
+   XPCOMUtils.defineLazyGetter(this, "loadContext", getLoadContext);
+   XPCOMUtils.defineLazyGetter(this, "browser", getBrowser);
+ }
+ 
+ HttpResponse.headerTypes = ["Location", "Refresh"];
+ 
+ HttpResponse.prototype.removeResponseHeader = function() {
+   this.httpChannel.setResponseHeader(this.redirHeaderType, "", false);
+ };
+ 
+ 
+ 
+ 
+ 
+ /**
+  * This function calls getResponseHeader(headerType). If there is no such
+  * header, that function will throw NS_ERROR_NOT_AVAILABLE.
+  */
+ function determineHttpHeader() {
+   this.containsRedirection = true;
+ 
+   for (let i in HttpResponse.headerTypes) {
+     try {
+       this.redirHeaderType = HttpResponse.headerTypes[i];
+       this.redirHeaderValue = this.httpChannel.getResponseHeader(this.redirHeaderType);
+       // In case getResponseHeader() didn't throw NS_ERROR_NOT_AVAILABLE,
+       // the header-type exists, so we return:
+       return;
+     } catch (e) {
+     }
+   }
+ 
+   // The following will be executed when there is no redirection:
+   this.containsRedirection = false;
+   this.redirHeaderType = null;
+   this.redirHeaderValue = null;
+ }
+ 
+ function getRawDestString() {
+   switch (this.redirHeaderType) {
+     case "Location":
+       return this.redirHeaderValue;
+ 
+     case "Refresh":
+       try {
+         // We can ignore the delay because we aren't manually doing
+         // the refreshes. Allowed refreshes we still leave to the browser.
+         // The rawDestString may be empty if the origin is what should be refreshed.
+         // This will be handled by DomainUtil.determineRedirectUri().
+         return DomainUtil.parseRefresh(this.redirHeaderValue).destURI;
+       } catch (e) {
+         Logger.warning(Logger.TYPE_HEADER_REDIRECT,
+             "Invalid refresh header: <" + this.redirHeaderValue + ">");
+         if (!Prefs.isBlockingDisabled()) {
+           this.removeResponseHeader();
+         }
+         return null;
+       }
+ 
+     default:
+       return null;
+   }
+ }
+ 
+ function getDestURI() {
+   return Services.io.newURI(this.rawDestString, null, this.originURI);
+ }
+ 
+ function getOriginURI() {
+   return Services.io.newURI(this.httpChannel.name, null, null);
+ }
+ 
+ function getLoadContext() {
+   // more info on the load context:
+   // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions
+ 
+   /* start - be careful when editing here */
+   try {
+     return this.httpChannel.notificationCallbacks
+                            .QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsILoadContext);
+   } catch (ex) {
+     try {
+       return this.httpChannel.loadGroup
+                              .notificationCallbacks
+                              .getInterface(Ci.nsILoadContext);
+     } catch (ex2) {
+       // fixme: the Load Context can't be found in case a favicon
+       //        request is redirected, that is, the server responds
+       //        with a 'Location' header when the server's
+       //        `favicon.ico` is requested.
+       Logger.warning(Logger.TYPE_HEADER_REDIRECT, "The redirection's " +
+                      "Load Context couldn't be found! " + ex2);
+       return null;
+     }
+   }
+   /* end - be careful when editing here */
+ }
+ 
+ /**
+  * Get the <browser> (nsIDOMXULElement) related to this request.
+  */
+ function getBrowser() {
+   let loadContext = this.loadContext;
+ 
+   if (loadContext === null) {
+     return null;
+   }
+ 
+   try {
+     if (loadContext.topFrameElement) {
+       // the top frame element should be already the browser element
+       return loadContext.topFrameElement;
+     } else {
+       // we hope the associated window is available. in multiprocessor
+       // firefox it's not available.
+       return WindowUtils.getBrowserForWindow(loadContext.topWindow);
+     }
+   } catch (e) {
+     Logger.warning(Logger.TYPE_HEADER_REDIRECT, "The browser for " +
+                    "the redirection's Load Context couldn't be " +
+                    "found! " + e);
+     return null;
+   }
+ }
++
diff --cc content/lib/logger.jsm
index 0000000,3ccd6e2..df1e33c
mode 000000,100644..100644
--- a/content/lib/logger.jsm
+++ b/content/lib/logger.jsm
@@@ -1,0 -1,201 +1,191 @@@
+ /*
+  * ***** 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 = ["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([
+   "lib/environment",
+   "lib/prefs"
+ ], this);
+ 
+ 
+ /**
+  * Provides logging methods
+  */
+ let Logger = (function() {
+ 
+   let self = {
+     TYPE_CONTENT: 1, // content whose origin isn't known more specifically
+     TYPE_META_REFRESH: 2, // info related to meta refresh
+     TYPE_HEADER_REDIRECT: 4, // info related to header redirects
+     TYPE_INTERNAL: 8, // internal happenings of the extension
+     TYPE_ERROR: 16, // errors
+     TYPE_POLICY: 32, // Policy changes, storage, etc.
+     TYPE_ALL: 0x0 - 1, // all
+ 
+     LEVEL_OFF: Number.MAX_VALUE, // no logging
+     LEVEL_SEVERE: 1000,
+     LEVEL_WARNING: 900,
+     LEVEL_INFO: 800,
+     LEVEL_DEBUG: 700,
+     LEVEL_ALL: Number.MIN_VALUE, // log everything
+   };
+ 
+   self._TYPE_NAMES = {};
+   self._TYPE_NAMES[self.TYPE_CONTENT.toString()] = "CONTENT";
+   self._TYPE_NAMES[self.TYPE_META_REFRESH.toString()] = "META_REFRESH";
+   self._TYPE_NAMES[self.TYPE_HEADER_REDIRECT.toString()] = "HEADER_REDIRECT";
+   self._TYPE_NAMES[self.TYPE_INTERNAL.toString()] = "INTERNAL";
+   self._TYPE_NAMES[self.TYPE_ERROR.toString()] = "ERROR";
+   self._TYPE_NAMES[self.TYPE_POLICY.toString()] = "POLICY";
+ 
+   self._LEVEL_NAMES = {};
+   self._LEVEL_NAMES[self.LEVEL_SEVERE.toString()] = "SEVERE";
+   self._LEVEL_NAMES[self.LEVEL_WARNING.toString()] = "WARNING";
+   self._LEVEL_NAMES[self.LEVEL_INFO.toString()] = "INFO";
+   self._LEVEL_NAMES[self.LEVEL_DEBUG.toString()] = "DEBUG";
+ 
+   // function to use to print out the log
+   self.printFunc = dump;
+ 
+ 
+ 
+ 
+   let initialized = false;
+ 
+   // initially, enable logging. later the logging preferences of the user will
+   // will be loaded.
+   let enabled = true;
+   // These can be set to change logging level, what types of messages are
+   // logged, and to enable/disable logging.
+   let level = self.LEVEL_INFO;
+   let types = self.TYPE_ALL;
+ 
+   function updateLoggingSettings(rp) {
+     enabled = rpPrefBranch.getBoolPref("log");
+     level = rpPrefBranch.getIntPref("log.level");
+     types = rpPrefBranch.getIntPref("log.types");
+   }
+ 
+   /**
+    * init() is called by doLog() until initialization was successful.
+    * For the case that nothing is logged at all, init is registered as a
+    * startup-function.
+    */
+   function init() {
+     if (initialized === true) {
+       // don't initialize several times
+       return;
+     }
+ 
+     // rpPrefBranch is available now.
+     ProcessEnvironment.obMan.observeRPPref(
+         ["log"],
+         function(subject, topic) {
+           if (topic === "nsPref:changed") {
+             updateLoggingSettings();
+           }
+         });
+     updateLoggingSettings();
+ 
+     // don't call init() anymore when doLog() is called
+     doLog = log;
+ 
+     initialized = true;
+   }
+ 
+   ProcessEnvironment.addStartupFunction(Environment.LEVELS.ESSENTIAL, init);
+ 
+ 
+ 
+ 
+   /**
+    * This function will be called in case Logger isn't fully initialized yet.
+    */
+   let initialLog = function() {
+     init();
+     log.apply(this, arguments);
+   };
+ 
+   let log = function(aLevel, aType, aMessage, aError) {
+     let shouldLog = (enabled && aLevel >= level && types & aType);
+ 
 -    // #ifdef UNIT_TESTING
 -    if (aType === self.TYPE_ERROR || aLevel === self.LEVEL_SEVERE) {
 -      // increment the error count
 -      let errorCount = rpPrefBranch.getIntPref("unitTesting.errorCount");
 -      rpPrefBranch.setIntPref("unitTesting.errorCount", ++errorCount);
 -      Services.prefs.savePrefFile(null);
 -
 -      // log even if logging is disabled
 -      shouldLog = true;
 -    }
 -    // #endif
+ 
+     if (shouldLog) {
+       let levelName = self._LEVEL_NAMES[aLevel.toString()];
+       let typeName = self._TYPE_NAMES[aType.toString()];
+ 
+       let stack = (aError && aError.stack) ?
+                   ", stack was:\n" + aError.stack : "";
+       self.printFunc("[RequestPolicy] [" + levelName + "] [" + typeName + "] "
+           + aMessage + stack + "\n");
+     }
+   };
+ 
+   /**
+    * Initially call initialLog() on doLog().
+    * After initialization it will be log().
+    */
+   let doLog = initialLog;
+ 
+ 
+ 
+   self.severe = doLog.bind(self, self.LEVEL_SEVERE);
+   self.severeError = doLog.bind(self, self.LEVEL_SEVERE, self.TYPE_ERROR);
+   self.warning = doLog.bind(self, self.LEVEL_WARNING);
+   self.info = doLog.bind(self, self.LEVEL_INFO);
+   self.debug = doLog.bind(self, self.LEVEL_DEBUG);
+   self.dump = doLog.bind(self, self.LEVEL_DEBUG, self.TYPE_INTERNAL);
+ 
+   self.vardump = function(obj, name, ignoreFunctions) {
+     if (name != undefined) {
+       self.dump(name + " : " + obj);
+     } else {
+       self.dump(obj);
+     }
+     for (var i in obj) {
+       try {
+         if (typeof obj[i] == 'function') {
+           if (!ignoreFunctions) {
+             self.dump("    => key: " + i + " / value: instanceof Function");
+           }
+         } else {
+           self.dump("    => key: " + i + " / value: " + obj[i]);
+         }
+       } catch (e) {
+         self.dump("    => key: " + i + " / value: [unable to access value]");
+       }
+     }
+   };
+ 
+   return self;
+ }());
++
diff --cc content/lib/manager-for-event-listeners.jsm
index 0000000,55dbfed..fe023e0
mode 000000,100644..100644
--- a/content/lib/manager-for-event-listeners.jsm
+++ b/content/lib/manager-for-event-listeners.jsm
@@@ -1,0 -1,152 +1,153 @@@
+ /*
+  * ***** 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([
+   "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() {
+           Logger.dump('From now on new event listeners will be ' +
+                       'added immediately. Environment: "' +
+                       self.environment.name + '"');
+           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) {
+     Logger.dump('Immediately adding event listener for "' +
+                 listener.eventType + '". Environment: "' +
+                 self.environment.name + '"');
+     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) {
+       Logger.dump('Lazily adding event listener for "' +
+                   listener.eventType + '". Environment: "' +
+                   self.environment.name + '"');
+       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 +
+                 '". Environment: "' + self.environment.name + '"');
+     listener.target.removeEventListener(listener.eventType, listener.callback,
+                                         listener.useCapture);
+   }
+ };
++
diff --cc content/lib/manager-for-message-listeners.jsm
index 0000000,35a2830..6139df6
mode 000000,100644..100644
--- a/content/lib/manager-for-message-listeners.jsm
+++ b/content/lib/manager-for-message-listeners.jsm
@@@ -1,0 -1,186 +1,187 @@@
+ /*
+  * ***** 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([
+   "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() {
+           Logger.dump('From now on new message listeners will be ' +
+                       'added immediately. Environment: "' +
+                       self.environment.name + '"');
+           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!");
+     }
+   }
+ }
+ 
+ 
+ /**
+  * Add a listener. The class will then take care about adding
+  * and removing that message listener.
+  *
+  * @param {string} aMessageName
+  * @param {function} aCallback
+  * @param {boolean} aAddImmediately - Whether the listener should be
+  *     added immediately, i.e. without waiting for the environment
+  *     to start up.
+  */
+ ManagerForMessageListeners.prototype.addListener = function(aMessageName,
+                                                             aCallback,
+                                                             aAddImmediately) {
+   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;
+   }
+   if (aMessageName.indexOf(C.MM_PREFIX) === 0) {
+     Logger.warning(Logger.TYPE_INTERNAL,
+                    "The message name that has been passed to " +
+                    "`addListener()` contains the MM Prefix. " +
+                    "Extracting the message name.");
+     aMessageName = aMessageName.substr(C.MM_PREFIX.length)
+   }
+ 
+   let listener = {
+     messageName: aMessageName,
+     messageID: C.MM_PREFIX + aMessageName,
+     callback: aCallback,
+     listening: false
+   };
+   if (aAddImmediately === true || self.addNewListenersImmediately) {
+     Logger.dump('Immediately adding message listener for "' +
+                 listener.messageName + '". Environment: "' +
+                 self.environment.name + '"');
+     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) {
+       Logger.dump('Lazily adding message listener for "' +
+                   listener.messageName + '". Environment: "' +
+                   self.environment.name + '"');
+       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 --cc content/lib/observer-manager.jsm
index 0000000,43e05f2..b934ae3
mode 000000,100644..100644
--- a/content/lib/observer-manager.jsm
+++ b/content/lib/observer-manager.jsm
@@@ -1,0 -1,183 +1,184 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ /* global Components */
+ /* exported EXPORTED_SYMBOLS, ObserverManager */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ let EXPORTED_SYMBOLS = ["ObserverManager"];
+ 
+ let globalScope = this;
+ 
+ /* global ScriptLoader */
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/utils/observers", /* global SingleTopicObserver,
+                             SinglePrefBranchObserver */
+   "lib/environment" /* global Environment, ProcessEnvironment */
+ ], globalScope);
+ 
+ ScriptLoader.defineLazyModuleGetters({
+   "lib/prefs": [
+     "rpPrefBranch", /* global rpPrefBranch */
+     "rootPrefBranch" /* global rootPrefBranch */
+   ]
+ }, globalScope);
+ 
+ 
+ 
+ // Load the Logger at startup-time, not at load-time!
+ // ( On load-time Logger might be null. )
+ let Logger;
+ ProcessEnvironment.addStartupFunction(Environment.LEVELS.BACKEND, function() {
+   Logger = ScriptLoader.importModule("lib/logger").Logger;
+ });
+ 
+ 
+ 
+ 
+ /**
+  * An ObserverManager provides an interface to `nsIObserverService` which takes
+  * care of unregistering the observed topics. Every ObserverManager is bound to
+  * an environment.
+  */
+ function ObserverManager(aEnv) {
+   let self = this;
+ 
+   self.environment = aEnv;
+ 
+   if (!!aEnv) {
+     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) {
+       Logger.warning(Logger.TYPE_INTERNAL, "No Environment was specified for " +
+                      "a new ObserverManager! This means that the observers " +
+                      "won't be unregistered!");
+     }
+   }
+ 
+   // an object holding all observers for unregistering when unloading the page
+   self.observers = [];
+ }
+ 
+ 
+ /**
+  * Define 'observe' functions. Those function can be called from anywhere;
+  * the caller hands over an object with the keys being the "IDs" and the values
+  * being the function that should be called when the "ID" is observed.
+  *
+  * The "ID" for each function might be something different.
+  */
+ {
+   //
+   // functions using nsIObserverService
+   //
+ 
+   /**
+    * Observe one single topic by using nsIObserverService. Details:
+    * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIObserverService#addObserver%28%29
+    *
+    * @param {string} aTopic - The topic to be observed.
+    * @param {Function} aCallback - The observer's callback function.
+    */
+   ObserverManager.prototype.observeSingleTopic = function(aTopic, aCallback) {
+     let self = this;
+     self.observers.push(new SingleTopicObserver(aTopic, aCallback));
+   };
+ 
+   /**
+    * Observe multiple topics.
+    *
+    * @param {Array} aTopics - A list of topics to be observed.
+    * @param {function} aCallback - the function to be called when one of the
+    *     the topics is observed.
+    */
+   ObserverManager.prototype.observe = function(aTopics, aCallback) {
+     let self = this;
+     aTopics.forEach(function(topic) {
+       self.observeSingleTopic(topic, aCallback);
+     });
+   };
+ 
+   /**
+    * A shorthand for calling observe() with topic "requestpolicy-prefs-changed".
+    */
+   ObserverManager.prototype.observePrefChanges = function(aCallback) {
+     let self = this;
+     self.observeSingleTopic("requestpolicy-prefs-changed", aCallback);
+   };
+ 
+   //
+   // functions using nsIPrefBranch
+   //
+ 
+   /**
+    * Observe one single subdomain of a Pref Branch (using nsIPrefBranch).
+    * Details: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPrefBranch#addObserver%28%29
+    *
+    * @param {string} aTopic - The topic to be observed.
+    * @param {Function} aCallback - The observer's callback function.
+    */
+   ObserverManager.prototype.observeSinglePrefBranch = function(aPrefBranch,
+                                                                aDomain,
+                                                                aCallback) {
+     let self = this;
+     let obs = new SinglePrefBranchObserver(aPrefBranch, aDomain, aCallback);
+     self.observers.push(obs);
+   };
+ 
+   ObserverManager.prototype.observeRPPref = function(aDomains, aCallback) {
+     let self = this;
+     aDomains.forEach(function(domain) {
+       self.observeSinglePrefBranch(rpPrefBranch, domain, aCallback);
+     });
+   };
+   ObserverManager.prototype.observeRootPref = function(aDomains, aCallback) {
+     let self = this;
+     aDomains.forEach(function(domain) {
+       self.observeSinglePrefBranch(rootPrefBranch, domain, aCallback);
+     });
+   };
+ }
+ 
+ 
+ 
+ /**
+  * The function will unregister all registered observers.
+  */
+ ObserverManager.prototype.unregisterAllObservers = function() {
+   let self = this;
+   while (self.observers.length > 0) {
+     let observer = self.observers.pop();
+     observer.unregister();
+   }
+ };
++
diff --cc content/lib/policy-manager.alias-functions.js
index 0000000,99de8cf..18dd2fe
mode 000000,100644..100644
--- a/content/lib/policy-manager.alias-functions.js
+++ b/content/lib/policy-manager.alias-functions.js
@@@ -1,0 -1,98 +1,99 @@@
+ /*
+  * ***** 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 PolicyManager = (function(self) {
+ 
+   self.addAllowRule = self.addRule.bind(this, C.RULE_ACTION_ALLOW);
+   self.addTemporaryAllowRule = self.addTemporaryRule.bind(this,
+                                                           C.RULE_ACTION_ALLOW);
+   self.removeAllowRule = self.removeRule.bind(this, C.RULE_ACTION_ALLOW);
+   self.addDenyRule = self.addRule.bind(this, C.RULE_ACTION_DENY);
+   self.addTemporaryDenyRule = self.addTemporaryRule.bind(this,
+                                                          C.RULE_ACTION_DENY);
+   self.removeDenyRule = self.removeRule.bind(this, C.RULE_ACTION_DENY);
+ 
+ 
+   function getRuleData(aOrigin, aDest) {
+     let ruleData = {};
+     if (aOrigin !== undefined) {
+       ruleData["o"] = {"h": aOrigin};
+     }
+     if (aDest !== undefined) {
+       ruleData["d"] = {"h": aDest};
+     }
+     return ruleData;
+   }
+ 
+ 
+   function allowOrigin(aOrigin, noStore) {
+     self.addAllowRule(getRuleData(aOrigin), noStore);
+   }
+   self.allowOrigin = function(aOrigin) {
+     allowOrigin(aOrigin, false);
+   };
+   self.allowOriginDelayStore = function(aOrigin) {
+     allowOrigin(aOrigin, true);
+   };
+ 
+ 
+   self.temporarilyAllowOrigin = function(aOrigin) {
+     PolicyManager.addTemporaryAllowRule(getRuleData(aOrigin));
+   };
+   self.temporarilyAllowDestination = function(aDest) {
+     self.addTemporaryAllowRule(getRuleData(undefined, aDest));
+   };
+ 
+ 
+   function allowDestination(aDest, noStore) {
+     self.addAllowRule(getRuleData(undefined, aDest), noStore);
+   }
+   self.allowDestination = function(aDest) {
+     allowDestination(aDest, false);
+   };
+   self.allowDestinationDelayStore = function(aDest) {
+     allowDestination(aDest, true);
+   };
+ 
+ 
+   function allowOriginToDestination(originIdentifier, destIdentifier, noStore) {
+     self.addAllowRule(getRuleData(originIdentifier, destIdentifier), noStore);
+   };
+   self.allowOriginToDestination = function(originIdentifier, destIdentifier) {
+     allowOriginToDestination(originIdentifier, destIdentifier, false);
+   };
+   self.allowOriginToDestinationDelayStore = function(originIdentifier,
+                                                      destIdentifier) {
+     allowOriginToDestination(originIdentifier, destIdentifier, true);
+   };
+ 
+ 
+   self.temporarilyAllowOriginToDestination = function(originIdentifier,
+                                                       destIdentifier) {
+     PolicyManager.addTemporaryAllowRule(getRuleData(originIdentifier,
+                                                     destIdentifier));
+   };
+ 
+   return self;
+ }(PolicyManager || {}));
++
diff --cc content/lib/policy-manager.jsm
index 0057594,0035a41..c9a4627
--- a/content/lib/policy-manager.jsm
+++ b/content/lib/policy-manager.jsm
@@@ -337,6 -342,11 +342,12 @@@ let PolicyManager = (function(self) 
        }
      }
      return result;
-   }
+   };
+ 
+   return self;
+ }(PolicyManager || {}));
+ 
+ 
+ Services.scriptloader.loadSubScript(
+     "chrome://requestpolicy/content/lib/policy-manager.alias-functions.js");
 +
- };
diff --cc content/lib/prefs.jsm
index 0000000,fe103a0..db5f4f5
mode 000000,100644..100644
--- a/content/lib/prefs.jsm
+++ b/content/lib/prefs.jsm
@@@ -1,0 -1,149 +1,150 @@@
+ /*
+  * ***** 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 = ['rpPrefBranch', 'rootPrefBranch', 'Prefs'];
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules(["lib/environment"], this);
+ 
+ 
+ 
+ let rpPrefBranch = Services.prefs.getBranch("extensions.requestpolicy.")
+     .QueryInterface(Ci.nsIPrefBranch2);
+ let rootPrefBranch = Services.prefs.getBranch("")
+     .QueryInterface(Ci.nsIPrefBranch2);
+ 
+ 
+ 
+ let Prefs = (function() {
+   let self = {};
+ 
+   let defaultAllow = true;
+   let defaultAllowSameDomain = true;
+   let blockingDisabled = false;
+ 
+ 
+ 
+   self.save = function() {
+     Services.prefs.savePrefFile(null);
+   };
+ 
+ 
+   function getRPBoolPref(aPrefName) {
+     return rpPrefBranch.getBoolPref(aPrefName);
+   }
+   function setRPBoolPref(aPrefName, aValue) {
+     rpPrefBranch.setBoolPref(aPrefName, aValue);
+   }
+   // not needed yet
+   //function getInvertedRPBoolPref(aPrefName) {
+   //  return !rpPrefBranch.getBoolPref(aPrefName);
+   //}
+   //function setInvertedRPBoolPref(aPrefName, aValue) {
+   //  rpPrefBranch.setBoolPref(aPrefName, !aValue);
+   //}
+ 
+   /**
+    * Define a list of pref aliases that will be available through
+    * `Prefs.getter_function_name()` and `Prefs.setter_function_name()`.
+    * Those functions will be added to `self` subsequently.
+    */
+   let rpPrefAliases = {
+     "bool": {
+       "defaultPolicy.allow": "DefaultAllow",
+       "defaultPolicy.allowSameDomain": "DefaultAllowSameDomain",
+ 
+       // As an example, this will become `isBlockingDisabled()` and
+       // `setBlockingDisabled()`:
+       "startWithAllowAllEnabled": "BlockingDisabled"
+     }
+   };
+ 
+   /**
+    * Dynamically create functions like `isDefaultAllow` or
+    * `setBlockingDisabled`.
+    */
+   {
+     for (let prefID in rpPrefAliases.bool) {
+       let prefName = rpPrefAliases.bool[prefID];
+ 
+       // define the pref's getter function to `self`
+       self["is"+prefName] = getRPBoolPref.bind(this, prefID);
+ 
+       // define the pref's getter function to `self`
+       self["set"+prefName] = setRPBoolPref.bind(this, prefID);
+     }
+   }
+ 
+   self.isPrefetchEnabled = function() {
+     return rootPrefBranch.getBoolPref("network.prefetch-next")
+         || !rootPrefBranch.getBoolPref("network.dns.disablePrefetch");
+   };
+ 
+   function isPrefEmpty(pref) {
+     try {
+       let value = rpPrefBranch.getComplexValue(pref, Ci.nsISupportsString).data;
+       return value == '';
+     } catch (e) {
+       return true;
+     }
+   }
+ 
+   self.oldRulesExist = function() {
+     return !(isPrefEmpty('allowedOrigins') &&
+              isPrefEmpty('allowedDestinations') &&
+              isPrefEmpty('allowedOriginsToDestinations'));
+   };
+ 
+ 
+ 
+   function observePref(subject, topic, data) {
+     if (topic == "nsPref:changed") {
+       // Send an observer notification that a pref that affects RP has been
+       // changed.
+       // TODO: also send the pref's name and its branch
+       Services.obs.notifyObservers(null, "requestpolicy-prefs-changed", null);
+     }
+   };
+ 
+   function registerPrefObserver() {
+     // observe everything on RP's pref branch
+     ProcessEnvironment.obMan.observeRPPref([""], observePref);
+ 
+     // observe what is needed else
+     ProcessEnvironment.obMan.observeRootPref([
+       "network.prefetch-next",
+       "network.dns.disablePrefetch"
+     ], observePref);
+   }
+   ProcessEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                         registerPrefObserver);
+ 
+   return self;
+ }());
++
diff --cc content/lib/request-processor.compat.js
index 0000000,4d63b30..d793dba
mode 000000,100644..100644
--- a/content/lib/request-processor.compat.js
+++ b/content/lib/request-processor.compat.js
@@@ -1,0 -1,320 +1,321 @@@
+ /*
+  * ***** 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/AddonManager.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/logger",
+   "lib/utils",
+   "lib/environment"
+ ], this);
+ 
+ 
+ 
+ let RequestProcessor = (function(self) {
+   let internal = Utils.moduleInternal(self);
+ 
+ 
+   let conflictingExtensions = [];
+   let compatibilityRules = [];
+   let topLevelDocTranslationRules = {};
+ 
+   // 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
+   function cleanup() {
+     AddonManager.removeAddonListener(addonListener);
+   }
+ 
+   ProcessEnvironment.addStartupFunction(Environment.LEVELS.BACKEND, init);
+   ProcessEnvironment.addShutdownFunction(Environment.LEVELS.BACKEND, cleanup);
+ 
+ 
+   function initializeExtensionCompatibility() {
+     if (compatibilityRules.length != 0) {
+       return;
+     }
+ 
+     var idArray = [];
+     idArray.push("greasefire at skrul.com"); // GreaseFire
+     idArray.push("{0f9daf7e-2ee2-4fcf-9d4f-d43d93963420}"); // Sage-Too
+     idArray.push("{899DF1F8-2F43-4394-8315-37F6744E6319}"); // NewsFox
+     idArray.push("brief at mozdev.org"); // Brief
+     idArray.push("foxmarks at kei.com"); // Xmarks Sync (a.k.a. Foxmarks)
+     // Norton Safe Web Lite Toolbar
+     idArray.push("{203FB6B2-2E1E-4474-863B-4C483ECCE78E}");
+     // Norton Toolbar (a.k.a. NIS Toolbar)
+     idArray.push("{0C55C096-0F1D-4F28-AAA2-85EF591126E7}");
+     // Norton Toolbar 2011.7.0.8
+     idArray.push("{2D3F3651-74B9-4795-BDEC-6DA2F431CB62}");
+     idArray.push("{c45c406e-ab73-11d8-be73-000a95be3b12}"); // Web Developer
+     idArray.push("{c07d1a49-9894-49ff-a594-38960ede8fb9}"); // Update Scanner
+     idArray.push("FirefoxAddon at similarWeb.com"); // SimilarWeb
+     idArray.push("{6614d11d-d21d-b211-ae23-815234e1ebb5}"); // Dr. Web Link Checker
+ 
+     for (let i in idArray) {
+       Logger.info(Logger.TYPE_INTERNAL, "Extension check: " + idArray[i]);
+       AddonManager.getAddonByID(idArray[i], initializeExtCompatCallback);
+     }
+   }
+ 
+ 
+ 
+ 
+   function initializeExtCompatCallback(ext) {
+     if (!ext) {
+       return;
+     }
+ 
+     if (ext.isActive === false) {
+       Logger.info(Logger.TYPE_INTERNAL, "Extension is not active: " + ext.name);
+       return;
+     }
+ 
+     switch (ext.id) {
+       case "greasefire at skrul.com" : // Greasefire
+         Logger.info(Logger.TYPE_INTERNAL,
+             "Using extension compatibility rules for: " + ext.name);
+         compatibilityRules.push(
+             ["file://", "http://userscripts.org/", ext.name]);
+         compatibilityRules.push(
+             ["file://", "http://static.userscripts.org/", ext.name]);
+         break;
+       case "{0f9daf7e-2ee2-4fcf-9d4f-d43d93963420}" : // Sage-Too
+       case "{899DF1F8-2F43-4394-8315-37F6744E6319}" : // NewsFox
+       case "brief at mozdev.org" : // Brief
+         Logger.info(Logger.TYPE_INTERNAL, "Conflicting extension: " + ext.name);
+         compatibilityRules.push(
+             ["resource://brief-content/", null, ext.name]);
+         conflictingExtensions.push({
+           "id" : ext.id,
+           "name" : ext.name,
+           "version" : ext.version
+         });
+         break;
+       case "foxmarks at kei.com" : // Xmarks Sync
+         Logger.info(Logger.TYPE_INTERNAL,
+             "Using extension compatibility rules for: " + ext.name);
+         compatibilityRules.push([
+           "https://login.xmarks.com/",
+           "https://static.xmarks.com/",
+           ext.name
+         ]);
+         break;
+       case "{203FB6B2-2E1E-4474-863B-4C483ECCE78E}" : // Norton Safe Web Lite
+       case "{0C55C096-0F1D-4F28-AAA2-85EF591126E7}" : // Norton NIS Toolbar
+       case "{2D3F3651-74B9-4795-BDEC-6DA2F431CB62}" : // Norton Toolbar
+         Logger.info(Logger.TYPE_INTERNAL,
+             "Using extension compatibility rules for: " + ext.name);
+         compatibilityRules.push([null, "symnst:", ext.name]);
+         compatibilityRules.push([null, "symres:", ext.name]);
+         break;
+       case "{c45c406e-ab73-11d8-be73-000a95be3b12}" : // Web Developer
+         Logger.info(Logger.TYPE_INTERNAL,
+             "Using extension compatibility rules for: " + ext.name);
+         compatibilityRules.push([
+           "about:blank",
+           "http://jigsaw.w3.org/css-validator/validator",
+           ext.name
+         ]);
+         compatibilityRules.push(
+             ["about:blank", "http://validator.w3.org/check", ext.name]);
+         break;
+       case "{c07d1a49-9894-49ff-a594-38960ede8fb9}" : // Update Scanner
+         Logger.info(Logger.TYPE_INTERNAL,
+             "Using extension compatibility rules for: " + ext.name);
+         var orig = "chrome://updatescan/content/diffPage.xul";
+         var translated = "data:text/html";
+         topLevelDocTranslationRules[orig] = translated;
+         break;
+       case "FirefoxAddon at similarWeb.com" : // SimilarWeb
+         Logger.info(Logger.TYPE_INTERNAL,
+             "Using extension compatibility rules for: " + ext.name);
+         compatibilityRules.push([
+           "http://api2.similarsites.com/",
+           "http://images2.similargroup.com/",
+           ext.name
+         ]);
+         compatibilityRules.push([
+           "http://www.similarweb.com/",
+           "http://go.similarsites.com/",
+           ext.name
+         ]);
+         break;
+       case "{6614d11d-d21d-b211-ae23-815234e1ebb5}" : // Dr. Web Link Checker
+         Logger.info(Logger.TYPE_INTERNAL,
+             "Using extension compatibility rules for: " + ext.name);
+         compatibilityRules.push([null, "http://st.drweb.com/", ext.name]);
+         break;
+       default :
+         Logger.severe(Logger.TYPE_INTERNAL,
+             "Unhandled extension (id typo?): " + ext.name);
+         break;
+     }
+   }
+ 
+ 
+ 
+ 
+   function initializeApplicationCompatibility() {
+     var appInfo = Cc["@mozilla.org/xre/app-info;1"].
+         getService(Ci.nsIXULAppInfo);
+ 
+     // Mozilla updates (doing this for all applications, not just individual
+     // applications from the Mozilla community that I'm aware of).
+     // At least the http url is needed for Firefox updates, adding the https
+     // one as well to be safe.
+     compatibilityRules.push(
+         ["http://download.mozilla.org/", null, appInfo.vendor]);
+     compatibilityRules.push(
+         ["https://download.mozilla.org/", null, appInfo.vendor]);
+     // There are redirects from 'addons' to 'releases' when installing addons
+     // from AMO. Adding the origin of 'releases' to be safe in case those
+     // start redirecting elsewhere at some point.
+     compatibilityRules.push(
+         ["http://addons.mozilla.org/", null, appInfo.vendor]);
+     compatibilityRules.push(
+         ["https://addons.mozilla.org/", null, appInfo.vendor]);
+     compatibilityRules.push(
+         ["http://releases.mozilla.org/", null, appInfo.vendor]);
+     compatibilityRules.push(
+         ["https://releases.mozilla.org/", null, appInfo.vendor]);
+     // Firefox 4 has the about:addons page open an iframe to the mozilla site.
+     // That opened page grabs content from other mozilla domains.
+     compatibilityRules.push([
+       "about:addons",
+       "https://services.addons.mozilla.org/",
+       appInfo.vendor
+     ]);
+     compatibilityRules.push([
+       "https://services.addons.mozilla.org/",
+       "https://static.addons.mozilla.net/",
+       appInfo.vendor
+     ]);
+     compatibilityRules.push([
+       "https://services.addons.mozilla.org/",
+       "https://addons.mozilla.org/",
+       appInfo.vendor]);
+     compatibilityRules.push([
+       "https://services.addons.mozilla.org/",
+       "https://www.mozilla.com/",
+       appInfo.vendor
+     ]);
+     compatibilityRules.push([
+       "https://services.addons.mozilla.org/",
+       "https://www.getpersonas.com/",
+       appInfo.vendor
+     ]);
+     compatibilityRules.push([
+       "https://services.addons.mozilla.org/",
+       "https://static-cdn.addons.mozilla.net/",
+       appInfo.vendor
+     ]);
+     compatibilityRules.push([
+       "https://services.addons.mozilla.org/",
+       "https://addons.cdn.mozilla.net/",
+       appInfo.vendor
+     ]);
+     // Firefox 4 uses an about:home page that is locally stored but can be
+     // the origin for remote requests. See bug #140 for more info.
+     compatibilityRules.push(["about:home", null, appInfo.vendor]);
+     // Firefox Sync uses a google captcha.
+     compatibilityRules.push([
+       "https://auth.services.mozilla.com/",
+       "https://api-secure.recaptcha.net/challenge?",
+       appInfo.vendor
+     ]);
+     compatibilityRules.push([
+       "https://api-secure.recaptcha.net/challenge?",
+       "https://www.google.com/recaptcha/api/challenge?",
+       appInfo.vendor
+     ]);
+     compatibilityRules.push([
+       "https://auth.services.mozilla.com/",
+       "https://www.google.com/recaptcha/api/",
+       appInfo.vendor
+     ]);
+     // Firefox 13 added links from about:newtab
+     compatibilityRules.push(["about:newtab", null, appInfo.vendor]);
+ 
+     // Flock
+     if (appInfo.ID == "{a463f10c-3994-11da-9945-000d60ca027b}") {
+       Logger.info(Logger.TYPE_INTERNAL,
+           "Application detected: " + appInfo.vendor);
+       compatibilityRules.push(
+           ["about:myworld", "http://www.flock.com/", appInfo.vendor]);
+       compatibilityRules.push(["about:flock", null, appInfo.vendor]);
+       compatibilityRules.push([
+         "http://www.flock.com/rss",
+         "http://feeds.feedburner.com/flock",
+         appInfo.vendor
+       ]);
+       compatibilityRules.push([
+         "http://feeds.feedburner.com/",
+         "http://www.flock.com/",
+         appInfo.vendor
+       ]);
+     }
+ 
+     // Seamonkey
+     if (appInfo.ID == "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}") {
+       Logger.info(Logger.TYPE_INTERNAL, "Application detected: Seamonkey");
+       compatibilityRules.push(["mailbox:", null, "Seamonkey"]);
+       compatibilityRules.push([null, "mailbox:", "Seamonkey"]);
+     }
+   }
+ 
+ 
+ 
+ 
+   self.getCompatibilityRules = function() {
+     return compatibilityRules;
+   };
+ 
+   self.getConflictingExtensions = function() {
+     return conflictingExtensions;
+   };
+ 
+   self.getTopLevelDocTranslation = function(uri) {
+     // We're not sure if the array will be fully populated during init. This
+     // is especially a concern given the async addon manager API in Firefox 4.
+     return topLevelDocTranslationRules[uri] || null;
+   };
+ 
+   return self;
+ }(RequestProcessor || {}));
++
diff --cc content/lib/request-processor.jsm
index 0000000,71b54ad..e90e4f6
mode 000000,100644..100644
--- a/content/lib/request-processor.jsm
+++ b/content/lib/request-processor.jsm
@@@ -1,0 -1,1075 +1,1076 @@@
+ /*
+  * ***** 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 Cr = Components.results;
+ const Cu = Components.utils;
+ 
+ let EXPORTED_SYMBOLS = ["RequestProcessor"];
+ 
+ const CP_OK = Ci.nsIContentPolicy.ACCEPT;
+ const CP_REJECT = Ci.nsIContentPolicy.REJECT_SERVER;
+ 
+ // A value intended to not conflict with aExtra passed to shouldLoad() by any
+ // other callers. Was chosen randomly.
+ const CP_MAPPEDDESTINATION = 0x178c40bf;
+ 
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/logger",
+   "lib/prefs",
+   "lib/policy-manager",
+   "lib/utils/domains",
+   "lib/utils",
+   "lib/request",
+   "lib/request-result",
+   "lib/request-set",
+   "lib/environment"
+ ], this);
+ ScriptLoader.defineLazyModuleGetters({
+   "main/content-policy": ["PolicyImplementation"]
+ }, this);
+ 
+ 
+ 
+ let RequestProcessor = (function(self) {
+   let internal = Utils.moduleInternal(self);
+ 
+ 
+   /**
+    * Number of elapsed milliseconds from the time of the last shouldLoad() call
+    * at which the cached results of the last shouldLoad() call are discarded.
+    *
+    * @type {number}
+    */
+   let lastShouldLoadCheckTimeout = 200;
+ 
+   // Calls to shouldLoad appear to be repeated, so successive repeated calls and
+   // their result (accept or reject) are tracked to avoid duplicate processing
+   // and duplicate logging.
+   /**
+    * Object that caches the last shouldLoad
+    * @type {Object}
+    */
+   let lastShouldLoadCheck = {
+     "origin" : null,
+     "destination" : null,
+     "time" : 0,
+     "result" : null
+   };
+ 
+   let historyRequests = {};
+ 
+   internal.submittedForms = {};
+   internal.submittedFormsReverse = {};
+ 
+   internal.clickedLinks = {};
+   internal.clickedLinksReverse = {};
+ 
+   internal.faviconRequests = {};
+ 
+   internal.mappedDestinations = {};
+ 
+   internal.requestObservers = [];
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+   function notifyRequestObserversOfBlockedRequest(request) {
+     for (var i = 0; i < internal.requestObservers.length; i++) {
+       if (!internal.requestObservers[i]) {
+         continue;
+       }
+       internal.requestObservers[i].observeBlockedRequest(request.originURI,
+           request.destURI, request.requestResult);
+     }
+   }
+ 
+   function notifyRequestObserversOfAllowedRequest(originUri,
+       destUri, requestResult) {
+     for (var i = 0; i < internal.requestObservers.length; i++) {
+       if (!internal.requestObservers[i]) {
+         continue;
+       }
+       internal.requestObservers[i].observeAllowedRequest(originUri, destUri,
+           requestResult);
+     }
+   }
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+   // We always call this from shouldLoad to reject a request.
+   function reject(reason, request) {
+     Logger.warning(Logger.TYPE_CONTENT, "** BLOCKED ** reason: " + reason +
+         ". " + request.detailsToString());
+ 
+     if (Prefs.isBlockingDisabled()) {
+       return CP_OK;
+     }
+ 
+     if (request.aContext) {
+       request.aContext.requestpolicyBlocked = true;
+     }
+ 
+     cacheShouldLoadResult(CP_REJECT, request.originURI, request.destURI);
+     internal.recordRejectedRequest(request);
+ 
+     if (Ci.nsIContentPolicy.TYPE_DOCUMENT === request.aContentType) {
+       // This was a blocked top-level document request. This may be due to
+       // a blocked attempt by javascript to set the document location.
+ 
+       let browser = request.getBrowser();
+       let window = request.getChromeWindow();
+ 
+       if (!browser || !window) {
+         Logger.warning(Logger.TYPE_CONTENT, "The user could not be notified " +
+             "about the blocked top-level document request!");
+       } else {
+         window.requestpolicy.overlay.observeBlockedTopLevelDocRequest(
+             browser, request.originURI, request.destURI);
+       }
+     }
+ 
+     return CP_REJECT;
+   }
+ 
+   internal.recordRejectedRequest = function(request) {
+     self._rejectedRequests.addRequest(request.originURI, request.destURI,
+         request.requestResult);
+     self._allowedRequests.removeRequest(request.originURI, request.destURI);
+     notifyRequestObserversOfBlockedRequest(request);
+   }
+ 
+   // We only call this from shouldLoad when the request was a remote request
+   // initiated by the content of a page. this is partly for efficiency. in other
+   // cases we just return CP_OK rather than return this function which
+   // ultimately returns CP_OK. Fourth param, "unforbidable", is set to true if
+   // this request shouldn't be recorded as an allowed request.
+   /**
+    * @param {string} reason
+    * @param {Request} request
+    * @param {boolean} unforbidable
+    */
+   function accept(reason, request, unforbidable) {
+     Logger.warning(Logger.TYPE_CONTENT, "** ALLOWED ** reason: " +
+         reason + ". " + request.detailsToString());
+ 
+     cacheShouldLoadResult(CP_OK, request.originURI, request.destURI);
+     // We aren't recording the request so it doesn't show up in the menu, but we
+     // want it to still show up in the request log.
+     if (unforbidable) {
+       notifyRequestObserversOfAllowedRequest(request.originURI, request.destURI,
+           request.requestResult);
+     } else {
+       internal.recordAllowedRequest(request.originURI, request.destURI, false,
+                                     request.requestResult);
+     }
+ 
+     return CP_OK;
+   }
+ 
+   function recordAllowedRequest(originUri, destUri, isInsert, requestResult) {
+     // Reset the accepted and rejected requests originating from this
+     // destination. That is, if this accepts a request to a uri that may itself
+     // originate further requests, reset the information about what that page is
+     // accepting and rejecting.
+     // If "isInsert" is set, then we don't want to clear the destUri info.
+     if (true !== isInsert) {
+       self._allowedRequests.removeOriginUri(destUri);
+       self._rejectedRequests.removeOriginUri(destUri);
+     }
+     self._rejectedRequests.removeRequest(originUri, destUri);
+     self._allowedRequests.addRequest(originUri, destUri, requestResult);
+     notifyRequestObserversOfAllowedRequest(originUri, destUri, requestResult);
+   }
+   internal.recordAllowedRequest = recordAllowedRequest;
+ 
+   function cacheShouldLoadResult(result, originUri, destUri) {
+     var date = new Date();
+     lastShouldLoadCheck.time = date.getTime();
+     lastShouldLoadCheck.destination = destUri;
+     lastShouldLoadCheck.origin = originUri;
+     lastShouldLoadCheck.result = result;
+   }
+ 
+   internal.checkByDefaultPolicy = function(originUri, destUri) {
+     if (Prefs.isDefaultAllow()) {
+       var result = new RequestResult(true,
+           REQUEST_REASON_DEFAULT_POLICY);
+       return result;
+     }
+ 
+     if (Prefs.isDefaultAllowSameDomain()) {
+       var originDomain = DomainUtil.getBaseDomain(originUri);
+       var destDomain = DomainUtil.getBaseDomain(destUri);
+ 
+       if (originDomain !== null && destDomain !== null) {
+         // apply this rule only if both origin and dest URIs
+         // do have a host.
+         return new RequestResult(originDomain === destDomain,
+             REQUEST_REASON_DEFAULT_SAME_DOMAIN);
+       }
+     }
+     // We probably want to allow requests from http:80 to https:443 of the same
+     // domain. However, maybe this is so uncommon it's not worth any extra
+     // complexity.
+     var originIdent = DomainUtil.getIdentifier(originUri, DomainUtil.LEVEL_SOP);
+     var destIdent = DomainUtil.getIdentifier(destUri, DomainUtil.LEVEL_SOP);
+     return new RequestResult(originIdent === destIdent,
+         REQUEST_REASON_DEFAULT_SAME_DOMAIN);
+   };
+ 
+   /**
+    * Determines if a request is a duplicate of the last call to shouldLoad().
+    * If it is, the cached result in lastShouldLoadCheck.result can be used.
+    * Using this simple cache of the last call to shouldLoad() keeps duplicates
+    * out of log data.
+    *
+    * Duplicate shouldLoad() calls can be produced for example by creating a
+    * page containing many <img> with the same image (same `src`).
+    *
+    * @param {Request} request
+    * @return {boolean} True if the request is a duplicate of the previous one.
+    */
+   function isDuplicateRequest(request) {
+     if (lastShouldLoadCheck.origin == request.originURI &&
+         lastShouldLoadCheck.destination == request.destURI) {
+       var date = new Date();
+       if (date.getTime() - lastShouldLoadCheck.time <
+           lastShouldLoadCheckTimeout) {
+         Logger.debug(Logger.TYPE_CONTENT,
+             "Using cached shouldLoad() result of " +
+             lastShouldLoadCheck.result + " for request to <" +
+             request.destURI + "> from <" + request.originURI + ">.");
+         return true;
+       } else {
+         Logger.debug(Logger.TYPE_CONTENT,
+             "shouldLoad() cache expired for result of " +
+             lastShouldLoadCheck.result + " for request to <" +
+             request.destURI + "> from <" + request.originURI + ">.");
+       }
+     }
+     return false;
+   }
+ 
+ 
+ 
+   function _getRequestsHelper(currentlySelectedOrigin, allRequestsOnDocument,
+       isAllowed) {
+     var result = new RequestSet();
+     var requests = allRequestsOnDocument.getAll();
+ 
+     // We're assuming ident is fullIdent (LEVEL_SOP). We plan to remove base
+     // domain and hostname levels.
+     for (var originUri in requests) {
+       if (DomainUtil.getBaseDomain(originUri) !== currentlySelectedOrigin) {
+         // only return requests from the given base domain
+         continue;
+       }
+       Logger.dump("test destBase: " + destBase);
+       for (var destBase in requests[originUri]) {
+         Logger.dump("test destBase: " + destBase);
+         for (var destIdent in requests[originUri][destBase]) {
+           Logger.dump("test destIdent: " + destIdent);
+           for (var destUri in requests[originUri][destBase][destIdent]) {
+             Logger.dump("test destUri: " + destUri);
+             var dest = requests[originUri][destBase][destIdent][destUri];
+             for (var i in dest) {
+               // TODO: This variable could have been created easily already in
+               //       getAllRequestsInBrowser(). ==> rewrite RequestSet to
+               //       contain a blocked list, an allowed list (and maybe a list
+               //       of all requests).
+               if (isAllowed === dest[i].isAllowed) {
+                 result.addRequest(originUri, destUri, dest[i]);
+               }
+             }
+           }
+         }
+       }
+     }
+ 
+     return result;
+   }
+ 
+ //  function _getOtherOriginsHelperFromDOM(document, reqSet) {
+ //    var documentUri = DomainUtil
+ //        .stripFragment(document.documentURI);
+ //    Logger.dump("Looking for other origins within DOM of "
+ //        + documentUri);
+ //    // TODO: Check other elements besides iframes and frames?
+ //    var frameTagTypes = {
+ //      "iframe" : null,
+ //      "frame" : null
+ //    };
+ //    for (var tagType in frameTagTypes) {
+ //      var iframes = document.getElementsByTagName(tagType);
+ //      for (var i = 0; i < iframes.length; i++) {
+ //        var child = iframes[i];
+ //        var childDocument = child.contentDocument;
+ //        // Flock's special home page is about:myworld. It has (i)frames in it
+ //        // that have no contentDocument. It's probably related to the fact that
+ //        // that is an xul page, but I have no reason to fully understand the
+ //        // problem in order to fix it.
+ //        if (!childDocument) {
+ //          continue;
+ //        }
+ //        var childUri = DomainUtil
+ //            .stripFragment(childDocument.documentURI);
+ //        if (childUri == "about:blank") {
+ //          // iframe empty or not loaded yet, or maybe blocked.
+ //          // childUri = child.src;
+ //          // If it's not loaded or blocked, it's not the origin for anything
+ //          // yet.
+ //          continue;
+ //        }
+ //        Logger.dump("Found DOM child " + tagType
+ //            + " with src <" + childUri + "> in document <" + documentUri + ">");
+ //        //var childUriIdent = DomainUtil.getIdentifier(childUri,
+ //        //    DomainUtil.LEVEL_SOP);
+ //        // if (!origins[childUriIdent]) {
+ //        //   origins[childUriIdent] = {};
+ //        // }
+ //        // origins[childUriIdent][childUri] = true;
+ //        reqSet.addRequest(documentUri, childUri);
+ //        _getOtherOriginsHelperFromDOM(childDocument, reqSet);
+ //      }
+ //    }
+ //  },
+ 
+   function _addRecursivelyAllRequestsFromURI(originURI, reqSet,
+       checkedOrigins) {
+     Logger.dump("Looking for other origins within allowed requests from "
+             + originURI);
+     if (!checkedOrigins[originURI]) {
+       // this "if" is needed for the first call of this function.
+       checkedOrigins[originURI] = true;
+     }
+     _addAllDeniedRequestsFromURI(originURI, reqSet);
+     var allowedRequests = RequestProcessor._allowedRequests
+         .getOriginUri(originURI);
+     if (allowedRequests) {
+       for (var destBase in allowedRequests) {
+         for (var destIdent in allowedRequests[destBase]) {
+           for (var destURI in allowedRequests[destBase][destIdent]) {
+             Logger.dump("Found allowed request to <"
+                 + destURI + "> from <" + originURI + ">");
+             reqSet.addRequest(originURI, destURI,
+                               allowedRequests[destBase][destIdent][destURI]);
+ 
+             if (!checkedOrigins[destURI]) {
+               // only check the destination URI if it hasn't been checked yet.
+               checkedOrigins[destURI] = true;
+ 
+               _addRecursivelyAllRequestsFromURI(destURI, reqSet,
+                   checkedOrigins);
+             }
+           }
+         }
+       }
+     }
+   }
+ 
+   function _addAllDeniedRequestsFromURI(originURI, reqSet) {
+     Logger.dump("Looking for other origins within denied requests from " +
+         originURI);
+     var requests = RequestProcessor._rejectedRequests.getOriginUri(originURI);
+     if (requests) {
+       for (var destBase in requests) {
+         for (var destIdent in requests[destBase]) {
+           for (var destUri in requests[destBase][destIdent]) {
+             Logger.dump("Found denied request to <" + destUri + "> from <" +
+                 originURI + ">");
+             reqSet.addRequest(originURI, destUri,
+                 requests[destBase][destIdent][destUri]);
+           }
+         }
+       }
+     }
+   }
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+     // TODO: make them private
+   self._rejectedRequests = new RequestSet();
+   self._allowedRequests = new RequestSet();
+ 
+ 
+ 
+   /**
+    * Process a NormalRequest.
+    *
+    * @param {NormalRequest} request
+    */
+   self.process = function(request) {
+     // uncomment for debugging:
+     //Logger.dump("request: " +
+     //            (request.aRequestOrigin ? request.aRequestOrigin.spec :
+     //             "<unknown>") +
+     //            " -->  "+request.aContentLocation.spec);
+     //Logger.vardump(request.aRequestOrigin);
+     //Logger.vardump(request.aContentLocation);
+     try {
+       if (request.isInternal()) {
+         Logger.debug(Logger.TYPE_CONTENT,
+                      "Allowing a request that seems to be internal. " +
+                      "Origin: " + request.originURI + ", Dest: " +
+                      request.destURI);
+         return CP_OK;
+       }
+ 
+       var originURI = request.originURI;
+       var destURI = request.destURI;
+ 
+       if (request.aRequestOrigin.scheme == "moz-nullprincipal") {
+         // Before RP has been forked, there was a hack: in case of a request
+         // with the origin's scheme being 'moz-nullprincipal', RequestPolicy
+         // used the documentURI of the request's context as the "real" origin
+         // URI.
+         //   (Note: RP assuemed that the context is always a document, but this
+         //    is in fact not always true.)
+         // The reason for using the context's documentURI was, according to
+         // @jsamuel's comment, that the request's origin was not always the
+         // correct URI; according to @jsamuel this was fixed in Firefox 16.
+         // Originally he wrote:
+         //   >  "[Since Fx 16] we should be able to count on the referrer
+         //   >  (aRequestOrigin) being set to something besides
+         //   >  moz-nullprincipal when there is a referrer."
+         // TODO: check whether the requests that are allowed by this case are
+         //       *definitely* internal request. Is it possible to determine
+         //       where this request originally came from?
+         //
+         // ### Links:
+         // * nsIPrincipal:
+         //   -> https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPrincipal
+         //
+         // * discussion about RequestPolicy with regard to detecting that
+         //   something has been entered in the url-bar -- it's the Mozilla Bug
+         //   about adding `aRequestPrincipal` to `shouldLoad()` and it's
+         //   milestone was Firefox 16.
+         //   -> https://bugzilla.mozilla.org/show_bug.cgi?id=767134#c15
+         if (request.aRequestPrincipal) {
+           Logger.warning(
+               Logger.TYPE_CONTENT,
+               "Allowing request that appears to be a URL entered in the " +
+               "location bar or some other good explanation: " + destURI);
+           return CP_OK;
+         }
+       }
+ 
+       if (request.aRequestOrigin.scheme == "view-source") {
+         var newOriginURI = originURI.split(":").slice(1).join(":");
+         Logger.info(Logger.TYPE_CONTENT,
+           "Considering view-source origin <"
+             + originURI + "> to be origin <" + newOriginURI + ">");
+         originURI = newOriginURI;
+         request.setOriginURI(originURI);
+       }
+ 
+       if (request.aContentLocation.scheme == "view-source") {
+         var newDestURI = destURI.split(":").slice(1).join(":");
+         if (newDestURI.indexOf("data:text/html") == 0) {
+           // "View Selection Source" has been clicked
+           Logger.info(Logger.TYPE_CONTENT,
+               "Allowing \"data:text/html\" view-source destination"
+                   + " (Selection Source)");
+           return CP_OK;
+         } else {
+           Logger.info(Logger.TYPE_CONTENT,
+               "Considering view-source destination <"
+                   + destURI + "> to be destination <" + newDestURI + ">");
+           destURI = newDestURI;
+           request.setDestURI(destURI);
+         }
+       }
+ 
+       if (originURI == "about:blank" && request.aContext) {
+         let domNode;
+         try {
+           domNode = request.aContext.QueryInterface(Ci.nsIDOMNode);
+         } catch (e if e.result == Cr.NS_ERROR_NO_INTERFACE) {}
+         if (domNode && domNode.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE) {
+           var newOriginURI;
+           if (request.aContext.documentURI &&
+               request.aContext.documentURI != "about:blank") {
+             newOriginURI = request.aContext.documentURI;
+           } else if (request.aContext.ownerDocument &&
+               request.aContext.ownerDocument.documentURI &&
+               request.aContext.ownerDocument.documentURI != "about:blank") {
+             newOriginURI = request.aContext.ownerDocument.documentURI;
+           }
+           if (newOriginURI) {
+             newOriginURI = DomainUtil.stripFragment(newOriginURI);
+             Logger.info(Logger.TYPE_CONTENT, "Considering origin <" +
+                         originURI + "> to be origin <" + newOriginURI + ">");
+             originURI = newOriginURI;
+             request.setOriginURI(originURI);
+           }
+         }
+       }
+ 
+ 
+       if (isDuplicateRequest(request)) {
+         return lastShouldLoadCheck.result;
+       }
+ 
+       // Sometimes, clicking a link to a fragment will result in a request
+       // where the origin is the same as the destination, but none of the
+       // additional content of the page is again requested. The result is that
+       // nothing ends up showing for blocked or allowed destinations because
+       // all of that data was cleared due to the new request.
+       // Example to test with: Click on "expand all" at
+       // http://code.google.com/p/SOME_PROJECT/source/detail?r=SOME_REVISION
+       if (originURI == destURI) {
+         Logger.warning(Logger.TYPE_CONTENT,
+             "Allowing (but not recording) request "
+                 + "where origin is the same as the destination: " + originURI);
+         return CP_OK;
+       }
+ 
+ 
+ 
+       if (request.aContext) {
+         let domNode;
+         try {
+           domNode = request.aContext.QueryInterface(Ci.nsIDOMNode);
+         } catch (e if e.result == Cr.NS_ERROR_NO_INTERFACE) {}
+ 
+         if (domNode && domNode.nodeName == "LINK" &&
+             (domNode.rel == "icon" || domNode.rel == "shortcut icon")) {
+           internal.faviconRequests[destURI] = true;
+         }
+       }
+ 
+ 
+ 
+       // Note: If changing the logic here, also make necessary changes to
+       // isAllowedRedirect).
+ 
+       // Checking for link clicks, form submissions, and history requests
+       // should be done before other checks. Specifically, when link clicks
+       // were done after allowed-origin and other checks, then links that
+       // were allowed due to other checks would end up recorded in the origin
+       // url's allowed requests, and woud then show up on one tab if link
+       // was opened in a new tab but that link would have been allowed
+       // regardless of the link click. The original tab would then show it
+       // in its menu.
+       if (internal.clickedLinks[originURI] &&
+           internal.clickedLinks[originURI][destURI]) {
+         // Don't delete the clickedLinks item. We need it for if the user
+         // goes back/forward through their history.
+         // delete internal.clickedLinks[originURI][destURI];
+ 
+         // We used to have this not be recorded so that it wouldn't cause us
+         // to forget blocked/allowed requests. However, when a policy change
+         // causes a page refresh after a link click, it looks like a link
+         // click again and so if we don't forget the previous blocked/allowed
+         // requests, the menu becomes inaccurate. Now the question is: what
+         // are we breaking by clearing the blocked/allowed requests here?
+         request.requestResult = new RequestResult(true,
+             REQUEST_REASON_LINK_CLICK);
+         return accept("User-initiated request by link click", request);
+ 
+       } else if (internal.submittedForms[originURI] &&
+           internal.submittedForms[originURI][destURI.split("?")[0]]) {
+         // Note: we dropped the query string from the destURI because form GET
+         // requests will have that added on here but the original action of
+         // the form may not have had it.
+         // Don't delete the clickedLinks item. We need it for if the user
+         // goes back/forward through their history.
+         // delete internal.submittedForms[originURI][destURI.split("?")[0]];
+ 
+         // See the note above for link clicks and forgetting blocked/allowed
+         // requests on refresh. I haven't tested if it's the same for forms
+         // but it should be so we're making the same change here.
+         request.requestResult = new RequestResult(true,
+             REQUEST_REASON_FORM_SUBMISSION);
+         return accept("User-initiated request by form submission", request);
+ 
+       } else if (historyRequests[destURI]) {
+         // When the user goes back and forward in their history, a request for
+         // the url comes through but is not followed by requests for any of
+         // the page's content. Therefore, we make sure that our cache of
+         // blocked requests isn't removed in this case.
+         delete historyRequests[destURI];
+         request.requestResult = new RequestResult(true,
+             REQUEST_REASON_HISTORY_REQUEST);
+         return accept("History request", request, true);
+       } else if (internal.userAllowedRedirects[originURI]
+           && internal.userAllowedRedirects[originURI][destURI]) {
+         // shouldLoad is called by location.href in overlay.js as of Fx
+         // 3.7a5pre and SeaMonkey 2.1a.
+         request.requestResult = new RequestResult(true,
+             REQUEST_REASON_USER_ALLOWED_REDIRECT);
+         return accept("User-allowed redirect", request, true);
+       }
+ 
+       if (request.aRequestOrigin.scheme == "chrome") {
+         if (request.aRequestOrigin.asciiHost == "browser") {
+           // "browser" origin shows up for favicon.ico and an address entered
+           // in address bar.
+           request.requestResult = new RequestResult(true,
+               REQUEST_REASON_USER_ACTION);
+           return accept(
+               "User action (e.g. address entered in address bar) or other good "
+                   + "explanation (e.g. new window/tab opened)", request);
+         } else {
+           // TODO: It seems sketchy to allow all requests from chrome. If I
+           // had to put my money on a possible bug (in terms of not blocking
+           // requests that should be), I'd put it here. Doing this, however,
+           // saves a lot of blocking of legitimate requests from extensions
+           // that originate from their xul files. If you're reading this and
+           // you know of a way to use this to evade RequestPolicy, please let
+           // me know, I will be very grateful.
+           request.requestResult = new RequestResult(true,
+               REQUEST_REASON_USER_ACTION);
+           return accept(
+               "User action (e.g. address entered in address bar) or other good "
+                   + "explanation (e.g. new window/tab opened)", request);
+         }
+       }
+ 
+       // This is mostly here for the case of popup windows where the user has
+       // allowed popups for the domain. In that case, the window.open() call
+       // that made the popup isn't calling the wrapped version of
+       // window.open() and we can't find a better way to register the source
+       // and destination before the request is made. This should be able to be
+       // removed if we can find a better solution for the allowed popup case.
+       if (request.aContext) {
+         let domNode;
+         try {
+           domNode = request.aContext.QueryInterface(Ci.nsIDOMNode);
+         } catch (e if e.result == Cr.NS_ERROR_NO_INTERFACE) {}
+ 
+         if (domNode && domNode.nodeName == "xul:browser" &&
+             domNode.currentURI && domNode.currentURI.spec == "about:blank") {
+           request.requestResult = new RequestResult(true,
+               REQUEST_REASON_NEW_WINDOW);
+           return accept("New window (should probably only be an allowed " +
+               "popup's initial request)", request, true);
+         }
+       }
+ 
+       // XMLHttpRequests made within chrome's context have these origins.
+       // Greasemonkey uses such a method to provide their cross-site xhr.
+       if (originURI == "resource://gre/res/hiddenWindow.html" ||
+           originURI == "resource://gre-resources/hiddenWindow.html") {
+       }
+ 
+       // Now that we have blacklists, a user could prevent themselves from
+       // being able to reload a page by blocking requests from * to the
+       // destination page. As a simple hack around this, for now we'll always
+       // allow request to the same origin. It would be nice to have a a better
+       // solution but I'm not sure what that solution is.
+       var originIdent = DomainUtil.getIdentifier(originURI);
+       var destIdent = DomainUtil.getIdentifier(destURI);
+       if (originIdent === destIdent &&
+           originIdent !== null && destIdent !== null) {
+         request.requestResult = new RequestResult(true,
+             REQUEST_REASON_IDENTICAL_IDENTIFIER);
+         return accept(
+             "Allowing request where origin protocol, host, and port are the" +
+             " same as the destination: " + originIdent, request);
+       }
+ 
+       request.requestResult = PolicyManager.checkRequestAgainstUserRules(
+           request.aRequestOrigin, request.aContentLocation);
+       for (var i = 0; i < request.requestResult.matchedDenyRules.length; i++) {
+         Logger.dump('Matched deny rules');
+         Logger.vardump(request.requestResult.matchedDenyRules[i]);
+       }
+       for (var i = 0; i < request.requestResult.matchedAllowRules.length; i++) {
+         Logger.dump('Matched allow rules');
+         Logger.vardump(request.requestResult.matchedAllowRules[i]);
+       }
+       // If there are both allow and deny rules, then fall back on the default
+       // policy. I believe this is effectively the same as giving precedence
+       // to allow rules when in default allow mode and giving precedence to
+       // deny rules when in default deny mode. It's just a different way of
+       // expressing the same logic. Now, whether that's the right logic we
+       // should be using to solve the problem of rule precedence and support
+       // for fine-grained rules overriding course-grained ones is a different
+       // question.
+       if (request.requestResult.allowRulesExist() &&
+           request.requestResult.denyRulesExist()) {
+         request.requestResult.resultReason =
+             REQUEST_REASON_DEFAULT_POLICY_INCONSISTENT_RULES;
+         if (Prefs.isDefaultAllow()) {
+           request.requestResult.isAllowed = true;
+           return accept("User policy indicates both allow and block. " +
+               "Using default allow policy", request);
+         } else {
+           request.requestResult.isAllowed = false;
+           return reject("User policy indicates both allow and block. " +
+               "Using default block policy", request);
+         }
+       }
+       if (request.requestResult.allowRulesExist()) {
+         request.requestResult.resultReason = REQUEST_REASON_USER_POLICY;
+         request.requestResult.isAllowed = true;
+         return accept("Allowed by user policy", request);
+       }
+       if (request.requestResult.denyRulesExist()) {
+         request.requestResult.resultReason = REQUEST_REASON_USER_POLICY;
+         request.requestResult.isAllowed = false;
+         return reject("Blocked by user policy", request);
+       }
+ 
+       request.requestResult = PolicyManager
+           .checkRequestAgainstSubscriptionRules(request.aRequestOrigin,
+               request.aContentLocation);
+       for (var i = 0; i < request.requestResult.matchedDenyRules.length; i++) {
+         Logger.dump('Matched deny rules');
+         Logger.vardump(
+             request.requestResult.matchedDenyRules[i]);
+       }
+       for (var i = 0; i < request.requestResult.matchedAllowRules.length; i++) {
+         Logger.dump('Matched allow rules');
+         Logger.vardump(
+             request.requestResult.matchedAllowRules[i]);
+       }
+       if (request.requestResult.allowRulesExist() &&
+           request.requestResult.denyRulesExist()) {
+         request.requestResult.resultReason =
+             REQUEST_REASON_DEFAULT_POLICY_INCONSISTENT_RULES;
+         if (Prefs.isDefaultAllow()) {
+           request.requestResult.isAllowed = true;
+           return accept(
+               "Subscription rules indicate both allow and block. " +
+               "Using default allow policy", request);
+         } else {
+           request.requestResult.isAllowed = false;
+           return reject("Subscription rules indicate both allow and block. " +
+               "Using default block policy", request);
+         }
+       }
+       if (request.requestResult.denyRulesExist()) {
+         request.requestResult.resultReason =
+             REQUEST_REASON_SUBSCRIPTION_POLICY;
+         request.requestResult.isAllowed = false;
+         return reject("Blocked by subscription policy", request);
+       }
+       if (request.requestResult.allowRulesExist()) {
+         request.requestResult.resultReason =
+             REQUEST_REASON_SUBSCRIPTION_POLICY;
+         request.requestResult.isAllowed = true;
+         return accept("Allowed by subscription policy", request);
+       }
+ 
+       let compatibilityRules = self.getCompatibilityRules();
+       for (var i = 0; i < compatibilityRules.length; i++) {
+         var rule = compatibilityRules[i];
+         var allowOrigin = rule[0] ? originURI.indexOf(rule[0]) == 0 : true;
+         var allowDest = rule[1] ? destURI.indexOf(rule[1]) == 0 : true;
+         if (allowOrigin && allowDest) {
+           request.requestResult = new RequestResult(true,
+               REQUEST_REASON_COMPATIBILITY);
+           return accept(
+               "Extension/application compatibility rule matched [" + rule[2] +
+               "]", request, true);
+         }
+       }
+ 
+       // If the destination has a mapping (i.e. it was originally a different
+       // destination but was changed into the current one), accept this
+       // request if the original destination would have been accepted.
+       // Check aExtra against CP_MAPPEDDESTINATION to stop further recursion.
+       if (request.aExtra != CP_MAPPEDDESTINATION &&
+           internal.mappedDestinations[destURI]) {
+         for (var mappedDest in internal.mappedDestinations[destURI]) {
+           var mappedDestUriObj = internal.mappedDestinations[destURI][mappedDest];
+           Logger.warning(Logger.TYPE_CONTENT,
+               "Checking mapped destination: " + mappedDest);
+           var mappedResult = PolicyImplementation.shouldLoad(
+               request.aContentType, mappedDestUriObj, request.aRequestOrigin,
+               request.aContext, request.aMimeTypeGuess, CP_MAPPEDDESTINATION);
+           if (mappedResult == CP_OK) {
+             return CP_OK;
+           }
+         }
+       }
+ 
+       request.requestResult = internal.checkByDefaultPolicy(originURI, destURI);
+       if (request.requestResult.isAllowed) {
+         return accept("Allowed by default policy", request);
+       } else {
+         // We didn't match any of the conditions in which to allow the request,
+         // so reject it.
+         return request.aExtra == CP_MAPPEDDESTINATION ? CP_REJECT :
+             reject("Denied by default policy", request);
+       }
+ 
+ 
+     } catch (e) {
+       Logger.severe(Logger.TYPE_ERROR,
+           "Fatal Error, " + e + ", stack was: " + e.stack);
+       Logger.severe(Logger.TYPE_CONTENT,
+           "Rejecting request due to internal error.");
+       return Prefs.isBlockingDisabled() ? CP_OK : CP_REJECT;
+     }
+   };
+ 
+   // RequestProcessor.finishProcessing = function(request, result) {
+   //   request.shouldLoadResult = result;
+   // };
+ 
+ 
+ 
+ 
+ 
+   /**
+    * Called as a http request is made. The channel is available to allow you to
+    * modify headers and such.
+    *
+    * Currently this just looks for prefetch requests that are getting through
+    * which we currently can't stop.
+    */
+   let examineHttpRequest = function(aSubject) {
+     var httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+     try {
+       // Determine if prefetch requests are slipping through.
+       if (httpChannel.getRequestHeader("X-moz") == "prefetch") {
+         // Seems to be too late to block it at this point. Calling the
+         // cancel(status) method didn't stop it.
+         Logger.warning(Logger.TYPE_CONTENT,
+             "Discovered prefetch request being sent to: " + httpChannel.name);
+       }
+     } catch (e) {
+       // No X-moz header.
+     }
+   };
+ 
+   ProcessEnvironment.obMan.observe(["http-on-modify-request"],
+                                    examineHttpRequest);
+ 
+ 
+ 
+ 
+ 
+ 
+   self.registerHistoryRequest = function(destinationUrl) {
+     destinationUrl = DomainUtil.ensureUriHasPath(
+         DomainUtil.stripFragment(destinationUrl));
+     historyRequests[destinationUrl] = true;
+     Logger.info(Logger.TYPE_INTERNAL,
+         "History item requested: <" + destinationUrl + ">.");
+   };
+ 
+   self.registerFormSubmitted = function(originUrl, destinationUrl) {
+     originUrl = DomainUtil.ensureUriHasPath(DomainUtil.stripFragment(originUrl));
+     destinationUrl = DomainUtil.ensureUriHasPath(
+         DomainUtil.stripFragment(destinationUrl));
+ 
+     Logger.info(Logger.TYPE_INTERNAL,
+         "Form submitted from <" + originUrl + "> to <" + destinationUrl + ">.");
+ 
+     // Drop the query string from the destination url because form GET requests
+     // will end up with a query string on them when shouldLoad is called, so
+     // we'll need to be dropping the query string there.
+     destinationUrl = destinationUrl.split("?")[0];
+ 
+     if (internal.submittedForms[originUrl] == undefined) {
+       internal.submittedForms[originUrl] = {};
+     }
+     if (internal.submittedForms[originUrl][destinationUrl] == undefined) {
+       // TODO: See timestamp note for registerLinkClicked.
+       internal.submittedForms[originUrl][destinationUrl] = true;
+     }
+ 
+     // Keep track of a destination-indexed map, as well.
+     if (internal.submittedFormsReverse[destinationUrl] == undefined) {
+       internal.submittedFormsReverse[destinationUrl] = {};
+     }
+     if (internal.submittedFormsReverse[destinationUrl][originUrl] == undefined) {
+       // TODO: See timestamp note for registerLinkClicked.
+       internal.submittedFormsReverse[destinationUrl][originUrl] = true;
+     }
+   };
+ 
+   self.registerLinkClicked = function(originUrl, destinationUrl) {
+     originUrl = DomainUtil.ensureUriHasPath(DomainUtil.stripFragment(originUrl));
+     destinationUrl = DomainUtil.ensureUriHasPath(
+         DomainUtil.stripFragment(destinationUrl));
+ 
+     Logger.info(Logger.TYPE_INTERNAL,
+         "Link clicked from <" + originUrl + "> to <" + destinationUrl + ">.");
+ 
+     if (internal.clickedLinks[originUrl] == undefined) {
+       internal.clickedLinks[originUrl] = {};
+     }
+     if (internal.clickedLinks[originUrl][destinationUrl] == undefined) {
+       // TODO: Possibly set the value to a timestamp that can be used elsewhere
+       // to determine if this is a recent click. This is probably necessary as
+       // multiple calls to shouldLoad get made and we need a way to allow
+       // multiple in a short window of time. Alternately, as it seems to always
+       // be in order (repeats are always the same as the last), the last one
+       // could be tracked and always allowed (or allowed within a small period
+       // of time). This would have the advantage that we could delete items from
+       // the clickedLinks object. One of these approaches would also reduce log
+       // clutter, which would be good.
+       internal.clickedLinks[originUrl][destinationUrl] = true;
+     }
+ 
+     // Keep track of a destination-indexed map, as well.
+     if (internal.clickedLinksReverse[destinationUrl] == undefined) {
+       internal.clickedLinksReverse[destinationUrl] = {};
+     }
+     if (internal.clickedLinksReverse[destinationUrl][originUrl] == undefined) {
+       // TODO: Possibly set the value to a timestamp, as described above.
+       internal.clickedLinksReverse[destinationUrl][originUrl] = true;
+     }
+   };
+ 
+   self.registerAllowedRedirect = function(originUrl, destinationUrl) {
+     originUrl = DomainUtil.ensureUriHasPath(DomainUtil.stripFragment(originUrl));
+     destinationUrl = DomainUtil.ensureUriHasPath(
+         DomainUtil.stripFragment(destinationUrl));
+ 
+     Logger.info(Logger.TYPE_INTERNAL, "User-allowed redirect from <" +
+         originUrl + "> to <" + destinationUrl + ">.");
+ 
+     if (internal.userAllowedRedirects[originUrl] == undefined) {
+       internal.userAllowedRedirects[originUrl] = {};
+     }
+     if (internal.userAllowedRedirects[originUrl][destinationUrl] == undefined) {
+       internal.userAllowedRedirects[originUrl][destinationUrl] = true;
+     }
+   };
+ 
+   /**
+    * Add an observer to be notified of all blocked and allowed requests. TODO:
+    * This should be made to accept instances of a defined interface.
+    *
+    * @param {Object} observer
+    */
+   self.addRequestObserver = function(observer) {
+     if (!("observeBlockedRequest" in observer)) {
+       throw "Observer passed to addRequestObserver does "
+           + "not have an observeBlockedRequest() method.";
+     }
+     Logger.debug(Logger.TYPE_INTERNAL,
+         "Adding request observer: " + observer.toString());
+     internal.requestObservers.push(observer);
+   };
+ 
+   /**
+    * Remove an observer added through addRequestObserver().
+    *
+    * @param {Object} observer
+    */
+   self.removeRequestObserver = function(observer) {
+     for (var i = 0; i < internal.requestObservers.length; i++) {
+       if (internal.requestObservers[i] == observer) {
+         Logger.debug(Logger.TYPE_INTERNAL,
+             "Removing request observer: " + observer.toString());
+         delete internal.requestObservers[i];
+         return;
+       }
+     }
+     Logger.warning(Logger.TYPE_INTERNAL,
+         "Could not find observer to remove " + "in removeRequestObserver()");
+   };
+ 
+ 
+ 
+   self.getDeniedRequests = function(currentlySelectedOrigin, allRequestsOnDocument) {
+     Logger.dump("## getDeniedRequests");
+     return _getRequestsHelper(currentlySelectedOrigin, allRequestsOnDocument,
+         false);
+   };
+ 
+   self.getAllowedRequests = function(currentlySelectedOrigin, allRequestsOnDocument) {
+     Logger.dump("## getAllowedRequests");
+     return _getRequestsHelper(currentlySelectedOrigin, allRequestsOnDocument,
+         true);
+   };
+ 
+   /**
+    * TODO: This comment is quite old. It might not be necessary anymore to
+    *       check the DOM since all requests are recorded, like:
+    *       RequestSet._origins[originURI][destBase][destIdent][destURI][i]
+    * Info: As soon as requests are saved per Tab, this function isn't needed
+    *       anymore.
+    *
+    * This will look both at the DOM as well as the recorded allowed requests to
+    * determine which other origins exist within the document. This includes
+    * other origins that have the same domain.
+    *
+    * The reason for also
+    * needing to check the DOM is that some sites (like gmail) will make multiple
+    * requests to the same uri for different iframes and this will cause us to
+    * only have in the recorded requests from a source uri the destinations from
+    * the most recent iframe that loaded that source uri. It may also help in
+    * cases where the user has multiple tabs/windows open to the same page.
+    *
+    * @param {}
+    *          browser
+    * @return {}
+    *          RequestSet
+    */
+   self.getAllRequestsInBrowser = function(browser) {
+     //var origins = {};
+     var reqSet = new RequestSet();
+ 
+     // If we get these from the DOM, then we won't know the relevant
+     // rules that were involved with allowing/denying the request.
+     // Maybe just look up the allowed/blocked requests in the
+     // main allowed/denied request sets before adding them.
+     //_getOtherOriginsHelperFromDOM(document, reqSet);
+ 
+     var uri = DomainUtil.stripFragment(browser.currentURI.spec);
+     _addRecursivelyAllRequestsFromURI(uri, reqSet, {});
+     return reqSet;
+   };
+ 
+   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');
++
diff --cc content/lib/request-processor.redirects.js
index 0000000,a6ea9e4..33673af
mode 000000,100644..100644
--- a/content/lib/request-processor.redirects.js
+++ b/content/lib/request-processor.redirects.js
@@@ -1,0 -1,413 +1,414 @@@
+ /*
+  * ***** 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 HTTPS_EVERYWHERE_REWRITE_TOPIC = "https-everywhere-uri-rewrite";
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/logger",
+   "lib/prefs",
+   "lib/policy-manager",
+   "lib/utils/domains",
+   "lib/utils",
+   "lib/request",
+   "lib/request-result",
+   "lib/http-response",
+   "lib/environment"
+ ], this);
+ 
+ 
+ 
+ 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.
+    */
+   internal.userAllowedRedirects = {};
+ 
+   internal.allowedRedirectsReverse = {};
+ 
+ 
+ 
+   ProcessEnvironment.obMan.observe(
+       ["http-on-examine-response"],
+       function(subject) {
+         examineHttpResponse(subject);
+       });
+   ProcessEnvironment.obMan.observe(
+       [HTTPS_EVERYWHERE_REWRITE_TOPIC],
+       function(subject, topic, data) {
+         handleHttpsEverywhereUriRewrite(subject, data);
+       });
+ 
+ 
+ 
+   function mapDestinations(origDestUri, newDestUri) {
+     origDestUri = DomainUtil.stripFragment(origDestUri);
+     newDestUri = DomainUtil.stripFragment(newDestUri);
+     Logger.info(Logger.TYPE_INTERNAL,
+         "Mapping destination <" + origDestUri + "> to <" + newDestUri + ">.");
+     if (!internal.mappedDestinations[newDestUri]) {
+       internal.mappedDestinations[newDestUri] = {};
+     }
+     internal.mappedDestinations[newDestUri][origDestUri] =
+         DomainUtil.getUriObject(origDestUri);
+   }
+ 
+   /**
+    * Handles observer notifications sent by the HTTPS Everywhere extension
+    * that inform us of URIs that extension has rewritten.
+    *
+    * @param nsIURI oldURI
+    * @param string newSpec
+    */
+   function handleHttpsEverywhereUriRewrite(oldURI, newSpec) {
+     oldURI = oldURI.QueryInterface(Ci.nsIURI);
+     mapDestinations(oldURI.spec, newSpec);
+   }
+ 
+   function checkRedirect(request) {
+     // TODO: Find a way to get rid of repitition of code between this and
+     // shouldLoad().
+ 
+     // Note: If changing the logic here, also make necessary changes to
+     // shouldLoad().
+ 
+     // This is not including link clicks, form submissions, and user-allowed
+     // redirects.
+ 
+     var originURI = request.originURI;
+     var destURI = request.destURI;
+ 
+     var originURIObj = DomainUtil.getUriObject(originURI);
+     var destURIObj = DomainUtil.getUriObject(destURI);
+ 
+     var result = PolicyManager.checkRequestAgainstUserRules(originURIObj,
+         destURIObj);
+     // For now, we always give priority to deny rules.
+     if (result.denyRulesExist()) {
+       result.isAllowed = false;
+       return result;
+     }
+     if (result.allowRulesExist()) {
+       result.isAllowed = true;
+       return result;
+     }
+ 
+     var result = PolicyManager.checkRequestAgainstSubscriptionRules(
+         originURIObj, destURIObj);
+     // For now, we always give priority to deny rules.
+     if (result.denyRulesExist()) {
+       result.isAllowed = false;
+       return result;
+     }
+     if (result.allowRulesExist()) {
+       result.isAllowed = true;
+       return result;
+     }
+ 
+     if (destURI[0] && destURI[0] == '/'
+         || destURI.indexOf(":") == -1) {
+       // Redirect is to a relative url.
+       // ==> allow.
+       return new RequestResult(true, REQUEST_REASON_RELATIVE_URL);
+     }
+ 
+     let compatibilityRules = self.getCompatibilityRules();
+     for (var i = 0; i < compatibilityRules.length; i++) {
+       var rule = compatibilityRules[i];
+       var allowOrigin = rule[0] ? originURI.indexOf(rule[0]) == 0 : true;
+       var allowDest = rule[1] ? destURI.indexOf(rule[1]) == 0 : true;
+       if (allowOrigin && allowDest) {
+         return new RequestResult(true, REQUEST_REASON_COMPATIBILITY);
+       }
+     }
+ 
+     var result = internal.checkByDefaultPolicy(originURI, destURI);
+     return result;
+   }
+ 
+ 
+   self.isAllowedRedirect = function(originURI, destURI) {
+     var request = new Request(originURI, destURI);
+     return (true === checkRedirect(request).isAllowed);
+   };
+ 
+ 
+ 
+   function processUrlRedirection(request) {
+     let httpResponse = request.httpResponse;
+     let httpChannel = httpResponse.httpChannel;
+     var originURI = request.originURI;
+     var destURI = request.destURI;
+     var headerType = httpResponse.redirHeaderType;
+ 
+     // Ignore redirects to javascript. The browser will ignore them, as well.
+     if (httpResponse.destURI.schemeIs("javascript")) {
+       Logger.warning(Logger.TYPE_HEADER_REDIRECT,
+           "Ignoring redirect to javascript URI <" + destURI + ">");
+       return;
+     }
+ 
+     request.requestResult = checkRedirect(request);
+     if (true === request.requestResult.isAllowed) {
+       Logger.warning(Logger.TYPE_HEADER_REDIRECT, "** ALLOWED ** '"
+           + headerType + "' header to <" + destURI + "> " + "from <" + originURI
+           + ">. Same hosts or allowed origin/destination.");
+       internal.recordAllowedRequest(originURI, destURI, false,
+                                     request.requestResult);
+       internal.allowedRedirectsReverse[destURI] = originURI;
+ 
+       // If this was a link click or a form submission, we register an
+       // additional click/submit with the original source but with a new
+       // destination of the target of the redirect. This is because future
+       // requests (such as using back/forward) might show up as directly from
+       // the initial origin to the ultimate redirected destination.
+       if (httpChannel.referrer) {
+         var realOrigin = httpChannel.referrer.spec;
+ 
+         if (internal.clickedLinks[realOrigin] &&
+             internal.clickedLinks[realOrigin][originURI]) {
+           Logger.warning(Logger.TYPE_HEADER_REDIRECT,
+               "This redirect was from a link click." +
+               " Registering an additional click to <" + destURI + "> " +
+               "from <" + realOrigin + ">");
+           self.registerLinkClicked(realOrigin, destURI);
+ 
+         } else if (internal.submittedForms[realOrigin]
+             && internal.submittedForms[realOrigin][originURI.split("?")[0]]) {
+           Logger.warning(Logger.TYPE_HEADER_REDIRECT,
+               "This redirect was from a form submission." +
+               " Registering an additional form submission to <" + destURI +
+               "> " + "from <" + realOrigin + ">");
+           self.registerFormSubmitted(realOrigin, destURI);
+         }
+       }
+ 
+       return;
+     }
+ 
+     // The header isn't allowed, so remove it.
+     try {
+       if (!Prefs.isBlockingDisabled()) {
+         httpResponse.removeResponseHeader();
+ 
+         let browser = request.browser;
+ 
+         if (browser !== null) {
+           // `browser` is null if it could not be found. One known
+           // example is a favicon request that is redirected.
+ 
+           // TODO: do not put data into the <browser> object. Maybe use
+           //       Map instead?
+ 
+           // save all blocked redirects directly in the browser element. the
+           // blocked elements will be checked later when the DOM content
+           // finished loading.
+           browser.requestpolicy = browser.requestpolicy || {blockedRedirects: {}};
+           browser.requestpolicy.blockedRedirects[originURI] = destURI;
+         }
+ 
+         // Cancel the request. As of Fx 37, this causes the location bar to
+         // show the URL of the previously displayed page.
+         httpChannel.cancel(Cr.NS_BINDING_ABORTED);
+ 
+         // TODO: show the redirect notification *only* when
+         //           a) a link has been clicked
+         //           b) an url has been entered.
+         //       In any other case the redirect should *not* cause a notific.
+         //       bar to be displayed, because the redirect hasn't been caused by
+         //       *explicit* user interaction.
+         //       Examples for such other cases are inline elements whose
+         //       destination causes a redirect (via a HTTP Header), e.g. <img>.
+         //    Note: As soon as this is fixed, enable this mozmill test:
+         //          tests/mozmill/tests/testRedirect/testInlineRedirect.js
+         showRedirectNotification(request) || Logger.warning(
+             Logger.TYPE_HEADER_REDIRECT,
+             "A redirect has been observed, but it was not possible to notify " +
+             "the user! The redirect was from page <" + request.originURI + "> " +
+             "to <" + request.destURI + ">.");
+ 
+         // We try to trace the blocked redirect back to a link click or form
+         // submission if we can. It may indicate, for example, a link that
+         // was to download a file but a redirect got blocked at some point.
+         var initialOrigin = originURI;
+         var initialDest = destURI;
+         // To prevent infinite loops, bound the number of iterations.
+         // Note that an apparent redirect loop doesn't mean a problem with a
+         // website as the site may be using other information, such as cookies
+         // that get set in the redirection process, to stop the redirection.
+         var iterations = 0;
+         const ASSUME_REDIRECT_LOOP = 100; // Chosen arbitrarily.
+         while (initialOrigin in internal.allowedRedirectsReverse &&
+                iterations++ < ASSUME_REDIRECT_LOOP) {
+           initialDest = initialOrigin;
+           initialOrigin = internal.allowedRedirectsReverse[initialOrigin];
+         }
+ 
+         if (initialOrigin in internal.clickedLinksReverse) {
+           for (var i in internal.clickedLinksReverse[initialOrigin]) {
+             // We hope there's only one possibility of a source page (that is,
+             // ideally there will be one iteration of this loop).
+             var sourcePage = i;
+           }
+ 
+           // Maybe we just record the clicked link and each step in between as
+           // an allowed request, and the final blocked one as a blocked request.
+           // That is, make it show up in the requestpolicy menu like anything
+           // else.
+           // We set the "isInsert" parameter so we don't clobber the existing
+           // info about allowed and deleted requests.
+           internal.recordAllowedRequest(sourcePage, initialOrigin, true,
+                                         request.requestResult);
+         }
+ 
+         // if (internal.submittedFormsReverse[initialOrigin]) {
+         // // TODO: implement for form submissions whose redirects are blocked
+         // }
+ 
+         internal.recordRejectedRequest(request);
+       }
+       Logger.warning(Logger.TYPE_HEADER_REDIRECT,
+           "** BLOCKED ** '" + headerType + "' header to <" + destURI + ">" +
+           " found in response from <" + originURI + ">");
+     } catch (e) {
+       Logger.severe(
+           Logger.TYPE_HEADER_REDIRECT, "Failed removing " +
+           "'" + headerType + "' header to <" + destURI + ">" +
+           "  in response from <" + originURI + ">. " + e, e);
+     }
+   }
+ 
+   function showRedirectNotification(request) {
+     let browser = request.browser;
+     if (browser === null) {
+       return false;
+     }
+ 
+     var window = browser.ownerGlobal;
+ 
+     Utils.tryMultipleTimes(function() {
+       var showNotification = Utils.getObjectPath(window, 'requestpolicy',
+           'overlay', '_showRedirectNotification');
+       if (!showNotification) {
+         return false;
+       }
+       return showNotification(browser, request.destURI, 0, request.originURI);
+     });
+     return true;
+   }
+ 
+ 
+ 
+ 
+ 
+   /**
+    * Called after a response has been received from the web server. Headers are
+    * available on the channel. The response can be accessed and modified via
+    * nsITraceableChannel.
+    */
+   function examineHttpResponse(aSubject) {
+     // Currently, if a user clicks a link to download a file and that link
+     // redirects and is subsequently blocked, the user will see the blocked
+     // destination in the menu. However, after they have allowed it from
+     // the menu and attempted the download again, they won't see the allowed
+     // request in the menu. Fixing that might be a pain and also runs the
+     // risk of making the menu cluttered and confusing with destinations of
+     // followed links from the current page.
+ 
+     // TODO: Make user aware of blocked headers so they can allow them if
+     // desired.
+ 
+     let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+     let httpResponse = new HttpResponse(httpChannel);
+ 
+     // the "raw" dest string might be a relative or absolute URI
+     let rawDestString = httpResponse.rawDestString;
+ 
+     if (httpResponse.containsRedirection === false || rawDestString === null) {
+       return;
+     }
+ 
+     let originString = httpResponse.originURI.specIgnoringRef;
+ 
+     // Allow redirects of requests from privileged code.
+     if (!isContentRequest(httpResponse)) {
+       // However, favicon requests that are redirected appear as non-content
+       // requests. So, check if the original request was for a favicon.
+       var originPath = httpResponse.originURI.path;
+       // We always have to check "/favicon.ico" because Firefox will use this
+       // as a default path and that request won't pass through shouldLoad().
+       if (originPath == "/favicon.ico" || internal.faviconRequests[originString]) {
+         // If the redirected request is allowed, we need to know that was a
+         // favicon request in case it is further redirected.
+         internal.faviconRequests[rawDestString] = true;
+         Logger.info(Logger.TYPE_HEADER_REDIRECT, "'" + httpResponse.redirHeaderType
+                 + "' header to <" + rawDestString + "> " + "from <" + originString
+                 + "> appears to be a redirected favicon request. "
+                 + "This will be treated as a content request.");
+       } else {
+         Logger.warning(Logger.TYPE_HEADER_REDIRECT,
+             "** ALLOWED ** '" + httpResponse.redirHeaderType +
+             "' header to <" + rawDestString + "> " +
+             "from <" + originString +
+             ">. Original request is from privileged code.");
+         return;
+       }
+     }
+ 
+     var request = new RedirectRequest(httpResponse);
+     processUrlRedirection(request);
+   };
+ 
+ 
+ 
+   /**
+    * Checks whether a request is initiated by a content window. If it's from a
+    * content window, then it's from unprivileged code.
+    */
+   function isContentRequest(httpResponse) {
+     let loadContext = httpResponse.loadContext;
+ 
+     if (loadContext === null) {
+       return false;
+     }
+ 
+     return !!loadContext.isContent;
+   }
+ 
+ 
+   return self;
+ }(RequestProcessor || {}));
++
diff --cc content/lib/request-result.jsm
index 404dbcd,2ee236a..197f32a
--- a/content/lib/request-result.jsm
+++ b/content/lib/request-result.jsm
@@@ -99,3 -102,3 +102,4 @@@ RequestResult.prototype = 
      return this.isAllowed ? false : !this.isDefaultPolicyUsed();
    }
  };
++
diff --cc content/lib/request-set.jsm
index 0000000,a9870a8..9c2e470
mode 000000,100644..100644
--- a/content/lib/request-set.jsm
+++ b/content/lib/request-set.jsm
@@@ -1,0 -1,241 +1,242 @@@
+ /*
+  * ***** 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 = ["RequestSet"];
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/logger",
+   "lib/utils/domains",
+   "lib/request-result"
+ ], this);
+ 
+ 
+ 
+ function getUriIdentifier(uri) {
+   try {
+     return DomainUtil.getIdentifier(uri, DomainUtil.LEVEL_SOP);
+   } catch (e) {
+     var msg = "getUriIdentifier exception on uri <" + uri + "> " +
+         ". Exception was: " + e;
+     throw new Error(msg);
+   }
+ }
+ 
+ 
+ function RequestSet() {
+   this._origins = {};
+ }
+ RequestSet.prototype = {
+   _origins : null,
+ 
+   print : function(name) {
+     var log = Logger;
+     log.dump("-------------------------------------------------");
+     log.dump("== Request Set <" + name + "> ==");
+     // "Take that, Big-O!"
+     var origins = this._origins;
+     for (var oUri in origins) {
+       log.dump("      " + "Origin uri: <" + oUri + ">");
+       for (var dBase in origins[oUri]) {
+         var dests = origins[oUri];
+         log.dump("        " + "Dest base domain: <" + dBase + ">");
+         for (var dIdent in dests[dBase]) {
+           log.dump("          " + "Dest identifier: <" + dIdent + ">");
+           for (var dUri in dests[dBase][dIdent]) {
+             log.dump("            " + "Dest uri: <" + dUri + ">");
+             for (var i in dests[dBase][dIdent][dUri]) {
+               log.dump("              " + "#: " + i);
+               for (var ruleStr in dests[dBase][dIdent][dUri][i]) {
+                 log.dump("                " + "Rule: <" + ruleStr + ">");
+               }
+             }
+           }
+         }
+       }
+     }
+     log.dump("-------------------------------------------------");
+   },
+ 
+   getAll : function() {
+     return this._origins;
+   },
+ 
+   // TODO: the name of this method, getAllMergedOrigins, is confusing. Is it
+   // getting all of the "merged origins" is it "getting all" and merging the
+   // origins when it does it?
+   getAllMergedOrigins : function() {
+     var result = {};
+     for (var originUri in this._origins) {
+       var dests = this._origins[originUri];
+       for (var destBase in dests) {
+         if (!result[destBase]) {
+            result[destBase] = {};
+         }
+         for (var destIdent in dests[destBase]) {
+           if (!result[destBase][destIdent]) {
+              result[destBase][destIdent] = {};
+           }
+           for (var destUri in dests[destBase][destIdent]) {
+             if (!result[destBase][destIdent][destUri]) {
+               result[destBase][destIdent][destUri] = dests[destBase][destIdent][destUri];
+             } else {
+               result[destBase][destIdent][destUri] =
+                     result[destBase][destIdent][destUri]
+                     .concat(dests[destBase][destIdent][destUri]);
+             }
+           }
+         }
+       }
+     }
+     return result;
+   },
+ 
+   getOriginUri : function(originUri) {
+     return this._origins[originUri] || {};
+   },
+ 
+   /**
+    * @param {Array} rules The rules that were triggered by this request.
+    */
+   addRequest : function(originUri, destUri, requestResult) {
+     if (requestResult == undefined) {
+       Logger.warning(Logger.TYPE_INTERNAL,
+           "addRequest() was called without a requestResult object!"
+           +" Creating a new one.\n"
+           +"\torigin: <"+originUri+">\n"
+           +"\tdestination: <"+destUri+">"
+       );
+       requestResult = new RequestResult();
+     }
+ 
+     if (!this._origins[originUri]) {
+       this._origins[originUri] = {};
+     }
+     var dests = this._origins[originUri];
+ 
+     var destBase = DomainUtil.getBaseDomain(destUri);
+     if (!dests[destBase]) {
+       dests[destBase] = {};
+     }
+ 
+     var destIdent = getUriIdentifier(destUri);
+     if (!dests[destBase][destIdent]) {
+       dests[destBase][destIdent] = {};
+     }
+ 
+     // if (typeof rules != "object") {
+     //   throw "addRequest 'rules' argument must be an object where each " +
+     //         "key/val is ruleStr/rule";
+     // }
+ /*
+     if (!dests[destBase][destIdent][destUri]) {
+       // TODO: this is a little sketchy. What if we clobber rules
+       // that were already here? Arguably if we are told to add the
+       // same origin and dest pair, this will happen. Is that supposed
+       // to be possible?
+       dests[destBase][destIdent][destUri] = requestResult;
+     } else {
+       // TODO: append rules, removing duplicates.
+     }
+     */
+     if (!dests[destBase][destIdent][destUri]) {
+       dests[destBase][destIdent][destUri] = [];
+     }
+     if (requestResult instanceof Array) {
+       dests[destBase][destIdent][destUri] =
+             dests[destBase][destIdent][destUri]
+             .concat(requestResult);
+     } else {
+       dests[destBase][destIdent][destUri].push(requestResult);
+     }
+   },
+ 
+   /**
+    */
+   removeRequest : function(originUri, destUri) {
+     if (!this._origins[originUri]) {
+       return;
+     }
+     var dests = this._origins[originUri];
+ 
+     var destBase = DomainUtil.getBaseDomain(destUri);
+     if (!dests[destBase]) {
+       return;
+     }
+ 
+     var destIdent = getUriIdentifier(destUri);
+     if (!dests[destBase][destIdent]) {
+       return;
+     }
+ 
+     if (!dests[destBase][destIdent][destUri]) {
+       return;
+     }
+     delete dests[destBase][destIdent][destUri];
+ 
+     if (Object.getOwnPropertyNames(dests[destBase][destIdent]).length > 0) {
+       return;
+     }
+     delete dests[destBase][destIdent];
+ 
+     if (Object.getOwnPropertyNames(dests[destBase]).length > 0) {
+       return;
+     }
+     delete dests[destBase];
+ 
+     if (Object.getOwnPropertyNames(dests).length > 0) {
+       return;
+     }
+     delete this._origins[originUri];
+   },
+ 
+   /**
+    */
+   removeOriginUri : function(originUri) {
+     delete this._origins[originUri];
+   },
+ 
+   containsBlockedRequests : function() {
+     var origins = this._origins
+     for (var originURI in origins) {
+       for (var destBase in origins[originURI]) {
+         for (var destIdent in origins[originURI][destBase]) {
+           for (var destURI in origins[originURI][destBase][destIdent]) {
+             for (var i in origins[originURI][destBase][destIdent][destURI]) {
+               if (true !==
+                   origins[originURI][destBase][destIdent][destURI][i].isAllowed) {
+                 return true;
+               }
+             }
+           }
+         }
+       }
+     }
+     return false;
+   }
+ };
++
diff --cc content/lib/request.jsm
index 88d902e,313aff1..bc85f04
--- a/content/lib/request.jsm
+++ b/content/lib/request.jsm
@@@ -332,3 -278,3 +278,4 @@@ function RedirectRequest(httpResponse) 
  }
  RedirectRequest.prototype = Object.create(Request.prototype);
  RedirectRequest.prototype.constructor = Request;
++
diff --cc content/lib/ruleset-storage.jsm
index e4c1ee8,33e406a..206a1cc
--- a/content/lib/ruleset-storage.jsm
+++ b/content/lib/ruleset-storage.jsm
@@@ -68,3 -79,3 +79,4 @@@ let RulesetStorage = 
    }
  
  };
++
diff --cc content/lib/ruleset.jsm
index 87386db,41e6401..373f401
--- a/content/lib/ruleset.jsm
+++ b/content/lib/ruleset.jsm
@@@ -1113,3 -1132,3 +1132,4 @@@ Ruleset.rawRuleToCanonicalString = func
  //   var secondStr = Ruleset.rawRuleToCanonicalString(second);
  //   return firstStr == secondStr;
  // }
++
diff --cc content/lib/script-loader.jsm
index 0000000,6476ba2..8b6b42e
mode 000000,100644..100644
--- a/content/lib/script-loader.jsm
+++ b/content/lib/script-loader.jsm
@@@ -1,0 -1,209 +1,210 @@@
+ /*
+  * ***** 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;
+ const Cr = Components.results;
+ 
+ let EXPORTED_SYMBOLS = ["ScriptLoader"];
+ 
+ // import some modules
+ // NOTICE: This file should NOT import any of RP's modules when it is loaded!
+ //         Doing so would be a bad practice, and might produce import() loops
+ //         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/';
+ 
+ function getModuleURI(id) {
+   return rpChromeContentURI + id + ".jsm";
+ }
+ 
+ /**
+  * If the ScriptLoader catches an Exception, it will be a severe error.
+  */
+ function logSevereError(msg, e) {
+   dump("[RequestPolicy] [SEVERE] [ERROR] " + msg + " " + e +
+        (e.stack ? ", stack was: " + e.stack : "") + "\n");
+ }
+ 
+ 
+ 
+ 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("lib/environment")] = true;
+ 
+ 
+   // contains the module IDs that are currently being imported initially and
+   // have not finished importing yet.
+   let modulesCurrentlyBeingImported = {};
+ 
+ 
+   let self = {
+     /**
+      * Unload all modules that have been imported.
+      * See https://developer.mozilla.org/en-US/docs/Components.utils.unload
+      */
+     unloadAllModules: function() {
+       for (let uri in importedModuleURIs) {
+         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
+      *        (optional) if not specified, one will be created.
+      *
+      * @return {Object} the scope
+      */
+     importModule: function(moduleID, scope) {
+       scope = scope || {};
+ 
+       // avoid import loops.
+       if (moduleID in modulesCurrentlyBeingImported) {
+         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);
+         importedModuleURIs[uri] = true;
+ 
+         if (moduleID in modulesCurrentlyBeingImported) {
+           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);
+       } catch (e) {
+         logSevereError('Failed to import module with ID "' + moduleID +
+                        '".', e);
+       }
+       return scope;
+     },
+ 
+     /**
+      * @param {Array} moduleIDs
+      *        the moduleIDs of the modules to import
+      * @param {Object} scope
+      *        (optional) if not specified, one will be created.
+      *
+      * @return {Object} the scope
+      */
+     importModules: function(moduleIDs, scope) {
+       scope = scope || {};
+ 
+       // caution: the modules should be imported in the order specified!
+       for (let i = 0, len = moduleIDs.length; i < len; ++i) {
+         self.importModule(moduleIDs[i], scope);
+       }
+ 
+       return scope;
+     },
+ 
+     /**
+      * @param {String} moduleID
+      * @param {Array} names
+      *                the names of the symbols to import
+      * @param {Object} scope
+      *        (optional) if not specified, one will be created.
+      *
+      * @return {Object} the scope
+      */
+     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];
+         XPCOMUtils.defineLazyModuleGetter(scope, name, uri);
+       }
+       importedModuleURIs[uri] = true;
+ 
+       return scope;
+     },
+ 
+     /**
+      * @param {Object} modules
+      *        An object with  moduleID:names  attributes which will be given to
+      *        self.defineLazyModuleGetter()
+      * @param {Object} scope
+      *        (optional) if not specified, one will be created.
+      *
+      * @return {Object} the scope
+      */
+     defineLazyModuleGetters: function(modules, scope) {
+       scope = scope || {};
+ 
+       for (let id in modules) {
+         if (modules.hasOwnProperty(id)) {
+           self.defineLazyModuleGetter(id, modules[id], scope);
+         }
+       }
+ 
+       return scope;
+     }
+   };
+ 
+   return self;
+ }());
++
diff --cc content/lib/subscription.jsm
index b1a3c24,a53103e..5aa7509
--- a/content/lib/subscription.jsm
+++ b/content/lib/subscription.jsm
@@@ -441,3 -442,3 +442,4 @@@ Subscription.prototype = 
    }
  
  };
++
diff --cc content/lib/utils.jsm
index 0000000,10ff720..a96c6c7
mode 000000,100644..100644
--- a/content/lib/utils.jsm
+++ b/content/lib/utils.jsm
@@@ -1,0 -1,192 +1,193 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ let EXPORTED_SYMBOLS = ["Utils"];
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ //Cu.import("resource://gre/modules/devtools/Console.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/prefs",
+   "lib/utils/constants",
+   "lib/environment"
+ ], this);
+ 
+ if (ProcessEnvironment.isMainProcess) {
+   Cu.import("resource://gre/modules/AddonManager.jsm");
+ }
+ 
+ 
+ 
+ 
+ 
+ let Utils = (function() {
+   let self = {};
+ 
+   /**
+    * Posts an action to the event queue of the current thread to run it
+    * asynchronously. Any additional parameters to this function are passed
+    * as parameters to the callback.
+    *
+    * @param {Function} callback
+    * @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() {
+         callback.apply(thisPtr, params);
+       }
+     };
+     self.threadManager.currentThread.dispatch(runnable,
+         Ci.nsIEventTarget.DISPATCH_NORMAL);
+   };
+   XPCOMUtils.defineLazyServiceGetter(self, "categoryManager",
+       "@mozilla.org/categorymanager;1", "nsICategoryManager");
+   XPCOMUtils.defineLazyServiceGetter(self, "threadManager",
+       "@mozilla.org/thread-manager;1", "nsIThreadManager");
+ 
+ 
+   /**
+    * Calls a function multiple times until it succeeds. The
+    * function must return TRUE on success.
+    *
+    * @param {function():boolean} aFunction
+    * @param {number} aTries - The number of tries.
+    */
+   self.tryMultipleTimes = function(aFunction, aTries=10) {
+     if (aTries <= 0) {
+       //console.log("no more tries!");
+       return;
+     }
+     let triesLeft = aTries - 1;
+     self.runAsync(function() {
+       if (aFunction.call(null, triesLeft) !== true) {
+         self.tryMultipleTimes(aFunction, triesLeft);
+       }
+     });
+   };
+ 
+   /**
+    * Return a nested property or `undefined` if it does not exist.
+    * Any element in the object chain may be undefined.
+    *
+    * Other implementations at http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key
+    *
+    * @param {Object} object
+    * @param {...string} properties
+    */
+   self.getObjectPath = function(object, ...properties) {
+     return properties.reduce(self.getObjectProperty, object);
+   };
+ 
+   /**
+    * @private
+    */
+   self.getObjectProperty = function(object, property) {
+     if (!!object && object.hasOwnProperty(property)) {
+       return object[property];
+     }
+     return undefined;
+   };
+ 
+ 
+ 
+ 
+   self.info = {};
+ 
+   // bad smell...
+   // get/set last/current RP version
+   if (ProcessEnvironment.isMainProcess) {
+     self.info.lastRPVersion = rpPrefBranch.getCharPref("lastVersion");
+ 
+     self.info.curRPVersion = "0.0";
+     // curRPVersion needs to be set asynchronously
+     AddonManager.getAddonByID(C.EXTENSION_ID, function(addon) {
+       rpPrefBranch.setCharPref("lastVersion", addon.version);
+       self.info.curRPVersion = addon.version;
+       if (self.info.lastRPVersion != self.info.curRPVersion) {
+         Services.prefs.savePrefFile(null);
+       }
+     });
+   }
+ 
+   // bad smell...
+   // get/set last/current app (e.g. firefox) version
+   if (ProcessEnvironment.isMainProcess) {
+     self.info.lastAppVersion = rpPrefBranch.getCharPref("lastAppVersion");
+ 
+     let curAppVersion = Services.appinfo.version;
+     self.info.curAppVersion = curAppVersion;
+     rpPrefBranch.setCharPref("lastAppVersion", curAppVersion);
+ 
+     if (self.info.lastAppVersion != self.info.curAppVersion) {
+       Services.prefs.savePrefFile(null);
+     }
+   }
+ 
+   let appID = Services.appinfo.ID;
+   self.info.isFirefox = appID === C.FIREFOX_ID;
+   self.info.isSeamonkey = appID === C.SEAMONKEY_ID;
+   self.info.isAustralis = self.info.isFirefox &&
+       Services.vc.compare(Services.appinfo.platformVersion, '29') >= 0;
+ 
+ 
+ 
+   /**
+    * This function returns and eventually creates a module's `internal`
+    * variable. The `internal` can be accessed from all submodules of that
+    * module (which might be in different files).
+    *
+    * The `internal` is added to `self`, and as soon as all modules have been
+    * loaded, i.e. when the startup functions are called, the `internal` is
+    * removed from `self` (the module is „sealed“).
+    *
+    *   This function can be used as follows:
+    *   let MyModule = (function(self) {
+    *     let internal = Utils.moduleInternal(self);
+    *   }(MyModule || {}));
+    *
+    * @param {Object} aModuleScope
+    * @returns {Object} the module's `internal`
+    */
+   self.moduleInternal = function(aModuleScope) {
+     aModuleScope.internal = aModuleScope.internal || {};
+     function sealInternal() {
+       delete aModuleScope.internal;
+     };
+     ProcessEnvironment.addStartupFunction(Environment.LEVELS.ESSENTIAL,
+                                           sealInternal);
+     return aModuleScope.internal;
+   };
+ 
+   return self;
+ }());
++
diff --cc content/lib/utils/constants.jsm
index 0000000,ef81422..c32fc1c
mode 000000,100644..100644
--- a/content/lib/utils/constants.jsm
+++ b/content/lib/utils/constants.jsm
@@@ -1,0 -1,54 +1,55 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ let EXPORTED_SYMBOLS = ["C"];
+ 
+ let C = {};
+ 
+ C.EXTENSION_ID = "requestpolicy at requestpolicy.com";
+ C.FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+ C.SEAMONKEY_ID = "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}";
+ 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
+ C.APP_STARTUP = 1; // The application is starting up.
+ C.APP_SHUTDOWN = 2; // The application is shutting down.
+ C.ADDON_ENABLE = 3; // The add-on is being enabled.
+ C.ADDON_DISABLE = 4; // The add-on is being disabled. (Also sent during uninstallation)
+ C.ADDON_INSTALL = 5; // The add-on is being installed.
+ C.ADDON_UNINSTALL = 6; // The add-on is being uninstalled.
+ C.ADDON_UPGRADE = 7; // The add-on is being upgraded.
+ C.ADDON_DOWNGRADE = 8; // The add-on is being downgraded.
+ 
+ // content policy
+ C.CP_OK = Ci.nsIContentPolicy.ACCEPT;
+ C.CP_REJECT = Ci.nsIContentPolicy.REJECT_SERVER;
+ 
+ C.RULE_ACTION_ALLOW = 1;
+ C.RULE_ACTION_DENY = 2;
++
diff --cc content/lib/utils/dom.jsm
index 0000000,899f6fa..785739f
mode 000000,100644..100644
--- a/content/lib/utils/dom.jsm
+++ b/content/lib/utils/dom.jsm
@@@ -1,0 -1,50 +1,51 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ let EXPORTED_SYMBOLS = ["DOMUtils"];
+ 
+ let DOMUtils = {};
+ 
+ /**
+  * Function that takes a DOM Element or an Array of DOM elements and removes
+  * all their children.
+  */
+ DOMUtils.removeChildren = function(aElements) {
+   // If aElements is not an Array, put the element in an Array.
+   let elements = Array.isArray(aElements) ? aElements : [aElements];
+   // Note on `isArray` (above):
+   //     using `instanceof` did not work. For details see
+   //     https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
+ 
+   for (let el of elements) {
+     while (el.firstChild) {
+       el.removeChild(el.firstChild);
+     }
+   }
+ };
++
diff --cc content/lib/utils/domains.jsm
index cc3e50e,585bbfb..77badcc
--- a/content/lib/utils/domains.jsm
+++ b/content/lib/utils/domains.jsm
@@@ -398,3 -360,3 +360,4 @@@ DomainUtil.hasStandardPort = function(u
           uri.port == 80 && uri.scheme == "http" ||
           uri.port == 443 && uri.scheme == "https";
  }
++
diff --cc content/lib/utils/files.jsm
index f6cb6f8,f8ca07c..a67f991
--- a/content/lib/utils/files.jsm
+++ b/content/lib/utils/files.jsm
@@@ -175,3 -202,3 +202,4 @@@ var FileUtil = 
      return file;
    }
  };
++
diff --cc content/lib/utils/observers.jsm
index 0000000,0906362..5ee484a
mode 000000,100644..100644
--- a/content/lib/utils/observers.jsm
+++ b/content/lib/utils/observers.jsm
@@@ -1,0 -1,92 +1,93 @@@
+ /*
+  * ***** 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 = ["SingleTopicObserver", "SinglePrefBranchObserver"];
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ 
+ /**
+  * Generic Observer class.
+  */
+ function Observer(aCallback) {
+   // As the `observe` function, take directly the parameter.
+   this.observe = aCallback;
+ 
+   // currently this obserer is not rgistered yet
+   this.isRegistered = false;
+ 
+   // register this observer
+   this.register();
+ }
+ Observer.prototype.register = function() {
+   if (!this.isRegistered) {
+     this._register();
+     this.isRegistered = true;
+   }
+ };
+ Observer.prototype.unregister = function() {
+   if (this.isRegistered) {
+     this._unregister();
+     this.isRegistered = false;
+   }
+ };
+ 
+ 
+ /**
+  * An instance of this class registers itself to `nsIObserverService` on behalf
+  * of some other object.
+  */
+ function SingleTopicObserver(aTopic, aCallback) {
+   this.topic = aTopic;
+   Observer.call(this, aCallback);
+ }
+ SingleTopicObserver.prototype = Object.create(Observer.prototype);
+ SingleTopicObserver.prototype.constructor = Observer;
+ 
+ SingleTopicObserver.prototype._register = function() {
+   Services.obs.addObserver(this, this.topic, false);
+ };
+ SingleTopicObserver.prototype._unregister = function() {
+   Services.obs.removeObserver(this, this.topic);
+ };
+ 
+ 
+ function SinglePrefBranchObserver(aBranch, aDomain, aCallback) {
+   this.branch = aBranch;
+   this.domain = aDomain;
+   Observer.call(this, aCallback);
+ }
+ SinglePrefBranchObserver.prototype = Object.create(Observer.prototype);
+ SinglePrefBranchObserver.prototype.constructor = Observer;
+ 
+ SinglePrefBranchObserver.prototype._register = function() {
+   this.branch.addObserver(this.domain, this, false);
+ };
+ SinglePrefBranchObserver.prototype._unregister = function() {
+   this.branch.removeObserver(this.domain, this);
+ };
++
diff --cc content/lib/utils/strings.jsm
index 0000000,0a657b9..3d5e986
mode 000000,100644..100644
--- a/content/lib/utils/strings.jsm
+++ b/content/lib/utils/strings.jsm
@@@ -1,0 -1,74 +1,75 @@@
+ /*
+  * ***** BEGIN LICENSE BLOCK *****
+  *
+  * RequestPolicy - A Firefox extension for control over cross-site requests.
+  * Copyright (c) 2008-2009 Justin Samuel
+  *
+  * 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ /**
+  * Note: The string utils are used also in the content process (see
+  * e10s/multiprocessor firefox), so this file shouldn't contain code which is
+  * limited to the chrome process.
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ let EXPORTED_SYMBOLS = ["StringUtils"];
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ 
+ 
+ let StringUtils = (function() {
+   let self = {};
+ 
+   XPCOMUtils.defineLazyGetter(self, "strbundle", function() {
+     return loadPropertiesFile(
+         "chrome://requestpolicy/locale/requestpolicy.properties");
+   });
+   // from https://developer.mozilla.org/en-US/Add-ons/
+   // How_to_convert_an_overlay_extension_to_restartless
+   // #Step_10.3A_Bypass_cache_when_loading_properties_files
+   function loadPropertiesFile(path)
+   {
+     /* HACK: The string bundle cache is cleared on addon shutdown, however it
+      * doesn't appear to do so reliably. Errors can erratically happen on next
+      * load of the same file in certain instances. (at minimum, when strings are
+      * added/removed) The apparently accepted solution to reliably load new
+      * versions is to always create bundles with a unique URL so as to bypass the
+      * cache. This is accomplished by passing a random number in a parameter after
+      * a '?'. (this random ID is otherwise ignored) The loaded string bundle is
+      * still cached on startup and should still be cleared out of the cache on
+      * addon shutdown. This just bypasses the built-in cache for repeated loads of
+      * the same path so that a newly installed update loads cleanly. */
+     return Services.strings.createBundle(path + "?" + Math.random());
+   }
+ 
+   self.$str = function(aName, aParams) {
+     if (!!aParams) {
+       return self.strbundle.formatStringFromName(aName, aParams,
+                                                  aParams.length);
+     } else {
+       return self.strbundle.GetStringFromName(aName);
+     }
+   };
+ 
+   return self;
+ }());
++
diff --cc content/lib/utils/windows.jsm
index 0000000,9364d1e..629e3bd
mode 000000,100644..100644
--- a/content/lib/utils/windows.jsm
+++ b/content/lib/utils/windows.jsm
@@@ -1,0 -1,147 +1,148 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ let EXPORTED_SYMBOLS = ["WindowUtils"];
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules(["lib/prefs"], this);
+ 
+ 
+ 
+ let WindowUtils = (function() {
+   let self = {};
+ 
+   self.getChromeWindow = function(aContentWindow) {
+     return aContentWindow.top.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebNavigation)
+                              .QueryInterface(Ci.nsIDocShellTreeItem)
+                              .rootTreeItem
+                              .QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindow);
+   };
+ 
+   self.getBrowserForWindow = function(aContentWindow) {
+     let win = self.getChromeWindow(aContentWindow);
+     let tabs = self.getTabsForWindow(win);
+     for (let tab of tabs) {
+       if (tab.linkedBrowser.contentWindow === aContentWindow.top) {
+         return tab.linkedBrowser;
+       }
+     }
+     return null;
+   };
+ 
+   self.getChromeWindowForDocShell = function(aDocShell) {
+     return aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
+                     .rootTreeItem
+                     .QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIDOMWindow);
+   };
+ 
+   self.getTabBrowser = function(window) {
+     // bug 1009938 - may be null in SeaMonkey
+     return window.gBrowser || window.getBrowser();
+   };
+ 
+   self.getTabsForWindow = function(window) {
+     return self.getTabBrowser(window).tabContainer.children;
+   };
+ 
+   //
+   // Private Browsing
+   //
+ 
+   // depending on the Firefox vesion, create the `isWindowPrivate` function
+   self.isWindowPrivate = (function() {
+     try {
+       // Firefox 20+
+       Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+ 
+       return (function(aWindow) {
+         return PrivateBrowsingUtils.isWindowPrivate(aWindow)
+       });
+     } catch(e) {
+       // pre Firefox 20
+       try {
+         let pbs = Cc["@mozilla.org/privatebrowsing;1"]
+             .getService(Ci.nsIPrivateBrowsingService);
+ 
+         return (function(aWindow) {
+           return pbs.privateBrowsingEnabled;
+         });
+       } catch(e) {
+         Components.utils.reportError(e);
+         // It's uncertain if private browsing is possible at all, so assume
+         // that Private Browsing is not possible.
+         return (function(aWindow) {
+           return true;
+         });
+       }
+     }
+   }());
+ 
+   /**
+    * Should it be possible to add permanent rules in that window?
+    *
+    * @return {boolean}
+    */
+   self.mayPermanentRulesBeAdded = function(aWindow) {
+     return self.isWindowPrivate(aWindow) === false ||
+         rpPrefBranch.getBoolPref("privateBrowsingPermanentWhitelisting");
+   };
+ 
+ 
+   //
+   // Window & DOM utilities
+   //
+ 
+   /**
+    * Wait for a window to be loaded and then add a list of Elements „by ID“ to
+    * a scope. The scope is optional, but in any case will be returned.
+    *
+    * @return {Object} the scope of the elements
+    */
+   self.getElementsByIdOnLoad = function(aWindow, aElementIDs, aScope,
+                                         aCallback) {
+     let scope = aScope || {};
+     let document = aWindow.document;
+     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;
+   };
+ 
+   return self;
+ }());
++
diff --cc content/lib/utils/xul.jsm
index 0000000,877e98a..b119c88
mode 000000,100644..100644
--- a/content/lib/utils/xul.jsm
+++ b/content/lib/utils/xul.jsm
@@@ -1,0 -1,175 +1,176 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/logger",
+   "lib/utils/strings",
+   "lib/utils/constants"
+ ], this);
+ 
+ let EXPORTED_SYMBOLS = ["XULUtils"];
+ 
+ let XULUtils = {};
+ 
+ let xulTrees = XULUtils.xulTrees = {};
+ 
+ let xulTreesScope = {
+   "exports": xulTrees,
+   "C": C,
+   "appID": Services.appinfo.ID
+ };
+ 
+ Services.scriptloader.loadSubScript(
+     'chrome://requestpolicy/content/ui/xul-trees.js',
+     xulTreesScope);
+ 
+ 
+ function getParentElement(doc, element) {
+   if (!element.parent) {
+     return false;
+   } else {
+     if (element.parent.id) {
+       return doc.getElementById(element.parent.id);
+     } else if (element.parent.special) {
+       switch (element.parent.special.type) {
+         case "__window__":
+           return doc.querySelector("window");
+ 
+         case "subobject":
+           let subobjectTree = element.parent.special.tree;
+           let parentElement = doc.getElementById(element.parent.special.id);
+           for (let i = 0, len = subobjectTree.length; i < len; ++i) {
+             parentElement = parentElement[subobjectTree[i]];
+           }
+           return parentElement;
+         default:
+           return false;
+       }
+     } else {
+       return false;
+     }
+   }
+ }
+ 
+ function isAttribute(element, attr) {
+   return attr != "children" && attr != "parent" && attr != "tag" &&
+           element.hasOwnProperty(attr);
+ }
+ 
+ function getAttrValue(element, attr) {
+   if (!isAttribute(element, attr)) {
+     return false;
+   }
+   let value = element[attr];
+   if (value.charAt(0) == "&" && value.charAt(value.length-1) == ";") {
+     try {
+       value = StringUtils.$str(value.substr(1, value.length-2));
+     } catch (e) {
+       Logger.severe(Logger.TYPE_ERROR, e, e);
+       return false;
+     }
+   }
+   return value;
+ }
+ 
+ function setAttributes(node, element) {
+   for (let attr in element) {
+     let value = getAttrValue(element, attr);
+     if (value) {
+       node.setAttribute(attr, value);
+     }
+   }
+ }
+ 
+ 
+ function recursivelyAddXULElements(doc, elements, parentElement=null) {
+   let parentElementIsSet = !!parentElement;
+ 
+   for (let i in elements) {
+     let element = elements[i];
+ 
+     if (!element || !element.tag) {
+       continue;
+     }
+     parentElement = parentElementIsSet ? parentElement :
+         getParentElement(doc, element);
+     if (false === parentElement) {
+       continue;
+     }
+     if (parentElement === null) {
+       Logger.warning(Logger.TYPE_ERROR,
+                      "parentElement of '"+element.id+"' is null!");
+       continue;
+     }
+ 
+     let newElem = doc.createElement(element.tag);
+     setAttributes(newElem, element);
+     if (element.children) {
+       recursivelyAddXULElements(doc, element.children, newElem);
+     }
+     parentElement.appendChild(newElem);
+   }
+ };
+ 
+ XULUtils.addTreeElementsToWindow = function(win, treeName) {
+   if (xulTrees.hasOwnProperty(treeName)) {
+     recursivelyAddXULElements(win.document, xulTrees[treeName]);
+   }
+ }
+ 
+ let elementIDsToRemove = {};
+ 
+ function getElementIDsToRemove(treeName) {
+   if (elementIDsToRemove.hasOwnProperty(treeName)) {
+     return elementIDsToRemove[treeName];
+   }
+   let ids = elementIDsToRemove[treeName] = [];
+   let tree = xulTrees[treeName];
+   for (let i in tree) {
+     ids.push(tree[i].id);
+   }
+   return ids;
+ }
+ 
+ XULUtils.removeTreeElementsFromWindow = function(win, treeName) {
+   if (!xulTrees.hasOwnProperty(treeName)) {
+     return;
+   }
+   let tree = xulTrees[treeName];
+   let elementIDs = getElementIDsToRemove(treeName);
+ 
+   for (let i in elementIDs) {
+     let id = elementIDs[i];
+     let node = win.document.getElementById(id);
+     if (node && node.parentNode) {
+       node.parentNode.removeChild(node);
+     }
+   }
+ }
++
diff --cc content/main/about-uri.jsm
index 0000000,3e0eb27..b60337f
mode 000000,100644..100644
--- a/content/main/about-uri.jsm
+++ b/content/main/about-uri.jsm
@@@ -1,0 -1,129 +1,130 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ const Cr = Components.results;
+ 
+ let EXPORTED_SYMBOLS = ["AboutRequestPolicy"];
+ 
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+ 
+ let globalScope = this;
+ 
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/environment",
+   "lib/utils"
+ ], globalScope);
+ 
+ 
+ var filenames = {
+   "basicprefs": "basicprefs.html",
+   "advancedprefs": "advancedprefs.html",
+   "yourpolicy": "yourpolicy.html",
+   "defaultpolicy": "defaultpolicy.html",
+   "subscriptions": "subscriptions.html",
+   "setup": "setup.html"
+ };
+ 
+ function getURI(aURI) {
+   let id;
+   let index = aURI.path.indexOf("?");
+   if (index >= 0 && aURI.path.length > index) {
+     id = aURI.path.substr(index+1);
+   }
+   if (!id || !(id in filenames)) {
+     id = "basicprefs";
+   }
+   return "chrome://requestpolicy/content/settings/" + filenames[id];
+ }
+ 
+ 
+ 
+ let AboutRequestPolicy = (function() {
+   let self = {
+     classDescription: "about:requestpolicy",
+     contractID: "@mozilla.org/network/protocol/about;1?what=requestpolicy",
+     classID: Components.ID("{ad30f46c-42a6-45cd-ad0b-08b37f87435a}"),
+     QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+ 
+     getURIFlags: function(aURI) {
+       return Ci.nsIAboutModule.ALLOW_SCRIPT;
+     },
+ 
+     newChannel: function(aURI) {
+       let uri = getURI(aURI)
+       let channel = Services.io.newChannel(uri, null, null);
+       channel.originalURI = aURI;
+       return channel;
+     },
+ 
+     //
+     // nsIFactory interface implementation
+     //
+ 
+     createInstance: function(outer, iid) {
+       if (outer) {
+         throw Cr.NS_ERROR_NO_AGGREGATION;
+       }
+       return self.QueryInterface(iid);
+     }
+   };
+ 
+ 
+   function registerFactory() {
+     Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+         .registerFactory(self.classID, self.classDescription,
+                          self.contractID, self);
+   }
+ 
+   ProcessEnvironment.addStartupFunction(
+       Environment.LEVELS.INTERFACE,
+       function () {
+         try {
+           registerFactory();
+         } catch (e if e.result === Cr.NS_ERROR_FACTORY_EXISTS) {
+           // When upgrading restartless the old factory might still exist.
+           Utils.runAsync(registerFactory);
+         }
+       });
+ 
+   function unregisterFactory() {
+     let registrar = Components.manager
+         .QueryInterface(Ci.nsIComponentRegistrar);
+     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 --cc content/main/content-policy.jsm
index 0000000,9b91249..ff03185
mode 000000,100644..100644
--- a/content/main/content-policy.jsm
+++ b/content/main/content-policy.jsm
@@@ -1,0 -1,187 +1,188 @@@
+ /*
+  * ***** 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;
+ const Cr = Components.results;
+ 
+ let EXPORTED_SYMBOLS = ["PolicyImplementation"];
+ 
+ let globalScope = this;
+ 
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/utils/constants",
+   "lib/logger",
+   "lib/request",
+   "lib/utils",
+   "lib/request-processor",
+   "lib/environment"
+ ], globalScope);
+ 
+ 
+ // TODO: implement nsIChannelEventSink to catch redirects as Adblock Plus does.
+ let PolicyImplementation = (function() {
+   let xpcom_categories = ["content-policy"];
+ 
+   let self = {
+     classDescription: "RequestPolicy JavaScript XPCOM Component",
+     classID:          Components.ID("{14027e96-1afb-4066-8846-e6c89b5faf3b}"),
+     contractID:       "@requestpolicy.com/requestpolicy-service;1"
+   };
+ 
+   /**
+    * Registers the content policy on startup.
+    */
+   function register() {
+     Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+                       .registerFactory(self.classID, self.classDescription,
+                                        self.contractID, self);
+ 
+     let catMan = Utils.categoryManager;
+     for (let category of xpcom_categories) {
+       catMan.addCategoryEntry(category, self.contractID, self.contractID, false,
+           true);
+     }
+ 
+     if (!self.mimeService) {
+       // self.rejectCode = typeof(/ /) == "object" ? -4 : -3;
+       self.rejectCode = Ci.nsIContentPolicy.REJECT_SERVER;
+       self.mimeService =
+           Cc['@mozilla.org/uriloader/external-helper-app-service;1']
+           .getService(Ci.nsIMIMEService);
+     }
+   }
+ 
+   ProcessEnvironment.addStartupFunction(
+       Environment.LEVELS.INTERFACE,
+       function () {
+         try {
+           register();
+         } catch (e if e.result === Cr.NS_ERROR_FACTORY_EXISTS) {
+           // When upgrading restartless the old factory might still exist.
+           Utils.runAsync(register);
+         }
+       });
+ 
+ 
+   function unregister() {
+     Logger.dump("shutting down PolicyImplementation...");
+ 
+     // Below the shouldLoad function is replaced by a new one
+     // which always allows *all* requests.
+     //
+     // What's the reason?
+     // ------------------
+     // The function for unregistering, which is `unregisterFactory`,
+     // has to be called async; this means that the unregistering
+     // will be done at a later time. However, it's necessary to
+     // disable blocking *right now*.
+     //
+     // Why is that necessary?
+     // ----------------------
+     // It's possible (or always true) that async functions
+     // get called *after* the addon finished shutting down.
+     // After the shutdown RequestPolicy's modules and
+     // functions can't be used anymore, the modules have
+     // been unloaded already. There might be still some
+     // objects or closures, but it's unreliable to use
+     // them.
+     // However, the shouldLoad function needs many of
+     // RequestPolicy's other modules and functions. So any
+     // call to RP's `shouldLoad` might cause exceptions,
+     // given that the call happens between now and the
+     // time when the factory is actually unregistered.
+ 
+     // Before defining the new shouldLoad ...
+     // ... save the return value in the closure of this function.
+     //     Similarly like described above this is necessary
+     //     because the `C` variable is not available anymore
+     //     after RP has been shut down.
+     var finalReturnValue = C.CP_OK;
+ 
+     // Actually create the final function, as it is described
+     // above.
+     self.shouldLoad = () => finalReturnValue;
+ 
+     let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+     let catMan = Utils.categoryManager;
+ 
+     for (let category of xpcom_categories) {
+       catMan.deleteCategoryEntry(category, self.contractID, false);
+     }
+ 
+     // This needs to run asynchronously, see bug 753687
+     Utils.runAsync(function() {
+       registrar.unregisterFactory(self.classID, self);
+     });
+   }
+ 
+   ProcessEnvironment.addShutdownFunction(Environment.LEVELS.INTERFACE,
+                                          unregister);
+ 
+   //
+   // nsISupports interface implementation
+   //
+ 
+   self.QueryInterface = XPCOMUtils.generateQI([Ci.nsIContentPolicy,
+                                                Ci.nsIObserver,
+                                                Ci.nsIFactory,
+                                                Ci.nsISupportsWeakReference]);
+ 
+   //
+   // nsIContentPolicy interface implementation
+   //
+ 
+   // https://developer.mozilla.org/en/nsIContentPolicy
+ 
+   self.shouldLoad = function(aContentType, aContentLocation, aRequestOrigin,
+       aContext, aMimeTypeGuess, aExtra, aRequestPrincipal) {
+     var request = new NormalRequest(
+         aContentType, aContentLocation, aRequestOrigin, aContext,
+         aMimeTypeGuess, aExtra, aRequestPrincipal);
+     return RequestProcessor.process(request);
+     // TODO: implement the following
+     // return request.shouldLoad(aContentType, aContentLocation, aRequestOrigin,
+     //     aContext, aMimeTypeGuess, aExtra, aRequestPrincipal);
+   };
+ 
+   self.shouldProcess = (() => C.CP_OK);
+ 
+   //
+   // nsIFactory interface implementation
+   //
+ 
+   self.createInstance = function(outer, iid) {
+     if (outer) {
+       throw Cr.NS_ERROR_NO_AGGREGATION;
+     }
+     return self.QueryInterface(iid);
+   };
+ 
+   return self;
+ }());
++
diff --cc content/main/default-pref-handler.js
index 0000000,77a62c7..17bc980
mode 000000,100644..100644
--- a/content/main/default-pref-handler.js
+++ b/content/main/default-pref-handler.js
@@@ -1,0 -1,114 +1,115 @@@
+ /*
+  * ***** BEGIN LICENSE BLOCK *****
+  *
+  * RequestPolicy - A Firefox extension for control over cross-site requests.
+  * Copyright (c) 2011 Justin Samuel
+  *
+  * 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 *****
+  */
+ 
+ // This file has to be called only once. It handles the default preferences [1],
+ // so it has to be called quite early at the extension startup.
+ //
+ // Note that this script may *only* be loaded from the main process!
+ // Also note that if possible this script shouldn't import any other of RP's
+ // modules, e.g. to prevent import() loops.
+ //
+ // [1] https://developer.mozilla.org/en-US/Add-ons/How_to_convert_an_overlay_extension_to_restartless#Step_4.3A_Manually_handle_default_preferences
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ 
+ 
+ let prefInitFunctions = {
+   getGenericPref: function(branch, prefName) {
+     switch (branch.getPrefType(prefName)) {
+       case 32:
+         // PREF_STRING
+         return this.getUCharPref(prefName, branch);
+ 
+       case 64:
+         // PREF_INT
+         return branch.getIntPref(prefName);
+ 
+       case 128:
+         // PREF_BOOL
+         return branch.getBoolPref(prefName);
+ 
+       case 0:
+       default:
+         // PREF_INVALID
+         return undefined;
+     }
+   },
+ 
+   setGenericPref: function(branch, prefName, prefValue) {
+     switch (typeof prefValue) {
+       case "string":
+         this.setUCharPref(prefName, prefValue, branch);
+         return;
+       case "number":
+         branch.setIntPref(prefName, prefValue);
+         return;
+       case "boolean":
+         branch.setBoolPref(prefName, prefValue);
+         return;
+     }
+   },
+ 
+   setDefaultPref: function(prefName, prefValue) {
+     var defaultBranch = Services.prefs.getDefaultBranch(null);
+     this.setGenericPref(defaultBranch, prefName, prefValue);
+   },
+ 
+   getUCharPref: function(prefName, branch) {  // Unicode getCharPref
+     branch = branch || Services.prefs;
+     return branch.getComplexValue(prefName, Ci.nsISupportsString).data;
+   },
+ 
+   setUCharPref: function(prefName, text, branch) { // Unicode setCharPref
+     var string = Cc["@mozilla.org/supports-string;1"]
+         .createInstance(Ci.nsISupportsString);
+     string.data = text;
+     branch = branch || Services.prefs;
+     branch.setComplexValue(prefName, Ci.nsISupportsString, string);
+   }
+ };
+ 
+ let defaultPrefScriptScope = {
+   pref: prefInitFunctions.setDefaultPref,
+   setGenericPref: prefInitFunctions.setGenericPref,
+   setUCharPref: prefInitFunctions.setUCharPref
+ };
+ 
+ 
+ //
+ // Load default preferences (if necessary)
+ //
+ 
+ try {
+   // this is necessary for restartless extensions:
+   // ( See https://developer.mozilla.org/en-US/Add-ons/
+   //   How_to_convert_an_overlay_extension_to_restartless
+   //   #Step_4.3A_Manually_handle_default_preferences )
+   Services.scriptloader.loadSubScript(
+       "chrome://requestpolicy/content/lib/default-preferences.js",
+       defaultPrefScriptScope);
+ } catch (e) {}
++
diff --cc content/main/pref-manager.jsm
index 0000000,74cb485..a6b8c52
mode 000000,100644..100644
--- a/content/main/pref-manager.jsm
+++ b/content/main/pref-manager.jsm
@@@ -1,0 -1,145 +1,146 @@@
+ /*
+  * ***** 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 = ["PrefManager"];
+ 
+ let globalScope = this;
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ Cu.import("resource://gre/modules/devtools/Console.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/utils/constants",
+   "lib/environment"
+ ], globalScope);
+ 
+ XPCOMUtils.defineLazyGetter(globalScope, "rpPrefBranch", function() {
+   return Services.prefs.getBranch("extensions.requestpolicy.")
+       .QueryInterface(Ci.nsIPrefBranch2);
+ });
+ XPCOMUtils.defineLazyGetter(globalScope, "rootPrefBranch", function() {
+   return Services.prefs.getBranch("").QueryInterface(Ci.nsIPrefBranch2);
+ });
+ 
+ 
+ 
+ 
+ 
+ let PrefManager = (function() {
+   let self = {};
+ 
+ 
+   // TODO: move to bootstrap.js
+   function handleUninstallOrDisable() {
+     var resetLinkPrefetch = rpPrefBranch.getBoolPref(
+         "prefetch.link.restoreDefaultOnUninstall");
+     var resetDNSPrefetch = rpPrefBranch.getBoolPref(
+         "prefetch.dns.restoreDefaultOnUninstall");
+ 
+     if (resetLinkPrefetch) {
+       if (rootPrefBranch.prefHasUserValue("network.prefetch-next")) {
+         rootPrefBranch.clearUserPref("network.prefetch-next");
+       }
+     }
+     if (resetDNSPrefetch) {
+       if (rootPrefBranch.prefHasUserValue("network.dns.disablePrefetch")) {
+         rootPrefBranch.clearUserPref("network.dns.disablePrefetch");
+       }
+     }
+     Services.prefs.savePrefFile(null);
+   }
+ 
+ 
+   self.init = function() {
+     // ================================
+     // manually handle RP's default preferences
+     // ----------------------------------------
+     // The following script needs to be called because bootsrapped addons have
+     // 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",
+         {});
+ 
+ 
+     // ================================
+     // Link/DNS prefetching
+     // --------------------
+     // Disable link prefetch.
+     if (rpPrefBranch.getBoolPref("prefetch.link.disableOnStartup")) {
+       if (rootPrefBranch.getBoolPref("network.prefetch-next")) {
+         rootPrefBranch.setBoolPref("network.prefetch-next", false);
+         console.info("Disabled link prefetch.");
+       }
+     }
+     // Disable DNS prefetch.
+     if (rpPrefBranch.getBoolPref("prefetch.dns.disableOnStartup")) {
+       // network.dns.disablePrefetch only exists starting in Firefox 3.1 (and it
+       // doesn't have a default value, at least in 3.1b2, but if and when it
+       // does have a default it will be false).
+       if (!rootPrefBranch.prefHasUserValue("network.dns.disablePrefetch") ||
+           !rootPrefBranch.getBoolPref("network.dns.disablePrefetch")) {
+         rootPrefBranch.setBoolPref("network.dns.disablePrefetch", true);
+         console.info("Disabled DNS prefetch.");
+       }
+     }
+ 
+ 
+     // ================================
+     // Clean up old, unused prefs (removed in 0.2.0).
+     // ----------------------------------------------
+     let deletePrefs = [
+       "temporarilyAllowedOrigins",
+       "temporarilyAllowedDestinations",
+       "temporarilyAllowedOriginsToDestinations"
+     ];
+     for (var i = 0; i < deletePrefs.length; i++) {
+       if (rpPrefBranch.prefHasUserValue(deletePrefs[i])) {
+         rpPrefBranch.clearUserPref(deletePrefs[i]);
+       }
+     }
+ 
+ 
+     Services.prefs.savePrefFile(null);
+   };
+ 
+ 
+   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 --cc content/main/requestpolicy-service.jsm
index 0000000,25e683e..718b3e5
mode 000000,100644..100644
--- a/content/main/requestpolicy-service.jsm
+++ b/content/main/requestpolicy-service.jsm
@@@ -1,0 -1,233 +1,234 @@@
+ /*
+  * ***** 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 = ["rpService"];
+ 
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules([
+   "lib/logger",
+   "lib/prefs",
+   "lib/utils/domains",
+   "lib/policy-manager",
+   "lib/subscription",
+   "lib/utils",
+   "lib/utils/constants",
+   "lib/environment"
+ ], this);
+ 
+ 
+ 
+ let rpService = (function() {
+   let self = {};
+ 
+   // /////////////////////////////////////////////////////////////////////////
+   // Internal Data
+   // /////////////////////////////////////////////////////////////////////////
+ 
+   let subscriptions = null;
+ 
+ 
+   // /////////////////////////////////////////////////////////////////////////
+   // Utility
+   // /////////////////////////////////////////////////////////////////////////
+ 
+ 
+   function loadConfigAndRules() {
+     subscriptions = new UserSubscriptions();
+     PolicyManager.loadUserRules();
+ 
+     var defaultPolicy = Prefs.isDefaultAllow() ? "allow" : "deny";
+ 
+     var failures = PolicyManager.loadSubscriptionRules(
+           subscriptions.getSubscriptionInfo(defaultPolicy));
+     // TODO: check a preference that indicates the last time we checked for
+     // updates. Don't do it if we've done it too recently.
+     // TODO: Maybe we should probably ship snapshot versions of the official
+     // rulesets so that they can be available immediately after installation.
+     var serials = {};
+     for (var listName in failures) {
+       serials[listName] = {};
+       for (var subName in failures[listName]) {
+         serials[listName][subName] = -1;
+       }
+     }
+     var loadedSubs = PolicyManager.getSubscriptionRulesets();
+     for (var listName in loadedSubs) {
+       for (var subName in loadedSubs[listName]) {
+         if (!serials[listName]) {
+           serials[listName] = {};
+         }
+         var rawRuleset = loadedSubs[listName][subName].rawRuleset;
+         serials[listName][subName] = rawRuleset._metadata['serial'];
+       }
+     }
+     function updateCompleted(result) {
+       Logger.info(Logger.TYPE_INTERNAL,
+           'Subscription updates completed: ' + result);
+     }
+     subscriptions.update(updateCompleted, serials, defaultPolicy);
+   }
+ 
+   // TODO: move to window manager
+   function showWelcomeWindow() {
+     if (!rpPrefBranch.getBoolPref("welcomeWindowShown")) {
+       var url = "about:requestpolicy?setup";
+ 
+       var wm = Cc['@mozilla.org/appshell/window-mediator;1'].
+           getService(Ci.nsIWindowMediator);
+       var windowtype = 'navigator:browser';
+       var mostRecentWindow  = wm.getMostRecentWindow(windowtype);
+ 
+       // the gBrowser object of the firefox window
+       var _gBrowser = mostRecentWindow.getBrowser();
+ 
+       if (typeof(_gBrowser.addTab) != "function") return;
+ 
+       _gBrowser.selectedTab = _gBrowser.addTab(url);
+ 
+       rpPrefBranch.setBoolPref("welcomeWindowShown", true);
+       Services.prefs.savePrefFile(null);
+     }
+   }
+ 
+ 
+ 
+ 
+ 
+   // /////////////////////////////////////////////////////////////////////////
+   // startup and shutdown functions
+   // /////////////////////////////////////////////////////////////////////////
+ 
+   // prepare back-end
+   ProcessEnvironment.addStartupFunction(Environment.LEVELS.BACKEND,
+                                         loadConfigAndRules);
+ 
+   function registerObservers() {
+     ProcessEnvironment.obMan.observe([
+       "sessionstore-windows-restored",
+       SUBSCRIPTION_UPDATED_TOPIC,
+       SUBSCRIPTION_ADDED_TOPIC,
+       SUBSCRIPTION_REMOVED_TOPIC,
+ 
+       // support for old browsers (Firefox <20)
+       // TODO: support per-window temporary rules
+       //       see https://github.com/RequestPolicyContinued/requestpolicy/issues/533#issuecomment-68851396
+       "private-browsing"
+     ], self.observe);
+   }
+   ProcessEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                         registerObservers);
+ 
+   ProcessEnvironment.addStartupFunction(
+       Environment.LEVELS.UI,
+       function(data, reason) {
+         if (reason !== C.APP_STARTUP) {
+           // In case of the app's startup `showWelcomeWindow()` will be
+           // called when "sessionstore-windows-restored" is observed.
+           showWelcomeWindow();
+         }
+       });
+ 
+ 
+ 
+ 
+ 
+   self.getSubscriptions = function() {
+     return subscriptions;
+   };
+ 
+ 
+ 
+ 
+   // /////////////////////////////////////////////////////////////////////////
+   // nsIObserver interface
+   // /////////////////////////////////////////////////////////////////////////
+ 
+   self.observe = function(subject, topic, data) {
+     switch (topic) {
+       case SUBSCRIPTION_UPDATED_TOPIC:
+         Logger.debug(Logger.TYPE_INTERNAL, 'XXX updated: ' + data);
+         // TODO: check if the subscription is enabled. The user might have
+         // disabled it between the time the update started and when it
+         // completed.
+         var subInfo = JSON.parse(data);
+         var failures = PolicyManager.loadSubscriptionRules(subInfo);
+         break;
+ 
+       case SUBSCRIPTION_ADDED_TOPIC:
+         Logger.debug(Logger.TYPE_INTERNAL, 'XXX added: ' + data);
+         var subInfo = JSON.parse(data);
+         var failures = PolicyManager.loadSubscriptionRules(subInfo);
+         var failed = false;
+         for (var listName in failures) {
+           failed = true;
+         }
+         if (failed) {
+           var serials = {};
+           for (var listName in subInfo) {
+             if (!serials[listName]) {
+               serials[listName] = {};
+             }
+             for (var subName in subInfo[listName]) {
+               serials[listName][subName] = -1;
+             }
+           }
+           let updateCompleted = function(result) {
+             Logger.info(Logger.TYPE_INTERNAL,
+                 'Subscription update completed: ' + result);
+           }
+           subscriptions.update(updateCompleted, serials);
+         }
+         break;
+ 
+       case SUBSCRIPTION_REMOVED_TOPIC:
+         Logger.debug(Logger.TYPE_INTERNAL, 'YYY: ' + data);
+         var subInfo = JSON.parse(data);
+         var failures = PolicyManager.unloadSubscriptionRules(subInfo);
+         break;
+ 
+       case "sessionstore-windows-restored":
+         showWelcomeWindow();
+         break;
+ 
+       // support for old browsers (Firefox <20)
+       case "private-browsing" :
+         if (data == "exit") {
+           PolicyManager.revokeTemporaryRules();
+         }
+         break;
+ 
+       default :
+         Logger.warning(Logger.TYPE_ERROR, "unknown topic observed: " + topic);
+     }
+   };
+ 
+   return self;
+ }());
++
diff --cc content/main/window-manager-toolbarbutton.js
index 0000000,e8c4210..10a2339
mode 000000,100644..100644
--- a/content/main/window-manager-toolbarbutton.js
+++ b/content/main/window-manager-toolbarbutton.js
@@@ -1,0 -1,152 +1,153 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ 
+ const toolbarButtonId = "requestpolicyToolbarButton";
+ 
+ 
+ Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ ScriptLoader.importModules(["lib/utils/xul", "lib/utils", "lib/logger"], this);
+ 
+ if (Utils.info.isAustralis) {
+   Components.utils.import("resource:///modules/CustomizableUI.jsm");
+ }
+ 
+ 
+ 
+ let rpWindowManager = (function(self) {
+ 
+   let isAustralis = Utils.info.isAustralis;
+ 
+   //
+   // Case 1: Australis (Firefox >= 29)
+   //
+ 
+   if (isAustralis) {
+     ProcessEnvironment.addStartupFunction(Environment.LEVELS.UI,
+                                           addToolbarButtonToAustralis);
+     ProcessEnvironment.addShutdownFunction(Environment.LEVELS.UI,
+                                            removeToolbarButtonFromAustralis);
+   }
+ 
+   function removeToolbarButtonFromAustralis() {
+     let tbb = XULUtils.xulTrees.toolbarbutton[0];
+     CustomizableUI.destroyWidget(tbb.id);
+   }
+ 
+   function addToolbarButtonToAustralis() {
+     let tbb = XULUtils.xulTrees.toolbarbutton[0];
+     CustomizableUI.createWidget({
+       id: tbb.id,
+       defaultArea: CustomizableUI.AREA_NAVBAR,
+       label: tbb.label,
+       tooltiptext: tbb.tooltiptext,
+       onCommand : function(aEvent) {
+         // Bad smell
+         let win = aEvent.target.ownerDocument.defaultView;
+         win.requestpolicy.overlay.openToolbarPopup(aEvent.target);
+       }
+     });
+   }
+ 
+ 
+   //
+   // Case 2: Gecko < 29
+   //
+ 
+ 
+   // this function can be deleted if Gecko < 29 isn't supported anymore
+   self.addToolbarButtonToWindow = function(win) {
+     if (!isAustralis) {
+       XULUtils.addTreeElementsToWindow(win, "toolbarbutton");
+       addToolbarButtonToNavBar(win);
+     }
+   };
+ 
+   self.removeToolbarButtonFromWindow = function(win) {
+     if (!isAustralis) {
+       XULUtils.removeTreeElementsFromWindow(win, "toolbarbutton");
+     }
+   };
+ 
+ 
+   function addToolbarButtonToNavBar(win) {
+     // SeaMonkey users have to use a toolbar button now. At the moment I can't
+     // justify a bunch of special cases to support the statusbar when such a
+     // tiny number of users have seamonkey and I can't even be sure that many of
+     // those users want a statusbar icon.
+     //if (!Utils.info.isFirefox) {
+     //  Logger.info(Logger.TYPE_INTERNAL,
+     //    "Not performing toolbar button check: not Firefox.");
+     //  return;
+     //}
+     let doc = win.document;
+ 
+     let isFirstRun = false;
+     if (Services.vc.compare(Utils.info.lastAppVersion, "0.0") <= 0) {
+       Logger.info(Logger.TYPE_INTERNAL, "This is the first run.");
+       isFirstRun = true;
+     }
+ 
+     let id = toolbarButtonId;
+ 
+     // find the toolbar in which the button has been placed by the user
+     let toolbarSelector = "[currentset^='" + id + ",'],[currentset*='," + id +
+         ",'],[currentset$='," + id + "']";
+     let toolbar = doc.querySelector(toolbarSelector);
+ 
+     if (!toolbar) {
+       // The button is in none of the toolbar "currentset"s. Either this is
+       // the first run or the button has been removed by the user (through
+       // customizing)
+       if (isFirstRun) {
+         toolbar = doc.getElementById("nav-bar");
+         toolbar.insertItem(id);
+         toolbar.setAttribute("currentset", toolbar.currentSet);
+         doc.persist(toolbar.id, "currentset");
+       }
+     } else {
+       // find the position of the button and insert.
+       let currentset = toolbar.getAttribute("currentset").split(",");
+       let index = currentset.indexOf(id);
+ 
+       let before = null;
+       for (let i = index + 1, len = currentset.length; i < len; i++) {
+         before = doc.getElementById(currentset[i]);
+         if (before) {
+           toolbar.insertItem(id, before);
+           break;
+         }
+       }
+       if (!before) {
+         toolbar.insertItem(id);
+       }
+     }
+   }
+ 
+ 
+   return self;
+ }(rpWindowManager || {}));
++
diff --cc content/main/window-manager.jsm
index 0000000,ba15538..16ec8c5
mode 000000,100644..100644
--- a/content/main/window-manager.jsm
+++ b/content/main/window-manager.jsm
@@@ -1,0 -1,237 +1,238 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ let EXPORTED_SYMBOLS = ["rpWindowManager"];
+ 
+ let globalScope = this;
+ 
+ 
+ let rpWindowManager = (function(self) {
+ 
+   const Ci = Components.interfaces;
+   const Cc = Components.classes;
+   const Cu = Components.utils;
+ 
+   Cu.import("resource://gre/modules/Services.jsm", globalScope);
+   Cu.import("resource://gre/modules/XPCOMUtils.jsm", globalScope);
+ 
+   Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm", globalScope);
+   ScriptLoader.importModules([
+     "lib/utils",
+     "lib/utils/xul",
+     "lib/utils/constants",
+     "lib/environment"
+   ], globalScope);
+ 
+   // import the WindowListener
+   Services.scriptloader.loadSubScript(
+       "chrome://requestpolicy/content/main/window-manager.listener.js",
+       globalScope);
+ 
+   let styleSheets = [
+     "chrome://requestpolicy/skin/requestpolicy.css"
+   ];
+   if (Utils.info.isSeamonkey) {
+     styleSheets.push("chrome://requestpolicy/skin/toolbarbutton-seamonkey.css");
+   } else {
+     styleSheets.push("chrome://requestpolicy/skin/toolbarbutton.css");
+   }
+ 
+   let frameScriptURI = "chrome://requestpolicy/content/ui/frame.js?" +
+       Math.random();
+ 
+ 
+ 
+   function loadIntoWindow(window) {
+     // ==================================
+     // # 1 : add all XUL elements
+     // --------------------------
+     try {
+       XULUtils.addTreeElementsToWindow(window, "mainTree");
+     } catch (e) {
+       Logger.warning(Logger.TYPE_ERROR,
+                      "Couldn't add tree elements to window. " + e, e);
+     }
+ 
+ 
+     // ==================================
+     // # 2 : create a scope variable for RP for this window
+     // ----------------------------------------------------
+     window.requestpolicy = {};
+ 
+ 
+     // ==================================
+     // # 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);
+     } catch (e) {
+       Logger.warning(Logger.TYPE_ERROR,
+                      "Error loading subscripts for window: " + e, e);
+     }
+ 
+ 
+     // ==================================
+     // # 4 : toolbar button
+     // --------------------
+     try {
+       self.addToolbarButtonToWindow(window);
+     } catch (e) {
+       Logger.warning(Logger.TYPE_ERROR, "Error while adding the toolbar " +
+                      "button to the window: "+e, e);
+     }
+ 
+ 
+     // ==================================
+     // # 5 : init the overlay
+     // ----------------------
+     try {
+       // init must be called last, because it assumes that
+       // everything else is ready
+       window.requestpolicy.overlay.init();
+     } catch (e) {
+       Logger.warning(Logger.TYPE_ERROR,
+                      "An error occurred while initializing the overlay: "+e, e);
+     }
+   }
+ 
+   function unloadFromWindow(window) {
+     // # 5 : the overlay cares itself about shutdown.
+     //       nothing to do here.
+ 
+ 
+     // # 4 : remove the toolbarbutton
+     // ------------------------------
+     self.removeToolbarButtonFromWindow(window);
+ 
+ 
+     // # 3 and 2 : remove the `requestpolicy` variable from the window
+     // ---------------------------------------------------------
+     // This wouldn't be needed when the window is closed, but this has to be
+     // done when RP is being disabled.
+     delete window.requestpolicy;
+ 
+ 
+     // # 1 : remove all XUL elements
+     XULUtils.removeTreeElementsFromWindow(window, "mainTree");
+   }
+ 
+ 
+ 
+ 
+ 
+   ProcessEnvironment.addStartupFunction(
+       Environment.LEVELS.INTERFACE,
+       function(data, reason) {
+         forEachOpenWindow(loadIntoWindow);
+         WindowListener.setLoadFunction(loadIntoWindow);
+         WindowListener.setUnloadFunction(unloadFromWindow);
+         WindowListener.startListening();
+ 
+         // Load the framescript into all existing tabs.
+         // Also tell the globalMM to load it into each new
+         // tab from now on.
+         var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+             .getService(Ci.nsIMessageListenerManager);
+         globalMM.loadFrameScript(frameScriptURI, true);
+       });
+ 
+   ProcessEnvironment.addStartupFunction(Environment.LEVELS.UI, loadStyleSheets);
+ 
+   ProcessEnvironment.addShutdownFunction(
+       Environment.LEVELS.INTERFACE,
+       function() {
+         // Stop loading framescripts into new tabs.
+         // --------------------------
+         // Note that it's not necessary to tell the framescripts'
+         // environments to shut down. Instead:
+         // - In case the window is closed, the framescript will shut
+         //   down on the ContentFrameMessageManager's "unload" event.
+         // - In case the addon is being disabled or firefox gets quit,
+         //   the ParentProcessEnvironment will send a message to all
+         //   children.
+         var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+             .getService(Ci.nsIMessageListenerManager);
+         globalMM.removeDelayedFrameScript(frameScriptURI);
+ 
+         forEachOpenWindow(unloadFromWindow);
+         WindowListener.stopListening();
+       });
+ 
+   ProcessEnvironment.addShutdownFunction(Environment.LEVELS.UI,
+                                          unloadStyleSheets);
+ 
+ 
+ 
+ 
+ 
+   function loadStyleSheets() {
+     let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
+         .getService(Ci.nsIStyleSheetService);
+ 
+     for (let i = 0, len = styleSheets.length; i < len; i++) {
+       let styleSheetURI = Services.io.newURI(styleSheets[i], null, null);
+       styleSheetService.loadAndRegisterSheet(styleSheetURI,
+           styleSheetService.USER_SHEET);
+     }
+   }
+   function unloadStyleSheets() {
+     // Unload stylesheets
+     let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
+         .getService(Ci.nsIStyleSheetService);
+ 
+     for (let i = 0, len = styleSheets.length; i < len; i++) {
+       let styleSheetURI = Services.io.newURI(styleSheets[i], null, null);
+       if (styleSheetService.sheetRegistered(styleSheetURI,
+           styleSheetService.USER_SHEET)) {
+         styleSheetService.unregisterSheet(styleSheetURI,
+             styleSheetService.USER_SHEET);
+       }
+     }
+   }
+ 
+   function forEachOpenWindow(functionToCall) {
+     // Apply a function to all open browser windows
+     let windows = Services.wm.getEnumerator("navigator:browser");
+     while (windows.hasMoreElements()) {
+       functionToCall(windows.getNext().QueryInterface(Ci.nsIDOMWindow));
+     }
+   }
+ 
+ 
+   return self;
+ }(rpWindowManager || {}));
+ 
+ 
+ // extend rpWindowManager
+ Services.scriptloader.loadSubScript(
+     "chrome://requestpolicy/content/main/window-manager-toolbarbutton.js",
+     globalScope);
++
diff --cc content/main/window-manager.listener.js
index 0000000,ba6fb4d..79b7fa6
mode 000000,100644..100644
--- a/content/main/window-manager.listener.js
+++ b/content/main/window-manager.listener.js
@@@ -1,0 -1,150 +1,151 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ let WindowListener = (function() {
+   let self = {};
+ 
+   let nextWinID = 0;
+   let listeners = {};
+ 
+ 
+   let addEvLis = function(eventName, winID) {
+     if ((typeof listeners[winID]) !== 'undefined' &&
+         listeners[winID][eventName] !== null) {
+       listeners[winID].window.addEventListener(eventName,
+                                                listeners[winID][eventName],
+                                                false);
+     }
+   };
+ 
+   let removeEvLis = function(eventName, winID) {
+     if (typeof listeners[winID] !== 'undefined' &&
+         listeners[winID][eventName] !== null) {
+       listeners[winID].window.removeEventListener(eventName,
+                                                   listeners[winID][eventName]);
+       if (eventName == 'unload') {
+         // when removing the 'unload' listener, also remove the 'load'
+         // listener and then delete listener[winID].
+         removeEvLis("load", winID);
+         // cleaning up -- listeners[winID] is not needed anymore
+         delete listeners[winID];
+       }
+     }
+   };
+ 
+ 
+ 
+   let addEventListenersToWindow = function(window) {
+     let winID = nextWinID++;
+ 
+     // ===========================
+     // create new functions specific for each window.
+     // ----------------------------------------------
+     let onLoad = function(event) {
+       removeEvLis("load", winID);
+       
+       if (window.document.documentElement.getAttribute("windowtype") ==
+           "navigator:browser") {
+         if (!!externalLoadFunction) {
+           externalLoadFunction(window);
+         }
+       } else {
+         removeEvLis("unload", winID);
+       }
+     };
+     let onUnload = function(event) {
+       removeEvLis("unload", onUnload);
+ 
+       if (window.document.documentElement.getAttribute("windowtype") ==
+           "navigator:browser") {
+         if (!!externalUnloadFunction) {
+           externalUnloadFunction(window);
+         }
+       }
+     };
+     // ===========================
+ 
+     listeners[winID] = {window: window, load: onLoad, unload: onUnload};
+ 
+     // Event handler for when the window is closed. We listen for "unload"
+     // rather than "close" because "close" will fire when a print preview
+     // opened from this window is closed.
+     addEvLis("unload", winID);
+ 
+     // Registers event handlers for documents loaded in the window.
+     addEvLis("load", winID);
+ 
+     return winID;
+   };
+ 
+ 
+   function removeAllEventListeners() {
+     for (let winID in listeners) {
+       removeEvLis("load", winID);
+       removeEvLis("unload", winID);
+     }
+     listeners = {};
+     nextWinID = 0;
+   }
+ 
+ 
+ 
+   // external functions to be called on "load" or "unload" events
+   let externalLoadFunction = null;
+   let externalUnloadFunction = null;
+   self.setLoadFunction = function(f) {
+     externalLoadFunction = f;
+   };
+   self.setUnloadFunction = function(f) {
+     externalUnloadFunction = f;
+   };
+ 
+ 
+   let listening = false;
+   self.startListening = function() {
+     if (listening === false) {
+       Services.wm.addListener(WindowListener);
+       listening = true;
+     }
+   };
+   self.stopListening = function() {
+     if (listening === true) {
+       Services.wm.removeListener(WindowListener);
+       listening = false;
+     }
+     // remove all "load" and "unload" event listeners.
+     removeAllEventListeners();
+   };
+ 
+ 
+ 
+   self.onOpenWindow = function(xulWindow) {
+     let window = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+         .getInterface(Ci.nsIDOMWindow);
+     addEventListenersToWindow(window);
+   };
+   self.onCloseWindow = function(xulWindow) {};
+   self.onWindowTitleChange = function(xulWindow, newTitle) {};
+ 
+   return self;
+ }());
++
diff --cc content/settings/advancedprefs.js
index 0000000,e8ac116..92fc69c
mode 000000,100644..100644
--- a/content/settings/advancedprefs.js
+++ b/content/settings/advancedprefs.js
@@@ -1,0 -1,133 +1,134 @@@
+ var PAGE_STRINGS = [
+   'basic',
+   'advanced',
+   'advancedPreferences',
+   'linkPrefetching',
+   'dnsPrefetching',
+   'enabled',
+   'disableOnStartup',
+   'restoreDefaultOnUninstall',
+   'menuPreferences',
+   'menuSorting',
+   'sortByNumRequests',
+   'sortByDestName',
+   'noSorting',
+   'hint',
+   'menuSortingHint',
+   'menuIndicatedInformation',
+   'menuIndicateNumRequests'
+ ];
+ 
+ $(function () {
+   common.localize(PAGE_STRINGS);
+ });
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ 
+ function updateDisplay() {
+   // Link prefetch.
+   $id('pref-linkPrefetch').checked =
+       rootPrefBranch.getBoolPref('network.prefetch-next');
+ 
+   $id('pref-prefetch.link.disableOnStartup').checked =
+       rpPrefBranch.getBoolPref('prefetch.link.disableOnStartup');
+ 
+   $id('pref-prefetch.link.restoreDefaultOnUninstall').checked =
+       rpPrefBranch.getBoolPref('prefetch.link.restoreDefaultOnUninstall');
+ 
+   // DNS prefetch.
+   $id('pref-dnsPrefetch').checked =
+       !rootPrefBranch.getBoolPref('network.dns.disablePrefetch');
+ 
+   $id('pref-prefetch.dns.disableOnStartup').checked =
+       rpPrefBranch.getBoolPref('prefetch.dns.disableOnStartup');
+ 
+   $id('pref-prefetch.dns.restoreDefaultOnUninstall').checked =
+       rpPrefBranch.getBoolPref('prefetch.dns.restoreDefaultOnUninstall');
+ 
+   // TODO: Create a class which acts as an API for preferences and which ensures
+   // that the returned value is always a valid value for "string" preferences.
+   var sorting = rpPrefBranch.getCharPref('menu.sorting');
+ 
+   if (sorting == $id('sortByNumRequests').value) {
+     $id('sortByNumRequests').checked = true;
+     $id('sortByDestName').checked = false;
+     $id('noSorting').checked = false;
+   } else if (sorting == $id('noSorting').value) {
+     $id('sortByNumRequests').checked = false;
+     $id('sortByDestName').checked = false;
+     $id('noSorting').checked = true;
+   } else {
+     $id('sortByNumRequests').checked = false;
+     $id('sortByDestName').checked = true;
+     $id('noSorting').checked = false;
+   }
+ 
+   $id('menu.info.showNumRequests').checked =
+       rpPrefBranch.getBoolPref('menu.info.showNumRequests');
+ }
+ 
+ 
+ function onload() {
+   updateDisplay();
+ 
+   // Link prefetch.
+   elManager.addListener($id('pref-linkPrefetch'), 'change', function (event) {
+     rootPrefBranch.setBoolPref('network.prefetch-next', event.target.checked);
+     Services.prefs.savePrefFile(null);
+   });
+ 
+   elManager.addListener(
+       $id('pref-prefetch.link.disableOnStartup'), 'change',
+       function (event) {
+         rpPrefBranch.setBoolPref('prefetch.link.disableOnStartup',
+                                  event.target.checked);
+         Services.prefs.savePrefFile(null);
+       });
+ 
+   elManager.addListener(
+       $id('pref-prefetch.link.restoreDefaultOnUninstall'), 'change',
+       function (event) {
+         rpPrefBranch.setBoolPref('prefetch.link.restoreDefaultOnUninstall', event.target.checked);
+         Services.prefs.savePrefFile(null);
+       });
+ 
+   // DNS prefetch.
+   elManager.addListener($id('pref-dnsPrefetch'), 'change', function (event) {
+     rootPrefBranch.setBoolPref('network.dns.disablePrefetch', !event.target.checked);
+     Services.prefs.savePrefFile(null);
+   });
+ 
+   elManager.addListener(
+       $id('pref-prefetch.dns.disableOnStartup'), 'change',
+       function (event) {
+         rpPrefBranch.setBoolPref('prefetch.dns.disableOnStartup', event.target.checked);
+         Services.prefs.savePrefFile(null);
+       });
+ 
+   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);
+   };
+   elManager.addListener($id('sortByNumRequests'), 'change', sortingListener);
+   elManager.addListener($id('sortByDestName'), 'change', sortingListener);
+   elManager.addListener($id('noSorting'), 'change', sortingListener);
+ 
+   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 --cc content/settings/basicprefs.js
index 0000000,34011d9..7d67c0d
mode 000000,100644..100644
--- a/content/settings/basicprefs.js
+++ b/content/settings/basicprefs.js
@@@ -1,0 -1,79 +1,80 @@@
+ var PAGE_STRINGS = [
+   'basic',
+   'advanced',
+   'webPages',
+   'indicateBlockedImages',
+   'dontIndicateBlacklisted',
+   'autoReload',
+   'menu',
+   'allowAddingNonTemporaryRulesInPBM'
+ ];
+ 
+ $(function () {
+   common.localize(PAGE_STRINGS);
+ });
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ 
+ function updateDisplay() {
+   var indicate = rpPrefBranch.getBoolPref('indicateBlockedObjects');
+   $id('pref-indicateBlockedObjects').checked = indicate;
+   $id('indicateBlockedImages-details').hidden = !indicate;
+ 
+   $id('pref-dontIndicateBlacklistedObjects').checked =
+       !rpPrefBranch.getBoolPref('indicateBlacklistedObjects');
+ 
+   $id('pref-autoReload').checked =
+       rpPrefBranch.getBoolPref('autoReload');
+ 
+   $id('pref-privateBrowsingPermanentWhitelisting').checked =
+       rpPrefBranch.getBoolPref('privateBrowsingPermanentWhitelisting');
+ 
+ //  if (rpPrefBranch.getBoolPref('defaultPolicy.allow')) {
+ //    var word = 'allow';
+ //  } else {
+ //    var word = 'block';
+ //  }
+ //  $id('defaultpolicyword').innerHTML = word;
+ }
+ 
+ 
+ function onload() {
+   updateDisplay();
+ 
+   elManager.addListener(
+       $id('pref-indicateBlockedObjects'), 'change',
+       function (event) {
+         rpPrefBranch.setBoolPref('indicateBlockedObjects', event.target.checked);
+         Services.prefs.savePrefFile(null);
+         updateDisplay();
+       });
+ 
+   elManager.addListener(
+       $id('pref-dontIndicateBlacklistedObjects'), 'change',
+       function (event) {
+         rpPrefBranch.setBoolPref('indicateBlacklistedObjects',
+                                  !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();
+   });
+ 
+   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 --cc content/settings/common.js
index f88cb55,0919023..1c580ca
--- a/content/settings/common.js
+++ b/content/settings/common.js
@@@ -243,3 -228,3 +228,4 @@@ common.localize = function(stringNames
  $(function() {
    common.localize(COMMON_STRINGS);
  });
++
diff --cc content/settings/defaultpolicy.js
index 0000000,b8c7fdd..68ba2dc
mode 000000,100644..100644
--- a/content/settings/defaultpolicy.js
+++ b/content/settings/defaultpolicy.js
@@@ -1,0 -1,79 +1,80 @@@
+ var PAGE_STRINGS = [
+   'yourPolicy',
+   'defaultPolicy',
+   'subscriptions',
+   'allowRequestsByDefault',
+   'blockRequestsByDefault',
+   'defaultPolicyDefinition',
+   'learnMore',
+   'allowRequestsToTheSameDomain',
+   'differentSubscriptionsAreAvailable',
+   'manageSubscriptions'
+ ];
+ 
+ $(function () {
+   common.localize(PAGE_STRINGS);
+ });
+ 
+ Cu.import("resource://gre/modules/Services.jsm");
+ 
+ 
+ function updateDisplay() {
+   var defaultallow = rpPrefBranch.getBoolPref('defaultPolicy.allow');
+   if (defaultallow) {
+     $id('defaultallow').checked = true;
+     $id('defaultdenysetting').hidden = true;
+   } else {
+     $id('defaultdeny').checked = true;
+     $id('defaultdenysetting').hidden = false;
+   }
+ 
+   var allowsamedomain = rpPrefBranch.getBoolPref('defaultPolicy.allowSameDomain');
+   $id('allowsamedomain').checked = allowsamedomain;
+ }
+ 
+ function showManageSubscriptionsLink() {
+   $id('subscriptionschanged').style.display = 'block';
+ }
+ 
+ function onload() {
+   updateDisplay();
+ 
+   elManager.addListener(
+       $id('defaultallow'), 'change',
+       function (event) {
+         var allow = event.target.checked;
+         rpPrefBranch.setBoolPref('defaultPolicy.allow', allow);
+         Services.prefs.savePrefFile(null);
+         // Reload all subscriptions because it's likely that different
+         // subscriptions will now be active.
+         common.switchSubscriptionPolicies();
+         updateDisplay();
+         showManageSubscriptionsLink();
+       });
+ 
+   elManager.addListener(
+       $id('defaultdeny'), 'change',
+       function (event) {
+         var deny = event.target.checked;
+         rpPrefBranch.setBoolPref('defaultPolicy.allow', !deny);
+         Services.prefs.savePrefFile(null);
+         // Reload all subscriptions because it's likely that different
+         // subscriptions will now be active.
+         common.switchSubscriptionPolicies();
+         updateDisplay();
+         showManageSubscriptionsLink();
+       });
+ 
+   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 --cc content/settings/oldrules.js
index 514336e,4eadd11..e015191
--- a/content/settings/oldrules.js
+++ b/content/settings/oldrules.js
@@@ -108,3 -108,3 +108,4 @@@ function onload() 
    populateRuleTable();
    $('#addhostwildcards').change(handleAddHostWildcardsChange);
  }
++
diff --cc content/settings/setup.js
index a24e91e,dd6ad4c..438dd73
--- a/content/settings/setup.js
+++ b/content/settings/setup.js
@@@ -134,3 -155,3 +155,4 @@@ function onload() 
    $('input[name=subscriptions]').change(handleSubscriptionsChange);
    $('#allowsamedomain').change(handleAllowSameDomainChange);
  }
++
diff --cc content/settings/subscriptions.js
index 6854cc0,d6d873c..5f47095
--- a/content/settings/subscriptions.js
+++ b/content/settings/subscriptions.js
@@@ -124,14 -121,20 +121,21 @@@ function onload() 
        Logger.dump('Skipping unexpected official subName: ' + subName);
        continue;
      }
-     el.addEventListener('change', handleSubscriptionCheckboxChange);
+     elManager.addListener(el, 'change', handleSubscriptionCheckboxChange);
    }
  
-   prefsChangedObserver = new common.PrefsChangedObserver(
-       function(subject, topic, data) {
-         updateDisplay();
-       });
-   window.addEventListener("beforeunload", function(event) {
-     prefsChangedObserver.unregister();
-   });
+   var selector = '[data-defaultpolicy=' +
+     (Prefs.isDefaultAllow() ? 'deny' : 'allow') + ']';
+   var matches = document.body.querySelectorAll(selector);
+   var hideElements = Array.prototype.slice.call(matches);
+   for (var i = 0; i < hideElements.length; i++) {
+     hideElements[i].style.display = 'none';
+   }
+ 
+   // call updateDisplay() every time a subscription is added or removed
+   WinEnv.obMan.observe([
+     SUBSCRIPTION_ADDED_TOPIC,
+     SUBSCRIPTION_REMOVED_TOPIC
+   ], updateDisplay);
  }
++
diff --cc content/settings/yourpolicy.js
index 5ddcf97,a9e1a6a..4f331f6
--- a/content/settings/yourpolicy.js
+++ b/content/settings/yourpolicy.js
@@@ -237,12 -231,13 +231,14 @@@ function onload() 
      }, SEARCH_DELAY);
    }, false);
    populateRuleTable(search.value);
-   if (rpService.oldRulesExist()) {
-     $('#oldrulesexist').show();
+   if (Prefs.oldRulesExist()) {
+     $id("oldrulesexist").hidden = false;
    }
  
-   rulesChangedObserver = new RulesChangedObserver();
-   window.addEventListener("beforeunload", function(event) {
-     rulesChangedObserver.unregister();
+   // observe rule changes and update the table then
+   WinEnv.obMan.observe(["requestpolicy-rules-changed"], function() {
+     var search = $id('rulesearch');
+     populateRuleTable(search.value);
    });
  }
++
diff --cc content/ui/classicmenu.js
index 0fe5832,0915048..4c2da18
--- a/content/ui/classicmenu.js
+++ b/content/ui/classicmenu.js
@@@ -100,28 -105,27 +105,28 @@@ requestpolicy.classicmenu = (function(
      var command = "requestpolicy.overlay.allowOriginToDestination('"
          + requestpolicy.menu._sanitizeJsFunctionArg(originHost) + "', '"
          + requestpolicy.menu._sanitizeJsFunctionArg(destHost) + "');";
-     var item = this.addMenuItem(menu, label, command);
+     var item = self.addMenuItem(menu, label, command);
      item.setAttribute("class", "requestpolicyAllowOriginToDest");
      return item;
-   },
+   };
  
  
-   addMenuItemTemporarilyAllowDest : function(menu, destHost) {
-     var label = requestpolicy.menu._strbundle.
-         getFormattedString("allowDestinationTemporarily", [destHost]);
+   self.addMenuItemTemporarilyAllowDest = function(menu, destHost) {
+     var label = StringUtils.$str("allowDestinationTemporarily", [destHost]);
      var command = "requestpolicy.overlay.temporarilyAllowDestination('"
          + requestpolicy.menu._sanitizeJsFunctionArg(destHost) + "');";
-     var item = this.addMenuItem(menu, label, command);
+     var item = self.addMenuItem(menu, label, command);
      item.setAttribute("class", "requestpolicyTemporary");
      return item;
-   },
+   };
  
-   addMenuItemAllowDest : function(menu, destHost) {
-     var label = requestpolicy.menu._strbundle.
-         getFormattedString("allowDestination", [destHost]);
+   self.addMenuItemAllowDest = function(menu, destHost) {
+     var label = StringUtils.$str("allowDestination", [destHost]);
      var command = "requestpolicy.overlay.allowDestination('"
          + requestpolicy.menu._sanitizeJsFunctionArg(destHost) + "');";
-     return this.addMenuItem(menu, label, command);
-   }
+     return self.addMenuItem(menu, label, command);
+   };
+ 
+   return self;
+ }());
 +
- }
diff --cc content/ui/frame.blocked-content.js
index 0000000,88290e7..61a7a12
mode 000000,100644..100644
--- a/content/ui/frame.blocked-content.js
+++ b/content/ui/frame.blocked-content.js
@@@ -1,0 -1,96 +1,97 @@@
+ /*
+  * ***** BEGIN LICENSE BLOCK *****
+  *
+  * RequestPolicy - A Firefox extension for control over cross-site requests.
+  * Copyright (c) 2011 Justin Samuel
+  *
+  * 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 ManagerForBlockedContent = (function() {
+   let self = {};
+ 
+ 
+   let missingImageDataUri = "data:image/png;base64,"
+       + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c"
+       + "6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0"
+       + "SU1FB9gMFRANL5LXnioAAAJWSURBVDjLnZI/ixtXFMV/972ZNzPSrmTtalex"
+       + "lsWBGMfEYOzaVciXyKdIkW/hFKnS22WafIDUxk0g2AQSgm0csIPWK42ktaSR"
+       + "NPP+pRBK5SLOqS7cew7ccw4xxrPJ+8XdHx4+7AE8e3Cj++zLm71fvrqT8x+Q"
+       + "AK35dJr2n/x89urTa+eDm/cS+eI2y3eT+Lx/bt8u1vNqfDH++teXdk/6ThAf"
+       + "UUBIgL9ku75z/8WL7LOlhXIGJ0Pyw75wMcnGv//xSQ2DH4ddu9k01dXWsWzc"
+       + "ofhYaiiViLjiWi9UWQa1gzcjWF7hgfzzW5ydnXB62JLjg0PTLfJertNepnQS"
+       + "IA+gE4Cs03UuNYYQYP4e5jPogmSG9vA6rrjC+0AxN2i5Qk0DpXVJhCQB0EVR"
+       + "rzqdFgB1DZfvCDHixiV2NqO6LHHKIKnQMoaWbFBgIrQVgIXaDc+JCHgP5QRZ"
+       + "r4jzGWFbo6yncRYviiiQKUhBRch3Lyix4bgPWsAkcDkmZAV2OiE0DaI1WoES"
+       + "hRKF3sWnmt01pFBnJydEpZDEwHSGt47lYsls43AIXjTWV9R1Qx0DGahqLyAh"
+       + "bqrj0/ib0nRzXNoyCo0Kkor2llV0eKOwdUMg4pSQA7JPQXvnJv1B+GlwOvrG"
+       + "laXB6fV2lb5t6qOtike56DSJgYDGBQcOAsQAfueBMeHR48fhadb1j/58HWAR"
+       + "dt6yBv7+/vpBe2o5OogxlcaKdt5aKCNsk309W0WxKQjmQ33/9mJVAdWHdmo/"
+       + "tNvtRZIkfCz+ZQwGg6rT6Zj/LTAajTbD4bD5WIF/AAseEisPFO8uAAAAAElF"
+       + "TkSuQmCC";
+ 
+   let transparentImageDataUri = "data:image/gif;base64,R0lGODlhAQABAIAAA"
+       + "AAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
+ 
+   self.indicateBlockedVisibleObjects = function(doc, blockedURIs) {
+     if (Object.getOwnPropertyNames(blockedURIs).length == 0) {
+       // there are no blocked uris
+       return;
+     }
+ 
+     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.
+     // Setting img.src to a broken resource:// causes "save page as" to fail
+     // for some earlier Fx 3.x versions. Also, using a broken resource://
+     // causes our width setting to be ignored, as does using null for img.src.
+     // With Firefox 4, setting img.src to null doesn't work reliably for
+     // having the rest of the styles (e.g. background and border) applied.
+     // So, for now we're punting on trying to display alt text. We'll just use
+     // a transparent image as the replacement image.
+     // Note that with our changes to the image here, "save page as" works but
+     // different data is saved depending on what type of "save page as" the
+     // user performs. With "save all files", the saved source includes the
+     // original, blocked image src. With "web page, complete" the saved source
+     // has changes we make here to show the blocked request indicator.
+ 
+     for (var i = 0; i < images.length; i++) {
+       var img = images[i];
+       // Note: we're no longer checking img.requestpolicyBlocked here.
+       if (!img.requestpolicyIdentified && img.src in blockedURIs) {
+         img.requestpolicyIdentified = true;
+         img.style.border = "solid 1px #fcc";
+         img.style.backgroundRepeat = "no-repeat";
+         img.style.backgroundPosition = "center center";
+         img.style.backgroundImage = "url('" + missingImageDataUri + "')";
+         if (!img.width) {
+           img.width = 50;
+         }
+         if (!img.height) {
+           img.height = 50;
+         }
+         img.title = "[" + blockedURIs[img.src].identifier + "]"
+             + (img.title ? " " + img.title : "")
+             + (img.alt ? " " + img.alt : "");
+         img.src = transparentImageDataUri;
+       }
+     }
+   };
+ 
+   return self;
+ }());
++
diff --cc content/ui/frame.dom-content-loaded.js
index 0000000,9809156..5ffc929
mode 000000,100644..100644
--- a/content/ui/frame.dom-content-loaded.js
+++ b/content/ui/frame.dom-content-loaded.js
@@@ -1,0 -1,315 +1,316 @@@
+ /*
+  * ***** BEGIN LICENSE BLOCK *****
+  *
+  * RequestPolicy - A Firefox extension for control over cross-site requests.
+  * Copyright (c) 2011 Justin Samuel
+  *
+  * 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 ManagerForDOMContentLoaded = (function() {
+   let self = {};
+ 
+   let {DomainUtil} = ScriptLoader.importModule("lib/utils/domains");
+   let {Utils} = ScriptLoader.importModule("lib/utils");
+ 
+ 
+   function htmlAnchorTagClicked(event) {
+     // 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
+     overlayComm.run(function() {
+       mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                          {origin: event.currentTarget.ownerDocument.URL,
+                           dest: event.currentTarget.href});
+     });
+   }
+ 
+ 
+   /**
+    * Determines if documentToCheck is the main document loaded in the currently
+    * active tab.
+    *
+    * @param {document} documentToCheck
+    * @return {Boolean}
+    */
+   function isActiveTopLevelDocument(documentToCheck) {
+     return documentToCheck === content.document;
+   }
+ 
+   /**
+    * Things to do when a page has loaded (after images, etc., have been loaded).
+    *
+    * @param {Event} event
+    */
+   function onDOMContentLoaded(event) {
+     // TODO: This is getting called multiple times for a page, should only be
+     // called once.
+     //    <--- the above comment is very old – is it still true that
+     //         onDOMContentLoaded is eventually called multiple times?
+     var doc = event.originalTarget;
+     if (doc.nodeName != "#document") {
+       // only documents
+       return;
+     }
+ 
+     onDocumentLoaded(doc);
+ 
+     overlayComm.run(function() {
+       let answers = mm.sendSyncMessage(C.MM_PREFIX + "notifyDocumentLoaded",
+                                        {documentURI: doc.documentURI});
+       if (answers.length === 0) {
+         Logger.warning(Logger.TYPE_ERROR, 'There seems to be no message ' +
+                        'listener for "notifyDocumentLoaded".');
+       } else {
+         // take only one answer. If there are more answers, they are ignored
+         // ==> there must be only one listener for 'notifyDocumentLoaded'
+         let answer = answers[0];
+ 
+         var blockedURIs = answer.blockedURIs;
+         //console.debug("Received " +
+         //              Object.getOwnPropertyNames(blockedURIs).length +
+         //              " blocked URIs.");
+ 
+         // Indicating blocked visible objects isn't an urgent task, so this should
+         // be done async.
+         Utils.runAsync(function() {
+           ManagerForBlockedContent.indicateBlockedVisibleObjects(doc, blockedURIs);
+         });
+       }
+ 
+       if (isActiveTopLevelDocument(doc)) {
+         mm.sendAsyncMessage(C.MM_PREFIX + "notifyTopLevelDocumentLoaded");
+       }
+     });
+   }
+ 
+   /**
+    * Things to do when a page or a frame within the page has loaded.
+    *
+    * @param {Event} event
+    */
+   function onDOMFrameContentLoaded(event) {
+     // TODO: This only works for (i)frames that are direct children of the main
+     // document, not (i)frames within those (i)frames.
+     var iframe = event.target;
+     // Flock's special home page is about:myworld. It has (i)frames in it
+     // that have no contentDocument. It's probably related to the fact that
+     // that is an xul page.
+     if (iframe.contentDocument === undefined) {
+       return;
+     }
+ 
+     // TODO: maybe this can check if the iframe's documentURI is in the other
+     // origins of the current document, and that way not just be limited to
+     // direct children of the main document. That would require building the
+     // other origins every time an iframe is loaded. Maybe, then, this should
+     // use a timeout like observerBlockedRequests does.
+     if (isActiveTopLevelDocument(iframe.ownerDocument)) {
+       overlayComm.run(function() {
+         mm.sendAsyncMessage(C.MM_PREFIX + "notifyDOMFrameContentLoaded");
+       });
+     }
+   }
+ 
+ 
+ 
+ 
+   /**
+    * 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(doc) {
+     // Create a new Environment for this Document and shut it down when
+     // the document is unloaded.
+     let DocEnv = new Environment(framescriptEnv, "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 = doc.getElementsByTagName("meta");
+     for (var i = 0; i < metaTags.length; i++) {
+       let metaTag = metaTags[i];
+       if (!metaTag.httpEquiv || metaTag.httpEquiv.toLowerCase() != "refresh") {
+         continue;
+       }
+ 
+       let originalDestURI = null;
+ 
+       // TODO: Register meta redirects so we can tell which blocked requests
+       // were meta redirects in the statusbar menu.
+       // TODO: move this logic to the requestpolicy service.
+ 
+       // The dest may be empty if the origin is what should be refreshed. This
+       // will be handled by DomainUtil.determineRedirectUri().
+       let {delay, destURI} = DomainUtil.parseRefresh(metaTag.content);
+ 
+       // If destURI isn't a valid uri, assume it's a relative uri.
+       if (!DomainUtil.isValidUri(destURI)) {
+         originalDestURI = destURI;
+         destURI = doc.documentURIObject.resolve(destURI);
+       }
+ 
+       metaRefreshes.push({delay: delay, destURI: destURI,
+                           originalDestURI: originalDestURI});
+     }
+ 
+     if (metaRefreshes.length > 0) {
+       // meta refreshes have been found.
+ 
+       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);
+       if (!docShell.allowMetaRedirects) {
+         Logger.warning(Logger.TYPE_META_REFRESH,
+             "Another extension disabled docShell.allowMetaRedirects.");
+       }
+ 
+       overlayComm.run(function() {
+         mm.sendAsyncMessage(C.MM_PREFIX + "handleMetaRefreshes",
+             {documentURI: documentURI, metaRefreshes: metaRefreshes});
+       });
+     }
+ 
+     // Find all anchor tags and add click events (which also fire when enter
+     // is pressed while the element has focus).
+     // This seems to be a safe approach in that the MDC states that javascript
+     // can't be used to initiate a click event on a link:
+     // http://developer.mozilla.org/en/DOM/element.click
+     // We keep this even though we have the document looking for clicks because
+     // for certain links the target will not be the link (and we can't use the
+     // currentTarget in the other case it seems, as we can here). There probably
+     // is some solution when handling the click events at the document level,
+     // 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.
+     // 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);
+       }
+     });
+ 
+     // maybe not needed?
+     //wrapWindowFunctions(doc.defaultView);
+     //DocEnv.addShutdownFunction(Environment.LEVELS.INTERFACE, function() {
+     //  unwrapWindowFunctions(doc.defaultView);
+     //});
+   }
+ 
+   /**
+    * This function wraps an existing method of a window object.
+    * If that method is being called after being wrapped, first the custom
+    * function will be called and then the original function.
+    *
+    * @param {Window} aWindow
+    * @param {String} aFunctionName The name of the window's method.
+    * @param {Function} aNewFunction
+    */
+   function wrapWindowFunction(aWindow, aFunctionName, aNewFunction) {
+     aWindow.rpOriginalFunctions = aWindow.rpOriginalFunctions || {};
+     let originals = aWindow.rpOriginalFunctions;
+ 
+     if (!(aFunctionName in originals)) {
+       originals[aFunctionName] = aWindow[aFunctionName];
+       aWindow[aFunctionName] = function() {
+         aNewFunction.apply(aWindow, arguments);
+         return originals[aFunctionName].apply(aWindow, arguments);
+       }
+     }
+   }
+   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
+    * can know the origin and destination URLs of the window being opened. Assume
+    * that if window.open() calls have made it this far, it's a window the user
+    * wanted open (e.g. they have allowed the popup). Unfortunately, this method
+    * (or our timing of doing self) doesn't seem to work for popups that are
+    * allowed popups (the user has allowed popups from the domain). So, the
+    * workaround was to also add the 'if(aContext.nodeName == "xul:browser" &&
+    * aContext.currentURI && aContext.currentURI.spec == "about:blank")' to
+    * shouldLoad().
+    *
+    * @param {Window} aWindow
+    */
+   function wrapWindowFunctions(aWindow) {
+     wrapWindowFunction(aWindow, "open",
+         function(url, windowName, windowFeatures) {
+           overlayComm.run(function() {
+             mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                                {origin: aWindow.document.documentURI,
+                                 dest: url});
+           });
+         });
+ 
+     wrapWindowFunction(aWindow, "openDialog",
+         function() {
+           // openDialog(url, name, features, arg1, arg2, ...)
+           overlayComm.run(function() {
+             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;
+   }
+ 
+ 
+   framescriptEnv.elManager.addListener(mm, "DOMContentLoaded",
+                                        onDOMContentLoaded, true);
+ 
+   // DOMFrameContentLoaded is same DOMContentLoaded but also fires for
+   // enclosed frames.
+   framescriptEnv.elManager.addListener(mm, "DOMFrameContentLoaded",
+                                        onDOMFrameContentLoaded, true);
+ 
+   return self;
+ }());
++
diff --cc content/ui/frame.js
index 0000000,b661ec3..bfc0889
mode 000000,100644..100644
--- a/content/ui/frame.js
+++ b/content/ui/frame.js
@@@ -1,0 -1,164 +1,165 @@@
+ /*
+  * ***** BEGIN LICENSE BLOCK *****
+  *
+  * RequestPolicy - A Firefox extension for control over cross-site requests.
+  * Copyright (c) 2011 Justin Samuel
+  *
+  * 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 *****
+  */
+ 
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Components.utils.import("resource://gre/modules/devtools/Console.jsm");
+ 
+ /**
+  * 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;
+ 
+   const Cu = Components.utils;
+ 
+   // import some modules
+   let {ScriptLoader} = Cu.import(
+       "chrome://requestpolicy/content/lib/script-loader.jsm", {});
+   let mod = {};
+   ScriptLoader.importModules([
+     "lib/utils/constants",
+     "lib/logger",
+     "lib/environment",
+     "lib/framescript-to-overlay-communication"
+   ], mod);
+   let {C, Logger, Environment, FrameScriptEnvironment,
+        FramescriptToOverlayCommunication} = mod;
+ 
+ 
+   let framescriptEnv = new FrameScriptEnvironment(mm);
+   let mlManager = framescriptEnv.mlManager;
+   let overlayComm = new FramescriptToOverlayCommunication(framescriptEnv);
+ 
+ 
+   // Create a scope for the sub-scripts, which also can
+   // be removed easily when the framescript gets unloaded.
+   var framescriptScope = {
+     "mm": mm,
+     "content": mm.content,
+     "Components": mm.Components,
+ 
+     "Ci": mm.Components.interfaces,
+     "Cc": mm.Components.classes,
+     "Cu": mm.Components.utils,
+ 
+     "ScriptLoader": ScriptLoader,
+     "C": C,
+     "Logger": Logger,
+     "console": console,
+     "Environment": Environment,
+ 
+     "framescriptEnv": framescriptEnv,
+     "mlManager": mlManager,
+     "overlayComm": overlayComm
+   };
+ 
+   function loadSubScripts() {
+     Services.scriptloader.loadSubScript(
+         'chrome://requestpolicy/content/ui/frame.blocked-content.js',
+         framescriptScope);
+     Services.scriptloader.loadSubScript(
+         'chrome://requestpolicy/content/ui/frame.dom-content-loaded.js',
+         framescriptScope);
+   }
+   framescriptEnv.addStartupFunction(Environment.LEVELS.ESSENTIAL,
+                                     loadSubScripts);
+ 
+   framescriptEnv.addShutdownFunction(Environment.LEVELS.ESSENTIAL, function() {
+     //console.debug("removing framescriptScope '" + framescriptEnv.uid + "'");
+     framescriptScope = null;
+   });
+ 
+ 
+ 
+ 
+   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);
+   });
+ 
+ 
+ 
+   // Listen for click events so that we can allow requests that result from
+   // user-initiated link clicks and form submissions.
+   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.
+     if (!event.mozInputSource) {
+       return;
+     }
+     // The following show up as button value 0 for links and form input submit buttons:
+     // * left-clicks
+     // * enter key while focused
+     // * space bar while focused (no event sent for links in this case)
+     if (event.button != 0) {
+       return;
+     }
+     // Link clicked.
+     // 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) {
+       overlayComm.run(function() {
+         sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                         {origin: event.target.ownerDocument.URL,
+                          dest: event.target.href});
+       });
+       return;
+     }
+     // Form submit button clicked. This can either be directly (e.g. mouseclick,
+     // enter/space while the the submit button has focus) or indirectly (e.g.
+     // pressing enter when a text input has focus).
+     if (event.target.nodeName.toLowerCase() == "input" &&
+         event.target.type.toLowerCase() == "submit" &&
+         event.target.form && event.target.form.action) {
+       overlayComm.run(function() {
+         sendSyncMessage(C.MM_PREFIX + "registerFormSubmitted",
+                         {origin: event.target.ownerDocument.URL,
+                          dest: event.target.form.action});
+       });
+       return;
+     }
+   };
+ 
+   framescriptEnv.addStartupFunction(Environment.LEVELS.INTERFACE, function() {
+     framescriptEnv.elManager.addListener(mm, "click", mouseClicked, true);
+   });
+ 
+ 
+   // start up the framescript's environment
+   framescriptEnv.startup();
+ }());
++
diff --cc content/ui/menu.js
index 0000000,d31fedd..675e451
mode 000000,100644..100644
--- a/content/ui/menu.js
+++ b/content/ui/menu.js
@@@ -1,0 -1,1215 +1,1216 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ 
+ requestpolicy.menu = (function() {
+ 
+   const Ci = Components.interfaces;
+   const Cc = Components.classes;
+   const Cu = Components.utils;
+ 
+ 
+   let {ScriptLoader} = (function() {
+     let mod = {};
+     Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm", mod);
+     return mod;
+   }());
+   // iMod: Alias for ScriptLoader.importModule
+   let iMod = ScriptLoader.importModule;
+   let {Logger} = iMod("lib/logger");
+   let {rpPrefBranch, Prefs} = iMod("lib/prefs");
+   let {RequestProcessor} = iMod("lib/request-processor");
+   let {PolicyManager} = iMod("lib/policy-manager");
+   let {DomainUtil} = iMod("lib/utils/domains");
+   let {Ruleset} = iMod("lib/ruleset");
+   let {GUIOrigin, GUIDestination,
+        GUILocation, GUILocationProperties} = iMod("lib/gui-location");
+   let {StringUtils} = iMod("lib/utils/strings");
+   let {DOMUtils} = iMod("lib/utils/dom");
+   let {WindowUtils} = iMod("lib/utils/windows");
+   let {C} = iMod("lib/utils/constants");
+ 
+   let gBrowser = WindowUtils.getTabBrowser(window);
+ 
+ 
+   let $id = document.getElementById.bind(document);
+ 
+ 
+   let initialized = false;
+ 
+ 
+   let self = {
+     addedMenuItems : [],
+ 
+     _originItem : null,
+     _originDomainnameItem : null,
+     _originNumRequestsItem : null,
+ 
+     _otherOriginsList : null,
+     _blockedDestinationsList : null,
+     _mixedDestinationsList : null,
+     _allowedDestinationsList : null,
+     _removeRulesList : null,
+     _addRulesList : null,
+ 
+     _isCurrentlySelectedDestBlocked : null,
+     _isCurrentlySelectedDestAllowed : null,
+ 
+     _ruleChangeQueues : {}
+   };
+ 
+   self.init = function() {
+     if (initialized === false) {
+       initialized = true;
+ 
+       self._originItem = document.getElementById("rp-origin");
+       self._originDomainnameItem = $id('rp-origin-domainname');
+       self._originNumRequestsItem = $id('rp-origin-num-requests');
+ 
+       self._otherOriginsList = $id("rp-other-origins-list");
+       self._blockedDestinationsList = $id("rp-blocked-destinations-list");
+       self._mixedDestinationsList = $id("rp-mixed-destinations-list");
+       self._allowedDestinationsList = $id("rp-allowed-destinations-list");
+       self._addRulesList = $id("rp-rules-add");
+       self._removeRulesList = $id("rp-rules-remove");
+ 
+       var conflictCount = RequestProcessor.getConflictingExtensions().length;
+       var hideConflictInfo = (conflictCount == 0);
+     }
+   };
+ 
+ 
+   self.prepareMenu = function() {
+     try {
+       var disabled = Prefs.isBlockingDisabled();
+       $id('rp-link-enable-blocking').hidden = !disabled;
+       $id('rp-link-disable-blocking').hidden = disabled;
+ 
+       $id('rp-revoke-temporary-permissions').hidden =
+           !PolicyManager.temporaryRulesExist();
+ 
+       self._currentUri = requestpolicy.overlay.getTopLevelDocumentUri();
+ 
+       try {
+         self._currentBaseDomain = DomainUtil.getBaseDomain(self._currentUri);
+         if (self._currentBaseDomain === null) {
+           Logger.info(Logger.TYPE_INTERNAL, "Unable to prepare menu because " +
+               "the current uri has no host: " + self._currentUri);
+           self._populateMenuForUncontrollableOrigin();
+           return;
+         }
+       } catch (e) {
+         Logger.info(Logger.TYPE_INTERNAL, "Unable to prepare menu because " +
+             "base domain can't be determined: " + self._currentUri);
+         self._populateMenuForUncontrollableOrigin();
+         return;
+       }
+ 
+       self._currentIdentifier = requestpolicy.overlay
+           .getTopLevelDocumentUriIdentifier();
+ 
+       //Logger.info(Logger.TYPE_POLICY,
+       //                              "self._currentUri: " + self._currentUri);
+       self._currentUriObj = DomainUtil.getUriObject(self._currentUri);
+ 
+       self._isChromeUri = self._currentUriObj.scheme == "chrome";
+       //self._currentUriIsHttps = self._currentUriObj.scheme == "https";
+ 
+       Logger.info(Logger.TYPE_INTERNAL,
+           "self._currentUri: " + self._currentUri);
+ 
+       if (self._isChromeUri) {
+         self._populateMenuForUncontrollableOrigin();
+         return;
+       }
+ 
+       // The fact that getAllRequestsInBrowser uses currentURI.spec directly
+       // from the browser is important because getTopLevelDocumentUri will
+       // not return the real URI if there is an applicable
+       // top-level document translation rule (these are used sometimes
+       // for extension compatibility). For example, this is essential to the
+       // menu showing relevant info when using the Update Scanner extension.
+       self._allRequestsOnDocument = RequestProcessor
+             .getAllRequestsInBrowser(gBrowser.selectedBrowser);
+       self._allRequestsOnDocument.print("_allRequestsOnDocument");
+ 
+       self._setPrivateBrowsingStyles();
+ 
+   //      var hidePrefetchInfo = !Prefs.isPrefetchEnabled();
+   //      self._itemPrefetchWarning.hidden = hidePrefetchInfo;
+   //      self._itemPrefetchWarningSeparator.hidden = hidePrefetchInfo;
+   //
+   //      if (isChromeUri) {
+   //        self._itemUnrestrictedOrigin.setAttribute("label",
+   //            StringUtils.$str("unrestrictedOrigin", ["chrome://"]));
+   //        self._itemUnrestrictedOrigin.hidden = false;
+   //        return;
+   //      }
+ 
+       self._populateOrigin();
+       self._populateOtherOrigins();
+       self._activateOriginItem($id("rp-origin"));
+ 
+     } catch (e) {
+       Logger.severe(Logger.TYPE_ERROR,
+           "Fatal Error, " + e + ", stack was: " + e.stack);
+       Logger.severe(Logger.TYPE_ERROR, "Unable to prepare menu due to error.");
+       throw e;
+     }
+   };
+ 
+   self._populateMenuForUncontrollableOrigin = function() {
+     self._originDomainnameItem.setAttribute('value',
+         StringUtils.$str('noOrigin'));
+     self._originNumRequestsItem.setAttribute('value', '');
+     self._originItem.removeAttribute("default-policy");
+     self._originItem.removeAttribute("requests-blocked");
+ 
+     DOMUtils.removeChildren([
+         self._otherOriginsList,
+         self._blockedDestinationsList,
+         self._mixedDestinationsList,
+         self._allowedDestinationsList,
+         self._removeRulesList,
+         self._addRulesList]);
+     $id('rp-other-origins').hidden = true;
+     $id('rp-blocked-destinations').hidden = true;
+     $id('rp-mixed-destinations').hidden = true;
+     $id('rp-allowed-destinations').hidden = true;
+     // TODO: show some message about why the menu is empty.
+   };
+ 
+   self._populateList = function(list, values) {
+     DOMUtils.removeChildren(list);
+ 
+     // check whether there are objects of GUILocation or just strings
+     var guiLocations = values[0] && (values[0] instanceof GUILocation);
+ 
+     if (true === guiLocations) {
+       // get prefs
+       var sorting = rpPrefBranch.getCharPref('menu.sorting');
+       var showNumRequests = rpPrefBranch.getBoolPref('menu.info.showNumRequests');
+ 
+       if (sorting == "numRequests") {
+         values.sort(GUILocation.sortByNumRequestsCompareFunction);
+       } else if (sorting == "destName") {
+         values.sort(GUILocation.compareFunction);
+       }
+ 
+       for (var i in values) {
+         var guiLocation = values[i];
+         var props = guiLocation.properties;
+ 
+         var num = undefined;
+         if (true === showNumRequests) {
+           num = props.numRequests;
+           if (props.numAllowedRequests > 0 && props.numBlockedRequests > 0) {
+             num += " (" + props.numBlockedRequests +
+                 "+" + props.numAllowedRequests + ")";
+           }
+         }
+         var newitem = self._addListItem(list, 'rp-od-item', guiLocation, num);
+ 
+         newitem.setAttribute("default-policy",
+             (props.numDefaultPolicyRequests > 0 ? "true" : "false"));
+         newitem.setAttribute("requests-blocked",
+             (props.numBlockedRequests > 0 ? "true" : "false"));
+       }
+     } else {
+       values.sort();
+       for (var i in values) {
+         self._addListItem(list, 'rp-od-item', values[i]);
+       }
+     }
+   };
+ 
+   self._populateOrigin = function() {
+     self._originDomainnameItem.setAttribute("value", self._currentBaseDomain);
+ 
+     var showNumRequests = rpPrefBranch
+         .getBoolPref('menu.info.showNumRequests');
+ 
+     var props = self._getOriginGUILocationProperties();
+ 
+     var numRequests = '';
+     if (true === showNumRequests) {
+       if (props.numAllowedRequests > 0 && props.numBlockedRequests > 0) {
+         numRequests = props.numRequests + " (" +
+             props.numBlockedRequests + "+" + props.numAllowedRequests + ")";
+       } else {
+         numRequests = props.numRequests;
+       }
+     }
+     self._originNumRequestsItem.setAttribute("value", numRequests);
+ 
+     self._originItem.setAttribute("default-policy",
+         (props.numDefaultPolicyRequests > 0 ? "true" : "false"));
+     self._originItem.setAttribute("requests-blocked",
+         (props.numBlockedRequests > 0 ? "true" : "false"));
+   };
+ 
+   self._populateOtherOrigins = function() {
+     var guiOrigins = self._getOtherOriginsAsGUILocations();
+     self._populateList(self._otherOriginsList, guiOrigins);
+     $id('rp-other-origins').hidden = guiOrigins.length == 0;
+   };
+ 
+   self._populateDestinations = function(originIdentifier) {
+     var destsWithBlockedRequests = self._getBlockedDestinationsAsGUILocations();
+     var destsWithAllowedRequests = self._getAllowedDestinationsAsGUILocations();
+ 
+     var destsWithSolelyBlockedRequests = [];
+     var destsMixed = [];
+     var destsWithSolelyAllowedRequests = [];
+ 
+     // Set operations would be nice. These are small arrays, so keep it simple.
+     for (var i = 0; i < destsWithBlockedRequests.length; i++) {
+       let blockedGUIDest = destsWithBlockedRequests[i];
+ 
+       if (false === GUILocation.existsInArray(blockedGUIDest,
+           destsWithAllowedRequests)) {
+         destsWithSolelyBlockedRequests.push(blockedGUIDest);
+       } else {
+         destsMixed.push(blockedGUIDest);
+       }
+     }
+     for (var i = 0; i < destsWithAllowedRequests.length; i++) {
+       let allowedGUIDest = destsWithAllowedRequests[i];
+ 
+       var indexRawBlocked = GUIDestination.
+           indexOfDestInArray(allowedGUIDest, destsWithBlockedRequests);
+       var destsMixedIndex = GUIDestination.
+           indexOfDestInArray(allowedGUIDest, destsMixed);
+ 
+       if (indexRawBlocked == -1) {
+         destsWithSolelyAllowedRequests.push(allowedGUIDest);
+       } else {
+         if (destsMixedIndex != -1) {
+           Logger.info(Logger.TYPE_INTERNAL,
+               "Merging dest: <" + allowedGUIDest + ">");
+           destsMixed[destsMixedIndex] = GUIDestination.merge(
+               allowedGUIDest, destsMixed[destsMixedIndex]);
+         } else {
+           // If the allowedGUIDest is in destsWithBlockedRequests and
+           // destsWithAllowedRequests, but not in destsMixed.
+           // This should never happen, the destsMixed destination should have
+           // been added in the destsWithBlockedRequests-loop.
+           Logger.warning(Logger.TYPE_INTERNAL, "mixed dest was" +
+               " not added to `destsMixed` list: <" + dest.dest + ">");
+           destsMixed.push(allowedGUIDest);
+         }
+       }
+     }
+ 
+     self._populateList(self._blockedDestinationsList,
+         destsWithSolelyBlockedRequests);
+     $id('rp-blocked-destinations').hidden =
+         destsWithSolelyBlockedRequests.length == 0;
+ 
+     self._populateList(self._mixedDestinationsList, destsMixed);
+     $id('rp-mixed-destinations').hidden = destsMixed.length == 0;
+ 
+     self._populateList(self._allowedDestinationsList,
+         destsWithSolelyAllowedRequests);
+     $id('rp-allowed-destinations').hidden =
+         destsWithSolelyAllowedRequests.length == 0;
+   };
+ 
+   self._populateDetails = function() {
+     var origin = self._currentlySelectedOrigin;
+     var dest = self._currentlySelectedDest;
+     DOMUtils.removeChildren([self._removeRulesList, self._addRulesList]);
+ 
+     var ruleData = {
+       'o' : {
+         'h' : self._addWildcard(origin)
+       }
+     };
+ 
+     let mayPermRulesBeAdded = WindowUtils.mayPermanentRulesBeAdded(window);
+ 
+     // Note: in PBR we'll need to still use the old string for the temporary
+     // rule. We won't be able to use just "allow temporarily".
+ 
+     if (!self._currentlySelectedDest) {
+       if (Prefs.isDefaultAllow()) {
+         // It seems pretty rare that someone will want to add a rule to block all
+         // requests from a given origin.
+         //if (mayPermRulesBeAdded === true) {
+         //  var item = self._addMenuItemDenyOrigin(
+         //    self._addRulesList, ruleData);
+         //}
+         //var item = self._addMenuItemTempDenyOrigin(self._addRulesList, ruleData);
+       } else {
+         if (mayPermRulesBeAdded === true) {
+           var item = self._addMenuItemAllowOrigin(self._addRulesList, ruleData);
+         }
+         var item = self._addMenuItemTempAllowOrigin(self._addRulesList, ruleData);
+       }
+     }
+ 
+     if (dest) {
+       ruleData['d'] = {
+         'h' : self._addWildcard(dest)
+       };
+       var destOnlyRuleData = {
+         'd' : {
+           'h' : self._addWildcard(dest)
+         }
+       };
+       //if (Prefs.isDefaultAllow()) {
+       if (self._isCurrentlySelectedDestAllowed ||
+            (!PolicyManager.ruleExists(C.RULE_ACTION_DENY, ruleData) &&
+             !PolicyManager.ruleExists(C.RULE_ACTION_DENY, destOnlyRuleData))) {
+         // show "Block requests" if the destination was allowed
+         // OR if there's no blocking rule (i.e. the request was blocked "by default")
+         //  -- this enables support for blacklisting.
+         if (!PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, ruleData) &&
+             !PolicyManager.ruleExists(C.RULE_ACTION_DENY, ruleData)) {
+           if (mayPermRulesBeAdded === true) {
+               var item = self._addMenuItemDenyOriginToDest(
+                   self._addRulesList, ruleData);
+           }
+           var item = self._addMenuItemTempDenyOriginToDest(
+             self._addRulesList, ruleData);
+         }
+ 
+         if (!PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, destOnlyRuleData) &&
+             !PolicyManager.ruleExists(C.RULE_ACTION_DENY, destOnlyRuleData)) {
+           if (mayPermRulesBeAdded === true) {
+             var item = self._addMenuItemDenyDest(
+                 self._addRulesList, destOnlyRuleData);
+           }
+           var item = self._addMenuItemTempDenyDest(
+               self._addRulesList, destOnlyRuleData);
+         }
+       }
+       if (self._isCurrentlySelectedDestBlocked ||
+            (!PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, ruleData) &&
+             !PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, destOnlyRuleData))) {
+         // show "Allow requests" if the destination was blocked
+         // OR if there's no allow-rule (i.e. the request was allowed "by default")
+         //  -- this enables support for whitelisting.
+         if (!PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, ruleData) &&
+             !PolicyManager.ruleExists(C.RULE_ACTION_DENY, ruleData)) {
+           if (mayPermRulesBeAdded === true) {
+             var item = self._addMenuItemAllowOriginToDest(
+                 self._addRulesList, ruleData);
+           }
+           var item = self._addMenuItemTempAllowOriginToDest(
+               self._addRulesList, ruleData);
+         }
+ 
+         if (!PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, destOnlyRuleData) &&
+             !PolicyManager.ruleExists(C.RULE_ACTION_DENY, destOnlyRuleData)) {
+           if (mayPermRulesBeAdded === true) {
+             var item = self._addMenuItemAllowDest(
+                 self._addRulesList, destOnlyRuleData);
+           }
+           var item = self._addMenuItemTempAllowDest(
+               self._addRulesList, destOnlyRuleData);
+         }
+       }
+     }
+ 
+     if (self._currentlySelectedDest) {
+       if (!Prefs.isDefaultAllow() &&
+           !Prefs.isDefaultAllowSameDomain()) {
+         self._populateDetailsAddSubdomainAllowRules(self._addRulesList);
+       }
+     }
+ 
+     self._populateDetailsRemoveAllowRules(self._removeRulesList);
+     self._populateDetailsRemoveDenyRules(self._removeRulesList);
+   };
+ 
+   self._addListItem = function(list, cssClass, value, numRequests) {
+     var hbox = document.createElement("hbox");
+     hbox.setAttribute("class", cssClass);
+     hbox.setAttribute("onclick", 'requestpolicy.menu.itemSelected(event);');
+     list.insertBefore(hbox, null);
+ 
+     var destLabel = document.createElement("label");
+     destLabel.setAttribute("value", value);
+     destLabel.setAttribute("class", "domainname");
+     destLabel.setAttribute("flex", "2");
+     hbox.insertBefore(destLabel, null);
+ 
+     if (numRequests) {
+       var numReqLabel = document.createElement("label");
+       numReqLabel.setAttribute("value", numRequests);
+       numReqLabel.setAttribute("class", "numRequests");
+       hbox.insertBefore(numReqLabel, null);
+     }
+ 
+     return hbox;
+   };
+ 
+   self._disableIfNoChildren = function(el) {
+     // TODO: this isn't working.
+     el.hidden = el.firstChild ? false : true;
+   };
+ 
+   self._setPrivateBrowsingStyles = function() {
+     let mayPermRulesBeAdded = WindowUtils.mayPermanentRulesBeAdded(window);
+     let val = mayPermRulesBeAdded === true ? '' : 'privatebrowsing';
+     $id('rp-details').setAttribute('class', val);
+   };
+ 
+   self._resetSelectedOrigin = function() {
+     self._originItem.setAttribute('selected-origin', 'false');
+     for (var i = 0; i < self._otherOriginsList.childNodes.length; i++) {
+       var child = self._otherOriginsList.childNodes[i];
+       child.setAttribute('selected-origin', 'false');
+     }
+   };
+ 
+   self._resetSelectedDest = function() {
+     for (var i = 0; i < self._blockedDestinationsList.childNodes.length; i++) {
+       var child = self._blockedDestinationsList.childNodes[i];
+       child.setAttribute('selected-dest', 'false');
+     }
+     for (var i = 0; i < self._mixedDestinationsList.childNodes.length; i++) {
+       var child = self._mixedDestinationsList.childNodes[i];
+       child.setAttribute('selected-dest', 'false');
+     }
+     for (var i = 0; i < self._allowedDestinationsList.childNodes.length; i++) {
+       var child = self._allowedDestinationsList.childNodes[i];
+       child.setAttribute('selected-dest', 'false');
+     }
+   };
+ 
+   self._activateOriginItem = function(item) {
+     if (item.id == 'rp-origin') {
+       // it's _the_ origin
+       self._currentlySelectedOrigin = self._originDomainnameItem.value;
+     } else if (item.parentNode.id == 'rp-other-origins-list') {
+       // it's an otherOrigin
+       self._currentlySelectedOrigin = item.getElementsByClassName("domainname")[0].value;
+     }
+     self._currentlySelectedDest = null;
+     // TODO: if the document's origin (rather than an other origin) is being
+     // activated, then regenerate the other origins list, as well.
+     self._resetSelectedOrigin();
+     item.setAttribute('selected-origin', 'true');
+     self._populateDestinations();
+     self._resetSelectedDest();
+     self._populateDetails();
+   };
+ 
+   self._activateDestinationItem = function(item) {
+     self._currentlySelectedDest = item.getElementsByClassName("domainname")[0].value;
+ 
+     if (item.parentNode.id == 'rp-blocked-destinations-list') {
+       self._isCurrentlySelectedDestBlocked = true;
+       self._isCurrentlySelectedDestAllowed = false;
+     } else if (item.parentNode.id == 'rp-allowed-destinations-list') {
+       self._isCurrentlySelectedDestBlocked = false;
+       self._isCurrentlySelectedDestAllowed = true;
+     } else {
+       self._isCurrentlySelectedDestBlocked = true;
+       self._isCurrentlySelectedDestAllowed = true;
+     }
+ 
+     self._resetSelectedDest();
+     item.setAttribute('selected-dest', 'true');
+     self._populateDetails();
+   };
+ 
+ 
+   self.itemSelected = function(event) {
+     var item = event.target;
+     // TODO: rather than compare IDs, this should probably compare against
+     // the elements we already have stored in variables. That is, assuming
+     // equality comparisons work that way here.
+     if (item.nodeName == "label" && item.parentNode.nodeName == "hbox") {
+       // item should be the <hbox>
+       item = item.parentNode;
+     }
+     if (item.id == 'rp-origin' ||
+         item.parentNode.id == 'rp-other-origins-list') {
+       self._activateOriginItem(item);
+     } else if (item.parentNode.id == 'rp-blocked-destinations-list' ||
+                item.parentNode.id == 'rp-mixed-destinations-list' ||
+                item.parentNode.id == 'rp-allowed-destinations-list') {
+       self._activateDestinationItem(item);
+     } else if (item.parentNode.id == 'rp-rule-options' ||
+                item.parentNode.id == 'rp-rules-remove' ||
+                item.parentNode.id == 'rp-rules-add') {
+       self._processRuleSelection(item);
+     } else {
+       Logger.severe(Logger.TYPE_ERROR,
+           'Unable to figure out which item type was selected.');
+     }
+   };
+ 
+   self._processRuleSelection = function(item) {
+     var ruleData = item.requestpolicyRuleData;
+     var ruleAction = item.requestpolicyRuleAction;
+ 
+     if (item.getAttribute('selected-rule') == 'true') {
+       item.setAttribute('selected-rule', 'false');
+       var undo = true;
+     } else {
+       item.setAttribute('selected-rule', 'true');
+       var undo = false;
+     }
+ 
+     if (!ruleData) {
+       Logger.severe(Logger.TYPE_ERROR,
+           'ruleData is empty in menu._processRuleSelection()');
+       return;
+     }
+     if (!ruleAction) {
+       Logger.severe(Logger.TYPE_ERROR,
+           'ruleAction is empty in menu._processRuleSelection()');
+       return;
+     }
+ 
+     var canonicalRule = Ruleset.rawRuleToCanonicalString(ruleData);
+     Logger.dump("ruleData: " + canonicalRule);
+     Logger.dump("ruleAction: " + ruleAction);
+     Logger.dump("undo: " + undo);
+ 
+     // TODO: does all of this get replaced with a generic rule processor that
+     // only cares whether it's an allow/deny and temporary and drops the ruleData
+     // argument straight into the ruleset?
+     var origin, dest;
+     if (ruleData['o'] && ruleData['o']['h']) {
+       origin = ruleData['o']['h'];
+     }
+     if (ruleData['d'] && ruleData['d']['h']) {
+       dest = ruleData['d']['h'];
+     }
+ 
+     if (!self._ruleChangeQueues[ruleAction]) {
+       self._ruleChangeQueues[ruleAction] = {};
+     }
+ 
+     if (undo) {
+       delete self._ruleChangeQueues[ruleAction][canonicalRule];
+     } else {
+       self._ruleChangeQueues[ruleAction][canonicalRule] = ruleData;
+     }
+   };
+ 
+   self.processQueuedRuleChanges = function() {
+     var rulesChanged = false;
+     for (var ruleAction in self._ruleChangeQueues) {
+       for (var canonicalRule in self._ruleChangeQueues[ruleAction]) {
+         var ruleData = self._ruleChangeQueues[ruleAction][canonicalRule];
+         self._processRuleChange(ruleAction, ruleData);
+         var rulesChanged = true;
+       }
+     }
+ 
+     self._ruleChangeQueues = {};
+     return rulesChanged;
+   };
+ 
+   self._processRuleChange = function(ruleAction, ruleData) {
+ 
+     switch (ruleAction) {
+       case 'allow':
+         PolicyManager.addAllowRule(ruleData);
+         break;
+       case 'allow-temp':
+         PolicyManager.addTemporaryAllowRule(ruleData);
+         break;
+       case 'stop-allow':
+         PolicyManager.removeAllowRule(ruleData);
+         break;
+       case 'deny':
+         PolicyManager.addDenyRule(ruleData);
+         break;
+       case 'deny-temp':
+         PolicyManager.addTemporaryDenyRule(ruleData);
+         break;
+       case 'stop-deny':
+         PolicyManager.removeDenyRule(ruleData);
+         break;
+       default:
+         throw 'action not implemented: ' + ruleAction;
+         break;
+     }
+   };
+ 
+ 
+   // Note by @jsamuel:
+   // „It's been too long since I looked at some of the new code.
+   //  I think I may have assumed that I'd get rid of the different strictness
+   //  levels and just use what is currently called LEVEL_SOP. If using anything
+   //  else there will be errors from within getDeniedRequests().“
+ 
+ 
+   self._getBlockedDestinationsAsGUILocations = function() {
+     var reqSet = RequestProcessor.getDeniedRequests(
+         self._currentlySelectedOrigin, self._allRequestsOnDocument);
+     var requests = reqSet.getAllMergedOrigins();
+ 
+     var result = [];
+     for (var destBase in requests) {
+       var properties = new GUILocationProperties();
+       properties.accumulate(requests[destBase], C.RULE_ACTION_DENY);
+       result.push(new GUIDestination(destBase, properties));
+     }
+     return result;
+   };
+ 
+   self._getAllowedDestinationsAsGUILocations = function() {
+     var reqSet = RequestProcessor.getAllowedRequests(
+         self._currentlySelectedOrigin, self._allRequestsOnDocument);
+     var requests = reqSet.getAllMergedOrigins();
+ 
+     var result = [];
+     for (var destBase in requests) {
+       // For everybody except users with default deny who are not allowing all
+       // requests to the same domain:
+       // Ignore the selected origin's domain when listing destinations.
+       if (Prefs.isDefaultAllow() || Prefs.isDefaultAllowSameDomain()) {
+         if (destBase == self._currentlySelectedOrigin) {
+           continue;
+         }
+       }
+ 
+       var properties = new GUILocationProperties();
+       properties.accumulate(requests[destBase], C.RULE_ACTION_ALLOW);
+       result.push(new GUIDestination(destBase, properties));
+     }
+     return result;
+   };
+ 
+   /**
+    * TODO: optimize this for performance (_getOriginGUILocationProperties and
+    * _getOtherOriginsAsGUILocations could be merged.)
+    *
+    * @return {GUILocationProperties}
+    *         the properties of the "main" origin (the one in the location bar).
+    */
+   self._getOriginGUILocationProperties = function() {
+     var allRequests = self._allRequestsOnDocument.getAll();
+ 
+     var allowSameDomain = Prefs.isDefaultAllow() ||
+         Prefs.isDefaultAllowSameDomain();
+ 
+     var properties = new GUILocationProperties();
+ 
+     for (var originUri in allRequests) {
+       var originBase = DomainUtil.getBaseDomain(originUri);
+       if (originBase !== self._currentBaseDomain) {
+         continue;
+       }
+ 
+       for (var destBase in allRequests[originUri]) {
+         properties.accumulate(allRequests[originUri][destBase]);
+       }
+     }
+     return properties;
+   };
+ 
+   self._getOtherOriginsAsGUILocations = function() {
+     var allRequests = self._allRequestsOnDocument.getAll();
+ 
+     var allowSameDomain = Prefs.isDefaultAllow() ||
+         Prefs.isDefaultAllowSameDomain();
+ 
+     var guiOrigins = [];
+     for (var originUri in allRequests) {
+       var originBase = DomainUtil.getBaseDomain(originUri);
+       if (originBase === self._currentBaseDomain) {
+         continue;
+       }
+ 
+       // TODO: we should prevent chrome://browser/ URLs from getting anywhere
+       // near here in the first place.
+       // Is this an issue anymore? This may have been slipping through due to
+       // a bug that has since been fixed. Disabling for now.
+       //if (originBase == 'browser') {
+       //  continue;
+       //}
+ 
+       var guiOriginsIndex = GUIOrigin.indexOfOriginInArray(originBase,
+           guiOrigins);
+       var properties;
+       if (guiOriginsIndex == -1) {
+         properties = new GUILocationProperties();
+       } else {
+         properties = guiOrigins[guiOriginsIndex].properties;
+       }
+       var addThisOriginBase = false;
+ 
+       for (var destBase in allRequests[originUri]) {
+         // Search for a destBase which wouldn't be allowed by the default policy.
+         // TODO: some users might want to know those "other origins" as well.
+         //       this should be made possible.
+ 
+         // For everybody except users with default deny who are not allowing all
+         // guiOrigins to the same domain:
+         // Only list other origins where there is a destination from that origin
+         // that is at a different domain, not just a different subdomain.
+         if (allowSameDomain && destBase == originBase) {
+           continue;
+         }
+         addThisOriginBase = true;
+         properties.accumulate(allRequests[originUri][destBase]);
+       }
+ 
+       if (addThisOriginBase && guiOriginsIndex == -1) {
+         guiOrigins.push(new GUIOrigin(originBase, properties));
+       }
+     }
+     return guiOrigins;
+   };
+ 
+   self._sanitizeJsFunctionArg = function(str) {
+     // strip single quotes and backslashes
+     return str.replace(/['\\]/g, "");
+   };
+ 
+   self._isIPAddressOrSingleName = function(hostname) {
+     return DomainUtil.isIPAddress(hostname) ||
+         hostname.indexOf(".") == -1;
+   };
+ 
+   self._addWildcard = function(hostname) {
+     if (self._isIPAddressOrSingleName(hostname)) {
+       return hostname;
+     } else {
+       return "*." + hostname;
+     }
+   };
+ 
+   // TODO: the 12 _addMenuItem* functions below hopefully can be refactored.
+ 
+   // Stop allowing
+ 
+   self._addMenuItemStopAllowingOrigin = function(list, ruleData, subscriptionOverride) {
+     var originHost = ruleData["o"]["h"];
+     var ruleAction = subscriptionOverride ? 'deny' : 'stop-allow';
+     return self._addMenuItemHelper(list, ruleData, 'stopAllowingOrigin', [originHost], ruleAction, 'rp-stop-rule rp-stop-allow');
+   };
+ 
+   self._addMenuItemStopAllowingDest = function(list, ruleData, subscriptionOverride) {
+     var destHost = ruleData["d"]["h"];
+     var ruleAction = subscriptionOverride ? 'deny' : 'stop-allow';
+     return self._addMenuItemHelper(list, ruleData, 'stopAllowingDestination', [destHost], ruleAction, 'rp-stop-rule rp-stop-allow');
+   };
+ 
+   self._addMenuItemStopAllowingOriginToDest = function(list, ruleData, subscriptionOverride) {
+     var originHost = ruleData["o"]["h"];
+     var destHost = ruleData["d"]["h"];
+     var ruleAction = subscriptionOverride ? 'deny' : 'stop-allow';
+     return self._addMenuItemHelper(list, ruleData, 'stopAllowingOriginToDestination', [originHost, destHost], ruleAction, 'rp-stop-rule rp-stop-allow');
+   };
+ 
+   // Allow
+ 
+   self._addMenuItemAllowOrigin = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'allowOrigin', [originHost], 'allow', 'rp-start-rule rp-allow');
+   };
+ 
+   self._addMenuItemAllowDest = function(list, ruleData) {
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'allowDestination', [destHost], 'allow', 'rp-start-rule rp-allow');
+   };
+ 
+   self._addMenuItemAllowOriginToDest = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'allowOriginToDestination', [originHost, destHost], 'allow', 'rp-start-rule rp-allow');
+   };
+ 
+   // Allow temp
+ 
+   self._addMenuItemTempAllowOrigin = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'allowOriginTemporarily', [originHost], 'allow-temp', 'rp-start-rule rp-allow rp-temporary');
+   };
+ 
+   self._addMenuItemTempAllowDest = function(list, ruleData) {
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'allowDestinationTemporarily', [destHost], 'allow-temp', 'rp-start-rule rp-allow rp-temporary');
+   };
+ 
+   self._addMenuItemTempAllowOriginToDest = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'allowOriginToDestinationTemporarily', [originHost, destHost], 'allow-temp', 'rp-start-rule rp-allow rp-temporary');
+   };
+ 
+   // Stop denying
+ 
+   self._addMenuItemStopDenyingOrigin = function(list, ruleData, subscriptionOverride) {
+     var originHost = ruleData["o"]["h"];
+     var ruleAction = subscriptionOverride ? 'allow' : 'stop-deny';
+     return self._addMenuItemHelper(list, ruleData, 'stopDenyingOrigin', [originHost], ruleAction, 'rp-stop-rule rp-stop-deny');
+   };
+ 
+   self._addMenuItemStopDenyingDest = function(list, ruleData, subscriptionOverride) {
+     var destHost = ruleData["d"]["h"];
+     var ruleAction = subscriptionOverride ? 'allow' : 'stop-deny';
+     return self._addMenuItemHelper(list, ruleData, 'stopDenyingDestination', [destHost], ruleAction, 'rp-stop-rule rp-stop-deny');
+   };
+ 
+   self._addMenuItemStopDenyingOriginToDest = function(list, ruleData, subscriptionOverride) {
+     var originHost = ruleData["o"]["h"];
+     var destHost = ruleData["d"]["h"];
+     var ruleAction = subscriptionOverride ? 'allow' : 'stop-deny';
+     return self._addMenuItemHelper(list, ruleData, 'stopDenyingOriginToDestination', [originHost, destHost], ruleAction, 'rp-stop-rule rp-stop-deny');
+   };
+ 
+   // Deny
+ 
+   self._addMenuItemDenyOrigin = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'denyOrigin', [originHost], 'deny', 'rp-start-rule rp-deny');
+   };
+ 
+   self._addMenuItemDenyDest = function(list, ruleData) {
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'denyDestination', [destHost], 'deny', 'rp-start-rule rp-deny');
+   };
+ 
+   self._addMenuItemDenyOriginToDest = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'denyOriginToDestination', [originHost, destHost], 'deny', 'rp-start-rule rp-deny');
+   };
+ 
+   // Deny temp
+ 
+   self._addMenuItemTempDenyOrigin = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'denyOriginTemporarily', [originHost], 'deny-temp', 'rp-start-rule rp-deny rp-temporary');
+   };
+ 
+   self._addMenuItemTempDenyDest = function(list, ruleData) {
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData, 'denyDestinationTemporarily', [destHost], 'deny-temp', 'rp-start-rule rp-deny rp-temporary');
+   };
+ 
+   self._addMenuItemTempDenyOriginToDest = function(list, ruleData) {
+     var originHost = ruleData["o"]["h"];
+     var destHost = ruleData["d"]["h"];
+     return self._addMenuItemHelper(list, ruleData,
+         'denyOriginToDestinationTemporarily', [originHost, destHost],
+         'deny-temp', 'rp-start-rule rp-deny rp-temporary');
+   };
+ 
+   self._addMenuItemHelper = function(list, ruleData, fmtStrName, fmtStrArgs,
+       ruleAction, cssClass) {
+     var label = StringUtils.$str(fmtStrName, fmtStrArgs);
+     var item = self._addListItem(list, 'rp-od-item', label);
+     item.requestpolicyRuleData = ruleData;
+     item.requestpolicyRuleAction = ruleAction;
+     //var statustext = ''; // TODO
+     item.setAttribute('class', 'rp-od-item ' + cssClass);
+     var canonicalRule = Ruleset.rawRuleToCanonicalString(ruleData);
+     if (self._ruleChangeQueues[ruleAction]) {
+       if (self._ruleChangeQueues[ruleAction][canonicalRule]) {
+         item.setAttribute('selected-rule', 'true');
+       }
+     }
+     return item;
+   };
+ 
+   self._ruleDataPartToDisplayString = function(ruleDataPart) {
+     var str = "";
+     if (ruleDataPart["s"]) {
+       str += ruleDataPart["s"] + "://";
+     }
+     str += ruleDataPart["h"] || "*";
+     if (ruleDataPart["port"]) {
+       str += ":" + ruleDataPart["port"];
+     }
+     // TODO: path
+     return str;
+   };
+ 
+   self._ruleDataToFormatVariables = function(rawRule) {
+     var fmtVars = [];
+     if (rawRule["o"]) {
+       fmtVars.push(self._ruleDataPartToDisplayString(rawRule["o"]));
+     }
+     if (rawRule["d"]) {
+       fmtVars.push(self._ruleDataPartToDisplayString(rawRule["d"]));
+     }
+     return fmtVars;
+   };
+ 
+   self._addMenuItemRemoveAllowRule = function(list, rawRule, subscriptionOverride) {
+     var fmtVars = self._ruleDataToFormatVariables(rawRule);
+ 
+     if (rawRule["o"] && rawRule["d"]) {
+       return self._addMenuItemStopAllowingOriginToDest(list, rawRule, subscriptionOverride);
+     } else if (rawRule["o"]) {
+       return self._addMenuItemStopAllowingOrigin(list, rawRule, subscriptionOverride);
+     } else if (rawRule["d"]) {
+       return self._addMenuItemStopAllowingDest(list, rawRule, subscriptionOverride);
+     } else {
+       throw "Invalid rule data: no origin or destination parts.";
+     }
+   };
+ 
+   self._addMenuItemRemoveDenyRule = function(list, rawRule, subscriptionOverride) {
+     var fmtVars = self._ruleDataToFormatVariables(rawRule);
+ 
+     if (rawRule["o"] && rawRule["d"]) {
+       return self._addMenuItemStopDenyingOriginToDest(list, rawRule, subscriptionOverride);
+     } else if (rawRule["o"]) {
+       return self._addMenuItemStopDenyingOrigin(list, rawRule, subscriptionOverride);
+     } else if (rawRule["d"]) {
+       return self._addMenuItemStopDenyingDest(list, rawRule, subscriptionOverride);
+     } else {
+       throw "Invalid rule data: no origin or destination parts.";
+     }
+   };
+ 
+   self._populateDetailsRemoveAllowRules = function(list) {
+     // TODO: can we avoid calling getAllowedRequests here and reuse a result
+     // from calling it earlier?
+ 
+     var reqSet = RequestProcessor.getAllowedRequests(
+         self._currentlySelectedOrigin, self._allRequestsOnDocument);
+     var requests = reqSet.getAllMergedOrigins();
+ 
+     //var rules = {};
+ 
+     var userRules = {};
+     var subscriptionRules = {};
+ 
+     //reqSet.print('allowedRequests');
+ 
+     // TODO: there is no dest if no dest is selected (origin only).
+     //var destBase = DomainUtil.getBaseDomain(
+     //      self._currentlySelectedDest);
+ 
+     for (var destBase in requests) {
+ 
+       if (self._currentlySelectedDest &&
+           self._currentlySelectedDest != destBase) {
+         continue;
+       }
+ 
+       for (var destIdent in requests[destBase]) {
+ 
+         var destinations = requests[destBase][destIdent];
+         for (var destUri in destinations) {
+ 
+           // This will be null when the request was denied because of a default
+           // allow rule. However about any other time?
+           // TODO: we at least in default allow mode, we need to give an option
+           // to add a deny rule for these requests.
+           if (!destinations[destUri]) {
+             Logger.dump("destinations[destUri] is null or undefined for " +
+                 "destUri: " + destUri);
+             continue;
+           }
+ 
+ 
+           var results = destinations[destUri][0]; // TODO: Do not look only
+           // at the first RequestResult object, but at all. (there might be
+           // several requests with identical origin and destination URI.)
+ 
+           for (var i in results.matchedAllowRules) {
+ 
+             var ruleset, match;
+             [ruleset, match] = results.matchedAllowRules[i];
+             var rawRule = Ruleset.matchToRawRule(match);
+ 
+             if (!self._currentlySelectedDest) {
+               if (rawRule['d'] && rawRule['d']['h']) {
+                 continue;
+               }
+             }
+ 
+             var rawRuleStr = Ruleset.rawRuleToCanonicalString(rawRule);
+             //Logger.info(Logger.TYPE_POLICY,
+             //       "matched allow rule: " + rawRuleStr);
+             // This is how we remove duplicates: if two rules have the same
+             // canonical string, they'll have in the same key.
+             if (ruleset.userRuleset) {
+               userRules[rawRuleStr] = rawRule;
+             } else {
+               subscriptionRules[rawRuleStr] = rawRule;
+             }
+           }
+         }
+       }
+     }
+ 
+     for (var i in userRules) {
+       self._addMenuItemRemoveAllowRule(list, userRules[i], false);
+     }
+     // TODO: for subscription rules, we need the effect of the menu item to be
+     // adding a deny rule instead of removing an allow rule. However, the text
+     // used for the item needs to be the same as removing an allow rule.
+     for (var i in subscriptionRules) {
+       self._addMenuItemRemoveAllowRule(list, subscriptionRules[i], true);
+     }
+   };
+ 
+   self._populateDetailsRemoveDenyRules = function(list) {
+     // TODO: can we avoid calling getDeniedRequests here and reuse a result
+     // from calling it earlier?
+ 
+     var reqSet = RequestProcessor.getDeniedRequests(
+         self._currentlySelectedOrigin, self._allRequestsOnDocument);
+     var requests = reqSet.getAllMergedOrigins();
+ 
+     //var rules = {};
+ 
+     var userRules = {};
+     var subscriptionRules = {};
+ 
+     reqSet.print('deniedRequests');
+ 
+     // TODO: there is no dest if no dest is selected (origin only).
+     //var destBase = DomainUtil.getBaseDomain(
+     //      self._currentlySelectedDest);
+ 
+     for (var destBase in requests) {
+ 
+       if (self._currentlySelectedDest &&
+         self._currentlySelectedDest != destBase) {
+         continue;
+       }
+ 
+       for (var destIdent in requests[destBase]) {
+ 
+         var destinations = requests[destBase][destIdent];
+         for (var destUri in destinations) {
+ 
+           // This will be null when the request was denied because of a default
+           // deny rule. However about any other time?
+           // TODO: we at least in default deny mode, we need to give an option
+           // to add a allow rule for these requests.
+           if (!destinations[destUri]) {
+             Logger.dump("destinations[destUri] is null or undefined for destUri: " + destUri);
+             continue;
+           }
+ 
+           var results = destinations[destUri][0]; // TODO: Do not look only
+           // at the first RequestResult object, but at all. (there may be
+           // several requests with identical origin and destination URI.)
+ 
+           for (var i in results.matchedDenyRules) {
+ 
+             var ruleset, match;
+             [ruleset, match] = results.matchedDenyRules[i];
+             var rawRule = Ruleset.matchToRawRule(match);
+ 
+             if (!self._currentlySelectedDest) {
+               if (rawRule['d'] && rawRule['d']['h']) {
+                 continue;
+               }
+             }
+ 
+             var rawRuleStr = Ruleset.rawRuleToCanonicalString(rawRule);
+             //Logger.info(Logger.TYPE_POLICY,
+             //       "matched allow rule: " + rawRuleStr);
+             // This is how we remove duplicates: if two rules have the same
+             // canonical string, they'll have in the same key.
+             if (ruleset.userRuleset) {
+               userRules[rawRuleStr] = rawRule;
+             } else {
+               subscriptionRules[rawRuleStr] = rawRule;
+             }
+           }
+         }
+       }
+     }
+ 
+     for (var i in userRules) {
+       self._addMenuItemRemoveDenyRule(list, userRules[i], false);
+     }
+     // TODO: for subscription rules, we need the effect of the menu item to be
+     // adding an allow rule instead of removing a deny rule. However, the text
+     // used for the item needs to be the same as removing a deny rule.
+     for (var i in subscriptionRules) {
+       self._addMenuItemRemoveDenyRule(list, subscriptionRules[i], true);
+     }
+   };
+ 
+   self._populateDetailsAddSubdomainAllowRules = function(list) {
+     var origin = self._currentlySelectedOrigin;
+ 
+     // TODO: can we avoid calling getDeniedRequests here and reuse a result
+     // from calling it earlier?
+ 
+     var reqSet = RequestProcessor.getDeniedRequests(
+         self._currentlySelectedOrigin, self._allRequestsOnDocument);
+     var requests = reqSet.getAllMergedOrigins();
+ 
+     var destHosts = {};
+ 
+     for (var destBase in requests) {
+       if (self._currentlySelectedDest &&
+           self._currentlySelectedDest != destBase) {
+         continue;
+       }
+       for (var destIdent in requests[destBase]) {
+         var destinations = requests[destBase][destIdent];
+         for (var destUri in destinations) {
+           destHosts[DomainUtil.getHost(destUri)] = null;
+         }
+       }
+     }
+ 
+     let mayPermRulesBeAdded = WindowUtils.mayPermanentRulesBeAdded(window);
+ 
+     for (var destHost in destHosts) {
+       var ruleData = {
+         'o' : {
+           'h' : self._addWildcard(origin)
+         },
+         'd' : {
+           'h': destHost
+         }
+       };
+       if (!PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, ruleData) &&
+           !PolicyManager.ruleExists(C.RULE_ACTION_DENY, ruleData)) {
+         if (mayPermRulesBeAdded === true) {
+           var item = self._addMenuItemAllowOriginToDest(list, ruleData);
+         }
+         var item = self._addMenuItemTempAllowOriginToDest(list, ruleData);
+       }
+ 
+       var destOnlyRuleData = {
+         'd' : {
+           'h': destHost
+         }
+       };
+       if (!PolicyManager.ruleExists(C.RULE_ACTION_ALLOW, destOnlyRuleData) &&
+           !PolicyManager.ruleExists(C.RULE_ACTION_DENY, destOnlyRuleData)) {
+         if (mayPermRulesBeAdded === true) {
+           var item = self._addMenuItemAllowDest(list, destOnlyRuleData);
+         }
+         var item = self._addMenuItemTempAllowDest(list, destOnlyRuleData);
+       }
+     }
+   };
+ 
+   return self;
+ }());
++
diff --cc content/ui/overlay.js
index 0000000,da44344..d407c86
mode 000000,100644..100644
--- a/content/ui/overlay.js
+++ b/content/ui/overlay.js
@@@ -1,0 -1,1224 +1,1225 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ 
+ /**
+  * Provides functionality for the overlay. An instance of this class exists for
+  * each tab/window.
+  */
+ requestpolicy.overlay = (function() {
+ 
+   const Ci = Components.interfaces;
+   const Cc = Components.classes;
+   const Cu = Components.utils;
+ 
+   let {ScriptLoader, XPCOMUtils} = (function() {
+     let mod = {};
+     Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm", mod);
+     Cu.import("resource://gre/modules/XPCOMUtils.jsm", mod);
+     return mod;
+   }());
+ 
+   // iMod: Alias for ScriptLoader.importModule
+   let iMod = ScriptLoader.importModule;
+   let {Environment, ProcessEnvironment} = 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");
+   let {PolicyManager} = iMod("lib/policy-manager");
+   let {DomainUtil} = iMod("lib/utils/domains");
+   let {StringUtils} = iMod("lib/utils/strings");
+   let {DOMUtils} = iMod("lib/utils/dom");
+   let {WindowUtils} = iMod("lib/utils/windows");
+   let {C} = iMod("lib/utils/constants");
+ 
+   let gBrowser = WindowUtils.getTabBrowser(window);
+ 
+   let $id = document.getElementById.bind(document);
+ 
+   //let _extensionConflictInfoUri = "http://www.requestpolicy.com/conflict?ext=";
+ 
+   //let _prefetchInfoUri = "http://www.requestpolicy.com/help/prefetch.html";
+   //let _prefetchDisablingInstructionsUri = "http://www.requestpolicy.com/help/prefetch.html#disable";
+ 
+ 
+   // create an environment for this overlay.
+   let OverlayEnvironment = new Environment(ProcessEnvironment, "OverlayEnv");
+   // manage this overlay's message listeners:
+   let mlManager = new ManagerForMessageListeners(OverlayEnvironment,
+                                                  window.messageManager);
+ 
+ 
+   let initialized = false;
+ 
+   let toolbarButtonId = "requestpolicyToolbarButton";
+ 
+   let overlayId = 0;
+ 
+   let blockedContentStateUpdateDelay = 250; // milliseconds
+   let blockedContentCheckTimeoutId = null;
+   let blockedContentCheckMinWaitOnObservedBlockedRequest = 500;
+   let blockedContentCheckLastTime = 0;
+ 
+   let popupElement = null;
+ 
+   //let statusbar = null;
+ 
+   // TODO: get back entry in context menu
+   // https://github.com/RequestPolicyContinued/requestpolicy/issues/353
+   //let rpContextMenu = null;
+ 
+   let toolbox = null;
+ 
+   let isFennec = false;
+ 
+ 
+ 
+   let self = {
+     // This is set by request-log.js when it is initialized. We don't need to worry
+     // about setting it here.
+     requestLog: null
+   };
+ 
+ 
+   self.toString = function() {
+     return "[requestpolicy.overlay " + overlayId + "]";
+   };
+ 
+   /**
+    * Initialize the object. This must be done after the DOM is loaded.
+    */
+   self.init = function() {
+     try {
+       if (initialized === false) {
+         initialized = true;
+         overlayId = (new Date()).getTime();
+ 
+         requestpolicy.menu.init();
+ 
+         popupElement = $id("rp-popup");
+ 
+         //statusbar = $id("status-bar");
+         //rpContextMenu = $id("requestpolicyContextMenu");
+         toolbox = $id("navigator-toolbox");
+ 
+         var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
+             .getService(Components.interfaces.nsIXULAppInfo);
+         isFennec = (appInfo.ID === "{a23983c0-fd0e-11dc-95ff-0800200c9a66}");
+ 
+         if (isFennec) {
+           Logger.dump("Detected Fennec.");
+           // Set an attribute for CSS usage.
+           popupElement.setAttribute("fennec", "true");
+           popupElement.setAttribute("position", "after_end");
+         }
+ 
+         // Register this window with the requestpolicy service so that we can be
+         // notified of blocked requests. When blocked requests happen, this
+         // object's observerBlockedRequests() method will be called.
+         RequestProcessor.addRequestObserver(self);
+ 
+         //self.setContextMenuEnabled(rpPrefBranch.getBoolPref("contextMenu"));
+ 
+         OverlayEnvironment.shutdownOnUnload(window);
+         OverlayEnvironment.startup();
+ 
+         // Tell the framescripts that the overlay is ready. The
+         // listener must be added immediately.
+         mlManager.addListener("isOverlayReady", function() {
+           return true;
+         });
+         window.messageManager.broadcastAsyncMessage(C.MM_PREFIX +
+                                                     "overlayIsReady", true);
+       }
+     } catch (e) {
+       Logger.severe(Logger.TYPE_ERROR,
+           "Fatal Error, " + e + ", stack was: " + e.stack);
+       Logger.severe(Logger.TYPE_ERROR,
+           "Unable to initialize requestpolicy.overlay.");
+       throw e;
+     }
+   };
+ 
+   //setContextMenuEnabled : function(isEnabled) {
+   //  rpContextMenu.setAttribute("hidden", !isEnabled);
+   //},
+ 
+   OverlayEnvironment.addShutdownFunction(
+       Environment.LEVELS.INTERFACE,
+       function() {
+         RequestProcessor.removeRequestObserver(self);
+         self._unwrapAddTab();
+         self._removeHistoryObserver();
+         self._removeLocationObserver();
+       });
+ 
+   OverlayEnvironment.addShutdownFunction(
+     Environment.LEVELS.UI,
+     function() {
+       let requestLog = $id("requestpolicy-requestLog");
+ 
+       // If the request log is found and is opened.
+       // The XUL elements of the request log might have already
+       // been removed.
+       if (!!requestLog && requestLog.hidden === false) {
+         self.toggleRequestLog();
+       }
+     });
+ 
+   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);
+ 
+       self._wrapAddTab();
+       self._addLocationObserver();
+       self._addHistoryObserver();
+     }
+   }
+   OverlayEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                         addTabContainerTabSelectListener);
+ 
+ 
+ 
+ 
+   mlManager.addListener("notifyDocumentLoaded", function(message) {
+     let {documentURI} = message.data;
+ 
+     // the <browser> element of the corresponding tab.
+     let browser = message.target;
+ 
+     let blockedURIs = {};
+ 
+     if (rpPrefBranch.getBoolPref("indicateBlockedObjects")) {
+       var indicateBlacklisted = rpPrefBranch
+           .getBoolPref("indicateBlacklistedObjects");
+ 
+       var rejectedRequests = RequestProcessor._rejectedRequests
+           .getOriginUri(documentURI);
+       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)};
+             }
+           }
+         }
+       }
+     }
+ 
+     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];
+     }
+ 
+     // send the list of blocked URIs back to the frame script
+     return {blockedURIs: blockedURIs};
+   });
+ 
+ 
+ 
+   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);
+   });
+ 
+ 
+ 
+   self.handleMetaRefreshes = function(message) {
+     Logger.dump("Handling meta refreshes...");
+ 
+     let {documentURI, metaRefreshes} = message.data;
+     let browser = message.target;
+ 
+     for (let i = 0, len = metaRefreshes.length; i < len; ++i) {
+       let {delay, destURI, originalDestURI} = metaRefreshes[i];
+ 
+       Logger.info(Logger.TYPE_META_REFRESH, "meta refresh to <" +
+           destURI + "> (" + delay + " second delay) found in document at <" +
+           documentURI + ">");
+ 
+       if (originalDestURI) {
+         Logger.info(Logger.TYPE_META_REFRESH,
+             "meta refresh destination <" + originalDestURI + "> " +
+             "appeared to be relative to <" + documentURI + ">, so " +
+             "it has been resolved to <" + destURI + ">");
+       }
+ 
+       // We don't automatically perform any allowed redirects. Instead, we
+       // just detect when they will be blocked and show a notification. If
+       // the docShell has allowMetaRedirects disabled, it will be respected.
+       if (!Prefs.isBlockingDisabled() &&
+           !RequestProcessor.isAllowedRedirect(documentURI, destURI)) {
+         // Ignore redirects to javascript. The browser will ignore them, as well.
+         if (DomainUtil.getUriObject(destURI).schemeIs("javascript")) {
+           Logger.warning(Logger.TYPE_META_REFRESH,
+               "Ignoring redirect to javascript URI <" + destURI + ">");
+           continue;
+         }
+         // The request will be blocked by shouldLoad.
+         self._showRedirectNotification(browser, destURI, delay);
+       }
+     }
+   };
+ 
+ 
+   /**
+    * Takes an URI, crops it if necessary, and returns it.
+    * It's ensured that the returned URI isn't longer than a specified length,
+    * but the prePath is never cropped, so that the resulting string might be
+    * longer than aMaxLength.
+    *
+    * (There doesn't seem to be a way to use the xul crop attribute with the
+    * notification.)
+    *
+    * @param {String} aUri
+    * @param {Int} aMaxLength
+    *
+    * @returns {String} the URI, eventually cropped
+    *
+    */
+   function cropUri(aUri, aMaxLength) {
+     if (aUri.length < aMaxLength) {
+       return aUri;
+     } else {
+       let prePathLength = DomainUtil.getPrePath(aUri).length + 1;
+       let len = Math.max(prePathLength, aMaxLength);
+       return aUri.substring(0, len) + "...";
+     }
+   }
+ 
+ 
+   /**
+    * Shows a notification that a redirect was requested by a page (meta refresh
+    * or with headers).
+    *
+    * @param {<browser> element} browser
+    * @param {string} redirectTargetUri
+    * @param {number} delay
+    * @param {string=} redirectOriginUri
+    * @return {boolean} whether showing the notification succeeded
+    */
+   // TODO, bad smell: Instead of the <browser> etc. hand over a `Request`
+   //                  object that contains everything. This requires
+   //                  e.g. a `MetaRedirectRequest` class.
+   self._showRedirectNotification = function(browser, redirectTargetUri, delay,
+                                             redirectOriginUri) {
+     // TODO: Do something with the delay. Not sure what the best thing to do is
+     // without complicating the UI.
+ 
+     // TODO: The following error seems to be resulting when the notification
+     // goes away with a redirect, either after clicking "allow" or if the
+     // redirect is allowed and happens automatically.
+     //
+     // Source file: chrome://browser/content/browser.js
+     // Line: 3704
+     // ----------
+     // Error: self._closedNotification.parentNode is null
+     // Source file: chrome://global/content/bindings/notification.xml
+     // Line: 260
+ 
+     // redirectOriginUri is optional and is not necessary for <meta> redirects.
+     let isOriginUndefined = redirectOriginUri === undefined;
+     redirectOriginUri = redirectOriginUri || self.getTopLevelDocumentUri();
+ 
+     if (isFennec) {
+       Logger.warning(Logger.TYPE_INTERNAL,
+           "Should have shown redirect notification to <" + redirectTargetUri +
+           ">, but it's not implemented yet on Fennec.");
+       return false;
+     }
+ 
+     var notificationBox = gBrowser.getNotificationBox(browser);
+     var notificationValue = "request-policy-meta-redirect";
+ 
+     // prepare the notification's label
+     let notificationLabel;
+     if (isOriginUndefined) {
+       notificationLabel = StringUtils.$str("redirectNotification",
+           [cropUri(redirectTargetUri, 50)]);
+     } else {
+       notificationLabel = StringUtils.$str("redirectNotificationWithOrigin",
+           [cropUri(redirectOriginUri, 50), cropUri(redirectTargetUri, 50)]);
+     }
+ 
+ 
+     var addRuleMenuName = "requestpolicyRedirectAddRuleMenu";
+     var addRulePopup = $id(addRuleMenuName);
+     DOMUtils.removeChildren(addRulePopup);
+ 
+     let m = requestpolicy.menu;
+     var originBaseDomain = DomainUtil.getBaseDomain(redirectOriginUri);
+     var destBaseDomain = DomainUtil.getBaseDomain(redirectTargetUri);
+ 
+     var origin = null, dest = null;
+     if (originBaseDomain !== null) {
+       origin = m._addWildcard(originBaseDomain);
+     }
+     if (destBaseDomain !== null) {
+       dest = m._addWildcard(destBaseDomain);
+     }
+ 
+     let mayPermRulesBeAdded = WindowUtils.mayPermanentRulesBeAdded(window);
+ 
+     let cm = requestpolicy.classicmenu;
+ 
+     if (destBaseDomain !== null) {
+       cm.addMenuItemTemporarilyAllowDest(addRulePopup, dest);
+       if (mayPermRulesBeAdded) {
+         cm.addMenuItemAllowDest(addRulePopup, dest);
+       }
+     }
+ 
+     if (originBaseDomain !== null && destBaseDomain !== null) {
+       cm.addMenuSeparator(addRulePopup);
+     }
+ 
+     if (originBaseDomain !== null) {
+       cm.addMenuItemTemporarilyAllowOrigin(addRulePopup, origin);
+       if (mayPermRulesBeAdded) {
+         cm.addMenuItemAllowOrigin(addRulePopup, origin);
+       }
+     }
+ 
+     if (originBaseDomain !== null && destBaseDomain !== null) {
+       cm.addMenuSeparator(addRulePopup);
+ 
+       cm.addMenuItemTemporarilyAllowOriginToDest(addRulePopup, origin, dest);
+       if (mayPermRulesBeAdded) {
+         cm.addMenuItemAllowOriginToDest(addRulePopup, origin, dest);
+       }
+     }
+ 
+ 
+ 
+ 
+     var notification = notificationBox
+         .getNotificationWithValue(notificationValue);
+     if (notification) {
+       notification.label = notificationLabel;
+     } else {
+       var buttons = [
+         {
+           label: StringUtils.$str("allow"),
+           accessKey: StringUtils.$str("allow.accesskey"),
+           popup: null,
+           callback: function() {
+             // Fx 3.7a5+ calls shouldLoad for location.href changes.
+ 
+             // TODO: currently the allow button ignores any additional
+             //       HTTP response headers [1]. Maybe there is a way to take
+             //       those headers into account (e.g. `Set-Cookie`?), or maybe
+             //       this is not necessary at all.
+             // [1] https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields
+ 
+             RequestProcessor.registerAllowedRedirect(
+                 browser.documentURI.specIgnoringRef, redirectTargetUri);
+ 
+             browser.messageManager.sendAsyncMessage(C.MM_PREFIX + "setLocation",
+                 {uri: redirectTargetUri});
+           }
+         },
+         {
+           label: StringUtils.$str("deny"),
+           accessKey: StringUtils.$str("deny.accesskey"),
+           popup: null,
+           callback: function() {
+             // Do nothing. The notification closes when this is called.
+           }
+         },
+         {
+           label: StringUtils.$str("addRule"),
+           accessKey: StringUtils.$str("addRule.accesskey"),
+           popup: addRuleMenuName,
+           callback: null
+         }
+         // TODO: add a "read more about URL redirection" button, targetting to
+         //       https://en.wikipedia.org/wiki/URL_redirection
+       ];
+       const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+       notificationBox.appendNotification(notificationLabel, notificationValue,
+           "chrome://browser/skin/Info.png", priority, buttons);
+     }
+     return true;
+   };
+ 
+ 
+   /**
+    * Performs actions required to be performed after a tab change.
+    */
+   self.tabChanged = function() {
+     // TODO: verify the Fennec and all supported browser versions update the
+     // status bar properly with only the ProgressListener. Once verified,
+     // remove calls to tabChanged();
+     // self._updateBlockedContentState(content.document);
+   };
+ 
+   self._containsNonBlacklistedRequests = function(requests) {
+     for (let i = 0, len = requests.length; i < len; i++) {
+       if (!requests[i].isOnBlacklist()) {
+         // This request has not been blocked by the blacklist
+         return true;
+       }
+     }
+     return false;
+   };
+ 
+   /**
+    * Checks if the document has blocked content and shows appropriate
+    * notifications.
+    */
+   self._updateBlockedContentState = function() {
+     try {
+       let browser = gBrowser.selectedBrowser;
+       let uri = DomainUtil.stripFragment(browser.currentURI.spec);
+       Logger.debug(Logger.TYPE_INTERNAL,
+           "Checking for blocked requests from page <" + uri + ">");
+ 
+       // TODO: this needs to be rewritten. checking if there is blocked
+       // content could be done much more efficiently.
+       let documentContainsBlockedContent = RequestProcessor
+           .getAllRequestsInBrowser(browser).containsBlockedRequests();
+       self._setContentBlockedState(documentContainsBlockedContent);
+ 
+       let logText = documentContainsBlockedContent ?
+                     "Requests have been blocked." :
+                     "No requests have been blocked.";
+       Logger.debug(Logger.TYPE_INTERNAL, logText);
+     } catch (e) {
+       Logger.severeError(
+           "Unable to complete _updateBlockedContentState actions: " + e, e);
+     }
+   };
+ 
+   /**
+    * Sets the blocked content notifications visible to the user.
+    */
+   self._setContentBlockedState = function(isContentBlocked) {
+     var button = $id(toolbarButtonId);
+     if (button) {
+       button.setAttribute("requestpolicyBlocked", isContentBlocked);
+     }
+   };
+ 
+   /**
+    * Update RP's "permissive" status, which is to true or false.
+    */
+   function updatePermissiveStatus() {
+     var button = $id(toolbarButtonId);
+     if (button) {
+       let isPermissive = Prefs.isBlockingDisabled();
+       button.setAttribute("requestpolicyPermissive", isPermissive);
+     }
+   }
+   /**
+    * register a pref observer
+    */
+   function updatePermissiveStatusOnPrefChanges() {
+     OverlayEnvironment.obMan.observeRPPref(
+         ["startWithAllowAllEnabled"],
+         function(subject, topic, data) {
+           if (topic === "nsPref:changed") {
+             updatePermissiveStatus();
+           }
+         });
+   }
+   OverlayEnvironment.addStartupFunction(Environment.LEVELS.INTERFACE,
+                                         updatePermissiveStatusOnPrefChanges);
+   // initially set the Permissive Status
+   OverlayEnvironment.addStartupFunction(Environment.LEVELS.UI,
+                                         updatePermissiveStatus);
+ 
+   /**
+    * This function is called when any allowed requests happen. This must be as
+    * fast as possible because request processing blocks until this function
+    * returns.
+    *
+    * @param {}
+    *          originUri
+    * @param {}
+    *          destUri
+    */
+   self.observeAllowedRequest = function(originUri, destUri) {
+     if (self.requestLog) {
+       self.requestLog.addAllowedRequest(originUri, destUri);
+     }
+   };
+ 
+   /**
+    * This function is called when any blocked requests happen. This must be as
+    * fast as possible because request processing blocks until this function
+    * returns.
+    *
+    * @param {}
+    *          originUri
+    * @param {}
+    *          destUri
+    */
+   self.observeBlockedRequest = function(originUri, destUri) {
+     self._updateNotificationDueToBlockedContent();
+     if (self.requestLog) {
+       self.requestLog.addBlockedRequest(originUri, destUri);
+     }
+   };
+ 
+   /**
+    * This function gets called when a top-level document request has been
+    * blocked.
+    * This function is called during shouldLoad(). As shouldLoad shoudn't be
+    * blocked, it's better to set a timeout here.
+    */
+   self.observeBlockedTopLevelDocRequest = function (browser, originUri,
+                                                     destUri) {
+     // This function is called during shouldLoad() so set a timeout to
+     // avoid blocking shouldLoad.
+     window.setTimeout(function() {
+       requestpolicy.overlay._showRedirectNotification(browser, destUri, 0);
+     }, 0);
+   };
+ 
+   // TODO: observeBlockedFormSubmissionRedirect
+ 
+   self._updateNotificationDueToBlockedContent = function() {
+     if (!blockedContentCheckTimeoutId) {
+       self._updateBlockedContentStateAfterTimeout();
+     }
+   };
+ 
+   self._updateBlockedContentStateAfterTimeout = function() {
+     const browser = gBrowser.selectedBrowser;
+     blockedContentCheckTimeoutId = window.setTimeout(function() {
+       requestpolicy.overlay._updateBlockedContentState(browser);
+     }, blockedContentStateUpdateDelay);
+   };
+ 
+   self._stopBlockedContentCheckTimeout = function() {
+     if (blockedContentCheckTimeoutId) {
+       window.clearTimeout(blockedContentCheckTimeoutId);
+       blockedContentCheckTimeoutId = null;
+     }
+   };
+ 
+   /**
+    * Called as an event listener when popupshowing fires on the
+    * contentAreaContextMenu.
+    */
+   self._contextMenuOnPopupShowing = function() {
+     requestpolicy.overlay._wrapOpenLink();
+     /*requestpolicy.overlay._attachPopupToContextMenu();*/
+   };
+ 
+   /**
+    * Wraps (overrides) the following methods of gContextMenu
+    * - openLink()
+    * - openLinkInPrivateWindow()
+    * - openLinkInCurrent()
+    * so that RequestPolicy can register a link-click.
+    *
+    * The original methods are defined in Firefox' nsContextMenu.js:
+    * http://mxr.mozilla.org/mozilla-central/source/browser/base/content/nsContextMenu.js
+    *
+    * The openLinkInTab() method doesn't need to be wrapped because new tabs are already
+    * recognized by tabAdded(), which is wrapped elsewhere. The tabAdded() function ends up
+    * being called when openLinkInTab() is called.
+    *
+    * TODO: There are even more similar methods in gContextMenu (frame-specific),
+    *       and perhaps the number will increase in future. Frame-specific contextMenu-
+    *       entries are working, but are registered e.g. as "new window opened" by
+    *       the subsequent shouldLoad() call.
+    */
+   self._wrapOpenLink = function() {
+     if (!gContextMenu.requestpolicyMethodsOverridden) {
+       gContextMenu.requestpolicyMethodsOverridden = true;
+ 
+       gContextMenu.openLink = function() {
+         RequestProcessor.registerLinkClicked(this.target.ownerDocument.URL, this.linkURL);
+         return this.__proto__.openLink.call(this); // call the overridden method
+       };
+ 
+       // Below, we check whether the functions exist before overriding it, because
+       // those functions have been introduced in later versions of Firefox than openLink().
+ 
+       if (gContextMenu.openLinkInPrivateWindow) {
+         gContextMenu.openLinkInPrivateWindow = function() {
+           RequestProcessor.registerLinkClicked(this.target.ownerDocument.URL, this.linkURL);
+           return this.__proto__.openLinkInPrivateWindow.call(this);
+         };
+       }
+ 
+       if (gContextMenu.openLinkInCurrent) {
+         gContextMenu.openLinkInCurrent = function() {
+           RequestProcessor.registerLinkClicked(this.target.ownerDocument.URL, this.linkURL);
+           return this.__proto__.openLinkInCurrent.call(this);
+         };
+       }
+     }
+   };
+ 
+   /**
+    * Modifies the addTab() function so that RequestPolicy can be aware of the
+    * tab being opened. Assume that if the tab is being opened, it was an action
+    * the user wanted (e.g. the equivalent of a link click). Using a TabOpen
+    * event handler, I was unable to determine the referrer, so that approach
+    * doesn't seem to be an option. This doesn't actually wrap addTab because the
+    * extension TabMixPlus modifies the function rather than wraps it, so
+    * wrapping it will break tabs if TabMixPlus is installed.
+    */
+   self._wrapAddTab = function() {
+     if (!gBrowser.requestpolicyAddTabModified) {
+       gBrowser.requestpolicyAddTabModified = true;
+ 
+       // For reference, the addTab() function signature looks like this:
+       // function addTab(aURI, aReferrerURI, aCharset, aPostData, aOwner,
+       // aAllowThirdPartyFixup) {";
+       // where it's possible that only two arguments are used and aReferrerURI
+       // is a hash of the other arguments as well as new ones.
+       // See https://github.com/RequestPolicyContinued/requestpolicy/issues/38
+ 
+       // In order to keep our code from breaking if the signature of addTab
+       // changes (even just a change in variable names, for example), we'll
+       // simply insert our own line right after the first curly brace in the
+       // string representation of the addTab function.
+       var addTabString = gBrowser.addTab.toString();
+       var firstCurlyBrace = addTabString.indexOf("{");
+       var addTabParts = [];
+       // Includes the '{'
+       addTabParts[0] = addTabString.substring(0, firstCurlyBrace + 1);
+       // Starts after the '{'
+       addTabParts[1] = addTabString.substring(firstCurlyBrace + 1);
+ 
+       // We use 'arguments' so that we aren't dependent on the names of two
+       // parameters, as it seems not unlikely that these could change due to
+       // the second parameter's purpose having been changed.
+       var newFirstCodeLine = "\n    requestpolicy.overlay.tabAdded(arguments[0], arguments[1]);";
+       // Finally, add our line to the beginning of the addTab function.
+       eval("gBrowser.addTab = " + addTabParts[0] + newFirstCodeLine +
+            addTabParts[1]);
+     }
+   };
+ 
+ 
+ 
+   self._unwrapAddTab = function() {
+     if (gBrowser.requestpolicyAddTabModified === true) {
+       // get the addTab() function as a string
+       let addTabString = gBrowser.addTab.toString();
+ 
+       // define the regular expression that should find the existing code
+       // line that RequestPolicy added.
+       let codeLineRE = /(\n    )?requestpolicy\.overlay\.tabAdded\(arguments\[0\], arguments\[1\]\);/;
+ 
+       // use the regular expression
+       let newAddTabString = addTabString.replace(codeLineRE, "");
+ 
+       // apply the changes
+       eval("gBrowser.addTab = " + newAddTabString);
+ 
+       delete gBrowser.requestpolicyAddTabModified;
+     }
+   };
+ 
+   /**
+    * This is called by the modified addTab().
+    *
+    * @param {String}
+    *          url
+    * @param {nsIURI/hash}
+    *          referrerURI
+    */
+   self.tabAdded = function(url, referrerURI) {
+     // The second argument to addTab was changed to a hash.
+     // See https://github.com/RequestPolicyContinued/requestpolicy/issues/38
+     if (referrerURI && !(referrerURI instanceof Components.interfaces.nsIURI)) {
+       if ("referrerURI" in referrerURI) {
+         referrerURI = referrerURI.referrerURI;
+       } else {
+         referrerURI = null;
+       }
+     }
+     if (referrerURI) {
+       RequestProcessor.registerLinkClicked(referrerURI.spec, url);
+     }
+   };
+ 
+   self._addLocationObserver = function() {
+     self.locationListener = {
+       onLocationChange : function(aProgress, aRequest, aURI) {
+         // This gets called both for tab changes and for history navigation.
+         // The timer is running on the main window, not the document's window,
+         // so we want to stop the timer when the tab is changed.
+         requestpolicy.overlay._stopBlockedContentCheckTimeout();
+         requestpolicy.overlay
+             ._updateBlockedContentState(gBrowser.selectedBrowser);
+       },
+ 
+       QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener",
+                                              "nsISupportsWeakReference"])
+     };
+ 
+     // https://developer.mozilla.org/en/Code_snippets/Progress_Listeners
+     gBrowser.addProgressListener(self.locationListener);
+   };
+ 
+   self._removeLocationObserver = function() {
+     gBrowser.removeProgressListener(self.locationListener);
+   };
+ 
+   self._addHistoryObserver = function() {
+     // Implements nsISHistoryListener (and nsISupportsWeakReference)
+     self.historyListener = {
+       OnHistoryGoBack : function(backURI) {
+         RequestProcessor.registerHistoryRequest(backURI.asciiSpec);
+         return true;
+       },
+ 
+       OnHistoryGoForward : function(forwardURI) {
+         RequestProcessor.registerHistoryRequest(forwardURI.asciiSpec);
+         return true;
+       },
+ 
+       OnHistoryGotoIndex : function(index, gotoURI) {
+         RequestProcessor.registerHistoryRequest(gotoURI.asciiSpec);
+         return true;
+       },
+ 
+       OnHistoryNewEntry : function(newURI) {
+       },
+ 
+       OnHistoryPurge : function(numEntries) {
+         return true;
+       },
+ 
+       OnHistoryReload : function(reloadURI, reloadFlags) {
+         return true;
+       },
+ 
+       QueryInterface : function(aIID, aResult) {
+         if (aIID.equals(Components.interfaces.nsISHistoryListener) ||
+             aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+             aIID.equals(Components.interfaces.nsISupports)) {
+           return this;
+         }
+         throw Components.results.NS_NOINTERFACE;
+       },
+ 
+       GetWeakReference : function() {
+         return Components.classes["@mozilla.org/appshell/appShellService;1"]
+             .createInstance(Components.interfaces.nsIWeakReference);
+       }
+     };
+ 
+     // there seems to be a bug in Firefox ESR 24 -- the session history is
+     // null. After waiting a few miliseconds it's available. To be sure this
+     let tries = 0, waitTime = 20, maxTries = 10;
+     let tryAddingSHistoryListener = function() {
+       ++tries;
+       try {
+         let sHistory = gBrowser.webNavigation.sessionHistory;
+         sHistory.addSHistoryListener(self.historyListener);
+         return;
+       } catch (e) {
+         if (tries >= maxTries) {
+           Logger.severeError("Can't add session history listener, even " +
+               "after " + tries + " tries. "+e, e);
+           return;
+         }
+         // call this function again in a few miliseconds.
+         setTimeout(tryAddingSHistoryListener, waitTime);
+       }
+     };
+     tryAddingSHistoryListener();
+   };
+ 
+   self._removeHistoryObserver = function() {
+     var sHistory = gBrowser.webNavigation.sessionHistory;
+     try {
+       sHistory.removeSHistoryListener(self.historyListener);
+     } catch (e) {
+       // When closing the last window in a session where additional windows
+       // have been opened and closed, this will sometimes fail (bug #175).
+     }
+   };
+ 
+   /**
+    * Called before the popup menu is shown.
+    *
+    * @param {Event}
+    *          event
+    */
+   self.onPopupShowing = function(event) {
+   //    if (event.currentTarget != event.originalTarget) {
+   //      return;
+   //    }
+     requestpolicy.menu.prepareMenu();
+   };
+ 
+   /**
+    * Called after the popup menu has been hidden.
+    *
+    * @param {Event}
+    *          event
+    */
+   self.onPopupHidden = function(event) {
+     var rulesChanged = requestpolicy.menu.processQueuedRuleChanges();
+     if (rulesChanged || self._needsReloadOnMenuClose) {
+       if (rpPrefBranch.getBoolPref("autoReload")) {
+         let mm = gBrowser.selectedBrowser.messageManager;
+         mm.sendAsyncMessage(C.MM_PREFIX + "reload");
+       }
+     }
+     self._needsReloadOnMenuClose = false;
+   //    if (event.currentTarget != event.originalTarget) {
+   //      return;
+   //    }
+     // Leave the popup attached to the context menu, as we consider that the
+     // default location for it.
+     //self._attachPopupToContextMenu();
+   };
+ 
+   /**
+    * Determines the top-level document's uri identifier based on the current
+    * identifier level setting.
+    *
+    * @return {String} The current document's identifier.
+    */
+   self.getTopLevelDocumentUriIdentifier = function() {
+     return DomainUtil.getIdentifier(self.getTopLevelDocumentUri());
+   };
+ 
+   /**
+    * Get the top-level document's uri.
+    */
+   self.getTopLevelDocumentUri = function() {
+     let uri = gBrowser.selectedBrowser.currentURI.spec;
+     return RequestProcessor.getTopLevelDocTranslation(uri) ||
+         DomainUtil.stripFragment(uri);
+   };
+ 
+   /**
+    * Toggles disabling of all blocking for the current session.
+    *
+    * @param {Event}
+    *          event
+    */
+   self.toggleTemporarilyAllowAll = function(event) {
+     var disabled = !Prefs.isBlockingDisabled();
+     Prefs.setBlockingDisabled(disabled);
+ 
+     // Change the link displayed in the menu.
+     $id("rp-link-enable-blocking").hidden = !disabled;
+     $id("rp-link-disable-blocking").hidden = disabled;
+   };
+ 
+   /**
+    * Allows requests from the specified origin to any destination for the
+    * duration of the browser session.
+    */
+   self.temporarilyAllowOrigin = function(originHost) {
+     PolicyManager.temporarilyAllowOrigin(originHost);
+   };
+ 
+   /**
+    * Allows the current document's origin to request from any destination for
+    * the duration of the browser session.
+    *
+    * @param {Event}
+    *          event
+    */
+   self.temporarilyAllowCurrentOrigin = function(event) {
+     // Note: the available variable "content" is different than the avaialable
+     // "window.target".
+     var host = self.getTopLevelDocumentUriIdentifier();
+     PolicyManager.temporarilyAllowOrigin(host);
+   };
+ 
+   /**
+    * Allows a destination to be requested from any origin for the duration of
+    * the browser session.
+    *
+    * @param {String}
+    *          destHost
+    */
+   self.temporarilyAllowDestination = function(destHost) {
+     PolicyManager.temporarilyAllowDestination(destHost);
+   };
+ 
+   /**
+    * Allows a destination to be requested from a single origin for the duration
+    * of the browser session.
+    *
+    * @param {String}
+    *          originHost
+    * @param {String}
+    *          destHost
+    */
+   self.temporarilyAllowOriginToDestination = function(originHost, destHost) {
+     PolicyManager.temporarilyAllowOriginToDestination(originHost, destHost);
+   };
+ 
+   /**
+    * Allows requests from an origin, including in future browser sessions.
+    */
+   self.allowOrigin = function(originHost) {
+     PolicyManager.allowOrigin(originHost);
+   };
+ 
+   /**
+    * Allows the current document's origin to request from any destination,
+    * including in future browser sessions.
+    *
+    * @param {Event}
+    *          event
+    */
+   self.allowCurrentOrigin = function(event) {
+     var host = self.getTopLevelDocumentUriIdentifier();
+     PolicyManager.allowOrigin(host);
+   };
+ 
+   /**
+    * Allows requests to a destination, including in future browser sessions.
+    *
+    * @param {String}
+    *          destHost
+    */
+   self.allowDestination = function(destHost) {
+     PolicyManager.allowDestination(destHost);
+   };
+ 
+   /**
+    * Allows requests to a destination from a single origin, including in future
+    * browser sessions.
+    *
+    * @param {String}
+    *          originHost
+    * @param {String}
+    *          destHost
+    */
+   self.allowOriginToDestination = function(originHost, destHost) {
+     PolicyManager.allowOriginToDestination(originHost, destHost);
+   };
+ 
+   /**
+    * Revokes all temporary permissions granted during the current session.
+    *
+    * @param {Event}
+    *          event
+    */
+   self.revokeTemporaryPermissions = function(event) {
+     PolicyManager.revokeTemporaryRules();
+     self._needsReloadOnMenuClose = true;
+     popupElement.hidePopup();
+   };
+ 
+   self._openInNewTab = function(uri) {
+     gBrowser.selectedTab = gBrowser.addTab(uri);
+   };
+ 
+   self.openMenuByHotkey = function() {
+     // Ideally we'd put the popup in its normal place based on the rp toolbar
+     // button but let's not count on that being visible. So, we'll be safe and
+     // anchor it within the content element. However, there's no good way to
+     // right-align a popup. So, we can either let it be left aligned or we can
+     // figure out where we think the top-left corner should be. And that's what
+     // we do.
+     // The first time the width will be 0. The default value is determined by
+     // logging it or you can probably figure it out from the CSS which doesn't
+     // directly specify the width of the entire popup.
+     //Logger.dump('popup width: ' + popup.clientWidth);
+     var popupWidth = popupElement.clientWidth ? 730 : popupElement.clientWidth;
+     var anchor = $id("content");
+     var contentWidth = anchor.clientWidth;
+     // Take a few pixels off so it doesn't cover the browser chrome's border.
+     var xOffset = contentWidth - popupWidth - 2;
+     popupElement.openPopup(anchor, "overlap", xOffset);
+   };
+ 
+   //  showExtensionConflictInfo : function() {
+   //    var ext = RequestProcessor.getConflictingExtensions();
+   //    var extJson = JSON.stringify(ext);
+   //    self._openInNewTab(self._extensionConflictInfoUri
+   //        + encodeURIComponent(extJson));
+   //  },
+ 
+   //  showPrefetchInfo : function() {
+   //    self._openInNewTab(self._prefetchInfoUri);
+   //  },
+   //
+   //  showPrefetchDisablingInstructions : function() {
+   //    self._openInNewTab(self._prefetchDisablingInstructionsUri);
+   //  },
+ 
+   self.openToolbarPopup = function(anchor) {
+   //    requestpolicy.overlay._toolbox.insertBefore(requestpolicy.overlay.popupElement,
+   //        null);
+     popupElement.openPopup(anchor, "after_start", 0, 0, true, true);
+   };
+ 
+   function openLinkInNewTab(url, relatedToCurrent) {
+     window.openUILinkIn(url, "tab", {relatedToCurrent: !!relatedToCurrent});
+     popupElement.hidePopup();
+   }
+ 
+   self.openPrefs = openLinkInNewTab.bind(this, "about:requestpolicy", true);
+   self.openPolicyManager = openLinkInNewTab.bind(this,
+       "about:requestpolicy?yourpolicy", true);
+   self.openHelp = openLinkInNewTab.bind(this,
+       "https://github.com/RequestPolicyContinued/requestpolicy/wiki/Help-and-Support");
+ 
+ 
+   self.clearRequestLog = function() {
+     self.requestLog.clear();
+   };
+ 
+   self.toggleRequestLog = function() {
+     var requestLog = $id("requestpolicy-requestLog");
+     var requestLogSplitter = $id("requestpolicy-requestLog-splitter");
+     var requestLogFrame = $id("requestpolicy-requestLog-frame");
+     //var openRequestLog = $id("requestpolicyOpenRequestLog");
+ 
+     // TODO: figure out how this should interact with the new menu.
+     //var closeRequestLog = $id("requestpolicyCloseRequestLog");
+     var closeRequestLog = {};
+ 
+     if (requestLog.hidden) {
+       requestLogFrame.setAttribute("src",
+           "chrome://requestpolicy/content/ui/request-log.xul");
+       requestLog.hidden = requestLogSplitter.hidden = closeRequestLog.hidden = false;
+       //openRequestLog.hidden = true;
+     } else {
+       requestLogFrame.setAttribute("src", "about:blank");
+       requestLog.hidden = requestLogSplitter.hidden = closeRequestLog.hidden = true;
+       //openRequestLog.hidden = false;
+       self.requestLog = null;
+     }
+   };
+ 
+   return self;
+ }());
++
diff --cc content/ui/request-log.filtering.js
index 0000000,2908378..9326f2a
mode 000000,100644..100644
--- a/content/ui/request-log.filtering.js
+++ b/content/ui/request-log.filtering.js
@@@ -1,0 -1,110 +1,111 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ 
+ window.requestpolicy.requestLog = (function (self) {
+ 
+   const Ci = Components.interfaces;
+   const Cc = Components.classes;
+   const Cu = Components.utils;
+ 
+   let {ScriptLoader, Services} = (function() {
+     let mod = {};
+     Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm", mod);
+     Cu.import("resource://gre/modules/Services.jsm", mod);
+     return mod;
+   }());
+   let {WindowUtils} = ScriptLoader.importModule("lib/utils/windows");
+ 
+   let filterText = null;
+ 
+   // TODO: use the Window Environment instead
+   let elements = WindowUtils.getElementsByIdOnLoad(window, {
+         filterTextbox: "requestpolicy-requestLog-requestFilter",
+         clearFilterButton: "requestpolicy-requestLog-clearFilter"
+       });
+ 
+   self.filterChanged = function() {
+     let filterValue = elements.filterTextbox.value;
+ 
+     // create a new regular expression
+     filterText = filterValue.length === 0 ? null : new RegExp(filterValue, 'i');
+     // enable/disable the "Clear Filter" button
+     elements.clearFilterButton.disabled = filterValue.length === 0;
+ 
+     loadTable();
+   };
+ 
+   self.clearFilter = function() {
+     elements.filterTextbox.value = "";
+     elements.filterTextbox.focus();
+     self.filterChanged();
+   }
+ 
+ 
+ 
+ 
+   /**
+    * Check if the row should be displayed or filtered out.
+    *
+    * This function searches the first two columns for the filterText.
+    *
+    * @param {Array} aRow
+    */
+   self.isRowFilteredOut = function(aRow) {
+     if (filterText === null) {
+       return false;
+     }
+     // The row is filtered out in case *all* searches in the columns *failed*.
+     return aRow[0].search(filterText) === -1 &&
+         aRow[1].search(filterText) === -1;
+   };
+ 
+   function addRowOrFilterOut(aRow) {
+     if (self.isRowFilteredOut(aRow)) {
+       return;
+     }
+     self.visibleRows.push(aRow);
+   }
+ 
+   // This function is called every time the tree is sorted, filtered or reloaded
+   function loadTable() {
+     let oldRowCount = self.treeView.rowCount;
+ 
+     if (!filterText) {
+       // there's no filter ==> show all rows
+       self.visibleRows = self.rows;
+     } else {
+       // filter out the rows we want to display
+       self.visibleRows = [];
+       self.rows.forEach(addRowOrFilterOut);
+     }
+ 
+     // notify that the table rows has changed
+     let newRowCount = self.treeView.rowCount;
+     self.treebox.rowCountChanged(0, newRowCount-oldRowCount);
+   }
+ 
+ 
+   return self;
+ }(window.requestpolicy.requestLog || {}));
++
diff --cc content/ui/request-log.interface.js
index 0000000,941346b..1daef7d
mode 000000,100644..100644
--- a/content/ui/request-log.interface.js
+++ b/content/ui/request-log.interface.js
@@@ -1,0 -1,134 +1,135 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ window.requestpolicy = window.requestpolicy || {};
+ 
+ window.requestpolicy.requestLog = (function (self) {
+ 
+   const Ci = Components.interfaces;
+   const Cc = Components.classes;
+   const Cu = Components.utils;
+ 
+   let {ScriptLoader, Services} = (function() {
+     let mod = {};
+     Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm", mod);
+     Cu.import("resource://gre/modules/Services.jsm", mod);
+     return mod;
+   }());
+   let {DomainUtil} = ScriptLoader.importModule("lib/utils/domains");
+   let {StringUtils} = ScriptLoader.importModule("lib/utils/strings");
+   let {Utils} = ScriptLoader.importModule("lib/utils");
+ 
+ 
+ 
+ 
+   self.clear = function() {
+     var count = self.treeView.rowCount;
+     if (count == 0) {
+       return;
+     }
+     self.rows = [];
+     self.visibleRows = [];
+     if (!self.treebox) {
+       return;
+     }
+     self.treebox.rowCountChanged(0, -count);
+   };
+ 
+   /**
+    * Copy the content of a cell to the clipboard. The row used will be the one
+    * selected when the context menu was opened.
+    */
+   self.copyToClipboard = function(columnName) {
+     var content = self.treeView.getCellText(self.tree.currentIndex,
+         self.tree.columns.getNamedColumn(columnName));
+ 
+     const clipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+         .getService(Components.interfaces.nsIClipboardHelper);
+     clipboardHelper.copyString(content);
+   };
+ 
+   /**
+    * Open the content of a cell in a new tab. The row used will be the one
+    * selected when the context menu was opened.
+    */
+   self.openInNewTab = function(columnName) {
+     var content = self.treeView.getCellText(self.tree.currentIndex,
+         self.tree.columns.getNamedColumn(columnName));
+ 
+     var forbidden = true;
+     try {
+       var uri = DomainUtil.getUriObject(content);
+       if (uri.scheme == 'http' || uri.scheme == 'https' || uri.scheme == 'ftp') {
+         forbidden = false;
+       }
+     } catch (e) {
+     }
+ 
+     if (forbidden) {
+       var alertTitle = StringUtils.$str("actionForbidden");
+       var alertText = StringUtils.$str("urlCanOnlyBeCopiedToClipboard");
+       Services.prompt.alert(null, alertTitle, alertText);
+       return;
+     }
+ 
+     window.top.openUILinkIn(content, "tab", {relatedToCurrent: true});
+   };
+ 
+ 
+ 
+ 
+   function addRow(aRow) {
+     self.rows.push(aRow);
+ 
+     if (!self.isRowFilteredOut(aRow)) {
+       if (self.isEmptyMessageDisplayed) {
+         // If this were to be called in a multithreaded manner, there's probably
+         // a race condition here.
+         self.visibleRows.shift();
+         self.isEmptyMessageDisplayed = false;
+         self.treebox.rowCountChanged(0, -1);
+       }
+ 
+       self.visibleRows.push(aRow);
+ 
+       if (!self.treebox) {
+         return;
+       }
+ 
+       self.treebox.rowCountChanged(0, 1);
+     }
+   }
+ 
+   self.addAllowedRequest = function(originURI, destURI) {
+     addRow([originURI, destURI, false, (new Date()).toLocaleTimeString()]);
+   };
+ 
+   self.addBlockedRequest = function(originURI, destURI) {
+     addRow([originURI, destURI, true, (new Date()).toLocaleTimeString()]);
+   };
+ 
+ 
+ 
+   return self;
+ }(window.requestpolicy.requestLog || {}));
++
diff --cc content/ui/request-log.js
index 0000000,a60714e..6a02c1d
mode 000000,100644..100644
--- a/content/ui/request-log.js
+++ b/content/ui/request-log.js
@@@ -1,0 -1,81 +1,82 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ window.requestpolicy = window.requestpolicy || {};
+ 
+ window.requestpolicy.requestLog = (function (self) {
+ 
+   const Ci = Components.interfaces;
+   const Cc = Components.classes;
+   const Cu = Components.utils;
+ 
+   let {ScriptLoader} = (function() {
+     let mod = {};
+     Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm", mod);
+     return mod;
+   }());
+   let {StringUtils} = ScriptLoader.importModule("lib/utils/strings");
+   let {WindowUtils} = ScriptLoader.importModule("lib/utils/windows");
+   let {Environment,
+        ProcessEnvironment} = ScriptLoader.importModule("lib/environment");
+ 
+   // create a new Environment for this window
+   var WinEnv = new Environment(ProcessEnvironment, "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 = [];
+   self.visibleRows = [];
+ 
+ 
+ 
+   function init() {
+     self.tree = $id("requestpolicy-requestLog-tree")
+ 
+     self.tree.view = self.treeView;
+ 
+     showLogIsEmptyMessage();
+ 
+     // Give the requestpolicy overlay direct access to the the request log.
+     window.parent.requestpolicy.overlay.requestLog = self;
+   }
+   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);
+ 
+ 
+ 
+   return self;
+ }(window.requestpolicy.requestLog || {}));
++
diff --cc content/ui/request-log.tree-view.js
index 0000000,6f87f0d..5f6b98c
mode 000000,100644..100644
--- a/content/ui/request-log.tree-view.js
+++ b/content/ui/request-log.tree-view.js
@@@ -1,0 -1,185 +1,186 @@@
+ /*
+  * ***** 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 *****
+  */
+ 
+ window.requestpolicy = window.requestpolicy || {};
+ 
+ window.requestpolicy.requestLog = (function (self) {
+ 
+   const Ci = Components.interfaces;
+   const Cc = Components.classes;
+   const Cu = Components.utils;
+ 
+ 
+   let {ScriptLoader} = (function() {
+     let mod = {};
+     Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm", mod);
+     return mod;
+   }());
+   let {StringUtils} = ScriptLoader.importModule("lib/utils/strings");
+ 
+ 
+ 
+ 
+   self.treebox = null;
+ 
+   self.columnNameToIndexMap = {
+     "requestpolicy-requestLog-origin" : 0,
+     "requestpolicy-requestLog-destination" : 1,
+     "requestpolicy-requestLog-blocked" : 2,
+     "requestpolicy-requestLog-time" : 3
+   };
+ 
+   let aserv = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomService);
+ 
+ 
+   function getVisibleRowAtIndex(index) {
+     return self.visibleRows[self.visibleRows.length - index - 1];
+   };
+ 
+ 
+ 
+   //
+   // the interface.
+   // see https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Tutorial/Custom_Tree_Views
+   //
+ 
+   self.treeView = {
+     /**
+      * "This property should be set to the total number of rows in the tree."
+      * (getter function)
+      */
+     get rowCount () {
+       return self.visibleRows.length;
+     },
+ 
+     /**
+      * "This method should return the text contents at the specified row and
+      * column."
+      */
+     setTree: function(aTreebox) {
+       self.treebox = aTreebox;
+     },
+ 
+     /**
+      * This method is called once to set the tree element on the view.
+      */
+     getCellText: function(aIndex, aColumn) {
+       // Row 0 is actually the last element in the array so that we don't have to
+       // unshift() the array and can just push().
+       // TODO: Do an actual speed test with push vs. unshift to see if it matters
+       // with this javascript array implementation, though I'm assuming it does.
+       var columnIndex = self.columnNameToIndexMap[aColumn.id];
+       if (columnIndex != 2) {
+         return getVisibleRowAtIndex(aIndex)[self.columnNameToIndexMap[aColumn.id]];
+       }
+     },
+ 
+     isContainer: function(aIndex) {
+       return false;
+     },
+ 
+     isContainerOpen: function(aIndex) {
+       return false;
+     },
+ 
+     isContainerEmpty: function(aIndex) {
+       return false;
+     },
+ 
+     isSeparator: function(aIndex) {
+       return false;
+     },
+ 
+     isSorted: function() {
+       return false;
+     },
+ 
+     isEditable: function(aIndex, aColumn) {
+       return false;
+     },
+ 
+     getParentIndex: function(aIndex) {
+       return -1;
+     },
+ 
+     getLevel: function(aIndex) {
+       return 0;
+     },
+ 
+     hasNextSibling: function(aIndex, aAfter) {
+       return false;
+     },
+ 
+     toggleOpenState: function(aIndex) {},
+ 
+     getImageSrc: function(aIndex, aColumn) {
+       if (self.columnNameToIndexMap[aColumn.id] == 2) {
+         if (getVisibleRowAtIndex(aIndex)[2]) {
+           return "chrome://requestpolicy/skin/dot.png";
+         }
+       }
+     },
+ 
+     getProgressMode: function(aIndex, aColumn) {},
+     getCellValue: function(aIndex, aColumn) {},
+     cycleHeader: function(col, elem) {},
+     selectionChanged: function() {},
+     cycleCell: function(aIndex, aColumn) {},
+     performAction: function(action) {},
+     performActionOnCell: function(action, aIndex, aColumn) {},
+ 
+     getRowProperties: function(aIndex, aProps) {
+       var returnValue = (getVisibleRowAtIndex(aIndex)[2]) ? "blocked" : "allowed";
+ 
+       if (aProps) {
+         // Gecko version < 22
+         aProps.AppendElement(aserv.getAtom(returnValue));
+       } else {
+         // Gecko version >= 22
+         return returnValue;
+       }
+     },
+ 
+     getCellProperties: function(aIndex, aColumn, aProps) {
+       if (self.columnNameToIndexMap[aColumn.id] == 2) {
+         if (getVisibleRowAtIndex(aIndex)[2]) {
+           if (aProps) {
+             // Gecko version < 22
+             aProps.AppendElement(aserv.getAtom("blocked"));
+           } else {
+             // Gecko version >= 22
+             return "blocked";
+           }
+         }
+       }
+     },
+ 
+     getColumnProperties: function(aColumn, aProps) {
+       if (!aProps) {
+         return "";
+       }
+     }
+   };
+ 
+   return self;
+ }(window.requestpolicy.requestLog || {}));
++
diff --cc content/ui/xul-trees.js
index 0000000,fab25d1..58b8fb8
mode 000000,100644..100644
--- a/content/ui/xul-trees.js
+++ b/content/ui/xul-trees.js
@@@ -1,0 -1,185 +1,186 @@@
+ /*
+  * ***** 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 {tag: "http"://www.gnu.org/licenses}.
+  *
+  * ***** END LICENSE BLOCK *****
+  */
+ 
+ // differences in seamonkey:
+ // https://developer.mozilla.org/en-US/Add-ons/SeaMonkey_2
+ let isSeamonkey = appID === C.SEAMONKEY_ID;
+ 
+ 
+ exports.toolbarbutton = [
+   {parent: {special: {type: "subobject", id: "navigator-toolbox",
+       tree: ["palette"]}}, // ("#navigator-toolbox".palette)
+     tag: "toolbarbutton", id: "requestpolicyToolbarButton",
+     label: "RequestPolicy", tooltiptext: "RequestPolicy",
+     popup: "rp-popup"
+   }
+ ];
+ 
+ exports.mainTree = [
+   {parent: {id: (isSeamonkey ? "taskPopup" : "menu_ToolsPopup")},
+       tag: "menu", id: "requestpolicyToolsMenuPopup", label: "RequestPolicy",
+       accesskey: "r",
+   children: [
+     {tag: "menupopup",
+     children: [
+       {tag: "menuitem", label: "&rp.menu.managePolicies;", accesskey: "m",
+           oncommand: "requestpolicy.overlay.openPolicyManager();"},
+       {tag: "menuitem", label: "&rp.requestLog.title;", accesskey: "l",
+           oncommand: "requestpolicy.overlay.toggleRequestLog(event);"},
+       {tag: "menuitem", label: "&rp.menu.preferences;", accesskey: "p",
+           oncommand: "requestpolicy.overlay.openPrefs(event);"}
+     ]}
+   ]},
+ 
+ 
+   {parent: {special: {type: "__window__"}},
+       tag: "keyset", id: "requestpolicyKeyset",
+   children: [
+     {tag: "key", key: "r", modifiers: "accel alt",
+         oncommand: "requestpolicy.overlay.openMenuByHotkey()"}
+   ]},
+ 
+ 
+   {parent: {special: {type: "__window__"}},
+       tag: "popupset", id: "requestpolicyPopupset",
+   children: [
+     {tag: "menupopup", id: "requestpolicyRedirectAddRuleMenu"},
+     {tag: "menupopup", id: "rp-popup", noautohide: "true",
+         position: "after_start",
+         onpopupshowing: "requestpolicy.overlay.onPopupShowing(event);",
+         onpopuphidden: "requestpolicy.overlay.onPopupHidden(event);",
+     children: [
+       {tag: "vbox", id: "rp-contents",
+       children: [
+         {tag: "hbox", id: "rp-main",
+         children: [
+           {tag: "vbox", id: "rp-origins-destinations",
+           children: [
+             {tag: "hbox", id: "rp-origin", "class": "rp-od-item",
+                 onclick: "requestpolicy.menu.itemSelected(event);",
+             children: [
+               {tag: "label", id: "rp-origin-domainname", "class": "domainname",
+                   flex: "2"},
+               {tag: "label", id: "rp-origin-num-requests",
+                   "class": "numRequests"}
+             ]},
+             {tag: "vbox", id: "rp-other-origins",
+             children: [
+               {tag: "label", id: "rp-other-origins-title",
+                   value: "&rp.menu.otherOrigins;"},
+               {tag: "vbox", id: "rp-other-origins-list",
+                   "class": "rp-label-list"}
+             ]},
+             {tag: "vbox", id: "rp-blocked-destinations",
+             children: [
+               {tag: "label", id: "rp-blocked-destinations-title",
+                   value: "&rp.menu.blockedDestinations;"},
+               {tag: "vbox", id: "rp-blocked-destinations-list",
+                   "class": "rp-label-list"}
+             ]},
+             {tag: "vbox", id: "rp-mixed-destinations",
+             children: [
+               {tag: "label", id: "rp-mixed-destinations-title",
+                   value: "&rp.menu.mixedDestinations;"},
+               {tag: "vbox", id: "rp-mixed-destinations-list",
+                   "class": "rp-label-list"}
+             ]},
+             {tag: "vbox", id: "rp-allowed-destinations",
+             children: [
+               {tag: "label", id: "rp-allowed-destinations-title",
+                   value: "&rp.menu.allowedDestinations;"},
+               {tag: "vbox", id: "rp-allowed-destinations-list",
+                   "class": "rp-label-list"}
+             ]}
+           ]},
+           {tag: "vbox", id: "rp-details",
+           children: [
+             {tag: "vbox", id: "rp-rules-remove"},
+             {tag: "vbox", id: "rp-rules-add"},
+             {tag: "vbox", id: "rp-rules-info"}
+           ]}
+         ]},
+         {tag: "hbox", id: "rp-revoke-temporary-permissions", hidden: "true",
+         children: [
+           {tag: "label", value: "&rp.menu.revokeTemporaryPermissions;",
+               onclick: "requestpolicy.overlay.revokeTemporaryPermissions();"}
+         ]},
+         {tag: "hbox", id: "rp-footer",
+         children: [
+           {tag: "hbox", id: "rp-footer-links",
+           children: [
+             {tag: "label", id: "rp-link-enable-blocking",
+                 "class": "rp-footer-link", value: "&rp.menu.enableBlocking;",
+                 onclick: "requestpolicy.overlay.toggleTemporarilyAllowAll();"},
+             {tag: "label", id: "rp-link-disable-blocking",
+                 "class": "rp-footer-link", value: "&rp.menu.disableBlocking;",
+                 onclick: "requestpolicy.overlay.toggleTemporarilyAllowAll();"},
+             {tag: "label", id: "rp-link-help", "class": "rp-footer-link",
+                 value: "&rp.menu.help;",
+                 onclick: "requestpolicy.overlay.openHelp();"},
+             {tag: "label", id: "rp-link-prefs", "class": "rp-footer-link",
+                 value: "&rp.menu.preferences;",
+                 onclick: "requestpolicy.overlay.openPrefs(event);"},
+             {tag: "label", id: "rp-link-policies", "class": "rp-footer-link",
+                 value: "&rp.menu.managePolicies;",
+                 onclick: "requestpolicy.overlay.openPolicyManager();"},
+             {tag: "label", id: "rp-link-request-log", "class": "rp-footer-link",
+                 value: "&rp.requestLog.title;",
+                 onclick: "requestpolicy.overlay.toggleRequestLog(event);"}
+           ]}
+         ]}
+       ]}
+     ]}
+   ]},
+ 
+ 
+   {parent: {id: "appcontent"},
+       tag: "splitter", id: "requestpolicy-requestLog-splitter", hidden: "true"},
+ 
+   {parent: {id: "appcontent"},
+       tag: "vbox", id: "requestpolicy-requestLog", height: "300",
+       hidden: "true", persist: "height",
+   children: [
+     {tag: "toolbox", id: "requestpolicy-requestLog-header",
+     children: [
+       {tag: "toolbar", id: "requestpolicy-requestLog-toolbar",
+           align: "center",
+       children: [
+         {tag: "label", id: "requestpolicy-requestLog-title",
+             control: "requestpolicy-requestLog-frame",
+             value: "&rp.requestLog.title;", crop: "end"},
+         {tag: "button", id: "requestpolicy-requestLog-clear",
+             label: "&rp.requestLog.clear;",
+             oncommand: "requestpolicy.overlay.clearRequestLog();"},
+         {tag: "vbox", flex: "1"},
+         {tag: "toolbarbutton", id: "requestpolicy-requestLog-close",
+             align: "right",
+             oncommand: "requestpolicy.overlay.toggleRequestLog()"}
+       ]}
+     ]},
+     // The src of this iframe is set to
+     // chrome://requestpolicy/content/ui/request-log.xul in overlay.js
+     {tag: "iframe", id: "requestpolicy-requestLog-frame", type: "chrome",
+         flex: "1"}
+   ]}
+ ];
++
diff --cc locale/de/requestpolicy.dtd
index 0000000,fd07f0b..fd07f0b
mode 000000,100644..100644
--- a/locale/de/requestpolicy.dtd
+++ b/locale/de/requestpolicy.dtd
diff --cc locale/de/requestpolicy.properties
index 0000000,7c93d2d..7c93d2d
mode 000000,100644..100644
--- a/locale/de/requestpolicy.properties
+++ b/locale/de/requestpolicy.properties
diff --cc locale/en-US/requestpolicy.dtd
index 0000000,547de3e..547de3e
mode 000000,100644..100644
--- a/locale/en-US/requestpolicy.dtd
+++ b/locale/en-US/requestpolicy.dtd
diff --cc locale/eo/requestpolicy.dtd
index 0000000,13c6291..13c6291
mode 000000,100644..100644
--- a/locale/eo/requestpolicy.dtd
+++ b/locale/eo/requestpolicy.dtd
diff --cc locale/es-MX/requestpolicy.dtd
index 0000000,1c406c2..1c406c2
mode 000000,100644..100644
--- a/locale/es-MX/requestpolicy.dtd
+++ b/locale/es-MX/requestpolicy.dtd
diff --cc locale/eu/requestpolicy.dtd
index 0000000,1649291..1649291
mode 000000,100644..100644
--- a/locale/eu/requestpolicy.dtd
+++ b/locale/eu/requestpolicy.dtd
diff --cc locale/fr/requestpolicy.dtd
index 0000000,408f3b3..408f3b3
mode 000000,100644..100644
--- a/locale/fr/requestpolicy.dtd
+++ b/locale/fr/requestpolicy.dtd
diff --cc locale/it/requestpolicy.dtd
index 0000000,989ee26..989ee26
mode 000000,100644..100644
--- a/locale/it/requestpolicy.dtd
+++ b/locale/it/requestpolicy.dtd
diff --cc locale/ja/requestpolicy.dtd
index 0000000,e5bdb56..e5bdb56
mode 000000,100644..100644
--- a/locale/ja/requestpolicy.dtd
+++ b/locale/ja/requestpolicy.dtd
diff --cc locale/ja/requestpolicy.properties
index 0000000,c05e9c4..c05e9c4
mode 000000,100644..100644
--- a/locale/ja/requestpolicy.properties
+++ b/locale/ja/requestpolicy.properties
diff --cc locale/ko-KR/requestpolicy.dtd
index 0000000,5e305dc..5e305dc
mode 000000,100644..100644
--- a/locale/ko-KR/requestpolicy.dtd
+++ b/locale/ko-KR/requestpolicy.dtd
diff --cc locale/lv-LV/requestpolicy.dtd
index 0000000,fb2e6c0..fb2e6c0
mode 000000,100644..100644
--- a/locale/lv-LV/requestpolicy.dtd
+++ b/locale/lv-LV/requestpolicy.dtd
diff --cc locale/nl/requestpolicy.dtd
index 0000000,7f226c7..7f226c7
mode 000000,100644..100644
--- a/locale/nl/requestpolicy.dtd
+++ b/locale/nl/requestpolicy.dtd
diff --cc locale/pt-BR/requestpolicy.dtd
index 0000000,b12563e..b12563e
mode 000000,100644..100644
--- a/locale/pt-BR/requestpolicy.dtd
+++ b/locale/pt-BR/requestpolicy.dtd
diff --cc locale/ru-RU/requestpolicy.dtd
index 0000000,c37616a..c37616a
mode 000000,100644..100644
--- a/locale/ru-RU/requestpolicy.dtd
+++ b/locale/ru-RU/requestpolicy.dtd
diff --cc locale/sk-SK/requestpolicy.dtd
index 0000000,2531d8d..2531d8d
mode 000000,100644..100644
--- a/locale/sk-SK/requestpolicy.dtd
+++ b/locale/sk-SK/requestpolicy.dtd
diff --cc locale/sv-SE/requestpolicy.dtd
index 0000000,12538bf..12538bf
mode 000000,100644..100644
--- a/locale/sv-SE/requestpolicy.dtd
+++ b/locale/sv-SE/requestpolicy.dtd
diff --cc locale/tr/requestpolicy.dtd
index 0000000,5e04879..5e04879
mode 000000,100644..100644
--- a/locale/tr/requestpolicy.dtd
+++ b/locale/tr/requestpolicy.dtd
diff --cc locale/uk-UA/requestpolicy.dtd
index 0000000,1dd6ad3..1dd6ad3
mode 000000,100644..100644
--- a/locale/uk-UA/requestpolicy.dtd
+++ b/locale/uk-UA/requestpolicy.dtd
diff --cc locale/zh-CN/requestpolicy.dtd
index 0000000,6be29ff..6be29ff
mode 000000,100644..100644
--- a/locale/zh-CN/requestpolicy.dtd
+++ b/locale/zh-CN/requestpolicy.dtd
diff --cc locale/zh-TW/requestpolicy.dtd
index 0000000,2ff4e42..2ff4e42
mode 000000,100644..100644
--- a/locale/zh-TW/requestpolicy.dtd
+++ b/locale/zh-TW/requestpolicy.dtd
diff --cc skin/close.png
index 6d37d63,6d37d63..6d37d63
Binary files differ
diff --cc skin/dot.png
index b5c4988,b5c4988..b5c4988
Binary files differ
diff --cc skin/menu-other-origins.png
index b0b6d08,b0b6d08..b0b6d08
Binary files differ
diff --cc skin/requestpolicy-icon-24-allowed.png
index cd092a9,cd092a9..cd092a9
Binary files differ
diff --cc skin/requestpolicy-icon-24-blocked.png
index 6af940c,6af940c..6af940c
Binary files differ
diff --cc skin/requestpolicy-icon-24-disabled.png
index 64e79e6,64e79e6..64e79e6
Binary files differ
diff --cc skin/requestpolicy-icon-32-allowed.png
index 4d6e274,4d6e274..4d6e274
Binary files differ
diff --cc skin/requestpolicy-icon-32-blocked.png
index e63147d,e63147d..e63147d
Binary files differ
diff --cc skin/requestpolicy-icon-32-disabled.png
index 8575dde,8575dde..8575dde
Binary files differ
diff --cc skin/requestpolicy-icon-32.png
index 73c7d7f,73c7d7f..73c7d7f
Binary files differ
diff --cc skin/requestpolicy-icon-allowed.png
index 67cdf00,67cdf00..67cdf00
mode 100644,100755..100755
Binary files differ
diff --cc skin/requestpolicy-icon-blocked.png
index 75a4e61,75a4e61..75a4e61
mode 100644,100755..100755
Binary files differ
diff --cc skin/requestpolicy-icon-disabled.png
index 1731917,1731917..1731917
mode 100644,100755..100755
Binary files differ
diff --cc skin/requestpolicy-statusbar-allowed.png
index b57cc75,b57cc75..b57cc75
mode 100644,100755..100755
Binary files differ
diff --cc skin/requestpolicy-statusbar-blocked.png
index 28280ca,28280ca..28280ca
mode 100644,100755..100755
Binary files differ
diff --cc skin/requestpolicy-statusbar-disabled.png
index 983b886,983b886..983b886
mode 100644,100755..100755
Binary files differ
diff --cc skin/toolbarbutton-seamonkey.css
index 0000000,cbbe439..cbbe439
mode 000000,100644..100644
--- a/skin/toolbarbutton-seamonkey.css
+++ b/skin/toolbarbutton-seamonkey.css
diff --cc skin/toolbarbutton.css
index 0000000,370cc00..370cc00
mode 000000,100644..100644
--- a/skin/toolbarbutton.css
+++ b/skin/toolbarbutton.css

-- 
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