[Pkg-mozext-commits] [nosquint] 04/47: Import of 1.0.0 release into git

David Prévot taffit at moszumanska.debian.org
Tue Apr 28 01:41:16 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 620c2e88691cdc864e59f3448baca1eaebb4d585
Author: Jason Tackaberry <tack at urandom.ca>
Date:   Fri Jan 13 19:41:13 2012 -0500

    Import of 1.0.0 release into git
---
 src/chrome.manifest                  |    3 +-
 src/chrome.manifest.in               |    3 +-
 src/content/help.js                  |    9 +
 src/content/help.xul                 |   22 +
 src/content/init.js                  |   10 +-
 src/content/nosquint.js              |  756 +++++++++++----
 src/content/overlay.xul              |   29 +-
 src/content/prefs.css                |   11 -
 src/content/prefs.js                 |  236 ++++-
 src/content/prefs.xul                |  190 ++--
 src/content/two-level-tlds           | 1696 ++++++++++++++++++++++++++++++++++
 src/defaults/preferences/nosquint.js |   11 +-
 src/install.rdf                      |   46 +-
 src/locale/en-US/help.dtd            |    2 +
 src/locale/en-US/help.html           |  276 ++++++
 src/locale/en-US/overlay.dtd         |    3 +-
 src/locale/en-US/prefs.dtd           |   42 +-
 src/locale/en-US/prefs.properties    |    3 +
 src/skin/icon-enlarge-16.png         |  Bin 0 -> 961 bytes
 src/skin/icon-enlarge-24.png         |  Bin 0 -> 1479 bytes
 src/skin/icon-reduce-16.png          |  Bin 0 -> 673 bytes
 src/skin/icon-reduce-24.png          |  Bin 0 -> 697 bytes
 src/skin/toolbar.css                 |   15 +
 23 files changed, 3014 insertions(+), 349 deletions(-)

diff --git a/src/chrome.manifest b/src/chrome.manifest
index c01d036..e87d7a0 100644
--- a/src/chrome.manifest
+++ b/src/chrome.manifest
@@ -3,4 +3,5 @@ overlay chrome://browser/content/browser.xul chrome://nosquint/content/overlay.x
 
 locale nosquint en-US locale/en-US/
 
-skin nosquint classic/1.0 jar:chrome:/nosquint.jar!/skin/
+skin nosquint classic/1.0 skin/
+style chrome://global/content/customizeToolbar.xul chrome://nosquint/skin/toolbar.css
diff --git a/src/chrome.manifest.in b/src/chrome.manifest.in
index 4086a0d..c576e6c 100644
--- a/src/chrome.manifest.in
+++ b/src/chrome.manifest.in
@@ -3,4 +3,5 @@ overlay chrome://browser/content/browser.xul chrome://nosquint/content/overlay.x
 
 locale nosquint en-US ${JAR}locale/en-US/
 
-skin nosquint classic/1.0 jar:chrome:/nosquint.jar!/skin/
+skin nosquint classic/1.0 ${JAR}skin/
+style chrome://global/content/customizeToolbar.xul chrome://nosquint/skin/toolbar.css
diff --git a/src/content/help.js b/src/content/help.js
new file mode 100644
index 0000000..f67cd4c
--- /dev/null
+++ b/src/content/help.js
@@ -0,0 +1,9 @@
+var NoSquintHelp = {
+
+    init: function(doc) {
+        var b = doc.getElementById("nosquint-help-browser");
+        //b.webBrowserFind.searchString = "NoSquint";
+        //b.webBrowserFind.findNext();
+    }
+};
+
diff --git a/src/content/help.xul b/src/content/help.xul
new file mode 100644
index 0000000..d0dcf28
--- /dev/null
+++ b/src/content/help.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css"  type="text/css"?>
+<!DOCTYPE window SYSTEM "chrome://nosquint/locale/help.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        title="&ns.help.title;"
+        buttons="accept" 
+        ondialogaccept="return"
+        id="nosquint-help-dialog"
+        persist="width height"
+        width="600"
+        height="700"
+        onload="NoSquintHelp.init(document)"> 
+
+    <script src="help.js" />
+
+    <dialogheader title="&ns.help.subtitle;" description="&ns.help.title;" />
+    <groupbox flex="1">
+        <browser type="content" src="chrome://nosquint/locale/help.html" id="nosquint-help-browser" flex="1" />
+    </groupbox>
+</dialog>
diff --git a/src/content/init.js b/src/content/init.js
index a3f8dde..0ee6c4a 100644
--- a/src/content/init.js
+++ b/src/content/init.js
@@ -3,17 +3,25 @@ window.addEventListener("unload", NoSquint.destroy, false);
 
 
 ZoomManager.prototype.getInstance().reset = function() {
+    if (ZoomManager.prototype.getInstance().textZoom == NoSquint.defaultZoomLevel)
+        return;
+
     ZoomManager.prototype.getInstance().textZoom = NoSquint.defaultZoomLevel;
     NoSquint.saveCurrentZoom();
+    NoSquint.updateStatus();
 }
 
 ZoomManager.prototype.getInstance().enlarge = function() {
-    // FIXME: do we want to update any other tabs of pages in this domain?
+    // FIXME: do we want to update any other tabs of pages in this site?
     ZoomManager.prototype.getInstance().textZoom += NoSquint.zoomIncrement;
     NoSquint.saveCurrentZoom();
+    NoSquint.updateStatus();
+    dump("Enlarge text\n");
 }
 
 ZoomManager.prototype.getInstance().reduce = function() {
     ZoomManager.prototype.getInstance().textZoom -= NoSquint.zoomIncrement;
     NoSquint.saveCurrentZoom();
+    NoSquint.updateStatus();
+    dump("Reduce text\n");
 }
diff --git a/src/content/nosquint.js b/src/content/nosquint.js
index e02dce8..73ccb9c 100644
--- a/src/content/nosquint.js
+++ b/src/content/nosquint.js
@@ -1,65 +1,222 @@
-var SLDs = [ 
-    "ac.uk", "co.uk", "gov.uk", "ltd.uk", "me.uk", "mod.uk", "net.uk", 
-    "nic.uk", "nhs.uk", "org.uk", "plc.uk", "police.uk", "sch.uk"
-];
+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");
+} 
 
 var NoSquint = {
 
-    prefs: null,
-    mousePrefs: null,
-    initialized: false,
-    url: null,
-    tabbrowser: null,
-    listeners: [],
-    rObserver: {
-        observe: function(subject, topic, data) { NoSquint.prefsChanged(); }
-    },
-    eatNextPrefChange: false,
-
-    // Prefs
-    domains: {},
+    TLDs: null,         // Hash of multi-level TLDs; shared between windows
+    prefs: null,        // Prefs service rooted at extensions.nosquint.
+    mousePrefs: null,   // Prefers service rooted at mousewheel.withcontrolkey.
+    initialized: false, // True when init() was called
+    prefsRecursion: 0,  // Recursion level in observe()
+    saveTimer: null,    // Timer for saveSiteList()
+    zoomAllTimer: null, // Timer for zoomAll()
+    pruneTimer: null,   // Timer for pruneSites()
+    sitesDirty: false,  // True when sites list needs saving
+    ignoreNextSitesChange: false,
+
+    /* Prefs */
+
+    // Sites hash is keyed on site name, with value being [level, timestamp, visits]
+    sites: {},
+    exceptions: [],
     defaultZoomLevel: 120,
+    saveDelay: 5000,
     zoomIncrement: 10,
-    rememberDomains: true,
-    wheelZoomEnabled: true,
-    wheelActionSave: -1,
+    rememberSites: true,
+    wheelZoomEnabled: false,
+    hideStatus: false,
+    forgetMonths: 6,
+
 
     init: function() {
         if (NoSquint.initialized)
             return;
 
+        var t0 = new Date().getTime();
+
+        /* The multi-level TLDs list is an object shared between all windows.
+         * First iterate over all existing windows to see if we can find it;
+         * 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;
+            }
+        }
+        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 = {};
+            for (var i in lines)
+                NoSquint.TLDs[lines[i]] = true;
+        }
+
+
         NoSquint.initPrefs();
 
-        NoSquint.tabbrowser = document.getElementById("content");
-        NoSquint.tabbrowser.addEventListener("DOMNodeInserted", NoSquint.handleNewBrowser, false);
         window.addEventListener("DOMMouseScroll", NoSquint.handleScrollWheel, false); 
-
-        var pbi = NoSquint.prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
-        pbi.addObserver("", NoSquint.rObserver, false);
+        gBrowser.tabContainer.addEventListener("TabOpen", NoSquint.handleNewTab, false);
+        gBrowser.tabContainer.addEventListener("TabClose", NoSquint.handleCloseTab, false);
 
         NoSquint.initialized = true;
+        // Zoom any tabs anther extension may have opened and attach listeners to them.
+        NoSquint.zoomAll(true);
+
+        var t1 = new Date().getTime();
+        dump("NoSquint: initialization took " + (t1-t0) + " ms\n");
     },
 
     destroy: function() {
         var pbi = NoSquint.prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
-        pbi.removeObserver("", NoSquint.rObserver);
-        // Restore previous mousewheel.withcontrolkey.action value
-        if (NoSquint.mousePrefs && NoSquint.wheelActionSave != -1) {
-            NoSquint.mousePrefs.setIntPref("action", NoSquint.wheelActionSave);
-            NoSquint.prefs.setIntPref("wheelActionSave", -1);
+        pbi.removeObserver("", this);
+        pbi = NoSquint.mousePrefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
+        pbi.removeObserver("", this);
+
+        if (NoSquint.sitesDirty)
+            NoSquint._saveSiteListTimer();
+
+        /* Even though we've removed the pref observers, they lamely still get
+         * invoked during setIntPref 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", 3);
+
+        gBrowser.tabContainer.removeEventListener("TabOpen", NoSquint.handleNewTab, false);
+        gBrowser.tabContainer.removeEventListener("TabClose", NoSquint.handleCloseTab, false);
+        gBrowser.tabContainer.removeEventListener("TabSelect", NoSquint.handleTabChanged, false);
+    },
+
+    getBaseDomainFromHost: function(host) {
+        if (host.match(/^[\d.]+$/) != null)
+            // IP address.
+            return host;
+
+        var parts = host.split('.');
+        var level2 = parts.slice(-2).join('.');
+        var level3 = parts.slice(-3).join('.');
+        if (NoSquint.TLDs[level3])
+            return parts.slice(-4).join('.');
+        else if (NoSquint.TLDs[level2])
+            return level3;
+        return level2;
+    },
+
+
+    processExceptions: function(exlist) {
+        // My eyes!  The googles do nothing!
+        function regexpify(pattern, re_star, re_dblstar) {
+            var parts = pattern.split(/(\[\*+\]|\*+)/);
+            var pattern = [];
+            var sub = [];
+            var length = 0;
+            var wildcards = {
+                '*': '(' + re_star + ')',
+                '**': '(' + re_dblstar + ')',
+                '[*]': re_star,
+                '[**]': re_dblstar
+            };
+
+            for (var i = 0, n = 1; i < parts.length; i++) {
+                var part = parts[i];
+                if (part == '')
+                    continue;
+                if (wildcards[part]) 
+                    pattern.push(wildcards[part]);
+                else {
+                    length += part.length;
+                    pattern.push('(' + part + ')');
+                }
+
+                if (part[0] == '[')
+                    sub.push(part.slice(1, -1));
+                else
+                    sub.push('$' + n++);
+
+            }
+            return [ length, pattern.join(''), sub.join('') ];
+        }
+
+        var exceptions = [];
+        for (var i = 0; i < exlist.length; i++) {
+            var exc = exlist[i]
+            if (!exc)
+                continue;
+            // Escape metacharacters except *
+            exc = exc.replace(/([^\w*\[\]])/g, '\\$1');
+            // Split into host,path parts.
+            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, re_host, sub_host, len_path, re_path, sub_path]);
         }
+        return exceptions;
+    },
+
+
+    getSiteFromURI: function(URI) {
+        //var t0 = new Date().getTime();
+
+        var uri_host = URI.asciiHost;
+        var uri_path = URI.path;
+        var base = NoSquint.getBaseDomainFromHost(uri_host);
+
+        var match = null;
+        var match_weight = 0;
+
+        for (var i in NoSquint.exceptions) {
+            var [len_host, re_host, sub_host, len_path, 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 m2 = uri_path.match(new RegExp('^(' + re_path + ')'));
 
-        // Clean up active progress listeners, unregistering DOMNodeRemoved event listeners
-        for (var i = 0; i < NoSquint.listeners.length; i++) {
-            var browser = NoSquint.listeners[i].browser;
-            browser.parentNode.removeEventListener("DOMNodeRemoved", NoSquint.handleRemoveBrowser, false);
+            if (!m1 || !m2)
+                continue;
+
+            var cur_weight = len_host * 1000 + len_path;
+            if (cur_weight < match_weight)
+                continue;
+
+            site_host = m1[1].replace(new RegExp(re_host), sub_host);
+            site_path = m2[1].replace(new RegExp(re_path), sub_path);
+            match = site_host + site_path;
+            match_weight = cur_weight;
         }
-        NoSquint.listeners = [];
-        // Unregister the event listeners setup during init.
-        NoSquint.tabbrowser.removeEventListener("DOMNodeInserted", NoSquint.handleNewBrowser, false);
-        //window.removeEventListener("DOMMouseScroll", NoSquint.handleScrollWheel, false);
+        //var t1 = new Date().getTime();
+        //dump("NoSquint: getSiteFromURI took " + (t1-t0) + " ms\n");
+
+        if (match)
+            return match;
+        return base;
     },
 
+
     handleScrollWheel: function(event) {
         if (!event.ctrlKey || !NoSquint.wheelZoomEnabled)
             return;
@@ -73,38 +230,26 @@ var NoSquint = {
         event.preventDefault();
     },
 
-    getCurrentBrowser: function() {
-        var nodes = NoSquint.tabbrowser.mPanelContainer.getElementsByTagName("browser");
-        var cur = nodes[NoSquint.tabbrowser.mPanelContainer.selectedIndex];
-        return cur;
+
+    handleTabChanged: function(event) {
+        if (gBrowser.selectedBrowser._noSquintified)
+            NoSquint.updateStatus();
     },
 
-    getDomainFromHost: function(host) {
-        var domain = host.replace(/^.*?([^.]*\.[^.]*$)/, "$1");
-        // Check second-level domains list, if domain is one of these, then
-        // return third-level domain instead.
-        for (var n in SLDs) {
-            if (domain == SLDs[n])
-                return host.replace(/^.*?([^.]*\.[^.]*\.[^.]*$)/, "$1");
-        }
-        return domain;
+    handleNewTab: function(event) {
+        NoSquint.attach(event.target.linkedBrowser);
     },
 
-    handleNewBrowser: function(event) {
-        var nodes = NoSquint.tabbrowser.mPanelContainer.getElementsByTagName("browser");
-        var last = nodes[nodes.length - 1];
-        for (var i = 0; i < NoSquint.listeners.length; i++) {
-            if (NoSquint.listeners[i].browser == last) {
-                //alert("Not making lisener");
-                return;
-            }
-        }
-        last.parentNode.addEventListener("DOMNodeRemoved", NoSquint.handleRemoveBrowser, false);
+    handleCloseTab: function(event) {
+        var browser = event.target.linkedBrowser;
+        browser.removeProgressListener(browser._noSquintListener);
+    },
 
-        var listener = new ProgressListener(last);
+    attach: function(browser) {
+        var listener = new ProgressListener(browser);
+        browser.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+        browser._noSquintListener = listener;
         //alert("Create new listener");
-        NoSquint.listeners[NoSquint.listeners.length] = listener;
-        last.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
 
         /* Sometimes the onLocationChange handler of the ProgressListener will
          * get fired, and sometimes it won't.  My best guess is this is a
@@ -113,182 +258,371 @@ var NoSquint = {
          * browser explicitly for this initial page, rather than rely on the
          * progress handler.
          */
-        window.setTimeout(function() { NoSquint.zoom(last, null); }, 1);
+        setTimeout(function() { NoSquint.zoom(browser, null); }, 1);
     },
     
-    handleRemoveBrowser: function(event) {
-        var nodes = event.target.getElementsByTagName("browser");
-        if (nodes.length == 0) // should this ever happen?
+    updateStatus: function() {
+        if (NoSquint.hideStatus)
             return;
-        // Find the listener for this browser and remove it.
-        // XXX: should we assume nodes.length == 1 here, or should we iterate
-        // over each node?
-        var browser = nodes[0];
-        for (var i = 0; i < NoSquint.listeners.length; i++) {
-            if (NoSquint.listeners[i].browser == browser) {
-                NoSquint.listeners.splice(i, 1);
-                return;
-            }
-        }
+        var browser = gBrowser.selectedBrowser;
+        var level = Math.round(browser.markupDocumentViewer.textZoom * 100);
+        var label = level + "%";
+        if (browser._noSquintSite)
+            label += " (" + browser._noSquintSite + ")";
+        document.getElementById('nosquint-status').label = label;
     },
 
-    zoom: function(node, domain) {
-        if (!node)
-            return;
-        if (domain == null && node.currentURI)
-            domain = NoSquint.getDomainFromHost(node.currentURI.asciiHost);
+    getLevelForSite: function(site) {
+        if (!site)
+            return null;
 
-        if (!domain || !NoSquint.domains[domain] || !NoSquint.rememberDomains)
-            level = NoSquint.defaultZoomLevel;
-        else
-            level = NoSquint.domains[domain];
-
-        //alert("Set zoom for host: " + node + " -- " + domain + " -- " + level + " -- " + NoSquint.rememberDomains + " -- " + node.markupDocumentViewer.textZoom);
-        node.markupDocumentViewer.textZoom = level / 100.0;
+        if (NoSquint.sites[site])
+            return NoSquint.sites[site][0];
+        return null;
     },
 
-    zoomAll: function() {
-        var nodes;
-        try { 
-            nodes = NoSquint.tabbrowser.mPanelContainer.getElementsByTagName("browser"); 
-        } catch(ex) {
-            return;
-        }
-        for (var i = 0; i < nodes.length; i++) {
-            NoSquint.zoom(nodes[i]);
-        }
-    },
+    getLevelForBrowser: function(browser) {
+        if (!browser._noSquintSite)
+            browser._noSquintSite = NoSquint.getSiteFromURI(browser.currentURI);
 
-    onMenuItemCommand: function() {
-        var browser = NoSquint.getCurrentBrowser();
-        var domain = NoSquint.getDomainFromHost(browser.currentURI.asciiHost);
-        var level;
-        if (domain && NoSquint.domains[domain])
-            level = NoSquint.domains[domain];
-        else
-            level = "default";
-        window.openDialog("chrome://nosquint/content/prefs.xul", "", "chrome", domain, level);
+        if (NoSquint.rememberSites) {
+            var site = browser._noSquintSite;
+            var level = NoSquint.getLevelForSite(site);
+            if (level != null)
+                return level;
+        }
+        return NoSquint.defaultZoomLevel;
     },
 
-    initPrefs: function() {
-        if (NoSquint.prefs)
+    zoom: function(browser, level) {
+        if (!browser)
             return;
+        if (level == null)
+            level = NoSquint.getLevelForBrowser(browser);
 
-        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.");
+        browser.markupDocumentViewer.textZoom = level / 100.0;
+        browser._noSquintified = true;
+        if (browser == gBrowser.selectedBrowser)
+            NoSquint.updateStatus();
+    },
 
-        try { NoSquint.prefs.getIntPref("zoomlevel"); } 
-        catch (err) { NoSquint.prefs.setIntPref("zoomlevel", NoSquint.defaultZoomLevel); }
-        try { NoSquint.prefs.getIntPref("zoomIncrement"); } 
-        catch (err) { NoSquint.prefs.setIntPref("zoomIncrement", NoSquint.zoomIncrement); }
+    zoomAll: function(attach) {
+        dump("NoSquint: zooming all tabs; attach listeners = " + attach + "\n");
+        for (var i = 0; i < gBrowser.browsers.length; i++) {
+            var browser = gBrowser.browsers[i];
+            if (browser._noSquintSite)
+                delete browser._noSquintSite;
+            NoSquint.zoom(browser, null);
+            if (attach)
+                NoSquint.attach(browser);
+        }
+    },
 
-        try { NoSquint.prefs.getCharPref("domains"); }
-        catch (err) { NoSquint.prefs.setCharPref("domains", ""); }
+    queueZoomAll: function() {
+        dump("NoSquint: queuing zoom all\n");
+        if (NoSquint.zoomAllTimer != null)
+            clearTimeout(NoSquint.zoomAllTimer);
+        NoSquint.zoomAllTimer = setTimeout(function() { NoSquint.zoomAll(false); }, 1);
+    },
 
-        try { NoSquint.prefs.getBoolPref("rememberDomains"); } 
-        catch (err) { NoSquint.prefs.setBoolPref("rememberDomains", NoSquint.rememberDomains); }
+    openPrefsDialog: function() {
+        var browser = gBrowser.selectedBrowser;
+        var site = NoSquint.getSiteFromURI(browser.currentURI);
+        var level = NoSquint.getLevelForSite(site) || "default";
+        var url = browser.currentURI.asciiHost + browser.currentURI.path;
+        window.openDialog("chrome://nosquint/content/prefs.xul", "NoSquint Settings", "chrome", 
+                          site, level, url, NoSquint);
+    },
 
-        try { NoSquint.prefs.getBoolPref("wheelZoomEnabled"); } 
-        catch (err) { NoSquint.prefs.setBoolPref("wheelZoomEnabled", NoSquint.wheelZoomEnabled); }
 
-        try { NoSquint.wheelActionSave = NoSquint.prefs.getBoolPref("wheelActionSave"); } 
-        catch (err) { NoSquint.prefs.setIntPref("wheelActionSave", NoSquint.wheelActionSave); }
+    locationChanged: function(browser, uri) {
+        var site = NoSquint.getSiteFromURI(uri);
+        if (site != browser._noSquintSite)
+            // Site changed; update timestamp on new site.
+            NoSquint.updateSiteList(site, null, true);
+        browser._noSquintSite = site;
+        setTimeout(function() { NoSquint.zoom(browser, NoSquint.getLevelForBrowser(browser)); }, 1);
+    },
 
 
-        if (NoSquint.wheelActionSave == -1) {
-            NoSquint.wheelActionSave = NoSquint.mousePrefs.getIntPref("action");
-            NoSquint.prefs.setIntPref("wheelActionSave", NoSquint.wheelActionSave);
+    pruneSites: function() {
+        if (!NoSquint.rememberSites || NoSquint.forgetMonths == 0)
+            return;
+    
+        var remove = [];
+        var now = new Date();
+        for (var site in NoSquint.sites) {
+            if (!NoSquint.sites[site])
+                continue
+            var [level, timestamp, counter] = NoSquint.sites[site];
+            var age = now - new Date(timestamp);
+            var prune = (age > NoSquint.forgetMonths*30*24*60*60*1000);
+            if (prune)
+                remove.push(site);
+            dump("NoSquint: prune check: " + site + ", age=" + Math.round(age/1000/60/60/24) + 
+                 " days, prune=" + prune + "\n");
+        }
+        if (remove.length) {
+            for (var i = 0; i < remove.length; i++)
+                delete NoSquint.sites[remove[i]];
+            NoSquint.saveSiteList();
         }
 
-        NoSquint.mousePrefs.setIntPref("action", 0);
-        NoSquint.prefsChanged();
+        // Fire timer once a day.
+        if (NoSquint.pruneTimer == null)
+            NoSquint.pruneTimer = setTimeout(function() { NoSquint.pruneTimer = null; NoSquint.pruneSites(); }, 
+                                             24*60*60*1000);
+            
     },
 
-    initPrefsDialog: function(doc) {
-        NoSquint.initPrefs();
-        doc.getElementById("defaultZoomLevel").value = NoSquint.defaultZoomLevel;
-        doc.getElementById("zoomIncrement").value = NoSquint.zoomIncrement;
-        doc.getElementById("rememberDomains").selectedIndex = NoSquint.rememberDomains ? 1 : 0;
+    /* Saves the current tab's zoom level in the site list.
+     */
+    saveCurrentZoom: function() {
+        if (!NoSquint.rememberSites)
+            return;
+
+        var browser = gBrowser.selectedBrowser;
+        var current_level = Math.round(browser.markupDocumentViewer.textZoom * 100);
+        NoSquint.updateSiteList(browser, current_level);
     },
 
-    savePrefs: function(doc) {
-        if (doc) {
-            NoSquint.prefs.setIntPref("zoomlevel", doc.getElementById("defaultZoomLevel").value);
-            NoSquint.prefs.setIntPref("zoomIncrement", doc.getElementById("zoomIncrement").value);
-            var val = doc.getElementById("rememberDomains").selectedIndex == 0 ? false : true;
-            NoSquint.prefs.setBoolPref("rememberDomains", val);
-            if (window.arguments && window.arguments[0]) {
-                var domain = window.arguments[0];
-                var level = doc.getElementById("domainZoom").value;
-                var domains = NoSquint.prefs.getCharPref("domains");
-                var re = new RegExp(domain + "\\b=\\d+\\b", "ig");
-                if (level == "default")
-                    domains = domains.replace(re, "");
-                else if (domains.search(domain + "=") != -1)
-                    domains = domains.replace(re, domain + "=" + level);
+    updateSiteList: function(site_or_browser, level, update_timestamp) {
+        var site = site_or_browser;
+        if (typeof(site_or_browser) != "string")
+            site = site_or_browser._noSquintSite;
+        if (!site)
+            return false;
+        if (update_timestamp) {
+            if (!level && !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 (level) {
+            level = parseInt(level) || NoSquint.defaultZoomLevel;
+            if (level == NoSquint.defaultZoomLevel) {
+                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])
+                    NoSquint.sites[site] = [level, new Date().getTime(), 1];
                 else
-                    domains += " " + domain + "=" + level;
-
-                domains = domains.replace(/ +/g, " ");
-                NoSquint.prefs.setCharPref("domains", domains);
+                    NoSquint.sites[site][0] = level;
+                // TODO: go through current tabs and resize tabs for this site
             }
-            return;
+            NoSquint.saveSiteList();
         }
+        return true;
+    },
 
-        var domains = [];
-        for (var domain in NoSquint.domains) {
-            if (NoSquint.domains[domain]) {
-                domains[domains.length] = domain + "=" + NoSquint.domains[domain];
-            }
-        }
-        var domainList = domains.join(" ");
-        NoSquint.eatNextPrefChange = true;
-        NoSquint.prefs.setCharPref("domains", domainList);
+    /* Stores the site list in the prefs service.
+     *
+     * NOTE: This must only be called when the list has actually changed, or
+     * else the next time a change is made in the Settings dialog, it will
+     * be ignored.
+     */
+    saveSiteList: function() {
+        if (NoSquint.saveTimer != null)
+            clearTimeout(NoSquint.saveTimer);
+
+        NoSquint.sitesDirty = true;
+        // The list is actually saved (by default) 5s later, so if the user
+        // changes the zoom several times in a short period of time, we aren't
+        // needlessly iterating over the sites array.
+        NoSquint.saveTimer = setTimeout(function() { NoSquint._saveSiteListTimer(); }, NoSquint.saveDelay);
     },
 
-    prefsChanged: function() {
-        if (NoSquint.eatNextPrefChange) {
-            NoSquint.eatNextPrefChange = false;
-            return;
-        }
-        NoSquint.defaultZoomLevel = NoSquint.prefs.getIntPref("zoomlevel");
-        NoSquint.zoomIncrement = NoSquint.prefs.getIntPref("zoomIncrement");
-        NoSquint.wheelZoomEnabled = NoSquint.prefs.getBoolPref("wheelZoomEnabled");
-        // TODO: if rememberDomains has been changed from false to true, iterate
-        // over current browsers and remember current zoom levels for these windows.
-        NoSquint.rememberDomains = NoSquint.prefs.getBoolPref("rememberDomains");
-        var domainList = NoSquint.prefs.getCharPref("domains");
-        var domains = domainList.replace(/(^\s+|\s+$)/g, "").split(" ");
-        //var domains = domainList.split(" ");
-        NoSquint.domains = {};
-        for (var i = 0; i < domains.length; i++) {
-            var domain = domains[i].split("=");
-            if (domain.length != 2)
-                continue; // malformed
-            NoSquint.domains[domain[0]] = parseInt(domain[1]);
+    _saveSiteListTimer: function() {
+        /* 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.
+         */
+        var t0 = new Date().getTime();
+        var sites = [];
+        for (var site in NoSquint.sites) {
+            if (!NoSquint.sites[site])
+                continue
+            var [level, timestamp, counter] = NoSquint.sites[site];
+            sites.push(site + "=" + level + "," + timestamp + "," + counter);
         }
-        NoSquint.zoomAll();
+        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.
+         */
+        NoSquint.ignoreNextSitesChange = true;
+        NoSquint.prefs.setCharPref("sites", siteList);
+        dump("NoSquint: Sites save took: " + (new Date().getTime() - t0) + "ms\n");
+        clearTimeout(NoSquint.saveTimer);
+        NoSquint.saveTimer = null;
+        NoSquint.sitesDirty = false;
     },
 
-    locationChanged: function(browser, uri) {
-        window.setTimeout(function() { NoSquint.zoom(browser, NoSquint.getDomainFromHost(uri.asciiHost)); }, 1);
+    initPrefs: function() {
+        if (NoSquint.prefs)
+            return;
+
+        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.");
+
+        // Backward compatibility: convert old prefs.
+        try { 
+            // In 0.9.80, domains renamed to sites
+            var domains = NoSquint.prefs.getCharPref("domains");
+            NoSquint.prefs.setCharPref("sites", domains);
+            NoSquint.prefs.clearUserPref("domains");
+        } catch (err) {}
+
+        try { 
+            // In 0.9.80, rememberDomains renamed to rememberSites
+            var rememberDomains = NoSquint.prefs.getBoolPref("rememberDomains");
+            NoSquint.prefs.setBoolPref("rememberSites", rememberDomains);
+            NoSquint.prefs.clearUserPref("rememberDomains");
+        } catch (err) {} 
+
+        var prefs = [
+            "zoomIncrement", "wheelZoomEnabled", "zoomIncrement", "hideStatus", "zoomlevel",
+            "sitesSaveDelay", "rememberSites", "exceptions", "sites", "forgetMonths"
+        ];
+        for (var i in prefs)
+            NoSquint.observe(null, "nsPref:changed", prefs[i]);
+
+        var pbi = NoSquint.prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
+        pbi.addObserver("", this, false);
+        pbi = NoSquint.mousePrefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
+        pbi.addObserver("", this, false);
     },
 
-    saveCurrentZoom: function() {
-        if (!NoSquint.rememberDomains)
+    observe: function(subject, topic, data) {
+        if (topic != "nsPref:changed" || typeof(NoSquint) == 'undefined' || !NoSquint.prefs)
             return;
 
-        var browser = NoSquint.getCurrentBrowser();
-        var domain = NoSquint.getDomainFromHost(browser.currentURI.asciiHost);
-        var level = Math.round(browser.markupDocumentViewer.textZoom * 100);
-        if (level != NoSquint.defaultZoomLevel)
-            NoSquint.domains[domain] = level;
-        else
-            delete NoSquint.domains[domain];
-
-        NoSquint.savePrefs(null);
+        NoSquint.prefsRecursion++;
+
+        switch (data) {
+            case "action":
+                if (NoSquint.prefsRecursion > 1)
+                    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) {
+                    NoSquint.prefs.setBoolPref("wheelZoomEnabled", true);
+                    //alert("Setting wheelZoomEnabled=true, action=0 because action == 3");
+                    NoSquint.mousePrefs.setIntPref("action", 0);
+                } else if (action != 0) {
+                    //alert("Setting wheelZoomEnabled=false because action != 3 == " + action);
+                    NoSquint.prefs.setBoolPref("wheelZoomEnabled", false);
+                }
+                break;
+
+            case "zoomlevel":
+                NoSquint.defaultZoomLevel = NoSquint.prefs.getIntPref("zoomlevel");
+                NoSquint.queueZoomAll();
+                break;
+
+            case "wheelZoomEnabled":
+                NoSquint.wheelZoomEnabled = NoSquint.prefs.getBoolPref("wheelZoomEnabled");
+                //alert("Pref wheelZoomEnabled changed to " + NoSquint.wheelZoomEnabled);
+                if (NoSquint.wheelZoomEnabled)
+                    NoSquint.mousePrefs.setIntPref("action", 0);
+                break;
+
+            case "zoomIncrement":
+                NoSquint.zoomIncrement = NoSquint.prefs.getIntPref("zoomIncrement");
+                break;
+
+            case "forgetMonths":
+                NoSquint.forgetMonths = NoSquint.prefs.getIntPref("forgetMonths");
+                NoSquint.pruneSites();
+                break;
+
+            case "hideStatus":
+                NoSquint.hideStatus = NoSquint.prefs.getBoolPref("hideStatus");
+                document.getElementById("nosquint-status").hidden = NoSquint.hideStatus;
+                if (NoSquint.hideStatus)
+                    gBrowser.tabContainer.removeEventListener("TabSelect", NoSquint.handleTabChanged, false);
+                else {
+                    gBrowser.tabContainer.addEventListener("TabSelect", NoSquint.handleTabChanged, false);
+                    NoSquint.handleTabChanged();
+                }
+                break;
+
+            case "rememberSites":
+                NoSquint.rememberSites = NoSquint.prefs.getBoolPref("rememberSites");
+                NoSquint.queueZoomAll();
+                break;
+
+            case "sitesSaveDelay":
+                NoSquint.saveDelay = NoSquint.prefs.getIntPref("sitesSaveDelay");
+                break;
+
+            case "exceptions":
+                // Parse exceptions list from prefs
+                var exlist = NoSquint.prefs.getCharPref("exceptions").replace(/(^\s+|\s+$)/g, "").split(" ");
+                //var list = NoSquint.parseExceptions(NoSquint.prefs.getCharPref("exceptions"));
+                NoSquint.exceptions = NoSquint.processExceptions(exlist);
+                NoSquint.queueZoomAll();
+                break;
+
+            case "sites":
+                /* Parse site list from prefs.  The prefs string a list of site specs,
+                 * delimited by a space, in the form sitename=level,timestamp,visits.
+                 * Spaces are not allowed in any value.  Level, timestamp, and visits
+                 * are all integers.  The parsing code tries to be robust and handle
+                 * malformed entries gracefully (in case the user edits them manually
+                 * and screws up).
+                 */
+                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]) || NoSquint.defaultZoomLevel, now, 1];
+                    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 (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.queueZoomAll();
+                break;
+            }
+        NoSquint.prefsRecursion--;
     }
 };
 
@@ -310,15 +644,29 @@ ProgressListener.prototype.QueryInterface = function(aIID) {
 
 ProgressListener.prototype.onLocationChange = function(progress, request, uri) {
     //alert("Location change: " + uri.spec + " -- old: " + this.lastURI);
-    if (uri.spec == this.lastURI)
-        return;
+    /* XXX: it makes sense that if the URI hasn't changed we don't need to
+     * change zoom, but there seems to be a bug in ff where if the page contains
+     * frames, reloads will not apply the browser's zoom level to the frame. 
+     * So this we make sure we reset the textzoom level for all page loads.
+     */
     this.lastURI = uri.spec;
     NoSquint.locationChanged(this.browser, uri);
 }
 
+ProgressListener.prototype.onStateChange = function(progress) {
+    if (!progress.isLoadingDocument) {
+        if (NoSquint.sitesDirty)
+            NoSquint.saveSiteList();
+    } else if (NoSquint.saveTimer) {
+        // Browser is not idle and we have a sites list save pending.  Clear
+        // the timer, we will save it out after we're done loading all pages.
+        clearTimeout(NoSquint.saveTimer);
+        NoSquint.saveTimer = null;
+    }
+}
+
 ProgressListener.prototype.onProgressChange =
 ProgressListener.prototype.onStatusChange =
-ProgressListener.prototype.onStateChange =
 ProgressListener.prototype.onSecurityChange =
 ProgressListener.prototype.onLinkIconAvailable = function() {
     return 0;
diff --git a/src/content/overlay.xul b/src/content/overlay.xul
index e2930aa..483403d 100644
--- a/src/content/overlay.xul
+++ b/src/content/overlay.xul
@@ -1,12 +1,37 @@
 <?xml version="1.0"?>
 <!DOCTYPE overlay SYSTEM "chrome://nosquint/locale/overlay.dtd">
+<?xml-stylesheet href="chrome://nosquint/skin/toolbar.css"  type="text/css"?>
+
 <overlay id="nosquint-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     <script src="nosquint.js" />
     <script src="init.js" />
 
+    <toolbarpalette id="BrowserToolbarPalette">
+        <toolbaritem id="nosquint-toolbar">
+            <toolbarbutton id="nosquint-button-reduce" class="toolbarbutton-1"
+                           label="Shrink Text" tooltiptext="Makes text smaller for this site"
+                            oncommand="ZoomManager.prototype.getInstance().reduce();" />
+            <toolbarbutton id="nosquint-button-enlarge" class="toolbarbutton-1"
+                           label="Enlarge Text" tooltiptext="Makes text larger for this site"
+                            oncommand="ZoomManager.prototype.getInstance().enlarge();" />
+        </toolbaritem>
+    </toolbarpalette>
+
+    <keyset id="mainKeyset">
+        <key id="nosquint-menu-key" key="0" modifiers="alt" command="cmd_noSquintPrefs" />
+    </keyset>
+
+    <commandset id="mainCommandSet">
+        <command id="cmd_noSquintPrefs" oncommand="NoSquint.openPrefsDialog()" />
+    </commandset>
+
     <menupopup id="menu_ToolsPopup">
-        <menuitem id="nosquint-menuitem" label="&nosquint;" oncommand="NoSquint.onMenuItemCommand(event);"
-                  insertafter="devToolsSeparator" />
+        <menuitem id="nosquint-menuitem" label="&nosquint.label;" oncommand="NoSquint.openPrefsDialog()"
+                  key="nosquint-menu-key" accesskey="&nosquint.accesskey;" insertafter="devToolsSeparator" />
     </menupopup>
 
+    <statusbar id="status-bar">
+        <statusbarpanel class="statusbarpanel-iconic-text" id="nosquint-status" label="100%" 
+                        onclick="NoSquint.openPrefsDialog()" src="chrome://nosquint/skin/icon-enlarge-16.png" />
+    </statusbar>
 </overlay> 
diff --git a/src/content/prefs.css b/src/content/prefs.css
index 5d70b8f..bb10dbf 100644
--- a/src/content/prefs.css
+++ b/src/content/prefs.css
@@ -6,17 +6,6 @@ label.percent {
     margin-left: -0.18em;
 }
 
-.tip {
-    margin-left: 4.7em;
-    margin-bottom: 1.0em;
-    margin-right: 0.5em;
-    color: #666;
-}
-
 .indent {
     margin-left: 2.5em;
 }
-
-#domainZoom-button {
-    margin-bottom: 0.75em;
-}
diff --git a/src/content/prefs.js b/src/content/prefs.js
index 6b022f6..e381883 100644
--- a/src/content/prefs.js
+++ b/src/content/prefs.js
@@ -1,24 +1,216 @@
-function domains_rb_select(doc) {
-    var label = doc.getElementById("domainZoom-label");
-    if (!window.arguments || window.arguments[0] == "") {
-        if (label.value.search("\\(") == -1)
-            label.value = label.value.replace(":", " ([no domain in URL])");
-        return;
-    }
-    var box = doc.getElementById("domainZoom-box");
-    var disabled = doc.getElementById("rememberDomains").selectedIndex == 0;
-    if (label.value.search("\\(") == -1) {
-        label.value = label.value.replace(":", " (" + window.arguments[0] + "):");
-        doc.getElementById("domainZoom").value = window.arguments[1];
-    }
+var NoSquintPrefs = {
+    prefs: null,
+    site: null,
+    level: null,
+    NoSquint: null,
+
+    init: function(doc) {
+        NoSquintPrefs.doc = doc;
+
+        if (window.arguments) {
+            NoSquintPrefs.site = window.arguments[0];
+            NoSquintPrefs.level = window.arguments[1];
+            NoSquintPrefs.url = window.arguments[2];
+            NoSquintPrefs.NoSquint = window.arguments[3];
+            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.")
+        }
+        doc.getElementById("defaultZoomLevel").value = NoSquintPrefs.prefs.getIntPref("zoomlevel");
+        doc.getElementById("zoomIncrement").value = NoSquintPrefs.prefs.getIntPref("zoomIncrement");
+        doc.getElementById("rememberSites").selectedIndex = NoSquintPrefs.prefs.getBoolPref("rememberSites") ? 1 : 0;
+        doc.getElementById("showStatus").checked = !NoSquintPrefs.prefs.getBoolPref("hideStatus");
+        doc.getElementById("wheelZoomEnabled").checked = NoSquintPrefs.prefs.getBoolPref("wheelZoomEnabled");
+
+        var forget_cb = doc.getElementById("siteForget");
+        var months = NoSquintPrefs.prefs.getIntPref("forgetMonths");
+        //if (months < 0 || months > 12 || months % 3 != 0)
+        //    months = 6;
+        forget_cb.checked = (months != 0);
+        if (months)
+            doc.getElementById("siteForget-menu").value = months;
+        forget_cb.addEventListener("CheckboxStateChange", NoSquintPrefs.forgetMonthsChecked, false);
+        NoSquintPrefs.forgetMonthsChecked();
+
+        NoSquintPrefs.sitesRadioSelect();
+        NoSquintPrefs.parseExceptions();
+        NoSquintPrefs.excListSelect();
+    },
+
+    parseExceptions: function() {
+        var exstr = NoSquintPrefs.prefs.getCharPref("exceptions");
+        // Trim whitespace and split on space.
+        exlist = exstr.replace(/(^\s+|\s+$)/g, "").split(" ");
+        for (var i = 0; i < exlist.length; i++) {
+            if (exlist[i])
+                NoSquintPrefs.exceptionsListAdd(exlist[i], false);
+        }
+        NoSquintPrefs.doc.getElementById("exceptionsList")._changed = false;
+    },
+
+    exceptionsListAdd: function(pattern, check_dupe) {
+        // Strip URI scheme from pattern (if it exists)
+        pattern = pattern.replace(/^\w+:\/\//, '');
+
+        var listbox = NoSquintPrefs.doc.getElementById("exceptionsList");
+        if (check_dupe) {
+            for (var i = 0; i < listbox.childNodes.length; i++) {
+                var node = listbox.childNodes[i];
+                if (node.childNodes[0].getAttribute("label") == pattern) {
+                    var bundle = NoSquintPrefs.doc.getElementById("nosquint-prefs-bundle");
+                    alert(bundle.getString('patternExists'));
+                    return;
+                }
+            }
+        }
+
+        var node = NoSquintPrefs.doc.createElement("listitem");
+        var li1 = NoSquintPrefs.doc.createElement("listcell");
+        li1.setAttribute("label", pattern);
+        node.appendChild(li1);
+        listbox.appendChild(node);
+        node.addEventListener("dblclick", NoSquintPrefs.buttonEditException, false);
+        listbox._changed = true;
+    },
+
+    textPatternKeyPress: function(event) {
+        if (event.keyCode == 13) {
+            NoSquintPrefs.buttonAddException();
+            return false;
+        }
+    },
+
+    textPatternChange: function() {
+        var pattern = NoSquintPrefs.doc.getElementById("pattern").value;
+        var exc_button = NoSquintPrefs.doc.getElementById("exceptionAdd-button");
+        exc_button.disabled = (pattern == '');
+        //var grp_button = NoSquintPrefs.doc.getElementById("groupAdd-button");
+        //exc_button.disabled = grp_button.disabled = (pattern == '');
+    },
+
+    excListKeyPress: function(event) {
+        if (event.keyCode == 13) {
+            NoSquintPrefs.buttonEditException();
+            return false;
+        }
+    },
+
+    excListSelect: function() {
+        var btn = NoSquintPrefs.doc.getElementById("exceptionRemove-button");
+        var listbox = NoSquintPrefs.doc.getElementById("exceptionsList");
+        btn.disabled = (listbox.selectedItems.length == 0);
+
+        var btn = NoSquintPrefs.doc.getElementById("exceptionEdit-button");
+        btn.disabled = listbox.selectedItems.length != 1;
+    },
 
-    for (i = 0; i < box.childNodes.length; i++)
-        box.childNodes[i].disabled = disabled;
-    doc.getElementById("domainZoom-button").disabled = disabled;
-    //doc.getElementById("domainZoom-grid").hidden = false;
-}
+    buttonCopyFromURL: function() {
+        var pattern = NoSquintPrefs.doc.getElementById("pattern");
+        pattern.value = NoSquintPrefs.url;
+        NoSquintPrefs.textPatternChange();
+    },
 
-function domains_use_default(doc) {
-    window.arguments[1] = "default";
-    doc.getElementById("domainZoom").value = "default";
-}
+    buttonAddException: function() {
+        var pattern = NoSquintPrefs.doc.getElementById("pattern");
+        NoSquintPrefs.exceptionsListAdd(pattern.value, true);
+        pattern.value = '';
+        NoSquintPrefs.textPatternChange();
+    },
+
+
+    buttonEditException: function() {
+        var listbox = NoSquintPrefs.doc.getElementById("exceptionsList");
+        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);
+            listbox._changed = true;
+        }
+    },
+
+    buttonRemoveException: function() {
+        var listbox = NoSquintPrefs.doc.getElementById("exceptionsList");
+        while (listbox.selectedItems.length)
+            listbox.removeChild(listbox.selectedItems[0]);
+        listbox._changed = true;
+    },
+
+    forgetMonthsChecked: function() {
+        var checked = NoSquintPrefs.doc.getElementById('siteForget').checked;
+        NoSquintPrefs.doc.getElementById('siteForget-menu').disabled = !checked;
+    },
+
+    sitesRadioSelect: function() {
+        var doc = NoSquintPrefs.doc;
+        var label = doc.getElementById("siteZoom-label");
+        if (NoSquintPrefs.site) {
+            if (label.value.search("\\(") == -1) {
+                label.value = label.value.replace(":", " (" + NoSquintPrefs.site + "):");
+                doc.getElementById("siteZoom").value = NoSquintPrefs.level;
+            }
+        }
+        else
+            doc.getElementById("siteZoom-box").style.display = "none";
+        if (!NoSquintPrefs.url)
+            doc.getElementById("copyURL-button").style.display = "none";
+
+        var disabled = doc.getElementById("rememberSites").selectedIndex == 0;
+        NoSquintPrefs.enableTree(doc.getElementById("siteZoom-box"), disabled);
+        NoSquintPrefs.enableTree(doc.getElementById("siteForget-box"), disabled);
+    },
+
+    enableTree: function(node, state) {
+        for (var i = 0; i < node.childNodes.length; i++) {
+            var child = node.childNodes[i];
+            if (state && child.disabled == false || child.disabled == true)
+                child.disabled = state;
+            if (child.childNodes.length)
+                NoSquintPrefs.enableTree(child, state);
+        }
+    },
+
+    buttonSitesUseDefault: function() {
+        NoSquintPrefs.doc.getElementById("siteZoom").value = "default";
+    },
+
+    help: function() {
+        window.openDialog("chrome://nosquint/content/help.xul", "NoSquint Help", "chrome");
+    },
+
+    close: function() {
+        var doc = NoSquintPrefs.doc;
+        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("zoomIncrement", doc.getElementById("zoomIncrement").value);
+        var val = doc.getElementById("rememberSites").selectedIndex == 0 ? false : true;
+        NoSquintPrefs.prefs.setBoolPref("rememberSites", val);
+
+        var listbox = NoSquintPrefs.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);
+            }
+            NoSquintPrefs.prefs.setCharPref("exceptions", exceptions.join(' '));
+        }
+        if (!NoSquintPrefs.doc.getElementById("siteForget").checked)
+            NoSquintPrefs.prefs.setIntPref("forgetMonths", 0);
+        else
+            NoSquintPrefs.prefs.setIntPref("forgetMonths", NoSquintPrefs.doc.getElementById("siteForget-menu").value);
+
+        if (!NoSquintPrefs.NoSquint)
+            return;
+
+        var level = doc.getElementById("siteZoom").value;
+        if (NoSquintPrefs.NoSquint.updateSiteList(NoSquintPrefs.site, level))
+            NoSquintPrefs.NoSquint.queueZoomAll();
+            //NoSquintPrefs.NoSquint.zoomAll(false);
+    }
+};
diff --git a/src/content/prefs.xul b/src/content/prefs.xul
index be25a58..7fcc362 100644
--- a/src/content/prefs.xul
+++ b/src/content/prefs.xul
@@ -6,76 +6,134 @@
 <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" 
-        ondialogaccept="NoSquint.savePrefs(document)"
+        buttons="help,accept,cancel" 
+        ondialogaccept="NoSquintPrefs.close()"
+        ondialoghelp="NoSquintPrefs.help()"
+        id="nosquint-prefs-dialog"
         persist="screenX screenY"
-        onload="NoSquint.initPrefsDialog(document)">
+        onload="NoSquintPrefs.init(document)">
 
-    <script src="nosquint.js" />
     <script src="prefs.js" />
 
-    <groupbox id="global">
-        <caption label="&ns.pref.globalbox.label;" />
-        <!-- XUL's grid container doesn't quite cut it. -->
-        <html:table cellspacing="0" cellpadding="0" border="0">
-            <html:tr>
-                <html:td align="right" style="white-space: nowrap;">
-                    <label>&ns.pref.level.label;:</label>
-                </html:td>
-                <html:td width="100%">
-                    <hbox align="center">
-                    <textbox id="defaultZoomLevel" size="5"/>
-                    <label class="percent">%</label>
-                    </hbox>
-                </html:td>
-            </html:tr>
-            <html:tr>
-                <html:td colspan="2" width="100%">
-                    <html:div class="tip">&ns.pref.level.tip;</html:div>
-                </html:td>
-            </html:tr>
-            <html:tr>
-                <html:td align="right" style="white-space: nowrap;">
-                    <label>&ns.pref.increment.label;:</label>
-                </html:td>
-                <html:td width="100%">
-                    <hbox align="center">
-                    <textbox id="zoomIncrement" size="5"/>
-                    <label class="percent">%</label>
+    <stringbundleset id="stringbundleset">
+        <stringbundle id="nosquint-prefs-bundle" src="chrome://nosquint/locale/prefs.properties" />
+    </stringbundleset>
+
+    <tabbox flex="1">
+        <tabs>
+            <tab label="&ns.pref.tab.options.label;" />
+            <tab label="&ns.pref.tab.exceptions.label;" />
+        </tabs>
+
+        <tabpanels flex="1">
+            <!-- Options Tab -->
+            <tabpanel id="optionstab" flex="1">
+                <vbox flex="1">
+                    <groupbox>
+                        <caption label="&ns.pref.general.caption;" />
+                        <grid>
+                            <columns>
+                                <column />
+                                <column />
+                                <column />
+                            </columns>
+                            <rows>
+                                <row align="center">
+                                    <label>&ns.pref.general.level.label;:</label>
+                                    <textbox id="defaultZoomLevel" size="5"/>
+                                    <label class="percent">%</label>
+                                </row>
+                                <row align="center">
+                                    <hbox>
+                                        <spacer flex="1" />
+                                        <label>&ns.pref.general.increment.label;:</label>
+                                    </hbox>
+                                    <textbox id="zoomIncrement" size="5"/>
+                                    <label class="percent">%</label>
+                                </row>
+                            </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" />
+                        </vbox>
+                    </groupbox>
+
+                    <groupbox id="site">
+                        <caption label="&ns.pref.site.caption;" />
+                        <radiogroup id="rememberSites" onselect="NoSquintPrefs.sitesRadioSelect()">
+                            <radio label="&ns.pref.site.noRemember.label;" />
+                            <radio label="&ns.pref.site.remember.label;" />
+                        </radiogroup>
+                        <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>
+                        <grid id="siteZoom-box" class="indent">
+                            <rows>
+                                <row align="center">
+                                    <label disabled="true" id="siteZoom-label" value="&ns.pref.site.current.label;:" />
+                                    <textbox disabled="true" id="siteZoom" size="5"/>
+                                    <label disabled="true" class="percent">%</label>
+                                </row>
+                                <row align="top">
+                                    <label />
+                                    <button  disabled="true" label="&ns.pref.site.resetButton.label;" 
+                                             id="siteZoom-button" oncommand="NoSquintPrefs.buttonSitesUseDefault()" />
+                                </row>
+                            </rows>
+                        </grid>
+                    </groupbox>
+                </vbox>
+            </tabpanel>
+
+
+            <!-- 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>
+
+                    <label value="&ns.pref.exceptions.pattern.label;:" />
+                    <textbox id="pattern" width="100%" oninput="NoSquintPrefs.textPatternChange()" 
+                             onkeypress="return NoSquintPrefs.textPatternKeyPress(event)" />
+                    <hbox>
+                        <button label="&ns.pref.exceptions.copyButton.label;" id="copyURL-button"
+                                accesskey="&ns.pref.exceptions.copyButton.accesskey;"
+                                oncommand="NoSquintPrefs.buttonCopyFromURL()" />
+                        <spacer flex="1" />
+                        <button label="&ns.pref.exceptions.addButton.label;" icon="add" id="exceptionAdd-button" 
+                                accesskey="&ns.pref.exceptions.addButton.accesskey;"
+                                disabled="true" oncommand="NoSquintPrefs.buttonAddException()" />
                     </hbox>
-                </html:td>
-            </html:tr>
-            <html:tr>
-                <html:td colspan="2" width="100%">
-                    <html:div class="tip">&ns.pref.increment.tip;</html:div>
-                </html:td>
-            </html:tr>
-        </html:table>
-    </groupbox>
 
-    <groupbox id="domain">
-        <caption label="&ns.pref.domainbox.label;" />
-        <radiogroup id="rememberDomains" onselect="domains_rb_select(document)">
-            <radio label="&ns.pref.noRememberDomains;" />
-            <radio label="&ns.pref.rememberDomains;" />
-        </radiogroup>
-        <grid id="domainZoom-grid" class="indent">
-            <columns>
-                <column />
-                <column />
-                <column />
-            </columns>
-            <rows>
-                <row align="center" id="domainZoom-box">
-                    <label disabled="true" id="domainZoom-label" value="&ns.pref.domainZoom.label;:" />
-                    <textbox disabled="true" id="domainZoom" size="5"/>
-                    <label disabled="true" class="percent">%</label>
-                </row>
-                <row>
-                    <label />
-                    <button  disabled="true" label="Use Default" id="domainZoom-button" onclick="domains_use_default(document)" />
-                </row>
-            </rows>
-        </grid>
-    </groupbox>
+                    <separator />
+
+                <listbox id="exceptionsList" flex="1" seltype="multiple" rows="8" 
+                         onkeypress="return NoSquintPrefs.excListKeyPress(event)" 
+                         onselect="NoSquintPrefs.excListSelect()">
+                    <listhead>
+                        <listheader label="&ns.pref.exceptions.list.col1.label;" />
+                    </listhead>
+                </listbox>
+                <hbox>
+                    <button label="&ns.pref.exceptions.editButton.label;" id="exceptionEdit-button" 
+                            accesskey="&ns.pref.exceptions.editButton.accesskey;"
+                            oncommand="NoSquintPrefs.buttonEditException()" />
+                    <button label="&ns.pref.exceptions.removeButton.label;" icon="remove" id="exceptionRemove-button"
+                            accesskey="&ns.pref.exceptions.removeButton.accesskey;"
+                            oncommand="NoSquintPrefs.buttonRemoveException()" />
+                </hbox>
+                </vbox>
+            </tabpanel>
+
+        </tabpanels>
+    </tabbox>
 </dialog>
diff --git a/src/content/two-level-tlds b/src/content/two-level-tlds
new file mode 100644
index 0000000..74ae495
--- /dev/null
+++ b/src/content/two-level-tlds
@@ -0,0 +1,1696 @@
+2000.hu
+ab.ca
+ab.se
+abo.pa
+ac.ae
+ac.at
+ac.be
+ac.cn
+ac.com
+ac.cr
+ac.cy
+ac.fj
+ac.fk
+ac.gg
+ac.gn
+ac.id
+ac.il
+ac.im
+ac.in
+ac.ir
+ac.je
+ac.jp
+ac.ke
+ac.kr
+ac.ma
+ac.mw
+ac.ng
+ac.nz
+ac.om
+ac.pa
+ac.pg
+ac.ru
+ac.rw
+ac.se
+ac.th
+ac.tj
+ac.tz
+ac.ug
+ac.uk
+ac.vn
+ac.yu
+ac.za
+ac.zm
+ac.zw
+act.au
+ad.jp
+adm.br
+adult.ht
+adv.br
+adygeya.ru
+aero.mv
+aero.tt
+aeroport.fr
+agr.br
+agrar.hu
+agro.pl
+ah.cn
+aichi.jp
+aid.pl
+ak.us
+akita.jp
+al.us
+aland.fi
+alderney.gg
+alt.na
+alt.za
+altai.ru
+am.br
+amur.ru
+amursk.ru
+aomori.jp
+ar.us
+arkhangelsk.ru
+army.mil
+arq.br
+art.br
+art.do
+art.dz
+art.ht
+art.pl
+arts.co
+arts.ro
+arts.ve
+asn.au
+asn.lv
+ass.dz
+assedic.fr
+assn.lk
+asso.dz
+asso.fr
+asso.gp
+asso.ht
+asso.mc
+asso.re
+astrakhan.ru
+at.tt
+atm.pl
+ato.br
+au.com
+au.tt
+auto.pl
+av.tr
+avocat.fr
+avoues.fr
+az.us
+baikal.ru
+barreau.fr
+bashkiria.ru
+bbs.tr
+bc.ca
+bd.se
+be.tt
+bel.tr
+belgie.be
+belgorod.ru
+bib.ve
+bio.br
+bir.ru
+biz.az
+biz.cy
+biz.et
+biz.fj
+biz.mv
+biz.nr
+biz.om
+biz.pk
+biz.pl
+biz.pr
+biz.tj
+biz.tr
+biz.tt
+biz.vn
+bj.cn
+bl.uk
+bmd.br
+bolt.hu
+bourse.za
+br.com
+brand.se
+british-library.uk
+bryansk.ru
+buryatia.ru
+c.se
+ca.tt
+ca.us
+casino.hu
+cbg.ru
+cci.fr
+ch.vu
+chambagri.fr
+chel.ru
+chelyabinsk.ru
+cherkassy.ua
+chernigov.ua
+chernovtsy.ua
+chiba.jp
+chirurgiens-dentistes.fr
+chita.ru
+chukotka.ru
+chuvashia.ru
+cim.br
+city.hu
+city.za
+ck.ua
+club.tw
+cmw.ru
+cn.com
+cn.ua
+cng.br
+cnt.br
+co.ae
+co.ag
+co.ao
+co.at
+co.bw
+co.ck
+co.cr
+co.dk
+co.fk
+co.gg
+co.hu
+co.id
+co.il
+co.im
+co.in
+co.ir
+co.je
+co.jp
+co.ke
+co.kr
+co.ls
+co.ma
+co.mu
+co.mw
+co.nz
+co.om
+co.rw
+co.st
+co.th
+co.tj
+co.tt
+co.tv
+co.tz
+co.ug
+co.uk
+co.us
+co.ve
+co.vi
+co.yu
+co.za
+co.zm
+co.zw
+com.ac
+com.ae
+com.af
+com.ag
+com.ai
+com.al
+com.an
+com.ar
+com.au
+com.aw
+com.az
+com.bb
+com.bd
+com.bh
+com.bm
+com.bn
+com.bo
+com.br
+com.bs
+com.bt
+com.bz
+com.cd
+com.ch
+com.cn
+com.co
+com.cu
+com.cy
+com.dm
+com.do
+com.dz
+com.ec
+com.ee
+com.eg
+com.er
+com.es
+com.et
+com.fj
+com.fk
+com.fr
+com.ge
+com.gh
+com.gi
+com.gn
+com.gp
+com.gr
+com.gt
+com.gu
+com.hk
+com.hn
+com.hr
+com.ht
+com.io
+com.jm
+com.jo
+com.kg
+com.kh
+com.kw
+com.ky
+com.kz
+com.la
+com.lb
+com.lc
+com.li
+com.lk
+com.lr
+com.lv
+com.ly
+com.mg
+com.mk
+com.mm
+com.mn
+com.mo
+com.mt
+com.mu
+com.mv
+com.mw
+com.mx
+com.my
+com.na
+com.nc
+com.ng
+com.ni
+com.np
+com.nr
+com.om
+com.pa
+com.pe
+com.pf
+com.pg
+com.ph
+com.pk
+com.pl
+com.pr
+com.ps
+com.pt
+com.py
+com.qa
+com.re
+com.ro
+com.ru
+com.rw
+com.sa
+com.sb
+com.sc
+com.sd
+com.sg
+com.sh
+com.st
+com.sv
+com.sy
+com.tj
+com.tn
+com.tr
+com.tt
+com.tw
+com.ua
+com.uy
+com.ve
+com.vi
+com.vn
+com.vu
+com.ws
+com.ye
+conf.au
+conf.lv
+consulado.st
+coop.br
+coop.ht
+coop.mv
+coop.mw
+coop.tt
+cpa.pro
+cq.cn
+cri.nz
+crimea.ua
+csiro.au
+ct.us
+cul.na
+cv.ua
+d.se
+dagestan.ru
+dc.us
+de.com
+de.net
+de.tt
+de.us
+de.vu
+dk.org
+dk.tt
+dn.ua
+dnepropetrovsk.ua
+dni.us
+dns.be
+donetsk.ua
+dp.ua
+dpn.br
+dr.tr
+dudinka.ru
+e-burg.ru
+e.se
+e164.arpa
+ebiz.tw
+ecn.br
+ed.ao
+ed.cr
+ed.jp
+edu.ac
+edu.af
+edu.ai
+edu.al
+edu.an
+edu.ar
+edu.au
+edu.az
+edu.bb
+edu.bd
+edu.bh
+edu.bm
+edu.bn
+edu.bo
+edu.br
+edu.bt
+edu.ck
+edu.cn
+edu.co
+edu.cu
+edu.dm
+edu.do
+edu.dz
+edu.ec
+edu.eg
+edu.er
+edu.es
+edu.et
+edu.ge
+edu.gh
+edu.gi
+edu.gp
+edu.gr
+edu.gt
+edu.gu
+edu.hk
+edu.hn
+edu.ht
+edu.in
+edu.jm
+edu.jo
+edu.kg
+edu.kh
+edu.kw
+edu.ky
+edu.kz
+edu.lb
+edu.lc
+edu.lk
+edu.lr
+edu.lv
+edu.ly
+edu.mg
+edu.mm
+edu.mn
+edu.mo
+edu.mt
+edu.mv
+edu.mw
+edu.mx
+edu.my
+edu.na
+edu.ng
+edu.ni
+edu.np
+edu.nr
+edu.om
+edu.pa
+edu.pe
+edu.pf
+edu.ph
+edu.pk
+edu.pl
+edu.pr
+edu.ps
+edu.pt
+edu.py
+edu.qa
+edu.ru
+edu.rw
+edu.sa
+edu.sb
+edu.sc
+edu.sd
+edu.sg
+edu.sh
+edu.sk
+edu.st
+edu.sv
+edu.tj
+edu.tr
+edu.tt
+edu.tw
+edu.ua
+edu.uk
+edu.uy
+edu.ve
+edu.vi
+edu.vn
+edu.vu
+edu.ws
+edu.ye
+edu.yu
+edu.za
+edunet.tn
+ehime.jp
+ekloges.cy
+embaixada.st
+eng.br
+ens.tn
+ernet.in
+erotica.hu
+erotika.hu
+es.tt
+esp.br
+etc.br
+eti.br
+eu.com
+eu.org
+eu.tt
+eun.eg
+experts-comptables.fr
+f.se
+fam.pk
+far.br
+fareast.ru
+fax.nr
+fed.us
+fgov.be
+fh.se
+fhs.no
+fhsk.se
+fhv.se
+fi.cr
+fie.ee
+film.hu
+fin.ec
+fin.tn
+firm.co
+firm.ht
+firm.in
+firm.ro
+firm.ve
+fj.cn
+fl.us
+fm.br
+fnd.br
+folkebibl.no
+forum.hu
+fot.br
+fr.tt
+fr.vu
+from.hr
+fst.br
+fukui.jp
+fukuoka.jp
+fukushima.jp
+fylkesbibl.no
+g.se
+g12.br
+ga.us
+game.tw
+games.hu
+gb.com
+gb.net
+gc.ca
+gd.cn
+geek.nz
+gen.in
+gen.nz
+gen.tr
+geometre-expert.fr
+ggf.br
+gifu.jp
+gmina.pl
+go.cr
+go.id
+go.jp
+go.ke
+go.kr
+go.th
+go.tj
+go.tz
+go.ug
+gob.bo
+gob.do
+gob.es
+gob.gt
+gob.hn
+gob.mx
+gob.ni
+gob.pa
+gob.pe
+gob.pk
+gob.sv
+gok.pk
+gon.pk
+gop.pk
+gos.pk
+gouv.fr
+gouv.ht
+gouv.rw
+gov.ac
+gov.ae
+gov.af
+gov.ai
+gov.al
+gov.ar
+gov.au
+gov.az
+gov.bb
+gov.bd
+gov.bf
+gov.bh
+gov.bm
+gov.bo
+gov.br
+gov.bt
+gov.by
+gov.ch
+gov.ck
+gov.cn
+gov.co
+gov.cu
+gov.cx
+gov.cy
+gov.dm
+gov.do
+gov.dz
+gov.ec
+gov.eg
+gov.er
+gov.et
+gov.fj
+gov.fk
+gov.ge
+gov.gg
+gov.gh
+gov.gi
+gov.gn
+gov.gr
+gov.gu
+gov.hk
+gov.ie
+gov.il
+gov.im
+gov.in
+gov.io
+gov.ir
+gov.it
+gov.je
+gov.jm
+gov.jo
+gov.jp
+gov.kg
+gov.kh
+gov.kw
+gov.ky
+gov.kz
+gov.lb
+gov.lc
+gov.li
+gov.lk
+gov.lr
+gov.lt
+gov.lu
+gov.lv
+gov.ly
+gov.ma
+gov.mg
+gov.mm
+gov.mn
+gov.mo
+gov.mt
+gov.mv
+gov.mw
+gov.my
+gov.ng
+gov.np
+gov.nr
+gov.om
+gov.ph
+gov.pk
+gov.pl
+gov.pr
+gov.ps
+gov.pt
+gov.py
+gov.qa
+gov.ru
+gov.rw
+gov.sa
+gov.sb
+gov.sc
+gov.sd
+gov.sg
+gov.sh
+gov.sk
+gov.st
+gov.sy
+gov.tj
+gov.tn
+gov.to
+gov.tp
+gov.tr
+gov.tt
+gov.tv
+gov.tw
+gov.ua
+gov.uk
+gov.ve
+gov.vi
+gov.vn
+gov.ws
+gov.ye
+gov.za
+gov.zm
+gov.zw
+govt.nz
+gr.jp
+greta.fr
+grozny.ru
+grp.lk
+gs.cn
+gsm.pl
+gub.uy
+guernsey.gg
+gunma.jp
+gv.ao
+gv.at
+gx.cn
+gz.cn
+h.se
+ha.cn
+hb.cn
+he.cn
+health.vn
+herad.no
+hi.cn
+hi.us
+hiroshima.jp
+hk.cn
+hl.cn
+hn.cn
+hokkaido.jp
+hotel.hu
+hotel.lk
+hu.com
+huissier-justice.fr
+hyogo.jp
+i.se
+ia.us
+ibaraki.jp
+icnet.uk
+id.au
+id.fj
+id.ir
+id.lv
+id.ly
+id.us
+idf.il
+idn.sg
+idrett.no
+idv.hk
+idv.tw
+if.ua
+il.us
+imb.br
+in-addr.arpa
+in.th
+in.ua
+in.us
+ind.br
+ind.er
+ind.gg
+ind.gt
+ind.in
+ind.je
+ind.tn
+inf.br
+inf.cu
+info.au
+info.az
+info.co
+info.cy
+info.ec
+info.et
+info.fj
+info.ht
+info.hu
+info.mv
+info.nr
+info.pl
+info.pr
+info.ro
+info.sd
+info.tn
+info.tr
+info.tt
+info.ve
+info.vn
+ing.pa
+ingatlan.hu
+inima.al
+int.ar
+int.az
+int.bo
+int.co
+int.lk
+int.mv
+int.mw
+int.pt
+int.ru
+int.rw
+int.tj
+int.tt
+int.ve
+int.vn
+intl.tn
+ip6.arpa
+iris.arpa
+irkutsk.ru
+isa.us
+ishikawa.jp
+isla.pr
+it.ao
+it.tt
+ivano-frankivsk.ua
+ivanovo.ru
+iwate.jp
+iwi.nz
+iz.hr
+izhevsk.ru
+jamal.ru
+jar.ru
+jersey.je
+jet.uk
+jl.cn
+jobs.tt
+jogasz.hu
+jor.br
+joshkar-ola.ru
+js.cn
+jx.cn
+k-uralsk.ru
+k.se
+k12.ec
+k12.il
+k12.tr
+kagawa.jp
+kagoshima.jp
+kalmykia.ru
+kaluga.ru
+kamchatka.ru
+kanagawa.jp
+kanazawa.jp
+karelia.ru
+kawasaki.jp
+kazan.ru
+kchr.ru
+kemerovo.ru
+kh.ua
+khabarovsk.ru
+khakassia.ru
+kharkov.ua
+kherson.ua
+khmelnitskiy.ua
+khv.ru
+kids.us
+kiev.ua
+kirov.ru
+kirovograd.ua
+kitakyushu.jp
+km.ua
+kms.ru
+kobe.jp
+kochi.jp
+koenig.ru
+komforb.se
+komi.ru
+kommunalforbund.se
+kommune.no
+komvux.se
+konyvelo.hu
+kostroma.ru
+kr.ua
+krasnoyarsk.ru
+ks.ua
+ks.us
+kuban.ru
+kumamoto.jp
+kurgan.ru
+kursk.ru
+kustanai.ru
+kuzbass.ru
+kv.ua
+ky.us
+kyonggi.kr
+kyoto.jp
+la.us
+lakas.hu
+lanarb.se
+lanbib.se
+law.pro
+law.za
+lel.br
+lg.jp
+lg.ua
+lipetsk.ru
+lkd.co.im
+ln.cn
+ltd.co.im
+ltd.cy
+ltd.gg
+ltd.gi
+ltd.je
+ltd.lk
+ltd.uk
+lugansk.ua
+lutsk.ua
+lviv.ua
+m.se
+ma.us
+magadan.ru
+magnitka.ru
+mail.pl
+maori.nz
+mari-el.ru
+mari.ru
+marine.ru
+mat.br
+matsuyama.jp
+mb.ca
+md.us
+me.uk
+me.us
+med.br
+med.ec
+med.ee
+med.ht
+med.ly
+med.om
+med.pa
+med.pro
+med.sa
+med.sd
+medecin.fr
+media.hu
+media.pl
+mi.th
+mi.us
+miasta.pl
+mie.jp
+mil.ac
+mil.ae
+mil.ar
+mil.az
+mil.bd
+mil.bo
+mil.br
+mil.by
+mil.co
+mil.do
+mil.ec
+mil.eg
+mil.er
+mil.fj
+mil.ge
+mil.gh
+mil.gt
+mil.gu
+mil.hn
+mil.id
+mil.in
+mil.io
+mil.jo
+mil.kg
+mil.kh
+mil.kw
+mil.kz
+mil.lb
+mil.lt
+mil.lu
+mil.lv
+mil.mg
+mil.mv
+mil.my
+mil.no
+mil.np
+mil.nz
+mil.om
+mil.pe
+mil.ph
+mil.pl
+mil.ru
+mil.rw
+mil.se
+mil.sh
+mil.sk
+mil.st
+mil.tj
+mil.tr
+mil.tw
+mil.uk
+mil.uy
+mil.ve
+mil.ye
+mil.za
+miyagi.jp
+miyazaki.jp
+mk.ua
+mn.us
+mo.cn
+mo.us
+mob.nr
+mobi.tt
+mobil.nr
+mobile.nr
+mod.gi
+mod.om
+mod.uk
+mordovia.ru
+mosreg.ru
+ms.us
+msk.ru
+mt.us
+muni.il
+murmansk.ru
+mus.br
+museum.mn
+museum.mv
+museum.mw
+museum.no
+museum.om
+museum.tt
+music.mobi
+mytis.ru
+n.se
+nagano.jp
+nagasaki.jp
+nagoya.jp
+nakhodka.ru
+nalchik.ru
+name.ae
+name.az
+name.cy
+name.et
+name.fj
+name.hr
+name.mv
+name.my
+name.pr
+name.tj
+name.tr
+name.tt
+name.vn
+nara.jp
+nat.tn
+national-library-scotland.uk
+naturbruksgymn.se
+navy.mil
+nb.ca
+nc.us
+nd.us
+ne.jp
+ne.ke
+ne.kr
+ne.tz
+ne.ug
+ne.us
+nel.uk
+net.ac
+net.ae
+net.af
+net.ag
+net.ai
+net.al
+net.an
+net.ar
+net.au
+net.az
+net.bb
+net.bd
+net.bh
+net.bm
+net.bn
+net.bo
+net.br
+net.bs
+net.bt
+net.bz
+net.cd
+net.ch
+net.ck
+net.cn
+net.co
+net.cu
+net.cy
+net.dm
+net.do
+net.dz
+net.ec
+net.eg
+net.er
+net.et
+net.fj
+net.fk
+net.ge
+net.gg
+net.gn
+net.gp
+net.gr
+net.gt
+net.gu
+net.hk
+net.hn
+net.ht
+net.id
+net.il
+net.im
+net.in
+net.io
+net.ir
+net.je
+net.jm
+net.jo
+net.jp
+net.kg
+net.kh
+net.kw
+net.ky
+net.kz
+net.la
+net.lb
+net.lc
+net.li
+net.lk
+net.lr
+net.lu
+net.lv
+net.ly
+net.ma
+net.mm
+net.mo
+net.mt
+net.mv
+net.mw
+net.mx
+net.my
+net.na
+net.nc
+net.ng
+net.ni
+net.np
+net.nr
+net.nz
+net.om
+net.pa
+net.pe
+net.pg
+net.ph
+net.pk
+net.pl
+net.pr
+net.ps
+net.pt
+net.py
+net.qa
+net.ru
+net.rw
+net.sa
+net.sb
+net.sc
+net.sd
+net.sg
+net.sh
+net.st
+net.sy
+net.th
+net.tj
+net.tn
+net.tr
+net.tt
+net.tw
+net.ua
+net.uk
+net.uy
+net.ve
+net.vi
+net.vn
+net.vu
+net.ws
+net.ye
+net.za
+news.hu
+nf.ca
+ngo.lk
+ngo.ph
+ngo.pl
+ngo.za
+nh.us
+nhs.uk
+nic.im
+nic.in
+nic.tt
+nic.uk
+nieruchomosci.pl
+niigata.jp
+nikolaev.ua
+nj.us
+nkz.ru
+nl.ca
+nls.uk
+nm.cn
+nm.us
+nnov.ru
+no.com
+nom.ad
+nom.ag
+nom.br
+nom.co
+nom.es
+nom.fk
+nom.fr
+nom.mg
+nom.ni
+nom.pa
+nom.pe
+nom.pl
+nom.re
+nom.ro
+nom.ve
+nom.za
+nome.pt
+norilsk.ru
+not.br
+notaires.fr
+nov.ru
+novosibirsk.ru
+ns.ca
+nsk.ru
+nsn.us
+nsw.au
+nt.au
+nt.ca
+nt.ro
+ntr.br
+nu.ca
+nv.us
+nx.cn
+ny.us
+o.se
+od.ua
+odessa.ua
+odo.br
+off.ai
+og.ao
+oh.us
+oita.jp
+ok.us
+okayama.jp
+okinawa.jp
+omsk.ru
+on.ca
+or.at
+or.cr
+or.id
+or.jp
+or.ke
+or.kr
+or.th
+or.tz
+or.ug
+or.us
+orenburg.ru
+org.ac
+org.ae
+org.ag
+org.ai
+org.al
+org.an
+org.ar
+org.au
+org.az
+org.bb
+org.bd
+org.bh
+org.bm
+org.bn
+org.bo
+org.br
+org.bs
+org.bt
+org.bw
+org.bz
+org.cd
+org.ch
+org.ck
+org.cn
+org.co
+org.cu
+org.cy
+org.dm
+org.do
+org.dz
+org.ec
+org.ee
+org.eg
+org.er
+org.es
+org.et
+org.fj
+org.fk
+org.ge
+org.gg
+org.gh
+org.gi
+org.gn
+org.gp
+org.gr
+org.gt
+org.gu
+org.hk
+org.hn
+org.ht
+org.hu
+org.il
+org.im
+org.in
+org.io
+org.ir
+org.je
+org.jm
+org.jo
+org.jp
+org.kg
+org.kh
+org.kw
+org.ky
+org.kz
+org.la
+org.lb
+org.lc
+org.li
+org.lk
+org.lr
+org.ls
+org.lu
+org.lv
+org.ly
+org.ma
+org.mg
+org.mk
+org.mm
+org.mn
+org.mo
+org.mt
+org.mv
+org.mw
+org.mx
+org.my
+org.na
+org.nc
+org.ng
+org.ni
+org.np
+org.nr
+org.nz
+org.om
+org.pa
+org.pe
+org.pf
+org.ph
+org.pk
+org.pl
+org.pr
+org.ps
+org.pt
+org.py
+org.qa
+org.ro
+org.ru
+org.sa
+org.sb
+org.sc
+org.sd
+org.se
+org.sg
+org.sh
+org.st
+org.sv
+org.sy
+org.tj
+org.tn
+org.tr
+org.tt
+org.tw
+org.ua
+org.uk
+org.uy
+org.ve
+org.vi
+org.vn
+org.vu
+org.ws
+org.ye
+org.yu
+org.za
+org.zm
+org.zw
+oryol.ru
+osaka.jp
+oskol.ru
+otc.au
+oz.au
+pa.us
+palana.ru
+parliament.cy
+parliament.uk
+parti.se
+pb.ao
+pc.pl
+pe.ca
+pe.kr
+penza.ru
+per.kh
+per.sg
+perm.ru
+perso.ht
+pharmacien.fr
+pl.ua
+plc.co.im
+plc.ly
+plc.uk
+plo.ps
+pol.dz
+pol.ht
+pol.tr
+police.uk
+poltava.ua
+port.fr
+powiat.pl
+pp.az
+pp.ru
+pp.se
+ppg.br
+prd.fr
+prd.mg
+press.cy
+press.ma
+press.se
+presse.fr
+pri.ee
+principe.st
+priv.at
+priv.hu
+priv.no
+priv.pl
+pro.ae
+pro.br
+pro.cy
+pro.ec
+pro.fj
+pro.ht
+pro.mv
+pro.om
+pro.pr
+pro.tt
+pro.vn
+psc.br
+psi.br
+pskov.ru
+ptz.ru
+pub.sa
+publ.pt
+pvt.ge
+pyatigorsk.ru
+qc.ca
+qc.com
+qh.cn
+qld.au
+qsl.br
+re.kr
+realestate.pl
+rec.br
+rec.co
+rec.ro
+rec.ve
+red.sv
+reklam.hu
+rel.ht
+rel.pl
+res.in
+ri.us
+rnd.ru
+rnrt.tn
+rns.tn
+rnu.tn
+rovno.ua
+ru.com
+rubtsovsk.ru
+rv.ua
+ryazan.ru
+s.se
+sa.au
+sa.com
+sa.cr
+saga.jp
+saitama.jp
+sakhalin.ru
+samara.ru
+saotome.st
+sapporo.jp
+saratov.ru
+sark.gg
+sc.cn
+sc.ke
+sc.ug
+sc.us
+sch.ae
+sch.gg
+sch.ir
+sch.je
+sch.lk
+sch.ly
+sch.ng
+sch.om
+sch.sa
+sch.sd
+sch.uk
+sch.zm
+school.fj
+school.nz
+school.za
+sci.eg
+sd.cn
+sd.us
+se.com
+se.tt
+sebastopol.ua
+sec.ps
+sendai.jp
+seoul.kr
+sex.hu
+sex.pl
+sh.cn
+shiga.jp
+shimane.jp
+shizuoka.jp
+shop.ht
+shop.hu
+shop.pl
+simbirsk.ru
+sk.ca
+sklep.pl
+sld.do
+sld.pa
+slg.br
+smolensk.ru
+sn.cn
+snz.ru
+soc.lk
+soros.al
+sos.pl
+spb.ru
+sport.hu
+srv.br
+sshn.se
+stat.no
+stavropol.ru
+store.co
+store.ro
+store.st
+store.ve
+stv.ru
+suli.hu
+sumy.ua
+surgut.ru
+sx.cn
+syzran.ru
+szex.hu
+szkola.pl
+t.se
+takamatsu.jp
+tambov.ru
+targi.pl
+tas.au
+tatarstan.ru
+te.ua
+tec.ve
+tel.no
+tel.nr
+tel.tr
+telecom.na
+telememo.au
+ternopil.ua
+test.ru
+tirana.al
+tj.cn
+tlf.nr
+tm.cy
+tm.fr
+tm.hu
+tm.mc
+tm.mg
+tm.mt
+tm.pl
+tm.ro
+tm.se
+tm.za
+tmp.br
+tn.us
+tochigi.jp
+tokushima.jp
+tokyo.jp
+tom.ru
+tomsk.ru
+tottori.jp
+tourism.pl
+tourism.tn
+toyama.jp
+tozsde.hu
+travel.pl
+travel.tt
+trd.br
+tsaritsyn.ru
+tsk.ru
+tula.ru
+tur.br
+turystyka.pl
+tuva.ru
+tv.bo
+tv.br
+tv.sd
+tver.ru
+tw.cn
+tx.us
+tyumen.ru
+u.se
+udm.ru
+udmurtia.ru
+uk.com
+uk.net
+uk.tt
+ulan-ude.ru
+unam.na
+uniti.al
+upt.al
+uri.arpa
+urn.arpa
+us.com
+us.tt
+ut.us
+utazas.hu
+utsunomiya.jp
+uu.mt
+uy.com
+uzhgorod.ua
+va.us
+vatican.va
+vdonsk.ru
+vet.br
+veterinaire.fr
+vgs.no
+vic.au
+video.hu
+vinnica.ua
+vladikavkaz.ru
+vladimir.ru
+vladivostok.ru
+vn.ua
+volgograd.ru
+vologda.ru
+voronezh.ru
+vrn.ru
+vt.us
+vyatka.ru
+w.se
+wa.au
+wa.us
+wakayama.jp
+weather.mobi
+web.co
+web.do
+web.lk
+web.pk
+web.tj
+web.tr
+web.ve
+web.za
+wi.us
+wv.us
+www.ro
+wy.us
+x.se
+xj.cn
+xz.cn
+y.se
+yakutia.ru
+yamagata.jp
+yamaguchi.jp
+yamal.ru
+yamanashi.jp
+yaroslavl.ru
+yekaterinburg.ru
+yk.ca
+yn.cn
+yokohama.jp
+yuzhno-sakhalinsk.ru
+z.se
+za.com
+zaporizhzhe.ua
+zgrad.ru
+zhitomir.ua
+zj.cn
+zlg.br
+zp.ua
+zt.ua
diff --git a/src/defaults/preferences/nosquint.js b/src/defaults/preferences/nosquint.js
index 9980b33..38a87af 100644
--- a/src/defaults/preferences/nosquint.js
+++ b/src/defaults/preferences/nosquint.js
@@ -1,6 +1,9 @@
 pref("extensions.nosquint.zoomlevel", 120);
 pref("extensions.nosquint.zoomIncrement", 10);
-pref("extensions.nosquint.rememberDomains", true);
-pref("extensions.nosquint.domains", "");
-pref("extensions.nosquint.wheelZoomEnabled", true);
-pref("extensions.nosquint.wheelActionSave", -1);
+pref("extensions.nosquint.rememberSites", true);
+pref("extensions.nosquint.sites", "");
+pref("extensions.nosquint.sitesSaveDelay", 5000);
+pref("extensions.nosquint.exceptions", "*/~* *.sourceforge.net *.google.[*]");
+pref("extensions.nosquint.wheelZoomEnabled", false);
+pref("extensions.nosquint.hideStatus", false);
+pref("extensions.nosquint.forgetMonths", 6);
diff --git a/src/install.rdf b/src/install.rdf
index 7f2c252..40bf81d 100644
--- a/src/install.rdf
+++ b/src/install.rdf
@@ -1,28 +1,20 @@
 <?xml version="1.0"?>
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-  
-    <em:id>nosquint at urandom.ca</em:id>
-    <em:name>No Squint</em:name>
-    <em:version>0.9.1.1</em:version>
-    <em:description>Zooms text by user-configurable percentage</em:description>
-    <em:creator>Jason Tackaberry</em:creator>
-   
-    <em:homepageURL>http://urandom.ca/nosquint/</em:homepageURL>
-    <em:optionsURL>chrome://nosquint/content/prefs.xul</em:optionsURL>
-    <em:iconURL>chrome://nosquint/content/icon-32.png</em:iconURL>
-
-    <!-- Firefox -->
-    <em:targetApplication>
-      <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>1.5</em:minVersion>
-        <em:maxVersion>2.0.0.*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-
-</RDF>
+<RDF:RDF xmlns:em="http://www.mozilla.org/2004/em-rdf#"
+         xmlns:NC="http://home.netscape.com/NC-rdf#"
+         xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+  <RDF:Description RDF:about="urn:mozilla:install-manifest"
+                   em:id="nosquint at urandom.ca"
+                   em:name="No Squint"
+                   em:version="1.0.0"
+                   em:description="Zooms text by user-configurable percentage"
+                   em:creator="Jason Tackaberry"
+                   em:homepageURL="http://urandom.ca/nosquint/"
+                   em:optionsURL="chrome://nosquint/content/prefs.xul"
+                   em:iconURL="chrome://nosquint/content/icon-32.png">
+    <em:targetApplication RDF:resource="rdf:#$AZWNY2"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="rdf:#$AZWNY2"
+                   em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
+                   em:minVersion="2.0"
+                   em:maxVersion="2.0.0.*" />
+</RDF:RDF>
diff --git a/src/locale/en-US/help.dtd b/src/locale/en-US/help.dtd
new file mode 100644
index 0000000..6e6f28a
--- /dev/null
+++ b/src/locale/en-US/help.dtd
@@ -0,0 +1,2 @@
+<!ENTITY ns.help.title "NoSquint Help">
+<!ENTITY ns.help.subtitle "Help">
diff --git a/src/locale/en-US/help.html b/src/locale/en-US/help.html
new file mode 100644
index 0000000..a486128
--- /dev/null
+++ b/src/locale/en-US/help.html
@@ -0,0 +1,276 @@
+<html>
+
+<style type="text/css">
+    body {
+        font-size: 110%;
+        padding: 0;
+        margin: 0;
+        text-align: justify;
+    }
+
+    ol {
+        text-align: left;
+    }
+
+    h2,h3 {
+        padding-left: 1em;
+    }
+
+    p,ol,li {
+        margin-left: 1em;
+        margin-right: 1.5em;
+    }
+
+    div.indent {
+        padding-left: 0.5em;
+    }
+
+    ul li b {
+        background-color: #eee;
+        padding: 0.2em;
+    }
+    ol li {
+        margin-bottom: 0.5em;
+    }
+
+    code {
+        font-size: 115%;
+        background-color: #fafafa;
+    }
+    h2 {
+        margin-top: 0.5em;
+        padding: 5px;
+        border-top: 1px solid #aaa;
+        border-bottom: 1px solid #aaa;
+        background-color: #f1f4fd;
+        font-family: sans-serif;
+    }
+    h3 {
+        font-family: sans-serif;
+    }
+
+</style>
+
+<body>
+<h2>Options Tab</h2>
+<h3>General Options</h3>
+<ul>
+    <li>
+        <b>Default text zoom level</b>
+            <p>This is the zoom level applied to all pages by default.  A value
+            of 100% is the standard Firefox text size without NoSquint.  With
+            NoSquint, you can override this value to be larger or smaller.</p>
+
+            <p>Modifying the text zoom when visiting a web page will override this
+            value for that site.</p>
+    </li>
+    <li>
+        <b>Zoom increment</b>
+            <p>You can change the text zoom for a page from the View menu, by
+            using one of the text zoom shortcuts (ctrl-plus/minus or
+            ctrl-mousewheel), or by using the optional toolbar buttons.
+            NoSquint will remember these changes.  This setting specifies what
+            increment, in percent, to use when changing the zoom level.</p>
+    </li>
+    <li>
+        <b>Enable text zoom with ctrl-mousewheel</b>
+            <p>Selecting this option allows you to adjust the text zoom level
+            by pressing and holding the control key while moving the mousewheel
+            up or down.</p>
+    </li>
+    <li>
+        <b>Show current zoom level and site in status bar</b>
+            <p>Selecting this option shows the zoom level and site name in the
+            status bar for the current web page.</p>
+    </li>
+</ul>
+
+<h3>Site Options</h3>
+
+<p>To NoSquint, a site is a web location where all pages under that location
+have the same zoom level, and the site name is derived from the page's
+URL.</p>
+
+<p>In most cases, the site is the domain.  For example, if the current page is
+<code>www2.ibm.com/index.php</code>, NoSquint will consider the site name to be
+<code>ibm.com</code>.  NoSquint will also take into account common
+second-level domains.  For example, if you're visiting
+<code>www.bbc.co.uk</code>, NoSquint will consider the site name to be
+<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 Exceptions Tab.</p>
+
+<ul>
+    <li>
+        <b>Use the default text zoom level for all sites</b>
+            <p>One of NoSquint's features is the ability to remember custom
+            text zoom levels for individual sites.  If you're not interested
+            this and want to use the same level for all sites, or you just
+            don't want NoSquint to remember any manual changes, select this
+            option.</p>
+    </li>
+    <li>
+        <b>Remember text zoom level per site</b>
+            <p>With this option selected, NoSquint will remember any changes
+            you make to the text zoom level (via ctrl-plus/minus keys or
+            ctrl-mousewheel) for a given site.  Next time you visit that site,
+            NoSquint will change the text zoom to the level previously used on
+            that site.</p>
+    </li>
+    <li>
+        <b>Forget zoom settings for sites not visited in the last ...</b>
+            <p>With the "remember text zoom level per site" option enabled,
+            NoSquint keeps track of all zoom level 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
+            level) for the specified number of months, NoSquint will forget the
+            setting.</p>
+    </li>
+    <li>
+        <b>Zoom level for the current site (example.com)</b>
+            <p>Sets the text zoom level to the specified value for the site in
+            the current browser tab or window.</p>
+    </li>
+</ul>
+
+<h2>Exceptions Tab</h2>
+
+<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.  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.
+Before going into a detailed explanation, let's first examine some common
+use-cases.  Hopefully one of these examples applies to your case.</p>
+
+<ol>
+    <li>
+        <b>Problem:</b> different subdomains on SourceForge, e.g.
+        <code>freevo.sourceforge.net</code> and
+        <code>audacity.sourceforge.net</code>, are wrongly treated as the same
+        site (<code>sourceforge.net</code>)<br />
+
+        <b>Solution:</b> add an exception with the pattern
+        <code>*.sourceforge.net</code>
+    </li>
+
+    <li>
+        <b>Problem:</b> <code>example.com/users/mary</code> and
+        <code>example.com/users/john</code> are wrongly treated as the same
+        site (<code>example.com</code>)<br />
+
+        <b>Solution:</b> add an exception with the pattern
+        <code>example.com/users/*</code>
+    </li>
+
+    <li>
+        <b>Problem:</b> I use Google Mail (<code>mail.google.com</code>) and
+        Google Reader (<code>google.com/reader</code>) and would like these
+        treated as sites separate from <code>google.com</code><br />
+
+        <b>Solution:</b> add an exception with the pattern
+        <code>mail.google.com</code>; add another exception with the pattern
+        <code>google.com/reader</code>
+    </li>
+
+    <li>
+        <b>Problem:</b> my company's intranet is at
+        <code>intra.example.com</code>, but I'd like our wiki, located under
+        <code>intra.example.com/wiki</code> to be treated separately from the
+        rest of the intranet.<br />
+
+        <b>Solution:</b> add an exception with the pattern
+        <code>intra.example.com/wiki</code>
+    </li>
+
+    <li>
+        <b>Problem:</b> Google Mail, Google Groups, and in fact all hosts on
+        Google should be treated as separate sites.  But the country (the
+        top-level domain) shouldn't matter.  So <code>mail.google.ca</code>
+        should be the same site as <code>mail.google.de</code>, but a different
+        site from <code>groups.google.de</code>, which itself would be the same
+        site as <code>groups.google.fi</code><br />
+
+        <b>Solution:</b> add an exception with the pattern <code>*.google.[*]</code>
+    </li>
+
+    <li>
+        <b>Problem:</b> my company has several web applications that are
+        distributed across multiple servers.
+        <code>example.com/server1/apps/app1</code> should be considered the
+        same site as <code>example.com/server2/apps/app1</code>, but there
+        could also be <code>example.com/server1/apps/app3</code> that should be
+        a separate site.<br />
+
+        <b>Solution:</b> add an exception with the pattern
+        <code>example.com/[*]/apps/*</code>
+    </li>
+    <li>
+        <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/>
+
+        <b>Solution:</b> add an exception with the pattern
+        <code>example.com/[**]apps/*</code>
+    </li>
+</ol>
+</div>
+
+<h3>Gory Details</h3>
+<p>What follows is a technical explanation of exceptions.  If you're not an
+advanced user, this section may create a fair amount of confusion; refer
+instead to the use-cases above to follow by example.</p>
+
+<p>Exception patterns may contain zero or more of the following possible wildcards:
+<ul>
+    <li>
+        <code>*</code> – When included in the host name, matches any character
+        except a dot ('.').  When included in the path, matches any character
+        except a slash ('/').  Does not match the empty string.
+    </li>
+    <li>
+        <code>**</code> – Matches any sequence of characters.  Does match
+        the empty string.
+    </li>
+</ul>
+</p>
+
+<p>Site names are arbitrary strings that represent a given site and are
+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, 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>)
+will be used in the site name instead of the characters the wildcard matches.
+This is allows you to group together locations.  For example, if the page's URL
+is <code>google.ca</code> and you've defined an exception
+<code>google.[*]</code>, the site name will be <code>google.*</code>.  Since
+<code>google.fi</code> matches this pattern, the site name would also be
+<code>google.*</code>.  Therefore, <code>google.ca</code> and
+<code>google.fi</code> would be considered the same site.</p>
+
+<p>Hostnames and paths are evaluated separately; an exception pattern is split
+into two sub-patterns, one for the host, and one for the path.  These
+sub-patterns match substrings, but hosts are right-anchored, while paths are
+left-anchored.  This means that any wildcards specified in the host name will
+not match any characters in the path, and vice versa.  When the hostname part
+of a pattern is only <code>*</code>, it matches the domain of the page's URL.
+For example, for <code>www.google.com</code>, a single <code>*</code> matches
+<code>google.com</code>; for <code>www.bbc.co.uk</code> it matches
+<code>bbc.co.uk</code>.</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 last one is chosen.</p>
+
+</body>
+</html>
diff --git a/src/locale/en-US/overlay.dtd b/src/locale/en-US/overlay.dtd
index dfffc68..b7f23f0 100644
--- a/src/locale/en-US/overlay.dtd
+++ b/src/locale/en-US/overlay.dtd
@@ -1 +1,2 @@
-<!ENTITY nosquint "NoSquint Settings">
+<!ENTITY nosquint.label "NoSquint Settings">
+<!ENTITY nosquint.accesskey "Q">
diff --git a/src/locale/en-US/prefs.dtd b/src/locale/en-US/prefs.dtd
index 7a1cfc7..a23d3c9 100644
--- a/src/locale/en-US/prefs.dtd
+++ b/src/locale/en-US/prefs.dtd
@@ -1,10 +1,34 @@
 <!ENTITY ns.pref.title "NoSquint Settings">
-<!ENTITY ns.pref.level.label "Default text zoom level">
-<!ENTITY ns.pref.level.tip "The zoom level applied to all pages by default.  Modifying the text zoom when visiting a web page will override this value for that domain.">
-<!ENTITY ns.pref.globalbox.label "Global Options">
-<!ENTITY ns.pref.increment.label "Zoom increment">
-<!ENTITY ns.pref.increment.tip "You can change the text zoom for a page from the View menu or using one of the text zoom shortcuts (ctrl-plus/minus, or ctrl-mousewheel).  NoSquint will remember these changes.  This setting specifies what increment to use when changing the zoom level.">
-<!ENTITY ns.pref.domainbox.label "Domain Options">
-<!ENTITY ns.pref.rememberDomains "Remember text zoom level per domain">
-<!ENTITY ns.pref.noRememberDomains "Use the default text zoom level (set above) for all domains">
-<!ENTITY ns.pref.domainZoom.label "Zoom level for this domain">
+<!ENTITY ns.pref.tab.options.label "Options">
+<!ENTITY ns.pref.tab.exceptions.label "Exceptions">
+
+<!ENTITY ns.pref.general.caption "General">
+<!ENTITY ns.pref.general.level.label "Default text zoom level">
+<!ENTITY ns.pref.general.increment.label "Zoom increment">
+<!ENTITY ns.pref.general.mousewheel.label "Enable text zoom with ctrl-mousewheel">
+<!ENTITY ns.pref.general.showstatus.label "Show current zoom level and site in status bar">
+
+<!ENTITY ns.pref.site.caption "Site">
+<!ENTITY ns.pref.site.noRemember.label "Use the default text zoom level (set above) for all sites">
+<!ENTITY ns.pref.site.remember.label "Remember text 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.site.current.label "Zoom level for the current site">
+<!ENTITY ns.pref.site.resetButton.label "Use Default">
+
+<!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.exceptions.pattern.label "Pattern for new exception">
+<!ENTITY ns.pref.exceptions.copyButton.label "Copy from URL">
+<!ENTITY ns.pref.exceptions.copyButton.accesskey "C">
+<!ENTITY ns.pref.exceptions.addButton.label "Add Exception">
+<!ENTITY ns.pref.exceptions.addButton.accesskey "A">
+<!ENTITY ns.pref.exceptions.list.col1.label "Exception Pattern">
+<!ENTITY ns.pref.exceptions.editButton.label "Edit Exception">
+<!ENTITY ns.pref.exceptions.editButton.accesskey "E">
+<!ENTITY ns.pref.exceptions.removeButton.label "Remove Exception">
+<!ENTITY ns.pref.exceptions.removeButton.accesskey "R">
+
diff --git a/src/locale/en-US/prefs.properties b/src/locale/en-US/prefs.properties
new file mode 100644
index 0000000..1608767
--- /dev/null
+++ b/src/locale/en-US/prefs.properties
@@ -0,0 +1,3 @@
+editPrompt=Specify pattern for exception
+editTitle=Edit Pattern
+patternExists=This pattern already exists in the Exceptions list.
diff --git a/src/skin/icon-enlarge-16.png b/src/skin/icon-enlarge-16.png
new file mode 100644
index 0000000..46a4b05
Binary files /dev/null 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
new file mode 100644
index 0000000..32b2846
Binary files /dev/null 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
new file mode 100644
index 0000000..e3841ee
Binary files /dev/null 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
new file mode 100644
index 0000000..cff1f1a
Binary files /dev/null and b/src/skin/icon-reduce-24.png differ
diff --git a/src/skin/toolbar.css b/src/skin/toolbar.css
new file mode 100644
index 0000000..f9e05fc
--- /dev/null
+++ b/src/skin/toolbar.css
@@ -0,0 +1,15 @@
+#nosquint-button-reduce {
+      list-style-image: url("chrome://nosquint/skin/icon-reduce-24.png");
+}
+
+toolbar[iconsize="small"] #nosquint-button-reduce  {
+      list-style-image: url("chrome://nosquint/skin/icon-reduce-16.png");
+}
+
+#nosquint-button-enlarge {
+      list-style-image: url("chrome://nosquint/skin/icon-enlarge-24.png");
+}
+
+toolbar[iconsize="small"] #nosquint-button-enlarge {
+      list-style-image: url("chrome://nosquint/skin/icon-enlarge-16.png");
+}

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