[Pkg-mozext-commits] [nosquint] 22/47: Import of 2.0b4 release into git

David Prévot taffit at moszumanska.debian.org
Tue Apr 28 01:41:18 UTC 2015


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

taffit pushed a commit to annotated tag 2.1.6
in repository nosquint.

commit 7507b8a09e35e036ccaf588d6e154d070104ae83
Author: Jason Tackaberry <tack at urandom.ca>
Date:   Fri Jan 13 19:41:14 2012 -0500

    Import of 2.0b4 release into git
---
 src/content/globalprefs.js           |  112 +++-
 src/content/globalprefs.xul          |  130 ++++-
 src/content/help.xul                 |    4 +-
 src/content/init.js                  |  101 ++--
 src/content/nosquint.js              | 1069 +++++++++++++++++++++++++---------
 src/content/overlay.xul              |   42 +-
 src/content/overlay_sanitize.xul     |    7 +-
 src/content/prefs.css                |   24 +
 src/content/sanitize.js              |  136 ++---
 src/content/siteprefs.js             |  124 +++-
 src/content/siteprefs.xul            |  137 +++--
 src/content/utils.js                 |   86 +++
 src/defaults/preferences/nosquint.js |    3 +-
 src/install.rdf                      |   10 +-
 src/locale/en-US/globalprefs.dtd     |   57 +-
 src/locale/en-US/help.html           |   66 +--
 src/locale/en-US/overlay.properties  |    2 -
 src/locale/en-US/siteprefs.dtd       |    3 +
 src/skin/icon-enlarge-16.png         |  Bin 3316 -> 841 bytes
 src/skin/icon-enlarge-24.png         |  Bin 3794 -> 1332 bytes
 src/skin/icon-reduce-16.png          |  Bin 3313 -> 823 bytes
 src/skin/icon-reduce-24.png          |  Bin 3736 -> 1266 bytes
 src/skin/icon-reset-16.png           |  Bin 3335 -> 842 bytes
 src/skin/icon-reset-24.png           |  Bin 3758 -> 1315 bytes
 src/skin/toolbar.css                 |    4 +-
 25 files changed, 1513 insertions(+), 604 deletions(-)

diff --git a/src/content/globalprefs.js b/src/content/globalprefs.js
index 80d7ae3..a7a7aa9 100644
--- a/src/content/globalprefs.js
+++ b/src/content/globalprefs.js
@@ -7,24 +7,20 @@ var NoSquintPrefs = {
     init: function(doc, dialog) {
         NoSquintPrefs.doc = doc;
         NoSquintPrefs.dialog = dialog;
+        var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                            .getService(Components.interfaces.nsIPrefService);
+        NoSquintPrefs.privacyBranch = prefService.getBranch('privacy.item.');
+
         if (window.arguments) {
-            NoSquintPrefs.site = window.arguments[0];
-            NoSquintPrefs.level = window.arguments[1];
-            NoSquintPrefs.url = window.arguments[2];
-            NoSquintPrefs.NoSquint = window.arguments[3];
+            NoSquintPrefs.NoSquint = window.arguments[0];
+            NoSquintPrefs.url = window.arguments[1];
             NoSquintPrefs.NoSquint.globalDialog = this;
             NoSquintPrefs.prefs = NoSquintPrefs.NoSquint.prefs;
         } else {
-            var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(
-                                      Components.interfaces.nsIPrefService);
-            NoSquintPrefs.prefs = prefs.getBranch("extensions.nosquint.")
+            NoSquintPrefs.prefs = prefService.getBranch("extensions.nosquint.")
         }
-        doc.getElementById("defaultZoomLevel").value = NoSquintPrefs.prefs.getIntPref("zoomlevel");
-        doc.getElementById("zoomIncrement").value = NoSquintPrefs.prefs.getIntPref("zoomIncrement");
-        doc.getElementById("rememberSites").selectedIndex = NoSquintPrefs.prefs.getBoolPref("rememberSites") ? 0 : 1;
-        doc.getElementById("showStatus").checked = !NoSquintPrefs.prefs.getBoolPref("hideStatus");
-        doc.getElementById("wheelZoomEnabled").checked = NoSquintPrefs.prefs.getBoolPref("wheelZoomEnabled");
 
+        // Options tab
         var forget_cb = doc.getElementById("siteForget");
         var months = NoSquintPrefs.prefs.getIntPref("forgetMonths");
         forget_cb.checked = (months != 0);
@@ -32,21 +28,53 @@ var NoSquintPrefs = {
             doc.getElementById("siteForget-menu").value = months;
         forget_cb.addEventListener("CheckboxStateChange", NoSquintPrefs.forgetMonthsChecked, false);
         NoSquintPrefs.forgetMonthsChecked();
+        doc.getElementById("siteSanitize").checked = NoSquintPrefs.privacyBranch.getBoolPref("extensions-nosquint");
+        doc.getElementById("rememberSites").selectedIndex = NoSquintPrefs.prefs.getBoolPref("rememberSites") ? 0 : 1;
 
+        // Zooming tab
+        doc.getElementById("fullZoomLevel").value = NoSquintPrefs.prefs.getIntPref("fullZoomLevel");
+        doc.getElementById("textZoomLevel").value = NoSquintPrefs.prefs.getIntPref("textZoomLevel");
+        doc.getElementById("zoomIncrement").value = NoSquintPrefs.prefs.getIntPref("zoomIncrement");
+        doc.getElementById("zoomImages").checked = NoSquintPrefs.prefs.getBoolPref("zoomImages");
+        doc.getElementById("showStatus").checked = !NoSquintPrefs.prefs.getBoolPref("hideStatus");
+        doc.getElementById("wheelZoomEnabled").checked = NoSquintPrefs.prefs.getBoolPref("wheelZoomEnabled");
         doc.getElementById('primaryZoomMethod-menu').value = NoSquintPrefs.prefs.getBoolPref("fullZoomPrimary") ? "full" : "text";
-
         NoSquintPrefs.sitesRadioSelect();
+
+        // Color tab
+        for each (var [id, defcolor] in [['colorText', '#000000'], ['colorBackground', '#ffffff'], 
+                                         ['linksUnvisited', '#0000ee'], ['linksVisited', '#551a8b']]) {
+            var color = NoSquintPrefs.prefs.getCharPref(id);
+            var cb = doc.getElementById(id);
+            var picker = cb.parentNode.childNodes[1];
+            picker.color = color == '0' ? defcolor : color;
+            cb.addEventListener("CheckboxStateChange", NoSquintPrefs.colorChecked, false);
+            cb.checked = color == '0' ? false : true;
+            NoSquintPrefs.colorChecked(null, cb);
+        }
+        doc.getElementById('colorBackgroundImages').checked = NoSquintPrefs.prefs.getBoolPref("colorBackgroundImages");
+        doc.getElementById('linksUnderline').checked = NoSquintPrefs.prefs.getBoolPref("linksUnderline");
+
+        // Exceptions tab.
         NoSquintPrefs.parseExceptions();
         NoSquintPrefs.excListSelect();
     },
 
+    colorChecked: function(event, cb) {
+        cb = cb || this;
+        var picker = cb.parentNode.childNodes[1];
+        picker.disabled = !cb.checked;
+        picker.style.opacity = cb.checked ? 1.0 : 0.2;
+    },
+
+
     parseExceptions: function() {
         var exstr = NoSquintPrefs.prefs.getCharPref("exceptions");
         // Trim whitespace and split on space.
         var exlist = exstr.replace(/(^\s+|\s+$)/g, "").split(" ");
         for (var i = 0; i < exlist.length; i++) {
             if (exlist[i])
-                NoSquintPrefs.exceptionsListAdd(exlist[i], false);
+                NoSquintPrefs.exceptionsListAdd(exlist[i].replace(/%20/g, ' '), false);
         }
         NoSquintPrefs.doc.getElementById("exceptionsList")._changed = false;
     },
@@ -124,9 +152,13 @@ var NoSquintPrefs = {
         var item = listbox.selectedItem;
         var pattern = item.childNodes[0].getAttribute('label');
         var bundle = NoSquintPrefs.doc.getElementById("nosquint-prefs-bundle");
-        var new_pattern = prompt(bundle.getString('editPrompt'),  pattern, bundle.getString('editTitle'));
-        if (new_pattern != null && new_pattern != pattern) {
-            item.childNodes[0].setAttribute('label', new_pattern);
+        var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                      .getService(Components.interfaces.nsIPromptService);
+        var input = {value: pattern};
+        prompts.prompt(window, bundle.getString('editTitle'), bundle.getString('editPrompt'),
+                        input, null, {});
+        if (input.value != null && input.value != pattern) {
+            item.childNodes[0].setAttribute('label', input.value);
             listbox._changed = true;
         }
     },
@@ -170,39 +202,69 @@ var NoSquintPrefs = {
     close: function() {
         var doc = NoSquintPrefs.doc;
 
+        if (doc.getElementById("pattern").value != '')
+            /* User entered stuff in exception input but OK'd dialog without
+             * adding the exception.  We assume here the user actually _wanted_
+             * the exception to be added, so add it automatically.  This is
+             * a bit of do-what-I-mean behaviour.
+             */
+            NoSquintPrefs.buttonAddException();
+            
         var full_zoom_primary = doc.getElementById("primaryZoomMethod-menu").value == "full";
         var force_zoom = NoSquintPrefs.prefs.getBoolPref("fullZoomPrimary") != full_zoom_primary;
         NoSquintPrefs.prefs.setBoolPref("fullZoomPrimary", full_zoom_primary);
 
+        NoSquintPrefs.prefs.setBoolPref("zoomImages", doc.getElementById("zoomImages").checked);
         NoSquintPrefs.prefs.setBoolPref("hideStatus", !doc.getElementById("showStatus").checked);
         NoSquintPrefs.prefs.setBoolPref("wheelZoomEnabled", doc.getElementById("wheelZoomEnabled").checked);
-        NoSquintPrefs.prefs.setIntPref("zoomlevel", doc.getElementById("defaultZoomLevel").value);
+        NoSquintPrefs.prefs.setIntPref("fullZoomLevel", doc.getElementById("fullZoomLevel").value);
+        NoSquintPrefs.prefs.setIntPref("textZoomLevel", doc.getElementById("textZoomLevel").value);
         NoSquintPrefs.prefs.setIntPref("zoomIncrement", doc.getElementById("zoomIncrement").value);
         var val = doc.getElementById("rememberSites").selectedIndex == 1 ? false : true;
         NoSquintPrefs.prefs.setBoolPref("rememberSites", val);
+        NoSquintPrefs.privacyBranch.setBoolPref("extensions-nosquint", 
+                                                doc.getElementById("siteSanitize").checked)
 
 
-        var listbox = NoSquintPrefs.doc.getElementById("exceptionsList");
+        var listbox = doc.getElementById("exceptionsList");
         if (listbox._changed) {
             var exceptions = [];
             for (var i = 0; i < listbox.getRowCount(); i++) {
                 var item = listbox.getItemAtIndex(i);
                 var pattern = item.childNodes[0].getAttribute('label');
-                exceptions.push(pattern);
+                exceptions.push(pattern.replace(/ /g, '%20'));
             }
             NoSquintPrefs.prefs.setCharPref("exceptions", exceptions.join(' '));
         }
-        if (!NoSquintPrefs.doc.getElementById("siteForget").checked)
+        if (!doc.getElementById("siteForget").checked)
             NoSquintPrefs.prefs.setIntPref("forgetMonths", 0);
         else
-            NoSquintPrefs.prefs.setIntPref("forgetMonths", NoSquintPrefs.doc.getElementById("siteForget-menu").value);
+            NoSquintPrefs.prefs.setIntPref("forgetMonths", doc.getElementById("siteForget-menu").value);
+
+        // Colors
+        for each (var id in ['colorText', 'colorBackground', 'linksUnvisited', 'linksVisited']) {
+            var cb = doc.getElementById(id);
+            var picker = cb.parentNode.childNodes[1];
+            NoSquintPrefs.prefs.setCharPref(id, cb.checked ? picker.color : '0');
+        }
+        NoSquintPrefs.prefs.setBoolPref("colorBackgroundImages", 
+                                        doc.getElementById("colorBackgroundImages").checked);
+        NoSquintPrefs.prefs.setBoolPref("linksUnderline", 
+                                        doc.getElementById("linksUnderline").checked);
 
-        if (!NoSquintPrefs.NoSquint)
+        var NoSquint = NoSquintPrefs.NoSquint;
+        if (!NoSquint)
             return;
 
-        NoSquintPrefs.NoSquint.globalDialog = null;
+        NoSquint.globalDialog = null;
         if (force_zoom)
-            NoSquintPrefs.NoSquint.queueZoomAll();
+            NoSquint.queueZoomAll();
+        NoSquint.queueStyleAll();
+        NoSquint.updateStatus();
+
+        if (NoSquint.siteDialog)
+            NoSquint.siteDialog.setValues(NoSquint.siteDialog.browser, NoSquint.siteDialog.browser._noSquintSite);
+
     },
 
     cancel: function() {
diff --git a/src/content/globalprefs.xul b/src/content/globalprefs.xul
index 7cc8837..7f49a06 100644
--- a/src/content/globalprefs.xul
+++ b/src/content/globalprefs.xul
@@ -14,7 +14,7 @@
         persist="screenX screenY"
         onload="NoSquintPrefs.init(document, this)">
 
-    <script src="globalprefs.js" />
+    <script type="application/x-javascript" src="chrome://nosquint/content/globalprefs.js" />
 
     <stringbundleset id="stringbundleset">
         <stringbundle id="nosquint-prefs-bundle" src="chrome://nosquint/locale/globalprefs.properties" />
@@ -23,6 +23,8 @@
     <tabbox flex="1">
         <tabs>
             <tab label="&ns.pref.tab.options.label;" />
+            <tab label="&ns.pref.tab.zooming.label;" />
+            <tab label="&ns.pref.tab.colors.label;" />
             <tab label="&ns.pref.tab.exceptions.label;" />
         </tabs>
 
@@ -30,8 +32,36 @@
             <!-- Options Tab -->
             <tabpanel id="optionstab" flex="1">
                 <vbox flex="1">
+                    <groupbox id="persistence">
+                        <caption label="&ns.pref.persistence.caption;" />
+                        <radiogroup id="rememberSites" onselect="NoSquintPrefs.sitesRadioSelect()">
+                            <radio label="&ns.pref.persistence.remember.label;" />
+                            <vbox class="indent" id='siteForget-box'>
+                                <hbox align="center">
+                                    <checkbox id="siteForget" label="&ns.pref.persistence.forget.label;" 
+                                              checked="false" />
+                                    <menulist id="siteForget-menu">
+                                        <menupopup>
+                                            <menuitem value="12" label="&ns.pref.persistence.forget.year;"/>
+                                            <menuitem value="6" label="&ns.pref.persistence.forget.6months;" selected="true" />
+                                            <menuitem value="3" label="&ns.pref.persistence.forget.3months;" />
+                                            <menuitem value="1" label="&ns.pref.persistence.forget.month;"/>
+                                        </menupopup>
+                                    </menulist>
+                                </hbox>
+                                <checkbox id="siteSanitize" label="&ns.pref.persistence.sanitize.label;" checked="false" />
+                            </vbox>
+                            <radio label="&ns.pref.persistence.noRemember.label;" />
+                        </radiogroup>
+                    </groupbox>
+                </vbox>
+            </tabpanel>
+
+            <!-- Zooming Tab -->
+            <tabpanel id="zoomingtab" flex="1">
+                <vbox flex="1">
                     <groupbox>
-                        <caption label="&ns.pref.general.caption;" />
+                        <caption label="&ns.pref.zooming.caption;" />
                         <grid>
                             <columns>
                                 <column />
@@ -41,21 +71,36 @@
                                 <row align="center">
                                     <hbox>
                                         <spacer flex="1" />
-                                        <label>&ns.pref.general.primaryMethod.label;:</label>
+                                        <label>&ns.pref.zooming.primaryMethod.label;:</label>
                                     </hbox>
                                     <menulist id="primaryZoomMethod-menu">
                                         <menupopup>
-                                            <menuitem value="full" label="&ns.pref.general.primaryMethod.full;" 
+                                            <menuitem value="full" label="&ns.pref.zooming.primaryMethod.full;" 
                                                       selected="true"/>
-                                            <menuitem value="text" label="&ns.pref.general.primaryMethod.text;"/>
+                                            <menuitem value="text" label="&ns.pref.zooming.primaryMethod.text;"/>
                                         </menupopup>
                                     </menulist>
                                 </row>
 
                                 <row align="center">
-                                    <label>&ns.pref.general.level.label;:</label>
+                                    <hbox>
+                                        <spacer flex="1" />
+                                        <label>&ns.pref.zooming.fullLevel.label;:</label>
+                                    </hbox>
+                                    <hbox align="center">
+                                        <textbox id="fullZoomLevel" size="2" type="number" min="40" 
+                                                 max="300" increment="5" />
+                                        <label class="percent">%</label>
+                                    </hbox>
+                                </row>
+
+                                <row align="center">
+                                    <hbox>
+                                        <spacer flex="1" />
+                                        <label>&ns.pref.zooming.textLevel.label;:</label>
+                                    </hbox>
                                     <hbox align="center">
-                                        <textbox id="defaultZoomLevel" size="2" type="number" min="40" 
+                                        <textbox id="textZoomLevel" size="2" type="number" min="40" 
                                                  max="300" increment="5" />
                                         <label class="percent">%</label>
                                     </hbox>
@@ -64,7 +109,7 @@
                                 <row align="center">
                                     <hbox>
                                         <spacer flex="1" />
-                                        <label>&ns.pref.general.increment.label;:</label>
+                                        <label>&ns.pref.zooming.increment.label;:</label>
                                     </hbox>
                                     <hbox align="center">
                                         <textbox id="zoomIncrement" size="2" type="number" min="1" 
@@ -75,35 +120,58 @@
                             </rows>
                         </grid>
                         <vbox>
-                            <checkbox id="wheelZoomEnabled" label="&ns.pref.general.mousewheel.label;" checked="false" />
-                            <checkbox id="showStatus" label="&ns.pref.general.showstatus.label;" checked="false" />
+                            <checkbox id="zoomImages" label="&ns.pref.zooming.images.label;" checked="false" />
+                            <checkbox id="wheelZoomEnabled" label="&ns.pref.zooming.mousewheel.label;" checked="false" />
+                            <checkbox id="showStatus" label="&ns.pref.zooming.showstatus.label;" checked="false" />
                         </vbox>
                     </groupbox>
-
-                    <groupbox id="site">
-                        <caption label="&ns.pref.site.caption;" />
-                        <radiogroup id="rememberSites" onselect="NoSquintPrefs.sitesRadioSelect()">
-                            <radio label="&ns.pref.site.remember.label;" />
-                            <hbox id="siteForget-box" align="center" class="indent">
-                                <checkbox id="siteForget" label="&ns.pref.site.forget.label;" checked="false" 
-                                          oncheck="document.getElementById('siteForget-menu').disabled = !this.checked" />
-                                <menulist id="siteForget-menu">
-                                    <menupopup>
-                                        <menuitem value="12" label="&ns.pref.site.forget.year;"/>
-                                        <menuitem value="6" label="&ns.pref.site.forget.6months;" selected="true" />
-                                        <menuitem value="3" label="&ns.pref.site.forget.3months;" />
-                                        <menuitem value="1" label="&ns.pref.site.forget.month;"/>
-                                    </menupopup>
-                                </menulist>
-                            </hbox>
-                            <radio label="&ns.pref.site.noRemember.label;" />
-                        </radiogroup>
-                    </groupbox>
                 </vbox>
             </tabpanel>
 
+            <!-- Colors Tab -->
+            <tabpanel id="colorstab" flex="1">
+                <vbox flex="1">
+                    <html:p style="margin: 0 7px 0.5em 7px; padding: 0;">&ns.pref.colors.info;</html:p>
+                    <hbox>
+                        <groupbox flex='1'>
+                            <caption label="&ns.pref.colors.colors.caption;" />
+                            <vbox>
+                                <hbox>
+                                    <checkbox id="colorText" label="&ns.pref.colors.colors.text.label;"
+                                              checked="false" flex="1" />
+                                    <colorpicker type='button' />
+                                </hbox>
+                                <hbox>
+                                    <checkbox id="colorBackground" label="&ns.pref.colors.colors.background.label;"
+                                              checked="false" flex="1" />
+                                    <colorpicker type='button' />
+                                </hbox>
+                                <checkbox id="colorBackgroundImages" label="&ns.pref.colors.colors.images.label;"
+                                          checked="false" flex="1" />
+                            </vbox>
+                        </groupbox>
+                        <groupbox flex='1'>
+                            <caption label="&ns.pref.colors.links.caption;" />
+                            <vbox>
+                                <hbox>
+                                    <checkbox id="linksUnvisited" label="&ns.pref.colors.links.unvisited.label;" 
+                                              checked="false" flex="1" />
+                                    <colorpicker type='button' />
+                                </hbox>
+                                <hbox>
+                                    <checkbox id="linksVisited" label="&ns.pref.colors.links.visited.label;"
+                                              checked="false" flex="1" />
+                                    <colorpicker type='button' />
+                                </hbox>
+                                <checkbox id="linksUnderline" label="&ns.pref.colors.links.underline.label;"
+                                          checked="false" flex="1" />
+                            </vbox>
+                        </groupbox>
+                    </hbox>
+                </vbox>
+            </tabpanel>
 
-            <!-- exceptions Tab -->
+            <!-- Exceptions Tab -->
             <tabpanel id="exceptionstab" flex="1">
                 <vbox flex="1">
                     <html:p style="margin: 0 7px 0.5em 7px; padding: 0;">&ns.pref.exceptions.info;</html:p>
diff --git a/src/content/help.xul b/src/content/help.xul
index d0dcf28..2f85296 100644
--- a/src/content/help.xul
+++ b/src/content/help.xul
@@ -9,11 +9,11 @@
         ondialogaccept="return"
         id="nosquint-help-dialog"
         persist="width height"
-        width="600"
+        width="800"
         height="700"
         onload="NoSquintHelp.init(document)"> 
 
-    <script src="help.js" />
+    <script type="application/x-javascript" src="chrome://nosquint/content/help.js" />
 
     <dialogheader title="&ns.help.subtitle;" description="&ns.help.title;" />
     <groupbox flex="1">
diff --git a/src/content/init.js b/src/content/init.js
index c571967..b339e8d 100644
--- a/src/content/init.js
+++ b/src/content/init.js
@@ -1,44 +1,61 @@
-// Global object for NoSquint.  'NoSquint' is the only name added to the global
-// namespace by this addon.
-NoSquint = {
-    id: 'NoSquint',
-    namespaces: [],
-    _initialized: false,
-    dialogs: {},            // dialogs namespace
-
-    ns: function(fn) {
-        var scope = {
-            extend: function(o) {
-                for (var key in o)
-                    this[key] = o[key];
-                }
-        };
-        scope = fn.apply(scope) || scope;
-        NoSquint.namespaces.push(scope);
-        return scope;
-    },
-
-    init: function() {
-        if (NoSquint._initialized)
-            return;
-        NoSquint._initialized = true;
-
-        for (let i = 0; i < NoSquint.namespaces.length; i++) {
-            var scope = NoSquint.namespaces[i];
-            if (scope.init !== undefined)
-                scope.init();
-        }
-    },
-
-    destroy: function() {
-        // Invoke destroy functions in all registered namespaces
-        for (let i = 0; i < NoSquint.namespaces.length; i++) {
-            var scope = NoSquint.namespaces[i];
-            if (scope.destroy !== undefined)
-                scope.destroy();
-        }
+window.addEventListener("load", NoSquint.init, false); 
+window.addEventListener("unload", NoSquint.destroy, false); 
+
+/* NoSquint hooks the ZoomManager, overriding its default functionality.
+ *
+ * The logic below should be well-behaved when the user uses exclusively
+ * full page or text-only zooms.
+ *
+ * If both zooms are in use (i.e. full and text != 100%), things can get
+ * a little dubious.  In general, if full zoom is not 100%, then we pretend
+ * as if full zoom is the primary method, regardless of whether it actually
+ * is.  The rationale is that full page zoom is more likely to affect logic
+ * used by people interfacing with ZoomManager.
+ *
+ * More details in ZoomManager.useFullZoom getter.
+ */
+
+// ZoomManager._nosquintOrigZoomGetter = ZoomManager.__lookupGetter__('zoom');
+// ZoomManager._nosquintOrigZoomSetter = ZoomManager.__lookupSetter__('zoom');
+
+ZoomManager.__defineSetter__('zoom', function(value) {
+    var viewer = getBrowser().mCurrentBrowser.markupDocumentViewer;
+    var updated = false;
+
+    if (ZoomManager.useFullZoom && viewer.fullZoom != value)
+        updated = viewer.fullZoom = value;
+    else if (!ZoomManager.useFullZoom && viewer.textZoom != value)
+        updated = viewer.textZoom = value;
+
+    if (updated != false) {
+        NoSquint.saveCurrentZoom();
+        NoSquint.updateStatus();
     }
-};
+});
 
-window.addEventListener("load", NoSquint.init, false); 
-window.addEventListener("unload", NoSquint.destroy, false);
+ZoomManager.__defineGetter__('zoom', function() {
+    var viewer = getBrowser().mCurrentBrowser.markupDocumentViewer;
+    return ZoomManager.useFullZoom ? viewer.fullZoom : viewer.textZoom;
+});
+
+ZoomManager.__defineGetter__('useFullZoom', function() {
+    /* Extensions (like all-in-one gestures) assume that zoom is either all
+     * full page or all text-only, which is of course quite reasonable given
+     * the ZoomManager interface assumes this too.
+     *
+     * So, regardless of what the primary zoom method is set to, if the
+     * current page has a full zoom level != 100%, then we always return
+     * true here.
+     * 
+     * This is to handle the uncommon case where the user has modified
+     * both text and full page zoom.  Extensions like AIO need to base
+     * decisions on whether or not the page is full-zoomed, not whether
+     * or not the user prefers full or text zoom.
+     */
+    var viewer = getBrowser().mCurrentBrowser.markupDocumentViewer;
+    return viewer.fullZoom != 1.0 ? true : NoSquint.fullZoomPrimary;
+});
+
+ZoomManager.enlarge = NoSquint.cmdEnlargePrimary;
+ZoomManager.reduce = NoSquint.cmdReducePrimary;
+ZoomManager.reset = NoSquint.cmdReset;
diff --git a/src/content/nosquint.js b/src/content/nosquint.js
index d7eea21..3d506a5 100644
--- a/src/content/nosquint.js
+++ b/src/content/nosquint.js
@@ -1,61 +1,47 @@
 // chrome://browser/content/browser.xul
-// open dialogs will raise if already open
-
-/* Returns a list of lines from a URL (such as chrome://).  This function
- * is a WTF; how more obsure could it possibly be to read a damn file?
- */
-function readLines(aURL) {
-  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
-                  .getService(Components.interfaces.nsIIOService);
-  var scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
-                         .getService(Components.interfaces.nsIScriptableInputStream);
-
-  var channel = ioService.newChannel(aURL, null, null);
-  var input = channel.open();
-  scriptableStream.init(input);
-  var str = scriptableStream.read(input.available());
-  scriptableStream.close();
-  input.close();
-  return str.split("\n");
-} 
-
-/* Generate eddies in the space-time continuum. */
-function debug(msg) {
-    dump("[nosquint] " + msg + "\n");
-}
-
 var NoSquint = {
 
     TLDs: null,                     // Hash of multi-level TLDs; shared between windows
     prefs: null,                    // Prefs service rooted at extensions.nosquint.
-    mousePrefs: null,               // Prefs service rooted at mousewheel.withcontrolkey
+    browserZoomPrefs: null,         // Prefs service rooted at browser.zoom
     initialized: false,             // True if init() was called
-    prefsRecursion: 0,              // Recursion level in observe()
     saveTimer: null,                // Timer for saveSiteList()
     zoomAllTimer: null,             // Timer for zoomAll()
+    styleAllTimer:null,             // Timer for styleAll()
     pruneTimer: null,               // Timer for pruneSites()
     sitesDirty: false,              // True when sites list needs saving
     ignoreNextSitesChange: false,   // ignores next update to sites pref
-    zoomManagerTimeout: null,       // timeout for ZoomManager.zoom setter
     globalDialog: null,             // NoSquintPrefs object if global prefs dialog open
     siteDialog: null,               // NoSquintSitePrefs object if site prefs dialog open
+    observer: null,                 // Instance attached to observer interface
+    origSiteSpecific: null,         // Original value of browser.zoom.siteSpecific
 
     /* Prefs */
-    // Sites hash is keyed on site name, with value being [level, timestamp, visits]
+    // Sites hash is keyed on site name, with value being:
+    //  [textlevel, timestamp, visits, fulllevel, textcolor, bgcolor, nobgimages, 
+    //   linkunvis, linkvis, linkunderline]
     sites: {},                      // extensions.nosquint.sites
     exceptions: [],                 // extensions.nosquint.exceptions
-    defaultZoomLevel: 120,          // extensions.nosquint.zoomlevel
+    defaultFullZoomLevel: 120,      // extensions.nosquint.fullZoomLevel
+    defaultTextZoomLevel: 100,      // extensions.nosquint.textZoomLevel
     saveDelay: 5000,                // extensions.nosquint.sitesSaveDelay
     zoomIncrement: 10,              // extensions.nosquint.zoomIncrement
     rememberSites: true,            // extensions.nosquint.rememberSites
-    wheelZoomEnabled: false,        // extensions.nosquint.wheelZoomEnabled
+    zoomImages: true,               // extensions.nosquint.zoomImages
+    wheelZoomEnabled: true,         // extensions.nosquint.wheelZoomEnabled
+    wheelZoomInvert: false,         // extensions.nosquint.wheelZoomInvert
     hideStatus: false,              // extensions.nosquint.hideStatus
     forgetMonths: 6,                // extensions.nosquint.forgetMonths
     fullZoomPrimary: false,         // extensions.nosquint.fullZoomPrimary
+    colorText: '0',                 // extensions.nosquint.colorText
+    colorBackground: '0',           // extensions.nosquint.colorBackground
+    colorBackgroundImages: false,   // extensions.nosquint.colorBackgroundImages
+    linksUnvisited: '0',            // extensions.nosquint.linksUnvisited
+    linksVisited: '0',              // extensions.nosquint.linksVisited
+    linksUnderline: false,          // extensions.nosquint.linksUnderline
 
 
     init: function() {
-        debug("start init");
         NoSquint.updateZoomMenu();
         if (NoSquint.initialized)
             return;
@@ -68,62 +54,90 @@ var NoSquint = {
          * this prevents us from parsing the ~2000 line file each time a window
          * is opened.  If not, read it from the two-level-tlds file.
          */
-        var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                           .getService(Components.interfaces.nsIWindowMediator);
-        var windows = wm.getEnumerator("navigator:browser");
-        var win;
-        while (win = windows.getNext()) {
-            if (win._noSquintTLDs) {
-                // Found, grab a reference to the object.
-                NoSquint.TLDs = window._noSquintTLDs = win._noSquintTLDs;
-                break;
-            }
-        }
+        NoSquint.TLDs = window_get_global('tlds');
         if (NoSquint.TLDs == null) {
             // TLDs list not found in any existing window.  Load the stored list,
             // which is borrowed from http://www.surbl.org/two-level-tlds
             lines = readLines('chrome://nosquint/content/two-level-tlds');
-            window._noSquintTLDs = NoSquint.TLDs = {};
+            //window._noSquintTLDs = NoSquint.TLDs = {};
+            NoSquint.TLDs = {};
             for (var i in lines)
                 NoSquint.TLDs[lines[i]] = true;
+            window_set_global('tlds', NoSquint.TLDs);
+            
         }
+        window._noSquint = NoSquint;
+
+        NoSquint.observer = new NoSquintObserver();
+        NoSquint.observer.watcher = {
+            onEnterPrivateBrowsing: function() {
+                // Switching the private browsing mode.  Store any current pending
+                // changes now.
+                if (NoSquint.sitesDirty)
+                    NoSquint._realSaveSiteList(true);
+                // Save current (non-private) site data for when we exit private
+                // browsing.
+                NoSquint._sites_save = NoSquint.cloneSites();
+            },
+
+            onExitPrivateBrowsing: function() {
+                // Restore previously saved site data and rezoom/style all tabs.
+                NoSquint.sites = NoSquint._sites_save;
+                NoSquint._sites_save = null;
+                NoSquint.zoomAll();
+                NoSquint.styleAll();
+            }
+        };
 
-        NoSquint.initPrefs();
+        // Init prefs, parsing site list.
+        NoSquint.initPrefs(true);
+
+        if (NoSquint.observer.inPrivateBrowsing)
+            NoSquint.observer.watcher.onEnterPrivateBrowsing();
 
         window.addEventListener("DOMMouseScroll", NoSquint.handleScrollWheel, false); 
+        window.addEventListener("resize", NoSquint.handleResize, false);
         gBrowser.tabContainer.addEventListener("TabOpen", NoSquint.handleNewTab, false);
         gBrowser.tabContainer.addEventListener("TabClose", NoSquint.handleCloseTab, false);
         gBrowser.tabContainer.addEventListener("TabSelect", NoSquint.handleTabChanged, false);
 
         // Zoom any tabs anther extension may have opened and attach listeners to them.
         NoSquint.zoomAll(true);
-
+        // Style all open tabs.
+        NoSquint.styleAll();
         var t1 = new Date().getTime();
         debug("initialization took " + (t1-t0) + " ms");
+        //NoSquint.openGlobalPrefsDialog();
     },
 
     destroy: function() {
         NoSquint.prefs.removeObserver("", NoSquint);
-        NoSquint.mousePrefs.removeObserver("", NoSquint);
+        NoSquint.browserZoomPrefs.removeObserver("", NoSquint);
 
-        if (NoSquint.sitesDirty) {
+        if (NoSquint.sitesDirty)
             NoSquint._realSaveSiteList();
-        }
 
         /* Even though we've removed the pref observers, they lamely still get
-         * invoked during setIntPref below; setting prefs to null here prevents
+         * invoked during setBoolPref below; setting prefs to null here prevents
          * full entry into observe().  We're done with it now anyway.
          */
         NoSquint.prefs = null;
-        // Restore mousewheel.withcontrolkey.action to default if wheel zoom enabled.
-        if (NoSquint.mousePrefs && NoSquint.wheelZoomEnabled && NoSquint.mousePrefs.getIntPref("action") == 0)
-            NoSquint.mousePrefs.setIntPref("action", 5);
+        // Reenable browser.zoom.siteSpecific
+        NoSquint.browserZoomPrefs.setBoolPref("siteSpecific", NoSquint.origSiteSpecific);
 
         gBrowser.tabContainer.removeEventListener("TabOpen", NoSquint.handleNewTab, false);
         gBrowser.tabContainer.removeEventListener("TabClose", NoSquint.handleCloseTab, false);
         gBrowser.tabContainer.removeEventListener("TabSelect", NoSquint.handleTabChanged, false);
+        window.removeEventListener("DOMMouseScroll", NoSquint.handleScrollWheel, false); 
+        window.removeEventListener("resize", NoSquint.handleResize, false);
     },
 
+    cloneSites: function() {
+        var sites = {};
+        for (var site in NoSquint.sites)
+            sites[site] = NoSquint.sites[site].slice();
+        return sites;
+    },
 
     /* Updates View | Zoom menu to replace the default Zoom In/Out menu
      * items with Primary Zoom In/Out and Secondary Zoom In/Out.  Also the
@@ -195,11 +209,20 @@ var NoSquint = {
 
     /* Handlers for toolar buttons */
     buttonEnlarge: function(event) {
+        var browser = getBrowser().mCurrentBrowser;
+        if (is_image(browser))
+            return browser._noSquintFit ? true : NoSquint.enlargeFullZoom();
         event.shiftKey ? NoSquint.cmdEnlargeSecondary() : NoSquint.cmdEnlargePrimary();
     },
     buttonReduce: function(event) {
+        var browser = getBrowser().mCurrentBrowser;
+        if (is_image(browser))
+            return browser._noSquintFit ? true : NoSquint.reduceFullZoom();
         event.shiftKey ? NoSquint.cmdReduceSecondary() : NoSquint.cmdReducePrimary();
     },
+    buttonReset: function(event) {
+        NoSquint.cmdReset();
+    },
 
     /* Handlers for commands defined in overlay.xul */
     cmdEnlargePrimary: function() {
@@ -232,23 +255,26 @@ var NoSquint = {
     },
 
     enlargeTextZoom: function() {
-        // FIXME: do we want to update any other tabs of pages in this site?
-        getBrowser().mCurrentBrowser.markupDocumentViewer.textZoom += (NoSquint.zoomIncrement / 100.0);
+        var mdv = getBrowser().mCurrentBrowser.markupDocumentViewer;
+        mdv.textZoom = Math.round(mdv.textZoom * 100.0 + NoSquint.zoomIncrement) / 100.0;
         NoSquint.saveCurrentZoom();
         NoSquint.updateStatus();
     },
     reduceTextZoom: function() {
-        getBrowser().mCurrentBrowser.markupDocumentViewer.textZoom -= (NoSquint.zoomIncrement / 100.0);
+        var mdv = getBrowser().mCurrentBrowser.markupDocumentViewer;
+        mdv.textZoom = Math.round(mdv.textZoom * 100.0 - NoSquint.zoomIncrement) / 100.0;
         NoSquint.saveCurrentZoom();
         NoSquint.updateStatus();
     },
     enlargeFullZoom: function() {
-        getBrowser().mCurrentBrowser.markupDocumentViewer.fullZoom += (NoSquint.zoomIncrement / 100.0);
+        var mdv = getBrowser().mCurrentBrowser.markupDocumentViewer;
+        mdv.fullZoom = Math.round(mdv.fullZoom * 100.0 + NoSquint.zoomIncrement) / 100.0;
         NoSquint.saveCurrentZoom();
         NoSquint.updateStatus();
     },
     reduceFullZoom: function() {
-        getBrowser().mCurrentBrowser.markupDocumentViewer.fullZoom -= (NoSquint.zoomIncrement / 100.0);
+        var mdv = getBrowser().mCurrentBrowser.markupDocumentViewer;
+        mdv.fullZoom = Math.round(mdv.fullZoom * 100.0 - NoSquint.zoomIncrement) / 100.0;
         NoSquint.saveCurrentZoom();
         NoSquint.updateStatus();
     },
@@ -272,11 +298,14 @@ var NoSquint = {
         }
     },
 
-    /* Left or right click on the status panel. */
+    /* Handle left/middle/right click on the status panel. */
     statusPanelClick: function(event) {
         if (event.button == 0)
             // Left click, open site prefs.
             return NoSquint.openSitePrefsDialog();
+        else if (event.button == 1)
+            // Middle click, open global prefs.
+            return NoSquint.openGlobalPrefsDialog();
 
         /* Right click.  Setup the context menu according to the current
          * browser tab: the site name is set, and the appropriate radio 
@@ -316,7 +345,7 @@ var NoSquint = {
      */
     openSitePrefsDialog: function() {
         var browser = gBrowser.selectedBrowser;
-        var site = NoSquint.getSiteFromURI(browser.currentURI);
+        var site = NoSquint.getSiteFromBrowser(browser);
         if (!site)
             return;
         if (NoSquint.siteDialog) {
@@ -334,40 +363,46 @@ var NoSquint = {
             return NoSquint.globalDialog.dialog.focus();
 
         var browser = gBrowser.selectedBrowser;
-        var site = NoSquint.getSiteFromURI(browser.currentURI);
-        var level = NoSquint.getLevelForSite(site)[0] || "default";
-        var url = browser.currentURI.asciiHost + browser.currentURI.path;
+        var host = browser.currentURI.asciiHost;
+        try {
+            if (browser.currentURI.port > 0)
+                host += ':' + browser.currentURI.port;
+        } catch (err) {};
         window.openDialog("chrome://nosquint/content/globalprefs.xul", "NoSquint Settings", "chrome", 
-                          site, level, url, NoSquint);
+                          NoSquint, host + browser.currentURI.path);
     },
 
     /* Apply increase/decrease for ctrl-mousewheel */
     handleScrollWheel: function(event) {
-        if (!event.ctrlKey || !NoSquint.wheelZoomEnabled)
+        if (!event.ctrlKey)
             return;
-        var browser = gBrowser.selectedBrowser;
-        var text = full = false;
-        var increment = NoSquint.zoomIncrement * (event.detail < 0 ? 1 : -1);
-
-        if (NoSquint.fullZoomPrimary && !event.shiftKey || !NoSquint.fullZoomPrimary && event.shiftKey)
-            full = Math.round((browser.markupDocumentViewer.fullZoom * 100) + increment);
-        else
-            text = Math.round((browser.markupDocumentViewer.textZoom * 100) + increment);
-
-        NoSquint.zoom(browser, text, full);
-        NoSquint.saveCurrentZoom();
+        if (NoSquint.wheelZoomEnabled) {
+            var browser = gBrowser.selectedBrowser;
+            var text = full = false;
+            var increment = NoSquint.zoomIncrement * (event.detail < 0 ? 1 : -1);
+            var img = is_image(browser);
+                
+            if (NoSquint.wheelZoomInvert)
+                increment *= -1;
+
+            if (NoSquint.fullZoomPrimary && !event.shiftKey || !NoSquint.fullZoomPrimary && event.shiftKey || img)
+                full = Math.round((browser.markupDocumentViewer.fullZoom * 100) + increment);
+            else
+                text = Math.round((browser.markupDocumentViewer.textZoom * 100) + increment);
 
+            if (!img || !browser._noSquintFit) {
+                NoSquint.zoom(browser, text, full);
+                if (browser._noSquintSite)
+                    NoSquint.saveCurrentZoom();
+            }
+        }
         event.stopPropagation();
         event.preventDefault();
     },
 
     handleTabChanged: function(event) {
-        if (gBrowser.selectedBrowser._noSquintified) {
-            // ZoomManager.zoom was set somewhere internally in FF.  Abort
-            // the pending zoom.
-            NoSquint.abortPendingZoomManager();
+        if (gBrowser.selectedBrowser._noSquintified)
             NoSquint.updateStatus();
-        }
     },
 
     handleNewTab: function(event) {
@@ -379,28 +414,6 @@ var NoSquint = {
         browser.removeProgressListener(browser._noSquintListener);
     },
 
-    /* In init.js, we hook the setter for ZoomManager.zoom to have it
-     * queue the requested zoom rather than apply it immediately.  This
-     * gives handleTabChanged() above and our custom ProgressListener an
-     * opportunity to abort the pending zoom, in order to fully bypass
-     * FF's new internal per-site zoom mechanism.
-     *
-     * If no ZoomManager zoom is pending, then the next zoom via the
-     * ZoomManager will be eaten.
-     */
-    abortPendingZoomManager: function() {
-        if (NoSquint.zoomManagerTimeout != null && NoSquint.zoomManagerTimeout != false) {
-            debug("aborting queued ZoomManager zoom");
-            clearTimeout(NoSquint.zoomManagerTimeout);
-            NoSquint.zoomManagerTimeout = null;
-            ZoomManager._nosquintPendingZoom = null;
-        } else {
-            debug("aborting next ZoomManager zoom");
-            NoSquint.zoomManagerTimeout = false;
-        }
-    },
-
-
     /* Given a FQDN, returns only the base domain, and honors two-level TLDs.
      * So for example, www.foo.bar.com returns bar.com, or www.foo.bar.co.uk
      * returns bar.co.uk.
@@ -432,7 +445,7 @@ var NoSquint = {
          * match later.  Hostname and path components are processed in
          * separate calls; re_star and re_dblstar define the regexp syntax
          * for * and ** wildcards for this pattern.  (This is because
-         * wildcards have different semantics for hos vs path.)
+         * wildcards have different semantics for host vs path.)
          *
          * Function returns a list of [length, pattern, sub] where length
          * is the number of literal (non-wildcard) characters, pattern is
@@ -470,7 +483,7 @@ var NoSquint = {
                 else
                     sub.push('$' + n++);
             }
-            return [ length, pattern.join(''), sub.join('') ];
+            return [length, pattern.join(''), sub.join('')];
         }
 
         var exceptions = [];
@@ -479,18 +492,32 @@ var NoSquint = {
             if (!exc)
                 continue;
             // Escape metacharacters except *
-            exc = exc.replace(/([^\w*\[\]])/g, '\\$1');
+            exc = exc.replace(/([^\w:*\[\]])/g, '\\$1');
             // Split into host and path parts, and regexpify separately.
-            var [_, exc_host, exc_path] = exc.match(/([^\/]+)(\\\/.*|$)/);
-            var [ len_host, re_host, sub_host] = regexpify(exc_host, '[^.:/]+', '.*');
-            var [ len_path, re_path, sub_path] = regexpify(exc_path, '[^/]+', '.*');
-            exceptions.push([len_host * 1000 + len_path, re_host, sub_host, re_path, sub_path]);
+            var [_, exc_host, exc_path] = exc.match(/([^\/]*)(\\\/.*|$)/);
+            var [len_host, re_host, sub_host] = regexpify(exc_host, '[^.:/]+', '.*');
+            var [len_path, re_path, sub_path] = regexpify(exc_path, '[^/]+', '.*');
+            if (exc_host.search(':') == -1)
+                re_host += '(:\\d+)';
+
+            debug("Parse exception: exc_host=" + exc_host + ", re_host=" + re_host + ", sub_host=" + sub_host + ", exc_path=" + exc_path + ", re_path=" + re_path + ", sub_path=" + sub_path);
+            exceptions.push([len_host * 1000 + len_path, exc_host, re_host, sub_host, re_path, sub_path]);
         }
         // Sort the exceptions such that the ones with the highest weights
         // (that is, the longest literal lengths) appear first.
         exceptions.sort(function(a, b) { return b[0] - a[0]; });
         return exceptions;
     },
+    
+
+    /* Given a browser, returns the site name.  Does not use the cached
+     * browser._noSquintSite value.
+     */
+    getSiteFromBrowser: function(browser) {
+        if (is_chrome(browser))
+            return null;
+        return NoSquint.getSiteFromURI(browser.currentURI);
+    },
 
     /* Given a URI, returns the site name, as computed based on user-defined
      * exceptions.  If no exception matches the URI, we fall back to the base
@@ -503,7 +530,20 @@ var NoSquint = {
 
         var uri_host = URI.asciiHost;
         var uri_path = URI.path;
+
+        try {
+            var uri_port = URI.port < 0 ? 0 : URI.port;
+        } catch (err) {
+            var uri_port = '0';
+        }
+
         var base = NoSquint.getBaseDomainFromHost(uri_host);
+        if (!base && !uri_host)
+            // file:// url, use base as /
+            base = '/';
+
+        uri_host += ':' + uri_port;
+
         var match = null;
         
         /* Iterate over each exception, trying to match it with the URI.
@@ -511,15 +551,17 @@ var NoSquint = {
          * sorted with highest weights first.
          */
         for (var i in NoSquint.exceptions) {
-            var [weight, re_host, sub_host, re_path, sub_path] = NoSquint.exceptions[i];
-            if (re_host != '([^.:/]+)')
-                var m1 = uri_host.match(new RegExp('(' + re_host + ')$'));
-            else
-                // Single star is base name
-                var m1 = [null, base];
+            var [weight, exc_host, re_host, sub_host, re_path, sub_path] = NoSquint.exceptions[i];
+            if (re_host.substr(0, 11) == '([^.:/]+)(:') // exc_host == *[:...]
+                // Single star is base name, so match just that, plus any port spec
+                // that's part of the exception.
+                re_host = '(' + base + ')' + re_host.substr(9);
 
+            var m1 = uri_host.match(new RegExp('(' + re_host + ')$'));
             var m2 = uri_path.match(new RegExp('^(' + re_path + ')'));
 
+            //debug("check site: host=" + uri_host + ", port=" + uri_port+ ", path=" + uri_path + ", base=" + base + " === exception info: re_host=" + re_host + ", sub_host=" + sub_host + ", re_path=" + re_path + ", sub_path=" + sub_path + " === results: m1=" + m1 + ", m2=" + m2);
+
             if (!m1 || !m2)
                 // No match
                 continue;
@@ -530,7 +572,7 @@ var NoSquint = {
             break;
         }
         var t1 = new Date().getTime();
-        debug("getSiteFromURI took " + (t1-t0) + " ms");
+        debug("getSiteFromURI took " + (t1-t0) + " ms: " + (match ? match : base));
 
         return match ? match : base;
     },
@@ -539,10 +581,22 @@ var NoSquint = {
     /* Attaches our custom ProgressListener to the given browser. */
     attach: function(browser) {
         var listener = new ProgressListener(browser);
+        debug('Attaching new progress listener');
         browser.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
         browser._noSquintListener = listener;
         // Zoom browser to the appropriate levels for the current URI.
         NoSquint.zoom(browser, null, null);
+
+        browser._noSquintStyles = [];
+        // Attach stylesheets to new frames as they load.
+        function handle_frame_load(event) {
+            var doc = event.target.contentWindow.document;
+            var head = doc.getElementsByTagName("head");
+            var style = browser._noSquintStyles[0].cloneNode(true);
+            browser._noSquintStyles.push(style);
+            head[0].appendChild(style);
+        }
+        browser.addEventListener("DOMFrameContentLoaded", handle_frame_load, true);
     },
     
     /* Updates the status panel and tooltip to reflect current site name
@@ -555,20 +609,98 @@ var NoSquint = {
         var browser = gBrowser.selectedBrowser;
         var text = Math.round(browser.markupDocumentViewer.textZoom * 100);
         var full = Math.round(browser.markupDocumentViewer.fullZoom * 100);
-        var [ text_default, full_default ] = NoSquint.getZoomDefaults();
+        var [text_default, full_default] = NoSquint.getZoomDefaults();
 
         var e = document.getElementById('nosquint-status')
-        if (NoSquint.fullZoomPrimary)
-            e.label = full + "%" + (text == text_default ? "" : (" / " + text + "%"));
+        if (browser._noSquintSite) {
+            if (NoSquint.fullZoomPrimary)
+                e.label = full + "%" + (text == 100 ? "" : (" / " + text + "%"));
+            else
+                e.label = text + "%" + (full == 100 ? "" : (" / " + full + "%"));
+            document.getElementById("nosquint-status-tooltip-site").value = browser._noSquintSite.replace(/%20/g, ' ');
+            document.getElementById("nosquint-status-tooltip-full").value = full + "%";
+            document.getElementById("nosquint-status-tooltip-text").value = text + "%";
+
+            var style = NoSquint.getStyleForBrowser(browser);
+            var label = document.getElementById('nosquint-status-tooltip-textcolor');
+            label.style.color = style.text == '0' ? 'inherit' : style.text;
+            label.style.backgroundColor = style.bg == '0' ? 'inherit' : style.bg;
+            label.value = style.text == '0' && style.bg == '0' ? 'Site Controlled' : 'Sample';
+
+            var vis = document.getElementById('nosquint-status-tooltip-vis-link');
+            var unvis = document.getElementById('nosquint-status-tooltip-unvis-link');
+            unvis.value = vis.value = "";
+            vis.style.color = vis.style.textDecoration = "inherit";
+            unvis.style.color = unvis.style.textDecoration = "inherit";
+
+            if (style.unvisited == '0' && style.visited == '0')
+                unvis.value = "Site Controlled";
+            else {
+                if (style.unvisited != '0') {
+                    unvis.value = "Unvisited";
+                    unvis.style.color = style.unvisited;
+                    unvis.style.textDecoration = style.underline ? 'underline' : 'inherit';
+                }
+                if (style.visited != '0') {
+                    vis.value = "Unvisited";
+                    vis.style.color = style.visited;
+                    vis.style.textDecoration = style.derline ? 'underline' : 'inherit';
+                }
+            }
+
+            document.getElementById("nosquint-status-tooltip").style.display = '';
+            e.style.fontStyle = e.style.color = 'inherit';
+        } else {
+            document.getElementById("nosquint-status-tooltip").style.display = 'none';
+            e.label = 'N/A';
+            /* LAME: The documentation for statusbarpanel says there is a
+             * disabled attribute.  The DOM Inspector says otherwise.  So we
+             * must simulate the disabled look.
+             */
+            e.style.color = '#777';
+            e.style.fontStyle = 'italic';
+        }
+    },
+
+    /* Gets the style parameters for the given site name.
+     */
+    getStyleForSite: function(site) {
+        if (site && NoSquint.sites[site]) {
+            var s = NoSquint.sites[site];
+            return { 
+                text: s[4],
+                bg: s[5],
+                bgimages: s[6],
+                unvisited: s[7],
+                visited: s[8],
+                underline: s[9]
+            };
+        }
+        return null;
+    },
+
+    getStyleForBrowser: function(browser) {
+        if (!browser._noSquintSite)
+            browser._noSquintSite = NoSquint.getSiteFromBrowser(browser);
+        if (NoSquint.rememberSites)
+            var style = NoSquint.getStyleForSite(browser._noSquintSite);
         else
-            e.label = text + "%" + (full == full_default ? "" : (" / " + full + "%"));
+            var style = {text: '0', bg: '0', bgimages: false, unvisited: '0', visited: '0', underline: false};
+        return NoSquint.applyStyleDefaults(style);
+    },
 
-        var site = browser._noSquintSite ? browser._noSquintSite : "(none)";
-        document.getElementById("nosquint-status-tooltip-site").value = site;
-        document.getElementById("nosquint-status-tooltip-full").value = full + "%";
-        document.getElementById("nosquint-status-tooltip-text").value = text + "%";
+    applyStyleDefaults: function(style) {
+        return {
+            text: (style && style.text != '0') ? style.text : NoSquint.colorText,
+            bg: (style && style.bg != '0') ? style.bg : NoSquint.colorBackground,
+            bgimages: (style && style.bgimages) ? style.bgimages : NoSquint.colorBackgroundImages,
+            unvisited: (style && style.unvisited != '0') ? style.unvisited : NoSquint.linksUnvisited,
+            visited: (style && style.visited != '0') ? style.visited : NoSquint.linksVisited,
+            underline: (style && style.underline) ? style.underline : NoSquint.linksUnderline
+        };
     },
 
+
     /* Gets the levels for the given site name.  (Note, this is the site name
      * as gotten from getSiteFromURI(), not the URI itself.)  Returns a
      * 2-tuple [text_size, full_size], or [null, null] if the site is not
@@ -580,29 +712,31 @@ var NoSquint = {
         return [null, null];
     },
 
+
     /* Returns a 2-tuple [text_default, full_default] representing the default
      * zoom levels.
      */
     getZoomDefaults: function() {
-        return [ NoSquint.fullZoomPrimary ? 100 : NoSquint.defaultZoomLevel,
-                 NoSquint.fullZoomPrimary ? NoSquint.defaultZoomLevel : 100 ];
+        return [NoSquint.defaultTextZoomLevel, NoSquint.defaultFullZoomLevel];
     },
 
-    /* Returns a 2-tuple [text_default, full_default] zoom levels for the 
-     * current browser.
+    /* Returns a 2-tuple [text, full] zoom levels for the given
+     * browser.
      */
     getLevelForBrowser: function(browser) {
         if (!browser._noSquintSite)
-            browser._noSquintSite = NoSquint.getSiteFromURI(browser.currentURI);
+            browser._noSquintSite = NoSquint.getSiteFromBrowser(browser);
 
-        var [ text_default, full_default ] = NoSquint.getZoomDefaults();
+        var [text_default, full_default] = NoSquint.getZoomDefaults();
 
         if (NoSquint.rememberSites) {
             var site = browser._noSquintSite;
-            var [ text, full ] = NoSquint.getLevelForSite(site);
-            return [ text || text_default, full || full_default ];
+            var [text, full] = NoSquint.getLevelForSite(site);
+            return [text || text_default, full || full_default];
         }
-        return [ text_default, full_default ];
+
+        // In global zoom mode, so return the global default levels.
+        return [text_default, full_default];
     },
 
 
@@ -615,11 +749,14 @@ var NoSquint = {
             return;
 
         if (text == null || full == null) {
-            var [ site_text, site_full ] = NoSquint.getLevelForBrowser(browser);
+            var [site_text, site_full] = NoSquint.getLevelForBrowser(browser);
             if (text == null)
                 text = text || site_text;
             if (full == null)
                 full = full || site_full;
+            // Only zoom web content, not chrome or plugins (e.g. PDF)
+            if (!browser._noSquintSite)
+                [text, full] = [100, 100];
         }
 
         debug("set zoom: text=" + text + ", full=" + full);
@@ -637,12 +774,14 @@ var NoSquint = {
      * for the current URIs of each browser.  If 'attach' is true, then
      * ProgressListeners are attached to each browser as well.  This is
      * useful on initialization, where we can hook into any tabs that may
-     * have been open prior to initialization.
+     * have been opened prior to initialization.
      */
-    zoomAll: function(attach) {
+    zoomAll: function(attach, site) {
         debug("zooming all tabs; attach listeners = " + attach);
         for (var i = 0; i < gBrowser.browsers.length; i++) {
             var browser = gBrowser.browsers[i];
+            if (site && site != browser._noSquintSite)
+                continue;
             if (browser._noSquintSite)
                 delete browser._noSquintSite;
             NoSquint.zoom(browser, null, null);
@@ -658,28 +797,153 @@ var NoSquint = {
      * multiple times, such as in the case of multiple preferences being
      * updated at once.
      */
-    queueZoomAll: function() {
-        if (!NoSquint.zoomAllTimer)
-            NoSquint.zoomAllTimer = setTimeout(function() { NoSquint.zoomAll(false); }, 1);
+    queueZoomAll: function(site, delay) {
+        if (!delay)
+            delay = 1;
+        if (NoSquint.zoomAllTimer)
+            clearTimeout(NoSquint.zoomAllTimer);
+        NoSquint.zoomAllTimer = setTimeout(function() { NoSquint.zoomAll(false, site); }, delay);
     },
 
+    queueStyleAll: function(site, delay) {
+        if (!delay)
+            delay = 1;
+        if (NoSquint.styleAllTimer)
+            clearTimeout(NoSquint.styleAllTimer);
+        NoSquint.styleAllTimer = setTimeout(function() { NoSquint.styleAll(site); }, delay);
+    },
+
+    styleAll: function(site) {
+        for (var i = 0; i < gBrowser.browsers.length; i++) {
+            var browser = gBrowser.browsers[i];
+            if (site && site != browser._noSquintSite || is_chrome(browser))
+                continue;
+            debug("STYLING: " + browser._noSquintSite);
+            NoSquint.style(browser);
+        }
+    },
+
+    handleResize: function(event) {
+        if (event.eventPhase != 2)
+            return;
+        for (var i = 0; i < gBrowser.browsers.length; i++) {
+            var browser = gBrowser.browsers[i];
+            if (browser._noSquintFit != undefined)
+                NoSquint.adjustImage(null, browser, -1);
+        }
+    },
+
+    adjustImage: function(event, browser, fit) {
+        if (event) {
+            event.stopPropagation();
+            event.preventDefault();
+        }
+        var doc = browser.docShell.document;
+        var img = doc.body.firstChild;
+        var styleobj = browser._noSquintStyles[0];
+        fit = fit == undefined ? !browser._noSquintFit : browser._noSquintFit;
+        // is any dimension of the image bigger than the window?
+        var is_bigger = img.naturalWidth >= doc.body.clientWidth || img.naturalHeight >= doc.body.clientHeight;
+        // is the aspect of the image larger than the window (i.e. is it wider)?
+        var is_wider = img.naturalWidth/img.naturalHeight > doc.body.clientWidth/doc.body.clientHeight;
+
+        var cursor = (!fit && !is_bigger) || (fit && is_bigger) ? "-moz-zoom-in" : "-moz-zoom-out";
+        var css = "* { cursor: " + cursor + " !important; }";
+        css += "img { cursor: " + cursor + " !important;";
+        css += "width: " + (fit && is_wider? "100%" : "auto") + " !important;";
+        css += "height: " + (fit && !is_wider ? "100%" : "auto") + " !important;}";
+        debug("Fitting: " + fit + ", css: " + css);
+        var title = doc.title.replace(/ *- Scaled \(\d+%\)$/, '');
+        if (fit) {
+            var ratio = is_wider ? doc.body.clientWidth / img.naturalWidth :
+                                   doc.body.clientHeight / img.naturalHeight;
+            debug("Scale: wider=" + is_wider + ", img=" + img.naturalWidth + "x" + img.naturalHeight+ ", window=" + doc.body.clientWidth + "x" + doc.body.clientHeight);
+            title += ' - Scaled (' + parseInt(ratio * 100) + '%)';
+            debug(title);
+        }
+        doc.title = title;
+        styleobj.textContent = css;
+        browser._noSquintFit = fit;
+    },
+
+
+    style: function(browser, style) {
+        var doc = browser.docShell.document;
+        var css = '';
+        if (!doc.documentElement)
+            // Nothing to style; chrome?
+            return;
+
+        if (browser._noSquintStyles.length == 0) {
+            // Create new style element for this document.
+            var styleobj = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
+            browser._noSquintStyles.push(styleobj);
+            doc.documentElement.appendChild(styleobj);
+        }
+
+        if (is_image(browser)) {
+            if (doc.body.firstChild) {
+                browser._noSquintFit = false;
+                NoSquint.adjustImage(null, browser, NoSquint.zoomImages ? undefined : -1);
+                doc.addEventListener("click", function(event) { 
+                    if (event.button == 0)
+                        return NoSquint.adjustImage(event, browser);
+                }, true);
+            }
+            return;
+        }
+
+        if (!style)
+            // No style specified, find for this browser.
+            style = NoSquint.getStyleForBrowser(browser);
+
+        if (style.text != '0' || style.bg != '0' || style.bgimages || 
+            style.unvisited != '0' || style.visited != '0' || style.underline) {
+            css = 'body,p,div,span,blockquote,h1,h2,h3,h4,h5,table,tr,th,td,iframe,a {';
+            if (style.text != '0')
+                css += 'color: ' + style.text + ' !important;';
+            if (style.bg != '0')
+                css += 'background-color: ' + style.bg + ' !important;';
+            if (style.bgimages)
+                css += 'background-image: none !important;';
+            css += '}\n';
+
+            if (style.unvisited != '0')
+                css += 'a:link { color: ' + style.unvisited + ' !important; }\n';
+            if (style.visited != '0')
+                css += 'a:visited { color: ' + style.visited + ' !important; }\n';
+            if (style.underline)
+                css += 'a { text-decoration: underline !important; }\n';
+        }
+        debug("Applying style [" + doc.documentElement + "]:" + css);
+
+        // Iterate over all style elements for this document (one element for each
+        // frame/iframe);
+        for each (var style in browser._noSquintStyles)
+            style.textContent = css;
+    },
+
+
     /* Callback from custom ProgressListener when the given browser's URI 
      * has changed.
      */
     locationChanged: function(browser, uri) {
-        var site = NoSquint.getSiteFromURI(uri);
+        var site = NoSquint.getSiteFromBrowser(browser);
+        debug("locationChanged: from " + browser._noSquintSite + " to " + site + ", uri=" + uri.spec);
         if (site != browser._noSquintSite)
             // Site accessed; update timestamp on new site.
-            NoSquint.updateSiteList(site, null, true);
+            NoSquint.updateSiteList(site, null, null, true);
+        else if (!NoSquint.rememberSites)
+            // We're in global mode and still on the same site, so do not
+            // rezoom, allowing us to maintain any temporary user levels.
+            return;
 
         browser._noSquintSite = site;
-        var [ text, full ] = NoSquint.getLevelForBrowser(browser);
-        NoSquint.zoom(browser, text, full);
+        NoSquint.zoom(browser);
         if (NoSquint.siteDialog && NoSquint.siteDialog.browser == browser)
             NoSquint.siteDialog.setValues(browser, site);
     },
 
-
     /* Called periodically (on startup, and once a day after that) in order to
      * remove remembered values for sites we haven't visited in forgetMonths.
      */
@@ -697,8 +961,7 @@ var NoSquint = {
             var prune = (age > NoSquint.forgetMonths*30*24*60*60*1000);
             if (prune)
                 remove.push(site);
-            debug("prune check: " + site + ", age=" + Math.round(age/1000/60/60/24) + 
-                 " days, prune=" + prune);
+            debug("prune check: " + site + ", age=" + Math.round(age/1000/60/60/24) + " days, prune=" + prune);
         }
         if (remove.length) {
             for (var i = 0; i < remove.length; i++)
@@ -720,8 +983,12 @@ var NoSquint = {
             return;
 
         var browser = gBrowser.selectedBrowser;
+        if (!browser._noSquintSite)
+            // Nothing to save.  Chrome maybe.
+            return;
         var text = Math.round(browser.markupDocumentViewer.textZoom * 100);
         var full = Math.round(browser.markupDocumentViewer.fullZoom * 100);
+        debug("saveCurrentZoom: " + browser._noSquintSite);
         NoSquint.updateSiteList(browser, [text, full]);
     },
 
@@ -732,48 +999,60 @@ var NoSquint = {
      *
      * Once updated, the site list is then queued for save in the prefs.
      */
-    updateSiteList: function(site_or_browser, levels, update_timestamp) {
+    updateSiteList: function(site_or_browser, levels, style, update_timestamp) {
+        if (!NoSquint.rememberSites)
+            return;
         var site = site_or_browser;
-        if (typeof(site_or_browser) != "string")
+        if (site_or_browser && typeof(site_or_browser) != "string")
             site = site_or_browser._noSquintSite;
         if (!site)
             return false;
 
         if (update_timestamp) {
-            if (!levels && !NoSquint.sites[site])
-                // No need to update the timestamp for a site we're not remembering.
-                return false;
-            NoSquint.sites[site][1] = new Date().getTime();
-            NoSquint.sites[site][2] += 1;
-            NoSquint.saveSiteList();
-        } 
-        if (levels) {
-            var [ text_default, full_default ] = NoSquint.getZoomDefaults();
-            var [ text, full ] = levels;
-            // Default zoom levels are stored as 0.
-            [ text, full ] = [ text == text_default ? 0 : text, full == full_default ? 0 : full ];
-
-            if (!text && !full) {
-                if (!NoSquint.sites[site])
-                    // No settings for this site, nothing to do.
-                    return;
-                // Setting site to default zoom level, remove it from list.
-                delete NoSquint.sites[site];
-            } else {
-                if (!NoSquint.sites[site])
-                    // New site record
-                    NoSquint.sites[site] = [text, new Date().getTime(), 1, full];
-                else {
-                    NoSquint.sites[site][0] = text;
-                    NoSquint.sites[site][3] = full;
-                }
-                // TODO: go through current tabs and resize tabs for this site
-            }
+            // When updating timestamp, levels == style == null.
+            if (NoSquint.sites[site]) {
+                NoSquint.sites[site][1] = new Date().getTime();
+                NoSquint.sites[site][2] += 1;
+                // XXX: do we bother saving site list here?  The overhead
+                // probably isn't worth it just for a timestamp update.
+            } 
+            return true;
+        }
 
-            // Queue site list save.
-            NoSquint.saveSiteList();
+        if (!NoSquint.sites[site])
+            // new site record
+            NoSquint.sites[site] = [0, new Date().getTime(), 1, 0, '0', '0', false, '0', '0', false];
+        var record = NoSquint.sites[site];
+
+        if (levels) {
+            // Update record with specified levels.
+            var [text_default, full_default] = NoSquint.getZoomDefaults();
+            var [text, full] = levels;
+            // Default zooms are stored as 0.
+            record[0] = text == text_default ? 0 : text;
+            record[3] = full == full_default ? 0 : full;
+            NoSquint.queueZoomAll(site, 1000);
         }
-        return true;
+        if (style) {
+            record[4] = style.text;
+            record[5] = style.bg;
+            record[6] = style.bgimages;
+            record[7] = style.unvisited;
+            record[8] = style.visited;
+            record[9] = style.underline;
+            NoSquint.queueStyleAll(site, 1000);
+        }
+
+        // Check newly updated record against defaults.  If all values are default, we
+        // remove the record.
+        if ([record[0]].concat(record.slice(3)).toString() == [0, 0, '0', '0', false, '0', '0', false].toString())
+            // All defaults.
+            delete NoSquint.sites[site];
+
+        debug("UPDATE SITE LIST: " + site + ": " + record);
+
+        // Queue site list save.
+        NoSquint.saveSiteList();
     },
 
     /* Queues a save of the site list in the prefs service.
@@ -796,46 +1075,44 @@ var NoSquint = {
     },
 
     /* Actually store the sites list. */
-    _realSaveSiteList: function() {
+    _realSaveSiteList: function(force) {
+        if (NoSquint.observer.inPrivateBrowsing && !force)
+            // Private Browsing mode is enabled; do not save site list.
+            return;
+
         /* XXX: this can take up to 20ms (!!!) even with a smallish sites list
-         * (about 50).  If it scales linearly, this could be a problem.  Need
-         * to do some more serious benchmarking here.  Looks like setCharPref
-         * can trigger pref observer handlers synchronously, so time elapsed
-         * includes the time the handlers take too.
+         * (about 50).  If it scales linearly or worse, this could be a
+         * problem.  Need to do some more serious benchmarking here.  Looks
+         * like setCharPref can trigger pref observer handlers synchronously,
+         * so time elapsed includes the time the handlers take too.
          */
         var t0 = new Date().getTime();
         var sites = [];
         for (var site in NoSquint.sites) {
             if (!NoSquint.sites[site])
-                continue
-            var [text, timestamp, counter, full] = NoSquint.sites[site];
-            sites.push(site + "=" + text + "," + timestamp + "," + counter + "," + full);
+                continue;
+            sites.push(site + "=" + NoSquint.sites[site].join(','));
         }
-        var siteList = sites.join(" ");
-        /* It's a precondition that the site list has changed, so when we set
-         * the pref it will fire a notification that we'll handle in 
-         * prefsChanged() which is not necessary here.  So set a flag that causes
-         * the next prefs notification for sites change to be ignored.
-         *
-         * TODO: this might not actually be relevant anymore, since the global
-         * prefs dialog no longer modifies the sites list.  Think on this, and
-         * if it's Think on this, and if that's the case, remove this obsolete
-         * comments and NoSquint.ignoreNextSitesChange.
-         * 
+
+        /* We're modifying the sites pref here.  Setting ignoreNextSitesChange=true
+         * causes the observer (in our current state) to not bother reparsing the
+         * sites pref because we know it's current.  In other words, we needn't
+         * respond to our own changes.
          */
         NoSquint.ignoreNextSitesChange = true;
-        NoSquint.prefs.setCharPref("sites", siteList);
+        NoSquint.prefs.setCharPref("sites", sites.join(" "));
         debug("sites save took: " + (new Date().getTime() - t0) + "ms");
         clearTimeout(NoSquint.saveTimer);
         NoSquint.saveTimer = null;
         NoSquint.sitesDirty = false;
+        debug("Full site list: " + sites);
     },
 
     /* Attach observers on extensions.nosquint and mousewheel.withcontrolkey
      * branches, and simulate a change to each of our prefs so that we can
      * load them.
      */
-    initPrefs: function() {
+    initPrefs: function(populate) {
         if (NoSquint.prefs)
             // Prefs already initialized.
             return;
@@ -843,7 +1120,7 @@ var NoSquint = {
         var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(
                           Components.interfaces.nsIPrefService);
         NoSquint.prefs = prefs.getBranch("extensions.nosquint.");
-        NoSquint.mousePrefs = prefs.getBranch("mousewheel.withcontrolkey.");
+        NoSquint.browserZoomPrefs = prefs.getBranch("browser.zoom.");
 
         // Backward compatibility: convert old prefs.
         try { 
@@ -860,19 +1137,61 @@ var NoSquint = {
             NoSquint.prefs.clearUserPref("rememberDomains");
         } catch (err) {} 
 
-        var prefs = [
-            "zoomIncrement", "wheelZoomEnabled", "zoomIncrement", "hideStatus", "zoomlevel", "action",
-            "sitesSaveDelay", "rememberSites", "exceptions", "sites", "forgetMonths", "fullZoomPrimary"
-        ];
-        for (var i in prefs)
-            // Simulate a change
-            NoSquint.observe(null, "nsPref:changed", prefs[i]);
+        if (NoSquint.prefs.getCharPref('prefsVersion') < '2.0') {
+            try {
+                // In 2.0, zoomlevel was split into fullZoomLevel and textZoomLevel
+                var zoomlevel = NoSquint.prefs.getIntPref("zoomlevel");
+                NoSquint.prefs.clearUserPref("zoomlevel");
+            } catch (err) {
+                // this was the default zoomlevel for < 2.0.
+                var zoomlevel = 120;
+            }
+
+            /* Previous versions of NoSquint set mousewheel.withcontrolkey.action=0
+             * under the assumption that we won't see DOMMouseScroll events otherwise.
+             * This was true with Firefox < 3, but apparently no longer the case with
+             * 3 and later.  So we restore the pref to its default value during this
+             * initial migration.  (The user might not want it restored, but this is
+             * the best we can do given what we know, and the correct thing to do
+             * in the common case.)
+             */
+            var mousePrefs = prefs.getBranch("mousewheel.withcontrolkey.");
+            mousePrefs.setIntPref("action", 3);
+
+            var fullZoomPrimary = NoSquint.prefs.getBoolPref("fullZoomPrimary");
+            if (fullZoomPrimary) {
+                NoSquint.prefs.setIntPref("fullZoomLevel", zoomlevel);
+                NoSquint.prefs.setIntPref("textZoomLevel", 100);
+            } else {
+                NoSquint.prefs.setIntPref("fullZoomLevel", 100);
+                NoSquint.prefs.setIntPref("textZoomLevel", zoomlevel);
+            }
+            NoSquint.prefs.setCharPref('prefsVersion', '2.0');
+        }
+
+        /* Disable browser.zoom.siteSpecific, which prevents Firefox from
+         * automatically applying zoom levels, as that is no NoSquint's job.
+         */
+        NoSquint.origSiteSpecific = NoSquint.browserZoomPrefs.getBoolPref('siteSpecific');
+        NoSquint.browserZoomPrefs.setBoolPref("siteSpecific", false);
+
+        if (populate) {
+            var prefs = [
+                "fullZoomLevel", "textZoomLevel", "zoomIncrement", "wheelZoomEnabled", "hideStatus",
+                "action", "sitesSaveDelay", "rememberSites", "exceptions", "sites", "forgetMonths",
+                "fullZoomPrimary", "wheelZoomInvert", "zoomImages", "colorText", "colorBackground", 
+                "colorBackgroundImages", "linksUnvisited", "linksVisited", "linksUnderline"
+            ];
+            for each (var pref in prefs)
+                // Simulate pref change for each pref to populate attributes
+                NoSquint.observe(null, "nsPref:changed", pref);
+        }
         
         // Attach observers to both branches.
         NoSquint.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
         NoSquint.prefs.addObserver("", NoSquint, false);
-        NoSquint.mousePrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
-        NoSquint.mousePrefs.addObserver("", NoSquint, false);
+        NoSquint.browserZoomPrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
+        NoSquint.browserZoomPrefs.addObserver("", NoSquint, false);
     },
 
     /* Callback from prefs observer when a pref has changed in one of the
@@ -883,41 +1202,36 @@ var NoSquint = {
             // Either not a pref change, or we are in the process of shutting down.
             return;
 
-        // Used for mousewheel.withcontrolkey.action
-        NoSquint.prefsRecursion++;
-
         switch (data) {
-            case "action":
-                if (NoSquint.prefsRecursion > 1)
-                    // This update comes from us, ignore it.
+            case "siteSpecific":
+                if (NoSquint.browserZoomPrefs.getBoolPref("siteSpecific") == false ||
+                    window_get_global('disabled'))
+                    // disabled, which is fine with us, so ignore.
                     break;
-                /* If mousewheel.withcontrolkey.action has changed (perhaps via another
-                 * extension or edited manually by the user) try to do something
-                 * sensible.  If the action is set to 3 (default) then we enable the No
-                 * Squint wheel zoom hooks and then set the action to 0 (otherwise we
-                 * will never see events.  If it is set to any other value, we disable
-                 * the hook for wheel zoom.
-                 */
-                var action = NoSquint.mousePrefs.getIntPref("action");
-                if (action == 3 || action == 5) {
-                    NoSquint.prefs.setBoolPref("wheelZoomEnabled", true);
-                    debug("setting wheelZoomEnabled=true, action=0 because action == " + action);
-                    NoSquint.mousePrefs.setIntPref("action", 0);
-                } else if (action != 0) {
-                    debug("setting wheelZoomEnabled=false because action != 3 == " + action);
-                    NoSquint.prefs.setBoolPref("wheelZoomEnabled", false);
-                }
+
+                // yes == 0, no or close == 1
+                if (popup('confirm', 'siteSpecificTitle', 'siteSpecificPrompt') == 1)
+                    popup('alert', 'siteSpecificBrokenTitle', 'siteSpecificBrokenPrompt');
+                else
+                    NoSquint.browserZoomPrefs.setBoolPref("siteSpecific", false);
                 break;
 
-            case "zoomlevel":
-                NoSquint.defaultZoomLevel = NoSquint.prefs.getIntPref("zoomlevel");
+            case "fullZoomLevel":
+                NoSquint.defaultFullZoomLevel = NoSquint.prefs.getIntPref("fullZoomLevel");
+                NoSquint.queueZoomAll();
+                break;
+
+            case "textZoomLevel":
+                NoSquint.defaultTextZoomLevel = NoSquint.prefs.getIntPref("textZoomLevel");
                 NoSquint.queueZoomAll();
                 break;
 
             case "wheelZoomEnabled":
                 NoSquint.wheelZoomEnabled = NoSquint.prefs.getBoolPref("wheelZoomEnabled");
-                if (NoSquint.wheelZoomEnabled)
-                    NoSquint.mousePrefs.setIntPref("action", 0);
+                break;
+
+            case "wheelZoomInvert":
+                NoSquint.wheelZoomInvert = NoSquint.prefs.getBoolPref("wheelZoomInvert");
                 break;
 
             case "zoomIncrement":
@@ -935,6 +1249,10 @@ var NoSquint = {
                 NoSquint.queueZoomAll();
                 break;
 
+            case "zoomImages":
+                NoSquint.zoomImages = NoSquint.prefs.getBoolPref("zoomImages");
+                break;
+
             case "hideStatus":
                 NoSquint.hideStatus = NoSquint.prefs.getBoolPref("hideStatus");
                 document.getElementById("nosquint-status").hidden = NoSquint.hideStatus;
@@ -944,6 +1262,7 @@ var NoSquint = {
 
             case "rememberSites":
                 NoSquint.rememberSites = NoSquint.prefs.getBoolPref("rememberSites");
+                // XXX: if false, should we clear sites?
                 NoSquint.queueZoomAll();
                 break;
 
@@ -959,53 +1278,121 @@ var NoSquint = {
                 break;
 
             case "sites":
-                /* Parse site list from prefs.  The prefs string a list of site
-                 * specs, delimited by a space, in the form
-                 * "sitename=text_level,timestamp,visits,full_level".  Spaces
-                 * are not allowed in any value; sitename is a string, all
-                 * other values are integers.  The parsing code tries to be
-                 * robust and handle malformed entries gracefully (in case the
-                 * user edits them manually and screws up).
-                 */
-                // TODO: look at nsIContentPrefService
                 if (NoSquint.ignoreNextSitesChange) {
                     NoSquint.ignoreNextSitesChange = false;
                     break;
                 }
-                var sitesStr = NoSquint.prefs.getCharPref("sites");
-
-                // Trim whitespace and split on space.
-                var sites = sitesStr.replace(/(^\s+|\s+$)/g, "").split(" ");
-                var now = new Date().getTime();
-                NoSquint.sites = {};
-                for (var i = 0; i < sites.length; i++) {
-                    var parts = sites[i].split("=");
-                    if (parts.length != 2)
-                        continue; // malformed
-                    var [site, info] = parts;
-                    var parts = info.split(',');
-                    NoSquint.sites[site] = [parseInt(parts[0]) || 0, now, 1, 0];
-                    if (parts.length > 1)
-                        NoSquint.sites[site][1] = parseInt(parts[1]) || now;
-                    if (parts.length > 2)
-                        NoSquint.sites[site][2] = parseInt(parts[2]) || 1;
-                    if (parts.length > 3)
-                        NoSquint.sites[site][3] = parseInt(parts[3]) || 0;
-
-                }
-                if (NoSquint.sitesDirty) {
-                    /* FIXME: looks like the sites list pref was updated (possibly by
-                     * another browser window) before we got a chance to write out our
-                     * changes.  We have lost them now; we should try to merge only
-                     * newer changes based on timestamp.
-                     */
-                    NoSquint.sitesDirty = false;
-                }
+                NoSquint.parseSitesPref();
                 NoSquint.queueZoomAll();
+                NoSquint.queueStyleAll();
                 break;
+
+            case "colorText":
+                NoSquint.colorText = NoSquint.prefs.getCharPref("colorText");
+                NoSquint.queueStyleAll();
+                break;
+
+            case "colorBackground":
+                NoSquint.colorBackground = NoSquint.prefs.getCharPref("colorBackground");
+                NoSquint.queueStyleAll();
+                break;
+
+            case "colorBackgroundImages":
+                NoSquint.colorBackgroundImages = NoSquint.prefs.getBoolPref("colorBackgroundImages");
+                NoSquint.queueStyleAll();
+                break;
+
+            case "linksUnvisited":
+                NoSquint.linksUnvisited = NoSquint.prefs.getCharPref("linksUnvisited");
+                NoSquint.queueStyleAll();
+                break;
+
+            case "linksVisited":
+                NoSquint.linksVisited = NoSquint.prefs.getCharPref("linksVisited");
+                NoSquint.queueStyleAll();
+                break;
+
+            case "linksUnderline":
+                NoSquint.linksUnderline = NoSquint.prefs.getBoolPref("linksUnderline");
+                NoSquint.queueStyleAll();
+                break;
+            }
+    },
+
+    /* Parses extensions.nosquint.sites pref into NoSquint.sites array.
+     */
+    parseSitesPref: function() {
+        /* Parse site list from prefs.  The prefs string a list of site specs, delimited by a space, in the
+         * form: 
+         *
+         *     sitename=text_level,timestamp,visits,full_level
+         *
+         * Spaces are not allowed in any value; sitename is a string, all other values are integers.  The
+         * parsing code tries to be robust and handle malformed entries gracefully (in case the user edits
+         * them manually and screws up).  Consequently it is ugly.
+         */
+        var sitesStr = NoSquint.prefs.getCharPref("sites");
+
+        // Trim whitespace and split on space.
+        var sites = sitesStr.replace(/(^\s+|\s+$)/g, "").split(" ");
+        var now = new Date().getTime();
+        NoSquint.sites = {};
+        for (var i = 0; i < sites.length; i++) {
+            var parts = sites[i].split("=");
+            if (parts.length != 2)
+                continue; // malformed
+            var [site, info] = parts;
+            var parts = info.split(',');
+            NoSquint.sites[site] = [parseInt(parts[0]) || 0, now, 1, 0, '0', '0', false, '0', '0', false];
+            if (parts.length > 1) // last visited timestamp
+                NoSquint.sites[site][1] = parseInt(parts[1]) || now;
+            if (parts.length > 2) // visit count
+                NoSquint.sites[site][2] = parseInt(parts[2]) || 1;
+            if (parts.length > 3) // full page zoom level
+                NoSquint.sites[site][3] = parseInt(parts[3]) || 0;
+            if (parts.length > 4) // text color
+                NoSquint.sites[site][4] = parts[4] || '0';
+            if (parts.length > 5) // bg color
+                NoSquint.sites[site][5] = parts[5] || '0';
+            if (parts.length > 6) // disable bg images
+                NoSquint.sites[site][6] = parts[6] == 'true' ? true : false;
+            if (parts.length > 7) // unvisited link color
+                NoSquint.sites[site][7] = parts[7] || '0';
+            if (parts.length > 8) // visited link color
+                NoSquint.sites[site][8] = parts[8] || '0';
+            if (parts.length > 9) // force underline links
+                NoSquint.sites[site][9] = parts[9] == 'true' ? true : false;
+
+        }
+        if (NoSquint.sitesDirty) {
+            /* FIXME: looks like the sites list pref was updated (possibly by
+             * another browser window) before we got a chance to write out our
+             * changes.  We have lost them now; we should try to merge only
+             * newer changes based on timestamp.
+             */
+            NoSquint.sitesDirty = false;
+        }
+    },
+
+    /* Removes all site settings for sites that were modified within the given
+     * range.  range is a 2-tuple (start, stop) where each are timestamps in
+     * milliseconds.  The newly sanitized site list is then immediately stored.
+     */
+    sanitize: function(range) {
+        if (range == undefined || !range) {
+            NoSquint.sites = {}
+        } else {
+            for (var site in NoSquint.sites) {
+                var timestamp = NoSquint.sites[site][1] * 1000;
+                if (timestamp >= range[0] && timestamp <= range[1])
+                    delete NoSquint.sites[site];
             }
-        NoSquint.prefsRecursion--;
+        }
+        NoSquint._realSaveSiteList();
+        NoSquint.queueZoomAll();
+        NoSquint.queueStyleAll();
     }
+
 };
 
 
@@ -1032,14 +1419,36 @@ ProgressListener.prototype.onLocationChange = function(progress, request, uri) {
      * zoom after that happens (to override it, in effect).
      */
     debug("Location change: " + uri.spec);
-    this.update = true;
-    NoSquint.abortPendingZoomManager();
+    //alert("Location change: " + uri.spec + ' -- doc uri:' + this.browser.docShell.document.URL);
+    this.style_applied = false;
+    this.zoom_override = false;
+    this.browser._noSquintStyles = [];
+    this.content_type = this.browser.docShell.document.contentType;
     NoSquint.locationChanged(this.browser, this.browser.currentURI);
 }
 
-ProgressListener.prototype.onStateChange = function(progress, request, state, status) {
+ProgressListener.prototype.onStateChange = function(progress, request, state, astatus) {
+    //debug("LISTENER: request=" + request + ", state=" + state + ", status=" + astatus);
+    //debug("STATE CHANGE: " + this.browser.docShell.document.contentType);
+
+    /* Check the current content type against the content type we initially got.
+     * This changes in the case when there's an error page (e.g. dns failure),
+     * which we treat as chrome and do not adjust.
+     */
+    var content_type = this.browser.docShell.document.contentType;
+    if (this.content_type != content_type) {
+        this.content_type = content_type;
+        if (is_chrome(this.browser)) {
+            this.browser._noSquintSite = null;
+            NoSquint.zoom(this.browser, 100, 100);
+        }
+    } else if (!this.style_applied && state & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
+        if (!is_chrome(this.browser) || is_image(this.browser))
+            NoSquint.style(this.browser);
+        this.style_applied = true;
+    }
     if (!progress.isLoadingDocument) {
-        // Document load is fone; queue a save of the site list if it has been
+        // Document load is done; queue a save of the site list if it has been
         // changed.
         if (NoSquint.sitesDirty)
             NoSquint.saveSiteList();
@@ -1057,3 +1466,101 @@ ProgressListener.prototype.onSecurityChange =
 ProgressListener.prototype.onLinkIconAvailable = function() {
     return 0;
 }
+
+// Custom observer attached to nsIObserverService.  Used to detect changes
+// to private browsing state, and addon disable/uninstall.  Some code
+// borrowed from https://developer.mozilla.org/En/Supporting_private_browsing_mode
+
+function NoSquintObserver() {  
+  this.init();  
+}
+
+NoSquintObserver.prototype = {  
+    _os: null,  
+    _inPrivateBrowsing: false, // whether we are in private browsing mode  
+    watcher: {}, // the watcher object  
+    _hooked: false,
+   
+    init: function () {  
+        this._inited = true;  
+        this._os = Components.classes["@mozilla.org/observer-service;1"]  
+                             .getService(Components.interfaces.nsIObserverService);  
+        this._hook();
+    },
+
+    _hook: function() {
+        this._os.addObserver(this, "private-browsing-granted", false);  
+        this._os.addObserver(this, "quit-application", false);  
+        this._os.addObserver(this, "em-action-requested", false);  
+        try {  
+            var pbs = Components.classes["@mozilla.org/privatebrowsing;1"]  
+                              .getService(Components.interfaces.nsIPrivateBrowsingService);  
+            this._inPrivateBrowsing = pbs.privateBrowsingEnabled;  
+        } catch(ex) {  
+            // ignore exceptions in older versions of Firefox  
+        }
+        this._hooked = true;
+    },
+
+    _unhook: function() {
+        this._os.removeObserver(this, "quit-application-granted");  
+        this._os.removeObserver(this, "private-browsing");  
+        this._hooked = false;
+    },
+
+    observe: function (subject, topic, data) {  
+        debug("OBSERVER: sub=" + subject + ", topic=" + topic + ", data=" + data);
+        switch (topic) {
+            case "private-browsing":
+                switch (data) {
+                    case "enter":
+                        this._inPrivateBrowsing = true;  
+                        if ("onEnterPrivateBrowsing" in this.watcher)
+                            this.watcher.onEnterPrivateBrowsing();  
+                        break;
+
+                    case "exit":
+                        this._inPrivateBrowsing = false;  
+                        if ("onExitPrivateBrowsing" in this.watcher)
+                            this.watcher.onExitPrivateBrowsing();  
+                        break;
+                }
+                break;
+
+            case "quit-application-granted":
+                this._unhook();
+                break;
+
+            case "em-action-requested":
+                switch (data) {
+                    case "item-disabled":
+                    case "item-uninstalled":
+                        var item = subject.QueryInterface(Components.interfaces.nsIUpdateItem);
+                        if (item.id != 'nosquint at urandom.ca' || window_get_global('disabled'))
+                            break;
+
+                        window_set_global('disabled', true);
+                        if (popup('confirm', 'disableTitle', 'disablePrompt') == 1) {
+                            // Clicked no
+                        } else {
+                            NoSquint.browserZoomPrefs.setBoolPref("siteSpecific", true);
+                        }
+                        debug("Disabling item: " + item.id);
+                        break;
+                    
+                    case "item-cancel-action":
+                        var item = subject.QueryInterface(Components.interfaces.nsIUpdateItem);
+                        if (item.id != 'nosquint at urandom.ca' || window_get_global('disabled') != true)
+                            break;
+                        NoSquint.browserZoomPrefs.setBoolPref("siteSpecific", false);
+                        debug("Enabling item: " + item.id);
+                        window_set_global('disabled', false);
+                }
+                break;
+        }
+    },  
+   
+    get inPrivateBrowsing() {  
+        return this._inPrivateBrowsing;  
+    },  
+}; 
diff --git a/src/content/overlay.xul b/src/content/overlay.xul
index ad034e5..638f4be 100644
--- a/src/content/overlay.xul
+++ b/src/content/overlay.xul
@@ -4,13 +4,9 @@
 
 <overlay id="nosquint-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml">
+    <script type="application/x-javascript" src="chrome://nosquint/content/utils.js" />
+    <script type="application/x-javascript" src="chrome://nosquint/content/nosquint.js" />
     <script type="application/x-javascript" src="chrome://nosquint/content/init.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/lib.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/interfaces.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/prefs.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/browser.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/cmd.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/zoommanager.js" />
 
     <stringbundleset id="stringbundleset">
         <stringbundle id="nosquint-overlay-bundle" src="chrome://nosquint/locale/overlay.properties" />
@@ -19,13 +15,13 @@
     <toolbarpalette id="BrowserToolbarPalette">
         <toolbarbutton id="nosquint-button-reduce" class="toolbarbutton-1"
                        label="Zoom Out" tooltiptext="Zoom out"
-                       oncommand="NoSquint.cmd.buttonReduce(event);" />
+                       oncommand="NoSquint.buttonReduce(event);" />
         <toolbarbutton id="nosquint-button-enlarge" class="toolbarbutton-1"
                        label="Zoom In" tooltiptext="Zoom in"
-                       oncommand="NoSquint.cmd.buttonEnlarge(event);" />
+                       oncommand="NoSquint.buttonEnlarge(event);" />
         <toolbarbutton id="nosquint-button-reset" class="toolbarbutton-1"
                        label="Reset Zoom" tooltiptext="Reset Zoom"
-                       oncommand="NoSquint.cmd.buttonReset(event);" />
+                       oncommand="NoSquint.buttonReset(event);" />
     </toolbarpalette>
 
     <keyset id="mainKeyset">
@@ -36,21 +32,21 @@
     </keyset>
 
     <commandset id="mainCommandSet">
-        <command id="cmd_noSquintPrefs" oncommand="NoSquint.cmd.openGlobalSettings()" />
-        <command id="cmd_fullZoomEnlarge" oncommand="NoSquint.cmd.enlargePrimary()" />
-        <command id="cmd_fullZoomReduce" oncommand="NoSquint.cmd.reducePrimary()" />
-        <command id="cmd_fullZoomReset" oncommand="NoSquint.cmd.reset()" />
-        <command id="cmd_noSquintEnlargeSecondary" oncommand="NoSquint.cmd.enlargeSecondary()" />
-        <command id="cmd_noSquintReduceSecondary" oncommand="NoSquint.cmd.reduceSecondary()" />
+        <command id="cmd_noSquintPrefs" oncommand="NoSquint.openGlobalPrefsDialog()" />
+        <command id="cmd_fullZoomEnlarge" oncommand="NoSquint.cmdEnlargePrimary()" />
+        <command id="cmd_fullZoomReduce" oncommand="NoSquint.cmdReducePrimary()" />
+        <command id="cmd_fullZoomReset" oncommand="NoSquint.cmdReset()" />
+        <command id="cmd_noSquintEnlargeSecondary" oncommand="NoSquint.cmdEnlargeSecondary()" />
+        <command id="cmd_noSquintReduceSecondary" oncommand="NoSquint.cmdReduceSecondary()" />
     </commandset>
 
     <popup id="contentAreaContextMenu">
       <menuitem id="nosquint-menu-settings" label="&ns.menu.context.label;"
-                accesskey="&ns.menu.context.accesskey;" oncommand="NoSquint.cmd.openSiteSettings();"/>
+                accesskey="&ns.menu.context.accesskey;" oncommand="NoSquint.openSitePrefsDialog();"/>
     </popup>
 
     <statusbar id="status-bar">
-        <tooltip id="nosquint-status-tooltip" orient="vertical" onpopupshowing="NoSquint.browser.updateStatusTooltip()">
+        <tooltip id="nosquint-status-tooltip" orient="vertical">
             <grid>
                 <columns>
                     <column />
@@ -107,7 +103,7 @@
             </grid>
         </tooltip>
 
-        <menupopup id="nosquint-status-popup" oncommand="NoSquint.cmd.popupItemSelect(event)">
+        <menupopup id="nosquint-status-popup" oncommand="NoSquint.popupItemSelect(event)">
             <menuitem id="nosquint-popup-site" label="Site" disabled="true" style="font-style: italic" />
             <menu label="&ns.menu.fullZoom.label;">
                 <menupopup id="nosquint-status-popup-full">
@@ -131,15 +127,15 @@
                     <menuitem type="radio" name="text" label="150%" />
                 </menupopup>
             </menu>
-            <menuitem id="nosquint-status-reset" label="&ns.menu.reset.label;" onclick="NoSquint.cmd.reset()" />
-            <menuitem label="&ns.menu.siteSettings.label;" onclick="NoSquint.cmd.openSiteSettings()" />
+            <menuitem id="nosquint-status-reset" label="&ns.menu.reset.label;" onclick="NoSquint.cmdReset()" />
+            <menuitem label="&ns.menu.siteSettings.label;" onclick="NoSquint.openSitePrefsDialog()" />
             <menuseparator />
-            <menuitem label="&ns.menu.globalSettings.label;" onclick="NoSquint.cmd.openGlobalSettings()" />
+            <menuitem label="&ns.menu.globalSettings.label;" onclick="NoSquint.openGlobalPrefsDialog()" />
         </menupopup>
 
         <statusbarpanel class="statusbarpanel-iconic-text" id="nosquint-status" label="100%" 
-                        onclick="NoSquint.cmd.statusPanelClick(event)" 
-                        src="chrome://nosquint/skin/icon-statusbar-16.png" 
+                        onclick="NoSquint.statusPanelClick(event)" 
+                        src="chrome://nosquint/skin/icon-enlarge-16.png" 
                         tooltip="nosquint-status-tooltip" />
     </statusbar>
 </overlay> 
diff --git a/src/content/overlay_sanitize.xul b/src/content/overlay_sanitize.xul
index 9615a56..4796764 100644
--- a/src/content/overlay_sanitize.xul
+++ b/src/content/overlay_sanitize.xul
@@ -1,8 +1,9 @@
 <?xml version="1.0"?>
 <!DOCTYPE overlay SYSTEM "chrome://nosquint/locale/overlay.dtd">
 <overlay id="nosquint-sanitize" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-    <script type="application/x-javascript" src="chrome://nosquint/content/init.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/lib.js" />
-    <script type="application/x-javascript" src="chrome://nosquint/content/prefs.js" />
+    <script type="application/x-javascript" src="chrome://nosquint/content/utils.js" />
     <script type="application/x-javascript" src="chrome://nosquint/content/sanitize.js" />
+    <script type="application/x-javascript">
+        attach("&ns.sanitize.label;");
+    </script>
 </overlay>
diff --git a/src/content/prefs.css b/src/content/prefs.css
index bb10dbf..f77c384 100644
--- a/src/content/prefs.css
+++ b/src/content/prefs.css
@@ -9,3 +9,27 @@ label.percent {
 .indent {
     margin-left: 2.5em;
 }
+
+button[dlgtype="extra1"] {
+      list-style-image: url("chrome://nosquint/skin/icon-enlarge-16.png");
+}
+
+#global-warning-box {
+    border: 1px solid #e0cd64;
+    background-color: #fffac4;
+    padding: 3px 15px 3px 15px;
+    margin: 0.5em 2em;
+    vertical-align: middle;
+    font-weight: bold;
+    -moz-border-radius: 20px;
+}
+
+#global-warning-box div {
+    padding-left: 0.7em;
+    width: 400px;
+    font-weight: normal;
+}
+
+#global-warning-box image {
+}
+
diff --git a/src/content/sanitize.js b/src/content/sanitize.js
index cb375c5..88173d3 100644
--- a/src/content/sanitize.js
+++ b/src/content/sanitize.js
@@ -1,81 +1,67 @@
 // chrome://browser/content/sanitize.xul 
-// chrome://browser/content/preferences/sanitize.xul 
-
-NoSquint.sanitizer = NoSquint.ns(function() { with (NoSquint) {
-    this.init = function() {
-        // Adds nosquint option to sanitizer UI
-        this.attachOption(NSQ.strings.sanitizeLabel)
-        if (typeof Sanitizer != 'undefined')
-            // Installs NoSquint hooks into the sanitizer
-            this.hookSanitizer();
+function hookSanitizer() {
+    window.removeEventListener('load', hookSanitizer, true);
+    Sanitizer.prototype.items['extensions-nosquint'] = {
+        clear: function() {
+            /* Find all NoSquint instances and force a site list save if dirty.
+             */
+            var last = null;
+            foreach_window(function(win) {
+                if (win._noSquint) {
+                    if (win._noSquint.sitesDirty)
+                        win._noSquint._realSaveSiteList();
+                    last = win._noSquint;
+                }
+            });
+            if (last)
+                last.sanitize(this.range);
+        },
+        get canClear() {
+            return true;
+        }
     };
+}
 
-    this.attachOption = function(label) {
-        var inSanitizeDialog = typeof(gSanitizePromptDialog) == 'object';
-        // TODO: put this into a convenience function in lib.js
-        var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                            .getService(Components.interfaces.nsIPrefBranch);
+function attach(label) {
+    window.addEventListener('load', hookSanitizer, true);
+    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
 
-        // pref domain is privacy.cpd. for Firefox 3.1+, and privacy.item. for 3.0
-        // and earlier.
-        var domain = 'privacy.cpd.';
-        if ($('privacy.item.cache'))
-            domain = 'privacy.item.';
-        var prefs = document.getElementsByTagName('preferences')[0];
-        var pref = document.createElement('preference');
-        pref.setAttribute('id', domain + 'extensions-nosquint');
-        pref.setAttribute('name', domain + 'extensions-nosquint');
-        pref.setAttribute('type', 'bool');
-        var value = prefService.getBoolPref(domain + 'extensions-nosquint');
-        pref.setAttribute('value', value);
-        prefService.setBoolPref(domain + 'extensions-nosquint', value);
-        prefs.appendChild(pref);
+    // pref domain is privacy.cpd. for Firefox 3.1+, and privacy.item. for 3.0
+    // and earlier.
+    var domain = 'privacy.cpd.';
+    if (document.getElementById('privacy.item.cache'))
+        domain = 'privacy.item.';
+    var prefs = document.getElementsByTagName('preferences')[0];
+    var pref = document.createElement('preference');
+    pref.setAttribute('id', domain + 'extensions-nosquint');
+    pref.setAttribute('name', domain + 'extensions-nosquint');
+    pref.setAttribute('type', 'bool');
+    var value = prefService.getBoolPref('privacy.item.extensions-nosquint');
+    pref.setAttribute('value', value);
+    prefService.setBoolPref('privacy.cpd.extensions-nosquint', value);
+    prefs.appendChild(pref);
 
-        if ($('itemList')) {
-            // In Clear Recent History dialog in Firefox 3.0
-            var check = $('itemList').appendItem(label);
-            check.setAttribute('type', 'checkbox');
-        } else {
-            // Firefox 3.0, or Firefox 3.5 in Settings, where the user sets which to enable/disable.
-            var check = document.createElement('checkbox');
-            check.setAttribute('label', label);
-            var rows = document.getElementsByTagName('rows');
-            if (rows.length) {
-                // Firefox 3.5
-                // Add new row to to rows.  TODO: append to last row if only has one column
-                var row = document.createElement('row');
-                row.appendChild(check);
-                rows[0].appendChild(row);
-            } else
-                // Firefox 3.0
-                document.getElementsByTagName('checkbox')[0].parentNode.appendChild(check);
-        }    
-        check.setAttribute('preference', domain + 'extensions-nosquint');
-        check.setAttribute('checked', value);
-
-        if (inSanitizeDialog) {
-            pref.setAttribute('readonly', 'true');
-            check.setAttribute('onsyncfrompreference', 'return gSanitizePromptDialog.onReadGeneric();');
-            if (prefService.getCharPref('extensions.nosquint.sites') == '') {
-                /* FIXME: a minor race condition: if user made first zoom change
-                 * and immediately opened sanitizer (before 5s timeout to store sites)
-                 * we will disable the checkbox when we shouldn't.
-                 */
-                check.setAttribute('disabled', true);
-                check.setAttribute('checked', false);
-            }
-        }
-    };
-
-    this.hookSanitizer = function() {
-        Sanitizer.prototype.items['extensions-nosquint'] = {
-            clear: function() {
-                NSQ.prefs.sanitize(this.range);
-            },
-            get canClear() {
-                return true;
-            }
-        };
-    };
+    if (document.getElementById('itemList')) {
+        // Firefox 3.5
+        var check = document.getElementById('itemList').appendItem(label);
+        check.setAttribute('type', 'checkbox');
+    } else {
+        // Firefox 3.0
+        var check = document.createElement('checkbox');
+        check.setAttribute('label', label);
+        document.getElementsByTagName('checkbox')[0].parentNode.appendChild(check);
+    }    
+    check.setAttribute('preference', domain + 'extensions-nosquint');
+    if (prefService.getCharPref('extensions.nosquint.sites') == '')
+        // FIXME: a minor race condition: if user made first zoom change
+        // and immediately opened sanitizer (before 5s timeout to store sites)
+        // we will disable the checkbox when we shouldn't.
+        check.setAttribute('disabled', true);
 
-}});
+     if (typeof(gSanitizePromptDialog) == 'object')
+     {  
+        pref.setAttribute('readonly', 'true');
+        check.setAttribute('onsyncfrompreference', 'return gSanitizePromptDialog.onReadGeneric();');
+     }
+}
diff --git a/src/content/siteprefs.js b/src/content/siteprefs.js
index 72928c9..c904ea8 100644
--- a/src/content/siteprefs.js
+++ b/src/content/siteprefs.js
@@ -3,6 +3,7 @@ var NoSquintSitePrefs = {
     browser: null,
     NoSquint: null,
     bundle: null,
+    updateTimer: null,
 
     init: function(doc, dialog) {
         NoSquintSitePrefs.doc = doc;
@@ -14,12 +15,48 @@ var NoSquintSitePrefs = {
 
         doc.getElementById('full-zoom-level').onchange = function() { NoSquintSitePrefs.valueChange('full', this); }
         doc.getElementById('text-zoom-level').onchange = function() { NoSquintSitePrefs.valueChange('text', this); }
+
         NoSquintSitePrefs.setValues(window.arguments[1], window.arguments[2]);
+
+        var update = function() { NoSquintSitePrefs.style(true, false); };
+        doc.getElementById('colorBackgroundImages').addEventListener("CheckboxStateChange", update, false);
+        doc.getElementById('linksUnderline').addEventListener("CheckboxStateChange", update, false);
+
+        for each (var id in ['colorText', 'colorBackground', 'linksUnvisited', 'linksVisited']) {
+            var cb = doc.getElementById(id);
+            cb.addEventListener("CheckboxStateChange", NoSquintSitePrefs.colorChecked, false);
+            var picker = cb.parentNode.childNodes[1];
+            picker.onchange = function() {
+                NoSquintSitePrefs.style(true, false);
+            }
+        }
+    },
+
+    colorChecked: function(event, cb) {
+        cb = cb || this;
+        var picker = cb.parentNode.childNodes[1];
+        picker.disabled = !cb.checked;
+        picker.style.opacity = cb.checked ? 1.0 : 0.2;
+        if (event)
+            // Only style() if we've been triggered by user checking the checkbox,
+            // not a call from elsewhere in this file.
+            NoSquintSitePrefs.style(true, false);
     },
 
     setValues: function(browser, site) {
         var doc = NoSquintSitePrefs.doc;
-        var [text, full] = NoSquintSitePrefs.NoSquint.getLevelForBrowser(browser);
+        if (NoSquintSitePrefs.NoSquint.rememberSites) {
+            var [text, full] = NoSquintSitePrefs.NoSquint.getLevelForBrowser(browser);
+            // We don't use getStyleForBrowser() because it also applies the default
+            // values.
+            var style = NoSquintSitePrefs.NoSquint.getStyleForSite(browser._noSquintSite);
+            doc.getElementById('global-warning-box').style.display = 'none';
+        } else {
+            var text = Math.round(browser.markupDocumentViewer.textZoom * 100);
+            var full = Math.round(browser.markupDocumentViewer.fullZoom * 100);
+            var style = {text: '0', bg: '0', bgimages: false, unvisited: '0', visited: '0', underline: false};
+            doc.getElementById('global-warning-box').style.display = '';
+        }
 
         NoSquintSitePrefs.browser = browser;
         NoSquintSitePrefs.site = site;
@@ -27,9 +64,24 @@ var NoSquintSitePrefs = {
         doc.getElementById('text-zoom-slider').value = text;
         doc.getElementById('full-zoom-slider').value = full;
 
-        var caption = doc.getElementById('site').childNodes[0];
-        caption.label = NoSquintSitePrefs.bundle.getString('settingsFor') + " " + site;
+        function setcolor(id, attr, def) {
+            var cb = doc.getElementById(id);
+            var picker = cb.parentNode.childNodes[1];
+            picker.color = (style && style[attr] != '0') ? style[attr] : def;
+            cb.checked = (style && style[attr] != '0') ? true : false;
+            NoSquintSitePrefs.colorChecked(null, cb);
+        }
+        setcolor('colorText', 'text', '#000000');
+        setcolor('colorBackground', 'bg', '#ffffff');
+        setcolor('linksUnvisited', 'unvisited', '#0000ee');
+        setcolor('linksVisited', 'visited', '#551a8b');
+        doc.getElementById('colorBackgroundImages').checked = style ? style.bgimages : false;
+        doc.getElementById('linksUnderline').checked = style ? style.underline : false;
 
+        var caption = doc.getElementById('site').childNodes[0];
+        //caption.label = NoSquintSitePrefs.bundle.getString('settingsFor') + " " + site;
+        caption.label = site;
+        window.sizeToContent();
     },
 
     sliderChange: function(which, slider) {
@@ -37,12 +89,63 @@ var NoSquintSitePrefs = {
         slider.value = parseInt(slider.value / 5) * 5;
         if (doc)
             doc.getElementById(which + '-zoom-level').value = slider.value;
+        NoSquintSitePrefs.queueUpdateZoom();
         return 5;
     },
 
     valueChange: function(which, textbox) {
         var doc = NoSquintSitePrefs.doc;
         doc.getElementById(which + '-zoom-slider').value = textbox.value;
+        NoSquintSitePrefs.queueUpdateZoom();
+    },
+
+    queueUpdateZoom: function() {
+        if (NoSquintSitePrefs.updateTimer)
+            return;
+        NoSquintSitePrefs.updateTimer = setTimeout(function() { NoSquintSitePrefs.updateZoom(); }, 400);
+    },
+
+    updateZoom: function() {
+        clearTimeout(NoSquintSitePrefs.updateTimer);
+        NoSquintSitePrefs.updateTimer = null;
+        NoSquintSitePrefs.zoom(true, false);
+    },
+
+    zoom: function(from_form, save) {
+        var doc = NoSquintSitePrefs.doc;
+        var browser = NoSquintSitePrefs.browser;
+        if (from_form) {
+            var text = doc.getElementById('text-zoom-level').value;
+            var full = doc.getElementById('full-zoom-level').value;
+        } else
+            var [text, full] = NoSquintSitePrefs.NoSquint.getLevelForBrowser(browser);
+
+        NoSquintSitePrefs.NoSquint.zoom(browser, text, full);
+        if (save)
+            NoSquintSitePrefs.NoSquint.saveCurrentZoom();
+    },
+
+    style: function(from_form, save) {
+        var style = null;
+        if (from_form) {
+            var doc = NoSquintSitePrefs.doc;
+            style = {};
+            for each (var [id, attr] in [['colorText', 'text'], ['colorBackground', 'bg'], 
+                                ['linksUnvisited', 'unvisited'], ['linksVisited', 'visited']]) {
+                var cb = doc.getElementById(id);
+                var picker = cb.parentNode.childNodes[1];
+                style[attr] = cb.checked ? picker.color : '0';
+            }
+            style.bgimages = doc.getElementById("colorBackgroundImages").checked;
+            style.underline = doc.getElementById("linksUnderline").checked;
+        }
+        if (save)
+            NoSquintSitePrefs.NoSquint.updateSiteList(NoSquintSitePrefs.site, null, style);
+        if (style)
+            style = NoSquintSitePrefs.NoSquint.applyStyleDefaults(style);
+        // FIXME: we've already updated site list from zoom, so we're doing it twice.
+        NoSquintSitePrefs.NoSquint.style(NoSquintSitePrefs.browser, style);
+        NoSquintSitePrefs.NoSquint.updateStatus();
     },
 
     buttonUseDefault: function(which) {
@@ -55,20 +158,17 @@ var NoSquintSitePrefs = {
 
 
     close: function() {
-        var doc = NoSquintSitePrefs.doc;
-        var browser = NoSquintSitePrefs.browser;
-        var [text_current, full_current] = NoSquintSitePrefs.NoSquint.getLevelForBrowser(browser);
-        var text = doc.getElementById('text-zoom-level').value;
-        var full = doc.getElementById('full-zoom-level').value;
-        if (text != text_current || full != full_current) {
-            NoSquintSitePrefs.NoSquint.zoom(browser, text, full);
-            NoSquintSitePrefs.NoSquint.saveCurrentZoom();
-        }
+        NoSquintSitePrefs.zoom(true, true);
+        NoSquintSitePrefs.style(true, true);
         NoSquintSitePrefs.NoSquint.siteDialog = null;
         NoSquintSitePrefs.NoSquint = null;
     },
 
     cancel: function() {
+        //var [text_current, full_current] = NoSquintSitePrefs.NoSquint.getLevelForBrowser(browser);
+        //NoSquintSitePrefs.zoom(text_current, full_current, false);
+        NoSquintSitePrefs.zoom(false, false);
+        NoSquintSitePrefs.style(false, false);
         NoSquintSitePrefs.NoSquint.siteDialog = null;
     }
 };
diff --git a/src/content/siteprefs.xul b/src/content/siteprefs.xul
index 3edbfef..93dfa37 100644
--- a/src/content/siteprefs.xul
+++ b/src/content/siteprefs.xul
@@ -1,19 +1,27 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin/global.css"  type="text/css"?>
 <?xml-stylesheet href="chrome://nosquint/content/prefs.css"  type="text/css"?>
-<!DOCTYPE window SYSTEM "chrome://nosquint/locale/siteprefs.dtd">
+<!DOCTYPE window [
+    <!ENTITY % siteDTD SYSTEM "chrome://nosquint/locale/siteprefs.dtd" >
+    %siteDTD;
+    <!ENTITY % globalDTD SYSTEM "chrome://nosquint/locale/globalprefs.dtd" >
+    %globalDTD;
+]>
 
 <dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
         title="&ns.pref.title;"
-        buttons="accept,cancel" 
+        buttons="extra1,accept,cancel" 
         ondialogaccept="NoSquintSitePrefs.close()"
         ondialogcancel="NoSquintSitePrefs.cancel()"
+        ondialogextra1="NoSquintSitePrefs.NoSquint.openGlobalPrefsDialog()"
+        buttonlabelextra1="&ns.pref.button.global.label;"
+        buttonaccesskeyextra1="&ns.pref.button.global.accesskey;"
         id="nosquint-siteprefs-dialog"
         persist="screenX screenY"
         onload="NoSquintSitePrefs.init(document, this)">
 
-    <script src="siteprefs.js" />
+    <script type="application/x-javascript" src="chrome://nosquint/content/siteprefs.js" />
 
     <stringbundleset id="stringbundleset">
         <stringbundle id="nosquint-prefs-bundle" src="chrome://nosquint/locale/siteprefs.properties" />
@@ -21,46 +29,91 @@
 
     <groupbox id="site">
         <caption />
-        <grid id="siteZoom-box" flex="1">
-            <columns>
-                <column />
-                <column />
-                <column />
-                <column flex="1" />
-                <column />
-            </columns>
-            <rows>
-                <row align="center">
-                    <hbox>
+        <vbox flex='1'>
+            <hbox id="global-warning-box" align="center">
+                <image class="alert-icon" />
+                <html:div>&ns.pref.globalWarning;</html:div>
+            </hbox>
+
+            <grid id="siteZoom-box" flex="1">
+                <columns>
+                    <column />
+                    <column />
+                    <column />
+                    <column flex="1" />
+                    <column />
+                </columns>
+                <rows>
+                    <row align="center">
+                        <hbox>
+                            <spacer flex="1" />
+                            <label value="&ns.pref.fullZoom.label;:" />
+                        </hbox>
+                        <scale id="full-zoom-slider" min="40" increment="1" max="300" style="width:200px"
+                               onchange="NoSquintSitePrefs.sliderChange('full', this)"/>
+                        <hbox align="center">
+                            <textbox id="full-zoom-level" size="2" type="number" min="40" max="300" increment="5" />
+                            <label class="percent">%</label>
+                        </hbox>
                         <spacer flex="1" />
-                        <label value="&ns.pref.fullZoom.label;:" />
-                    </hbox>
-                    <scale id="full-zoom-slider" min="40" increment="1" max="300" style="width:200px"
-                           onchange="NoSquintSitePrefs.sliderChange('full', this)"/>
-                    <hbox align="center">
-                        <textbox id="full-zoom-level" size="2" type="number" min="40" max="300" increment="5" />
-                        <label class="percent">%</label>
-                    </hbox>
-                    <spacer flex="1" />
-                    <button  label="&ns.pref.button.useDefault.label;"
-                             id="siteZoom-button" oncommand="NoSquintSitePrefs.buttonUseDefault('full')" />
-                </row>
-                <row align="center">
-                    <hbox>
+                        <button  label="&ns.pref.button.useDefault.label;"
+                                 id="siteZoom-button" oncommand="NoSquintSitePrefs.buttonUseDefault('full')" />
+                    </row>
+                    <row align="center">
+                        <hbox>
+                            <spacer flex="1" />
+                            <label value="&ns.pref.textZoom.label;:" />
+                        </hbox>
+                        <scale id="text-zoom-slider" min="40" increment="1" max="300" 
+                               onchange="NoSquintSitePrefs.sliderChange('text', this)"/>
+                        <hbox align="center">
+                            <textbox id="text-zoom-level" size="2" type="number" min="40" max="300" increment="5"  />
+                            <label class="percent">%</label>
+                        </hbox>
                         <spacer flex="1" />
-                        <label value="&ns.pref.textZoom.label;:" />
-                    </hbox>
-                    <scale id="text-zoom-slider" min="40" increment="1" max="300" 
-                           onchange="NoSquintSitePrefs.sliderChange('text', this)"/>
-                    <hbox align="center">
-                        <textbox id="text-zoom-level" size="2" type="number" min="40" max="300" increment="5"  />
-                        <label class="percent">%</label>
-                    </hbox>
-                    <spacer flex="1" />
-                    <button  label="&ns.pref.button.useDefault.label;"
-                             id="siteZoom-button" oncommand="NoSquintSitePrefs.buttonUseDefault('text')" />
-                </row>
-            </rows>
-        </grid>
+                        <button  label="&ns.pref.button.useDefault.label;"
+                                 id="siteZoom-button" oncommand="NoSquintSitePrefs.buttonUseDefault('text')" />
+                    </row>
+                </rows>
+            </grid>
+
+            <hbox flex='1' style='padding-top: 1em'>
+                <groupbox flex='1'>
+                    <caption label="&ns.pref.colors.colors.caption;" />
+                    <vbox>
+                        <hbox>
+                            <checkbox id="colorText" label="&ns.pref.colors.colors.text.label;"
+                                      checked="false" flex="1" />
+                            <colorpicker type='button' />
+                        </hbox>
+                        <hbox>
+                            <checkbox id="colorBackground" label="&ns.pref.colors.colors.background.label;"
+                                      checked="false" flex="1" />
+                            <colorpicker type='button' />
+                        </hbox>
+                        <checkbox id="colorBackgroundImages" label="&ns.pref.colors.colors.images.label;"
+                                  checked="false" flex="1" />
+                    </vbox>
+                </groupbox>
+                <groupbox flex='1'>
+                    <caption label="&ns.pref.colors.links.caption;" />
+                    <vbox>
+                        <hbox>
+                            <checkbox id="linksUnvisited" label="&ns.pref.colors.links.unvisited.label;" 
+                                      checked="false" flex="1" />
+                            <colorpicker type='button' />
+                        </hbox>
+                        <hbox>
+                            <checkbox id="linksVisited" label="&ns.pref.colors.links.visited.label;"
+                                      checked="false" flex="1" />
+                            <colorpicker type='button' />
+                        </hbox>
+                        <checkbox id="linksUnderline" label="&ns.pref.colors.links.underline.label;"
+                                  checked="false" flex="1" />
+                    </vbox>
+                </groupbox>
+            </hbox>
+
+        </vbox>
     </groupbox>
 </dialog>
diff --git a/src/content/utils.js b/src/content/utils.js
new file mode 100644
index 0000000..accd5d1
--- /dev/null
+++ b/src/content/utils.js
@@ -0,0 +1,86 @@
+/* Returns a list of lines from a URL (such as chrome://).  This function
+ * is a WTF; how more obsure could it possibly be to read a damn file?
+ */
+function readLines(aURL) {
+  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+                  .getService(Components.interfaces.nsIIOService);
+  var scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                         .getService(Components.interfaces.nsIScriptableInputStream);
+
+  var channel = ioService.newChannel(aURL, null, null);
+  var input = channel.open();
+  scriptableStream.init(input);
+  var str = scriptableStream.read(input.available());
+  scriptableStream.close();
+  input.close();
+  return str.split("\n");
+} 
+
+// XXX: don't forget to disable this for releases.
+function debug(msg) {
+//    dump("[nosquint] " + msg + "\n");
+}
+
+// TODO: benchmark this function, it is called a lot.
+function is_chrome(browser) {
+    var document = browser.docShell.document;
+    debug("IS CHROME: " + document.URL + " (" + browser.currentURI.spec + ")  -- type:" + document.contentType);
+    if (document.URL.replace(/#.*$/, '') != browser.currentURI.spec.replace(/#.*$/, ''))
+        /* Kludge: doc.URL doesn't match browser currentURI during host lookup failure,
+         * SSL cert errors, or other scenarios that result in an internal page being
+         * displayed that we consider chrome.
+         */
+        return true;
+    return document.URL == undefined || 
+           document.URL.search(/^about:/) != -1 ||
+           document.contentType.search(/^text\/(html|plain|css|xml|javascript)|^application\/(xhtml)/) != 0;
+}
+
+function is_image(browser) {
+    return browser.docShell.document.contentType.search(/^image\//) == 0;
+}
+
+
+function foreach_window(callback) {
+    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+                       .getService(Components.interfaces.nsIWindowMediator);
+    var windows = wm.getEnumerator("navigator:browser");
+    var win;
+    while (win = windows.getNext())
+        if (callback(win) == false)
+            break;
+}
+
+function window_get_global(name) {
+    var value = null;
+    foreach_window(function(win) {
+        if (win._noSquintGlobals != undefined && name in win._noSquintGlobals) {
+            value = win._noSquintGlobals[name];
+            return false;
+        }
+    });
+    return value;
+}
+
+function window_set_global(name, value) {
+    foreach_window(function(win) {
+        if (win._noSquintGlobals == undefined)
+            win._noSquintGlobals = {};
+        win._noSquintGlobals[name] = value;
+    });
+}
+
+function popup(type, title, text, bundle) {
+    if (!bundle)
+        bundle = document.getElementById("nosquint-overlay-bundle");
+    var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                  .getService(Components.interfaces.nsIPromptService);
+    if (type == "confirm") 
+        return prompts.confirmEx(window, bundle.getString(title),
+                                 bundle.getString(text),
+                                 prompts.STD_YES_NO_BUTTONS, null, null, null, 
+                                 null, {value: null});
+    else if (type == "alert")
+        return prompts.alert(window, bundle.getString(title), bundle.getString(text));
+    return null;
+}
diff --git a/src/defaults/preferences/nosquint.js b/src/defaults/preferences/nosquint.js
index 2ff5105..ef3790a 100644
--- a/src/defaults/preferences/nosquint.js
+++ b/src/defaults/preferences/nosquint.js
@@ -5,7 +5,7 @@ pref("extensions.nosquint.rememberSites", true);
 pref("extensions.nosquint.sites", "");
 pref("extensions.nosquint.sitesSaveDelay", 5000);
 pref("extensions.nosquint.exceptions", "*/~* *.sourceforge.net *.google.[*]");
-pref("extensions.nosquint.zoomImages", false);
+pref("extensions.nosquint.zoomImages", true);
 pref("extensions.nosquint.wheelZoomEnabled", true);
 pref("extensions.nosquint.wheelZoomInvert", false);
 pref("extensions.nosquint.hideStatus", false);
@@ -18,5 +18,4 @@ pref("extensions.nosquint.colorBackgroundImages", false);
 pref("extensions.nosquint.linksUnvisited", '0');
 pref("extensions.nosquint.linksVisited", '0');
 pref("extensions.nosquint.linksUnderline", false);
-pref("privacy.cpd.extensions-nosquint", true);
 pref("privacy.item.extensions-nosquint", true);
diff --git a/src/install.rdf b/src/install.rdf
index 081ba15..663c215 100644
--- a/src/install.rdf
+++ b/src/install.rdf
@@ -5,14 +5,14 @@
     <Description about="urn:mozilla:install-manifest">
     
         <em:id>nosquint at urandom.ca</em:id>
-        <em:name>NoSquint</em:name>
-        <em:version>2.0.5.1</em:version>
+        <em:name>No Squint</em:name>
+        <em:version>2.0b4</em:version>
         <em:description>Manage site-specific zoom levels and color settings</em:description>
         <em:creator>Jason Tackaberry</em:creator>
         <!-- optional items -->
         <em:homepageURL>http://urandom.ca/nosquint/</em:homepageURL>
-        <em:optionsURL>chrome://nosquint/content/dlg-global.xul</em:optionsURL>
-        <em:iconURL>chrome://nosquint/skin/logo-32.png</em:iconURL>
+        <em:optionsURL>chrome://nosquint/content/globalprefs.xul</em:optionsURL>
+        <em:iconURL>chrome://nosquint/content/icon-32.png</em:iconURL>
         <em:type>2</em:type> <!-- type=extension --> 
 
         <!-- Firefox -->
@@ -20,7 +20,7 @@
             <Description>
                 <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                 <em:minVersion>3.0b4pre</em:minVersion>
-                <em:maxVersion>4.0b3pre</em:maxVersion>
+                <em:maxVersion>3.5.*</em:maxVersion>
             </Description>
         </em:targetApplication>
     </Description>
diff --git a/src/locale/en-US/globalprefs.dtd b/src/locale/en-US/globalprefs.dtd
index 522b741..122af67 100644
--- a/src/locale/en-US/globalprefs.dtd
+++ b/src/locale/en-US/globalprefs.dtd
@@ -1,27 +1,44 @@
 <!ENTITY ns.pref.title "NoSquint Global Settings">
 <!ENTITY ns.pref.tab.options.label "Options">
+<!ENTITY ns.pref.tab.zooming.label "Zooming">
+<!ENTITY ns.pref.tab.colors.label "Colors">
 <!ENTITY ns.pref.tab.exceptions.label "Exceptions">
 
-<!ENTITY ns.pref.general.caption "General">
-
-<!ENTITY ns.pref.general.primaryMethod.label "Primary zoom method">
-<!ENTITY ns.pref.general.primaryMethod.full "Full Page Zoom (images and text)">
-<!ENTITY ns.pref.general.primaryMethod.text "Text Zoom (text only)">
-
-<!ENTITY ns.pref.general.level.label "Default primary zoom level">
-<!ENTITY ns.pref.general.increment.label "Zoom increment">
-<!ENTITY ns.pref.general.mousewheel.label "Enable zoom with ctrl-mousewheel">
-<!ENTITY ns.pref.general.showstatus.label "Show current zoom levels in status bar">
-
-<!ENTITY ns.pref.site.caption "Site">
-<!ENTITY ns.pref.site.noRemember.label "Use the default zoom level (set above) for all sites">
-<!ENTITY ns.pref.site.remember.label "Remember zoom level per site">
-<!ENTITY ns.pref.site.forget.label "Forget zoom settings for sites not visited in the last">
-<!ENTITY ns.pref.site.forget.year "Year">
-<!ENTITY ns.pref.site.forget.6months "Six Months">
-<!ENTITY ns.pref.site.forget.3months "Three Months">
-<!ENTITY ns.pref.site.forget.month "Month">
-<!ENTITY ns.pref.exceptions.info "Exceptions are an advanced feature that controls how NoSquint determines separate sites.  Click the Help button below for full details.">
+<!ENTITY ns.pref.zooming.caption "Default Zoom Options">
+
+<!ENTITY ns.pref.zooming.primaryMethod.label "Primary zoom method">
+<!ENTITY ns.pref.zooming.primaryMethod.full "Full Page Zoom (images and text)">
+<!ENTITY ns.pref.zooming.primaryMethod.text "Text Zoom (text only)">
+
+<!ENTITY ns.pref.zooming.fullLevel.label "Default full page zoom level">
+<!ENTITY ns.pref.zooming.textLevel.label "Default text-only zoom level">
+<!ENTITY ns.pref.zooming.increment.label "Zoom increment">
+<!ENTITY ns.pref.zooming.images.label "Zoom standalone images to fit browser window">
+<!ENTITY ns.pref.zooming.mousewheel.label "Enable zoom with ctrl-mousewheel">
+<!ENTITY ns.pref.zooming.showstatus.label "Show current zoom levels in status bar">
+
+<!ENTITY ns.pref.persistence.caption "Persistence">
+<!ENTITY ns.pref.persistence.noRemember.label "Use the global zoom and color settings for all sites">
+<!ENTITY ns.pref.persistence.remember.label "Remember zoom and color settings per site">
+<!ENTITY ns.pref.persistence.forget.label "Forget settings for sites not visited in the last">
+<!ENTITY ns.pref.persistence.forget.year "Year">
+<!ENTITY ns.pref.persistence.forget.6months "Six Months">
+<!ENTITY ns.pref.persistence.forget.3months "Three Months">
+<!ENTITY ns.pref.persistence.forget.month "Month">
+<!ENTITY ns.pref.persistence.sanitize.label "Erase NoSquint site history when Firefox clears private data">
+
+<!ENTITY ns.pref.colors.info "Enabling any of the color options below overrides the default for all pages.">
+<!ENTITY ns.pref.colors.colors.caption "Text and Background">
+<!ENTITY ns.pref.colors.colors.text.label "Text">
+<!ENTITY ns.pref.colors.colors.background.label "Background">
+<!ENTITY ns.pref.colors.colors.images.label "Disable background images">
+<!ENTITY ns.pref.colors.links.caption "Link Colors">
+<!ENTITY ns.pref.colors.links.unvisited.label "Unvisited">
+<!ENTITY ns.pref.colors.links.visited.label "Visited">
+<!ENTITY ns.pref.colors.links.underline.label "Always underline links">
+
+
+<!ENTITY ns.pref.exceptions.info "Exceptions are an advanced feature that controls how NoSquint distinguishes separate sites.  Click the Help button below for full details.">
 
 <!ENTITY ns.pref.exceptions.pattern.label "Pattern for new exception">
 <!ENTITY ns.pref.exceptions.copyButton.label "Copy from URL">
diff --git a/src/locale/en-US/help.html b/src/locale/en-US/help.html
index 7db3377..1baa13b 100644
--- a/src/locale/en-US/help.html
+++ b/src/locale/en-US/help.html
@@ -52,7 +52,7 @@
 </style>
 
 <body>
-<h2 id='general'>General Tab</h2>
+<h2>Options Tab</h2>
 
 <p>To NoSquint, a site is a web location where all pages have the same zoom
 level and color settings, and the site name is derived from the page's URL.</p>
@@ -65,28 +65,25 @@ second-level domains.  For example, if you're visiting
 <code>bbc.co.uk</code>.</p>
 
 <p>The default behaviour should work almost all the time.  When it doesn't, you
-can control how NoSquint determines site names in the 
-<a href='#exceptions'>Exceptions Tab</a>.</p>
+can control how NoSquint determines site names in the Exceptions Tab.</p>
 
-<h3>Per-site settings (color and zoom)</h3>
+<h3>Persistence</h3>
 <ul>
     <li>
-        <b>Remember per-site settings between Firefox restarts</b>
+        <b>Remember zoom and color settings per site</b>
             <p>With this option selected, NoSquint will remember any changes
-            you make to the zoom levels and color choices for a given site, and
-            the changes are stored on disk so they can be applied even if you
-            restart Firefox.</p>
-            <p>Both full page zoom and text zoom levels are remembered
+            you make to the zoom levels and color choices for a given site.
+            Both full page zoom and text zoom levels are remembered
             independently.  Next time you visit that site, NoSquint will use
             the zoom and colors previously used on that site.</p>
     </li>
     <li>
         <b>Forget settings for sites not visited in the last ...</b>
-            <p>With the "Remember per-site settings between Firefox restarts"
-            option enabled, NoSquint keeps track of all zoom level and color
-            changes for sites, even sites you only visit once.  This option is
-            house cleaning: if you haven't visited a site (for which you've set
-            a non-default zoom or color setting) for the specified number of
+            <p>With the "Remember zoom and color settings per site" option
+            enabled, NoSquint keeps track of all zoom level and color changes
+            for sites, even sites you only visit once.  This option is house
+            cleaning: if you haven't visited a site (for which you've set a
+            non-default zoom or color setting) for the specified number of
             months, NoSquint will forget the setting.</p>
     </li>
     <li>
@@ -102,20 +99,16 @@ can control how NoSquint determines site names in the
             not cleared.</p>
     </li>
     <li>
-        <b>Forget all per-site settings when Firefox closes</b>
-            <p>Per-site settings will be retained only for the current Firefox
-            session.  They are never written to disk, and will therefore not
-            be remembered if you restart Firefox.</p>
-            <p>An alternative would be to use Firefox's Private Browsing mode
-            (Firefox 3.5 and later), which NoSquint supports.  When Private
-            Browsing is activated, even if "Remember per-site settings between
-            Firefox restarts" is selected instead of this option, NoSquint will
-            never store any per-site settings to disk which have changed while
-            in that mode.</p>
+        <b>Use the global zoom and color settings for all sites</b>
+            <p>One of NoSquint's features is the ability to remember custom
+            zoom levels and color choices for individual sites.  If you're not
+            interested this and want to use the same settings (specified in the
+            Zooming and Color tabs) for all sites, or you just don't want
+            NoSquint to remember any manual changes, select this option.</p>
     </li>
 </ul>
 
-<h2 id='zooming'>Zooming Tab</h2>
+<h2>Zooming Tab</h2>
 
 <p>The options in this tab let you control the default zooming behavior as they
 apply to all sites.  You can override the zoom levels for individual sites using
@@ -184,7 +177,7 @@ icon, or selecting NoSquint Site Settings from the page's context menu.</p>
     </li>
 </ul>
 
-<h2 id='colors'>Colors Tab</h2>
+<h2>Colors Tab</h2>
 <p>Sometimes website creators choose to use rather questionable color choices
 which can significantly hinder readability.  NoSquint lets you override the
 standard text colors for <i>all</i> sites here.  Or (probably more usefully),
@@ -232,8 +225,8 @@ Settings from the page's context menu.</p>
 
 <p>Because not all web sites are structured the same, sometimes the default
 logic NoSquint uses to determine the site name doesn't work the way you want it
-to.  Using exceptions, you can control how NoSquint determines what constitutes
-a separate site.</p>
+to.  By way of exceptions, you can control how NoSquint determines what
+constitutes a separate site.</p>
 
 <h3>Use Cases</h3>
 <p>Exceptions are powerful and expressive, and unfortunately can be confusing.
@@ -305,7 +298,7 @@ case.</p>
         <code>example.com/[*]/apps/*</code>
     </li>
     <li>
-        <b>Problem:</b> same scenario as the previous one, but sometimes the
+        <b>Problem:</b> same scenario the previous one, but sometimes the
         server isn't in the URL, so <code>example.com/apps/app1</code> is
         the same site as <code>example.com/server1/apps/app1</code>.<br/>
 
@@ -339,9 +332,8 @@ computed by NoSquint based on the current page's URL and the user-defined list
 of exceptions.  For instance, both <code>foo.example.com</code> and
 <code>myapp.*.example.com</code> could be site names, depending on the
 exceptions defined.  NoSquint looks up zoom levels based on the site name.  The
-site name, as determined by NoSquint, can be viewed in the tooltip by hovering
-over the magnifying glass in the status bar, or by opening the Site Settings
-dialog by left clicking the magnifying glass in the status bar.</p>
+site name, as determined by NoSquint, is by default displayed in the status bar
+beside the current zoom level.</p>
 
 <p>When a wildcard is enclosed in square brackets (i.e. <code>[*]</code> or
 <code>[**]</code>), the literal wildcard (<code>*</code> or <code>**</code>)
@@ -377,10 +369,10 @@ exception begins with a <code>/</code> (front slash) – is applied only to
 <code>/C:/Users/*</code> for Windows Vista causes all home directories to be
 treated distinctly from one another.</p>
 
-<p>When multiple exception patterns match a page's URL, NoSquint will use the
-exception that matches the most non-wildcard characters in the host name.  If
-there are still multiple exceptions in that narrowed list, the exception that
-matches the most non-wildcard characters in the path is then chosen.  If still
-there are multiple exceptions, the first one that matched is chosen.</p>
+<p>When multiple exceptions match a page's URL, NoSquint will use the exception
+that matches the most non-wildcard characters in the host name.  If there are
+still multiple exceptions in that narrowed list, the exception that matches the
+most non-wildcard characters in the path is then chosen.  If still there are
+multiple exceptions, the first one that matched is chosen.</p>
 </body>
 </html>
diff --git a/src/locale/en-US/overlay.properties b/src/locale/en-US/overlay.properties
index 98fa412..1b4fa31 100644
--- a/src/locale/en-US/overlay.properties
+++ b/src/locale/en-US/overlay.properties
@@ -11,5 +11,3 @@ siteSpecificBrokenPrompt=NoSquint will now no longer work correctly until the br
 
 disableTitle=Restore Native Firefox Behavior?
 disablePrompt=Firefox natively provides less sophisticated per-site zooming, which NoSquint replaces.  Having disabled NoSquint, would you now like to restore Firefox's native behavior?
-
-sanitizeLabel=NoSquint Site History
diff --git a/src/locale/en-US/siteprefs.dtd b/src/locale/en-US/siteprefs.dtd
index 16c879f..6714a77 100644
--- a/src/locale/en-US/siteprefs.dtd
+++ b/src/locale/en-US/siteprefs.dtd
@@ -1,4 +1,7 @@
 <!ENTITY ns.pref.title "NoSquint Site Settings">
+<!ENTITY ns.pref.globalWarning "Values set here will be discarded when you leave this site because you are using global zoom and color settings for all sites.">
+<!ENTITY ns.pref.button.global.label "Global Settings">
+<!ENTITY ns.pref.button.global.accesskey "G">
 
 <!ENTITY ns.pref.fullZoom.label "Full zoom level">
 <!ENTITY ns.pref.textZoom.label "Text zoom level">
diff --git a/src/skin/icon-enlarge-16.png b/src/skin/icon-enlarge-16.png
index 9a48f6b..31ac736 100644
Binary files a/src/skin/icon-enlarge-16.png and b/src/skin/icon-enlarge-16.png differ
diff --git a/src/skin/icon-enlarge-24.png b/src/skin/icon-enlarge-24.png
index 4a7bad6..4421fe0 100644
Binary files a/src/skin/icon-enlarge-24.png and b/src/skin/icon-enlarge-24.png differ
diff --git a/src/skin/icon-reduce-16.png b/src/skin/icon-reduce-16.png
index 34a14c7..df5be3c 100644
Binary files a/src/skin/icon-reduce-16.png and b/src/skin/icon-reduce-16.png differ
diff --git a/src/skin/icon-reduce-24.png b/src/skin/icon-reduce-24.png
index 5b92e15..8864e76 100644
Binary files a/src/skin/icon-reduce-24.png and b/src/skin/icon-reduce-24.png differ
diff --git a/src/skin/icon-reset-16.png b/src/skin/icon-reset-16.png
index 34eff11..8e35414 100644
Binary files a/src/skin/icon-reset-16.png and b/src/skin/icon-reset-16.png differ
diff --git a/src/skin/icon-reset-24.png b/src/skin/icon-reset-24.png
index 8cfc2a1..a5ba6ea 100644
Binary files a/src/skin/icon-reset-24.png and b/src/skin/icon-reset-24.png differ
diff --git a/src/skin/toolbar.css b/src/skin/toolbar.css
index c1a8337..c06b485 100644
--- a/src/skin/toolbar.css
+++ b/src/skin/toolbar.css
@@ -22,8 +22,8 @@ toolbar[iconsize="small"] #nosquint-button-reset {
       list-style-image: url("chrome://nosquint/skin/icon-reset-16.png");
 }
 
-#nosquint-menu-settings, #nosquint-view-menu-settings {
-      list-style-image: url("chrome://nosquint/skin/icon-statusbar-16.png");
+#nosquint-menu-settings {
+      list-style-image: url("chrome://nosquint/skin/icon-enlarge-16.png");
 }
 
 #nosquint-status-reset {

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



More information about the Pkg-mozext-commits mailing list