[Pkg-mozext-commits] [nosquint] 12/47: Import of 2.0.1b1 release into git
David Prévot
taffit at moszumanska.debian.org
Tue Apr 28 01:41:17 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 fbb2801c89e19f13c72e20ccf69817dd12768198
Author: Jason Tackaberry <tack at urandom.ca>
Date: Fri Jan 13 19:41:13 2012 -0500
Import of 2.0.1b1 release into git
---
src/chrome.manifest | 2 +
src/chrome.manifest.in | 2 +
src/content/browser.js | 472 ++++++++++++++++
src/content/cmd.js | 184 +++++++
src/content/dlg-global.js | 241 ++++++++
src/content/dlg-global.xul | 218 ++++++++
src/content/dlg-help.js | 10 +
src/content/dlg-help.xul | 23 +
src/content/dlg-site.js | 176 ++++++
src/content/dlg-site.xul | 118 ++++
src/content/dlg-style.css | 35 ++
src/content/init.js | 80 ++-
src/content/interfaces.js | 211 +++++++
src/content/lib.js | 220 ++++++++
src/content/overlay.xul | 89 ++-
src/content/overlay_sanitize.xul | 8 +
src/content/prefs.js | 980 ++++++++++++++++++++++++++-------
src/content/sanitize.js | 81 +++
src/content/two-level-tlds | 91 +++
src/content/zoommanager.js | 62 +++
src/defaults/preferences/nosquint.js | 16 +-
src/install.rdf | 12 +-
src/locale/en-US/dlg-global.dtd | 53 ++
src/locale/en-US/dlg-global.properties | 3 +
src/locale/en-US/dlg-help.dtd | 2 +
src/locale/en-US/dlg-site.dtd | 7 +
src/locale/en-US/dlg-site.properties | 4 +
src/locale/en-US/help.html | 211 +++++--
src/locale/en-US/overlay.dtd | 7 +
src/locale/en-US/overlay.properties | 10 +
src/skin/icon-enlarge-16.png | Bin 652 -> 3316 bytes
src/skin/icon-enlarge-24.png | Bin 1281 -> 3794 bytes
src/skin/icon-reduce-16.png | Bin 654 -> 3313 bytes
src/skin/icon-reduce-24.png | Bin 1233 -> 3736 bytes
src/skin/icon-reset-16.png | Bin 0 -> 3335 bytes
src/skin/icon-reset-24.png | Bin 0 -> 3758 bytes
src/skin/icon-statusbar-16.png | Bin 0 -> 854 bytes
src/skin/logo-32.png | Bin 0 -> 2418 bytes
src/skin/toolbar.css | 16 +
39 files changed, 3313 insertions(+), 331 deletions(-)
diff --git a/src/chrome.manifest b/src/chrome.manifest
index e87d7a0..8fe1d1e 100644
--- a/src/chrome.manifest
+++ b/src/chrome.manifest
@@ -1,5 +1,7 @@
content nosquint content/
overlay chrome://browser/content/browser.xul chrome://nosquint/content/overlay.xul
+overlay chrome://browser/content/preferences/sanitize.xul chrome://nosquint/content/overlay_sanitize.xul
+overlay chrome://browser/content/sanitize.xul chrome://nosquint/content/overlay_sanitize.xul
locale nosquint en-US locale/en-US/
diff --git a/src/chrome.manifest.in b/src/chrome.manifest.in
index c576e6c..bc2ae92 100644
--- a/src/chrome.manifest.in
+++ b/src/chrome.manifest.in
@@ -1,5 +1,7 @@
content nosquint ${JAR}content/
overlay chrome://browser/content/browser.xul chrome://nosquint/content/overlay.xul
+overlay chrome://browser/content/preferences/sanitize.xul chrome://nosquint/content/overlay_sanitize.xul
+overlay chrome://browser/content/sanitize.xul chrome://nosquint/content/overlay_sanitize.xul
locale nosquint en-US ${JAR}locale/en-US/
diff --git a/src/content/browser.js b/src/content/browser.js
new file mode 100644
index 0000000..70af012
--- /dev/null
+++ b/src/content/browser.js
@@ -0,0 +1,472 @@
+// chrome://browser/content/browser.xul
+
+/******************************************************************************
+ * Browser
+ *
+ */
+NoSquint.browser = NoSquint.ns(function() { with (NoSquint) {
+ const CI = Components.interfaces;
+ this.id = 'NoSquint.browser';
+ var zoomAllTimer = null; // Timer for queueZoomAll()
+ var styleAllTimer = null; // Timer for queueStyleAll()
+ var updateStatusTimer = null; // Timer for queueUpdateStatus()
+
+ this.init = function() {
+ this.gBrowser = gBrowser;
+ this.updateZoomMenu();
+
+ this.observer = new NSQ.interfaces.Observer();
+ this.observer.watcher = {
+ onEnterPrivateBrowsing: function() {
+ this.closeSiteSettings();
+ // Switching the private browsing mode. Store any current pending
+ // changes now.
+ NSQ.prefs.saveSiteList(true);
+ // Save current (non-private) site data for when we exit private
+ // browsing.
+ this.origSites = NSQ.prefs.cloneSites();
+ },
+
+ onExitPrivateBrowsing: function() {
+ this.closeSiteSettings();
+ // Restore previously saved site data and rezoom/style all tabs.
+ NSQ.prefs.sites = this.origSites;
+ this.origSites = null;
+ NSQ.browser.zoomAll();
+ NSQ.browser.styleAll();
+ },
+
+ closeSiteSettings: function() {
+ if (NSQ.storage.dialogs.site)
+ NSQ.storage.dialogs.site.die();
+ }
+ };
+
+ if (this.observer.inPrivateBrowsing)
+ this.observer.watcher.onEnterPrivateBrowsing();
+
+ window.addEventListener('DOMMouseScroll', this.handleMouseScroll, false);
+ // XXX: used for image zoom, which feature is currently removed.
+ //window.addEventListener("resize", this.handleResize, false);
+ gBrowser.tabContainer.addEventListener('TabOpen', this.handleTabOpen, false);
+ gBrowser.tabContainer.addEventListener('TabSelect', this.handleTabSelect, false);
+ gBrowser.tabContainer.addEventListener('TabClose', this.handleTabClose, false);
+
+ this.zoomAll(null, true);
+ };
+
+ this.destroy = function() {
+ };
+
+
+ /* Event handlers. Reminder: 'this' will not be NSQ.browser
+ */
+
+ this.handleMouseScroll = function(event) {
+ if (!event.ctrlKey)
+ return;
+ if (NSQ.prefs.wheelZoomEnabled) {
+ var browser = gBrowser.selectedBrowser;
+ var text = full = false;
+ var increment = NSQ.prefs.zoomIncrement * (event.detail < 0 ? 1 : -1);
+ //var img = isImage(browser);
+ var img = false;
+
+ if (NSQ.prefs.wheelZoomInvert)
+ increment *= -1;
+
+ if (NSQ.prefs.fullZoomPrimary && !event.shiftKey || !NSQ.prefs.fullZoomPrimary && event.shiftKey || img)
+ full = Math.round((browser.markupDocumentViewer.fullZoom * 100) + increment);
+ else
+ text = Math.round((browser.markupDocumentViewer.textZoom * 100) + increment);
+
+ //if (!img || !browser.getUserData('nosquint').site) {
+ if (!img) {
+ NSQ.browser.zoom(browser, text, full);
+ NSQ.browser.saveCurrentZoom();
+ }
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ };
+
+
+ // Would be used for image zoom, but currently not implemented.
+ this.handleResize = function(event) {
+ };
+
+ this.handleTabOpen = function(event) {
+ var browser = event.target.linkedBrowser;
+ NSQ.browser.attach(browser);
+ NSQ.browser.zoom(browser);
+ };
+
+ this.handleTabSelect = function(event) {
+ NSQ.browser.updateStatus();
+ };
+
+ this.handleTabClose = function(event) {
+ var browser = event.target.linkedBrowser;
+ browser.removeProgressListener(browser.getUserData('nosquint').listener);
+ browser.setUserData('nosquint', null, null);
+ };
+
+
+ /* Updates View | Zoom menu to replace the default Zoom In/Out menu
+ * items with Primary Zoom In/Out and Secondary Zoom In/Out. Also the
+ * "Zoom Text Only" menuitem is replaced with an option to open the NS
+ * Global prefs.
+ */
+ this.updateZoomMenu = function() {
+ var popup = $('viewFullZoomMenu').childNodes[0];
+ var full_zoom_primary = NSQ.prefs.fullZoomPrimary;
+
+ if (!$('nosquint-view-menu-settings')) {
+ for (let [i, child] in enumerate(popup.childNodes)) {
+ if (child.id == 'toggle_zoom')
+ child.style.display = 'none';
+ if (child.nodeName != 'menuitem' || (child.command != 'cmd_fullZoomEnlarge' &&
+ child.command != 'cmd_fullZoomReduce'))
+ continue;
+
+ var icon = document.defaultView.getComputedStyle(child, null).getPropertyValue('list-style-image');
+ var enlarge = child.command == 'cmd_fullZoomEnlarge';
+ var item = document.createElement('menuitem');
+ var suffix = "noSquint" + (enlarge ? "Enlarge" : "Reduce") + "Secondary";
+ item.setAttribute("command", "cmd_" + suffix);
+ item.setAttribute("key", "key_" + suffix);
+ item.style.listStyleImage = icon;
+ popup.insertBefore(item, popup.childNodes[i + 2]);
+ }
+
+ var item = document.createElement('menuitem');
+ item.id = 'nosquint-view-menu-settings';
+ item.setAttribute('command', 'cmd_noSquintPrefs');
+ item.setAttribute('label', NSQ.strings.zoomMenuSettings);
+ popup.appendChild(item);
+ }
+
+ for (let child in iter(popup.childNodes)) {
+ if (child.nodeName != 'menuitem')
+ continue;
+ var command = child.getAttribute('command');
+ if (command == "cmd_fullZoomEnlarge")
+ child.setAttribute('label', NSQ.strings['zoomMenuIn' + (full_zoom_primary ? "Full" : "Text")]);
+ else if (command == "cmd_noSquintEnlargeSecondary")
+ child.setAttribute('label', NSQ.strings['zoomMenuIn' + (full_zoom_primary ? "Text" : "Full")]);
+ if (command == "cmd_fullZoomReduce")
+ child.setAttribute('label', NSQ.strings['zoomMenuOut' + (full_zoom_primary ? "Full" : "Text")]);
+ else if (command == "cmd_noSquintReduceSecondary")
+ child.setAttribute('label', NSQ.strings['zoomMenuOut' + (full_zoom_primary ? "Text" : "Full")]);
+ }
+ };
+
+
+ /* Updates the status panel and tooltip to reflect current site name
+ * and zoom levels.
+ */
+ this.updateStatus = function() {
+ var browser = gBrowser.selectedBrowser;
+ var site = browser.getUserData('nosquint').site;
+ // Disable/enable context menu item.
+ $('nosquint-menu-settings').disabled = (site === null);
+
+ if (updateStatusTimer) {
+ clearTimeout(updateStatusTimer);
+ updateStatusTimer = null;
+ }
+
+ if (NSQ.prefs.hideStatus)
+ // Pref indicates we're hiding status panel, no sense in updating.
+ return;
+
+ var text = Math.round(browser.markupDocumentViewer.textZoom * 100);
+ var full = Math.round(browser.markupDocumentViewer.fullZoom * 100);
+ var [text_default, full_default] = NSQ.prefs.getZoomDefaults();
+
+ var e = $('nosquint-status');
+ if (site) {
+ if (NSQ.prefs.fullZoomPrimary)
+ e.label = full + '%' + (text == 100 ? '' : (' / ' + text + '%'));
+ else
+ e.label = text + '%' + (full == 100 ? '' : (' / ' + full + '%'));
+ $('nosquint-status-tooltip-site').value = site.replace(/%20/g, ' ');
+ $('nosquint-status-tooltip-full').value = full + '%';
+ $('nosquint-status-tooltip-text').value = text + '%';
+
+ var style = this.getStyleForBrowser(browser);
+ var label = $('nosquint-status-tooltip-textcolor');
+ label.style.color = style.colorText || 'inherit';
+ label.style.backgroundColor = style.colorBackground || 'inherit';
+ label.value = (style.colorText || style.colorBackground) ? 'Sample' : 'Site Controlled';
+
+ var vis = $('nosquint-status-tooltip-vis-link');
+ var unvis = $('nosquint-status-tooltip-unvis-link');
+ unvis.value = vis.value = '';
+ vis.style.color = vis.style.textDecoration = 'inherit';
+ unvis.style.color = unvis.style.textDecoration = 'inherit';
+
+ if (!style.linksUnvisited && !style.linksVisited)
+ unvis.value = 'Site Controlled';
+ else {
+ for (let [attr, elem] in items({'linksUnvisited': unvis, 'linksVisited': vis})) {
+ if (style[attr]) {
+ elem.value = attr.replace('links', '');
+ elem.style.color = style[attr];
+ elem.style.textDecoration = style.linksUnderline ? 'underline' : 'inherit';
+ }
+ }
+ }
+ $('nosquint-status-tooltip').style.display = '';
+ e.style.fontStyle = e.style.opacity = 'inherit';
+ } else {
+ $('nosquint-status-tooltip').style.display = 'none';
+ e.label = 'N/A';
+ /* Lame: the documentation for statusbarpanel says there is a
+ * disabled attribute. The DOM Inspector says otherwise. So we
+ * must simulate the disabled look.
+ */
+ e.style.opacity = 0.5;
+ e.style.fontStyle = 'italic';
+ }
+ };
+
+ /* Queues an updateStatus().
+ */
+ this.queueUpdateStatus = function() {
+ if (!updateStatusTimer)
+ updateStatusTimer = setTimeout(function() NSQ.browser.updateStatus(), 1);
+ };
+
+ /* Given a browser, returns the site name. Does not use the cached
+ * site name user data attached to the browser.
+ */
+ this.getSiteFromBrowser = function(browser) {
+ if (isChrome(browser))
+ return null;
+ return NSQ.prefs.getSiteFromURI(browser.currentURI);
+ };
+
+ /* Returns a 2-tuple [text, full] zoom levels for the given browser.
+ * Defaults are applied.
+ */
+ this.getZoomForBrowser = function(browser) {
+ var site = browser.getUserData('nosquint').site;
+ if (site === null) {
+ site = this.getSiteFromBrowser(browser);
+ browser.getUserData('nosquint').site = site;
+ }
+
+ var [text, full] = NSQ.prefs.getZoomForSite(site);
+ var [text_default, full_default] = NSQ.prefs.getZoomDefaults();
+ return [text || text_default, full || full_default];
+ };
+
+
+ /* Saves the current tab's zoom level in the site list.
+ */
+ this.saveCurrentZoom = function() {
+ var browser = gBrowser.selectedBrowser;
+ var site = browser.getUserData('nosquint').site;
+ if (!site)
+ // Nothing to save. Chrome maybe.
+ return;
+
+ var text = Math.round(browser.markupDocumentViewer.textZoom * 100);
+ var full = Math.round(browser.markupDocumentViewer.fullZoom * 100);
+ debug("saveCurrentZoom(): site=" + site);
+ NSQ.prefs.updateSiteList(site, [text, full]);
+ };
+
+ this.attach = function(browser) {
+ var listener = new NSQ.interfaces.ProgressListener(browser);
+ browser.addProgressListener(listener, CI.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ var userData = {
+ listener: listener,
+ stylers: []
+ };
+ browser.setUserData('nosquint', userData, null);
+
+ browser.addEventListener('DOMFrameContentLoaded', function(event) {
+ var styler = NSQ.browser.getDocumentStyler(browser, event.target.contentWindow.document);
+ styler();
+ browser.getUserData('nosquint').stylers.push(styler);
+ }, true);
+
+ };
+
+
+ /* Zooms text and/or full zoom to the specified level. If text or full is
+ * null, the default for browser is used. If it is false, it is
+ * untouched. Status bar is updated, but new level is NOT saved.
+ */
+ this.zoom = function(browser, text, full) {
+ if (!browser || (text == false && full == false))
+ return false;
+
+ var t0 = new Date().getTime();
+ if (text == null || full == null) {
+ var [site_text, site_full] = this.getZoomForBrowser(browser);
+ if (text == null)
+ text = text || site_text;
+ if (full == null)
+ full = full || site_full;
+ // Only zoom web content, not chrome or plugins (e.g. PDF)
+ if (!browser.getUserData('nosquint').site)
+ [text, full] = [100, 100];
+ }
+
+ debug("zoom(): text=" + text + ", full=" + full);
+ if (text !== false)
+ browser.markupDocumentViewer.textZoom = text / 100.0;
+ if (full !== false)
+ browser.markupDocumentViewer.fullZoom = full / 100.0;
+ if (browser == gBrowser.selectedBrowser)
+ this.queueUpdateStatus();
+ var t1 = new Date().getTime();
+ debug('zoom(): took ' + (t1-t0));
+ return true;
+ };
+
+ /* Updates the zoom levels for all tabs; each tab is set to the levels
+ * for the current URIs of each browser. If 'attach' is true, then
+ * ProgressListeners are attached to each browser as well. This is
+ * useful on initialization, where we can hook into any tabs that may
+ * have been opened prior to initialization.
+ */
+ this.zoomAll = function(site, attach) {
+ debug("zoomAll(): site=" + site + ", attach=" + attach);
+ for (let browser in iter(gBrowser.browsers)) {
+ if (site && site != browser.getUserData('nosquint').site)
+ continue;
+ if (attach)
+ this.attach(browser);
+ this.zoom(browser);
+ }
+ clearTimeout(zoomAllTimer);
+ zoomAllTimer = null;
+ };
+
+ /* Queues a zoomAll. Useful when we might otherwise call zoomAll()
+ * multiple times, such as in the case of multiple preferences being
+ * updated at once.
+ */
+ this.queueZoomAll = function(site, delay) {
+ if (delay === undefined)
+ delay = 1;
+ if (!zoomAllTimer)
+ zoomAllTimer = setTimeout(function() NSQ.browser.zoomAll(site), delay);
+ };
+
+
+ /* Returns a style object for the given browser. Defaults are applied.
+ */
+ this.getStyleForBrowser = function(browser) {
+ var site = browser.getUserData('nosquint').site;
+ var style = NSQ.prefs.getStyleForSite(site);
+ return NSQ.prefs.applyStyleGlobals(style);
+ };
+
+ /* Returns CSS string for the given style object.
+ */
+ this.getCSSFromStyle = function(style) {
+ var css = '';
+ if (style.colorText || style.colorBackground || style.colorBackgroundImages) {
+ css += 'body,p,div,span,center,blockquote,h1,h2,h3,h4,h5,table,tr,th,td,iframe,a,b,i {';
+ if (style.colorText)
+ css += 'color: ' + style.colorText + ' !important;';
+ if (style.colorBackground)
+ css += 'background-color: ' + style.colorBackground + ' !important;';
+ if (style.colorBackgroundImages)
+ css += 'background-image: none !important;';
+ css += '}\n';
+ };
+
+ if (style.linksUnvisited)
+ css += 'a:link { color: ' + style.linksUnvisited + ' !important; }\n';
+ if (style.linksVisited)
+ css += 'a:visited { color: ' + style.linksVisited + ' !important; }\n';
+ if (style.linksUnderline)
+ css += 'a { text-decoration: underline !important; }\n';
+
+ return css;
+ };
+
+ /* Returns a function that, when invoked, will style the given document
+ * from the given browser. The styler function can be explicitly passed
+ * a style attributes object to override the calculated one for the site.
+ */
+ this.getDocumentStyler = function(browser, doc) {
+ var styleobj = null;
+ function styler(style) {
+ if (!style)
+ style = NSQ.browser.getStyleForBrowser(browser);
+
+ debug("styler(): enabled=" + style.enabled + ", obj=" + styleobj);
+ if (style.enabled) {
+ if (!styleobj) {
+ styleobj = doc.createElementNS('http://www.w3.org/1999/xhtml', 'style');
+ // This doesn't appear to be necessary, and in any case seems
+ // to not work when there are CSS problems on the site (like google
+ // sometimes has).
+ //var head = doc.getElementsByTagName('head');
+ //var node = (head ? head[0] : doc.documentElement);
+ //node.insertBefore(styleobj, node.childNodes[0]);
+ doc.documentElement.appendChild(styleobj);
+ }
+ var css = NSQ.browser.getCSSFromStyle(style);
+ styleobj.textContent = css;
+ } else if (styleobj) {
+ styleobj.parentNode.removeChild(styleobj);
+ // Must recreate style object if we want to attach later.
+ styleobj = null;
+ }
+ }
+ return styler;
+ };
+
+ /* Adds a styler to the given document if none exist, and invokes all
+ * attached stylers with the given style.
+ *
+ * If the document cannot be styled, false is returned. Otherwise, true.
+ */
+ this.style = function(browser, style) {
+ var doc = browser.docShell.document;
+ if (!doc.documentElement)
+ // Nothing to style; chrome?
+ return false;
+
+ var stylers = browser.getUserData('nosquint').stylers;
+ if (stylers.length == 0)
+ // Initial styling; attach styler for document (or frameset).
+ stylers.push(this.getDocumentStyler(browser, doc));
+
+ debug("style(): num stylers=" + stylers.length);
+ for (let styler in iter(stylers))
+ styler(style);
+
+ if (browser == gBrowser.selectedBrowser)
+ this.queueUpdateStatus();
+
+ return true;
+ };
+
+ this.styleAll = function(site) {
+ for (let browser in iter(gBrowser.browsers)) {
+ if (site && site != browser.getUserData('nosquint').site)
+ continue;
+ this.style(browser);
+ }
+ clearTimeout(styleAllTimer);
+ styleAllTimer = null;
+ };
+
+ /* Queues a styleAll.
+ */
+ this.queueStyleAll = function(site, delay) {
+ if (delay === undefined)
+ delay = 1;
+ if (!styleAllTimer)
+ styleAllTimer = setTimeout(function() NSQ.browser.styleAll(site), delay);
+ };
+}});
diff --git a/src/content/cmd.js b/src/content/cmd.js
new file mode 100644
index 0000000..eba119a
--- /dev/null
+++ b/src/content/cmd.js
@@ -0,0 +1,184 @@
+/******************************************************************************
+ * Commands
+ *
+ * Functions that are invoked as a result of some UI action.
+ *
+ */
+NoSquint.cmd = NoSquint.ns(function() { with (NoSquint) {
+
+ /* Handlers for toolar buttons */
+ this.buttonEnlarge = function(event) {
+ event.shiftKey ? NSQ.cmd.enlargeSecondary() : NSQ.cmd.enlargePrimary();
+ };
+
+ this.buttonReduce = function(event) {
+ event.shiftKey ? NSQ.cmd.reduceSecondary() : NoSquint.cmd.reducePrimary();
+ };
+
+ this.buttonReset = function(event) {
+ NSQ.cmd.reset();
+ };
+
+ /* Handlers for commands defined in overlay.xul */
+ this.enlargePrimary = function() {
+ NSQ.prefs.fullZoomPrimary ? NSQ.cmd.enlargeFullZoom() : NSQ.cmd.enlargeTextZoom();
+ };
+
+ this.reducePrimary = function() {
+ NSQ.prefs.fullZoomPrimary ? NSQ.cmd.reduceFullZoom() : NSQ.cmd.reduceTextZoom();
+ };
+
+ this.enlargeSecondary = function() {
+ NSQ.prefs.fullZoomPrimary ? NSQ.cmd.enlargeTextZoom() : NSQ.cmd.enlargeFullZoom();
+ };
+
+ this.reduceSecondary = function() {
+ NSQ.prefs.fullZoomPrimary ? NSQ.cmd.reduceTextZoom() : NSQ.cmd.reduceFullZoom();
+ };
+
+ this.reset = function() {
+ var [text, full] = NSQ.prefs.getZoomDefaults();
+ var viewer = getBrowser().mCurrentBrowser.markupDocumentViewer;
+ var updated = false;
+
+ if (Math.round(viewer.textZoom * 100.0) != text)
+ updated = viewer.textZoom = text / 100.0;
+ if (Math.round(viewer.fullZoom * 100.0) != full)
+ updated = viewer.fullZoom = full / 100.0;
+
+ if (updated != false) {
+ NSQ.browser.saveCurrentZoom();
+ NSQ.browser.updateStatus();
+ }
+ };
+
+ this.enlargeTextZoom = function() {
+ var browser = getBrowser().mCurrentBrowser;
+ if (isImage(browser))
+ return NSQ.cmd.enlargeFullZoom();
+ var mdv = browser.markupDocumentViewer;
+ mdv.textZoom = Math.round(mdv.textZoom * 100.0 + NSQ.prefs.zoomIncrement) / 100.0;
+ NSQ.browser.saveCurrentZoom();
+ NSQ.browser.updateStatus();
+ };
+
+ this.reduceTextZoom = function() {
+ var browser = getBrowser().mCurrentBrowser;
+ if (isImage(browser))
+ return NSQ.cmd.reduceFullZoom();
+ var mdv = browser.markupDocumentViewer;
+ mdv.textZoom = Math.round(mdv.textZoom * 100.0 - NSQ.prefs.zoomIncrement) / 100.0;
+ NSQ.browser.saveCurrentZoom();
+ NSQ.browser.updateStatus();
+ };
+
+ this.enlargeFullZoom = function() {
+ var browser = getBrowser().mCurrentBrowser;
+ if (isImage(browser) && browser.getUserData('nosquint').fit)
+ return;
+ var mdv = browser.markupDocumentViewer;
+ mdv.fullZoom = Math.round(mdv.fullZoom * 100.0 + NSQ.prefs.zoomIncrement) / 100.0;
+ NSQ.browser.saveCurrentZoom();
+ NSQ.browser.updateStatus();
+ };
+
+ this.reduceFullZoom = function() {
+ var browser = getBrowser().mCurrentBrowser;
+ if (isImage(browser) && browser.getUserData('nosquint').fit)
+ return;
+ var mdv = browser.markupDocumentViewer;
+ mdv.fullZoom = Math.round(mdv.fullZoom * 100.0 - NSQ.prefs.zoomIncrement) / 100.0;
+ NSQ.browser.saveCurrentZoom();
+ NSQ.browser.updateStatus();
+ };
+
+ /* Called when a menuitem from the status panel context menu is selected. */
+ this.popupItemSelect = function(event) {
+ var item = event.target;
+ var label = item.label;
+ if (label.search(/%$/) != -1) {
+ /* One of the radio menuitems for zoom level was selected (label
+ * ends in %). Set the zoom level based on the radio's group
+ * name.
+ */
+ var level = parseInt(label.replace(/%/, ''));
+ var browser = gBrowser.selectedBrowser;
+ if (item.getAttribute('name') == 'text')
+ NSQ.browser.zoom(browser, level, false);
+ else
+ NSQ.browser.zoom(browser, false, level);
+ NSQ.browser.saveCurrentZoom();
+ }
+ };
+
+ /* Handle left/middle/right click on the status panel. */
+ this.statusPanelClick = function(event) {
+ if (event.button == 0)
+ // Left click, open site prefs.
+ return NSQ.cmd.openSiteSettings();
+ else if (event.button == 1)
+ // Middle click, open global prefs.
+ return NSQ.cmd.openGlobalSettings();
+
+ /* Right click. Setup the context menu according to the current
+ * browser tab: the site name is set, and the appropriate radio
+ * menuitems get selected.
+ */
+ var popup = $('nosquint-status-popup');
+ var browser = gBrowser.selectedBrowser;
+ var site = browser.getUserData('nosquint').site;
+
+ // Hide all but the last menuitem if there is no site
+ for (let [n, child] in enumerate(popup.childNodes))
+ child.style.display = (site || n == popup.childNodes.length-1) ? '' : 'none';
+
+ var popup_text = $('nosquint-status-popup-text');
+ var popup_full = $('nosquint-status-popup-full');
+
+ var current_text = Math.round(browser.markupDocumentViewer.textZoom * 100);
+ var current_full = Math.round(browser.markupDocumentViewer.fullZoom * 100);
+
+ popup.childNodes[0].label = site;
+
+ for (let child in iter(popup_text.childNodes))
+ child.setAttribute('checked', child.label.replace(/%/, '') == current_text);
+ for (let child in iter(popup_full.childNodes))
+ child.setAttribute('checked', child.label.replace(/%/, '') == current_full);
+
+ popup.openPopupAtScreen(event.screenX, event.screenY, true);
+ };
+
+
+ /* Opens the site prefs dialog, or focuses it if it's already open.
+ * In either case, the values of the dialog are updated to reflect the
+ * current browser tab.
+ */
+ this.openSiteSettings = function() {
+ var browser = gBrowser.selectedBrowser;
+ if (!browser.getUserData('nosquint').site)
+ // Chrome
+ return;
+ var dlg = NSQ.storage.dialogs.site;
+ if (dlg)
+ return dlg.setBrowser(NSQ.browser, browser);
+ window.openDialog('chrome://nosquint/content/dlg-site.xul', null, 'chrome', NSQ.browser, browser);
+ };
+
+
+ /* Opens global prefs dialog or focuses it if it's already open. */
+ this.openGlobalSettings = function(browser) {
+ var dlg = NSQ.storage.dialogs.global;
+ if (dlg)
+ return dlg.focus();
+
+ browser = browser || gBrowser.selectedBrowser;
+ var host = browser.currentURI.asciiHost;
+ try {
+ if (browser.currentURI.port > 0)
+ host += ':' + browser.currentURI.port;
+ } catch (err) {};
+ var url = host + browser.currentURI.path;
+ window.openDialog('chrome://nosquint/content/dlg-global.xul', null, 'chrome', url);
+ };
+
+}});
diff --git a/src/content/dlg-global.js b/src/content/dlg-global.js
new file mode 100644
index 0000000..c40b2e2
--- /dev/null
+++ b/src/content/dlg-global.js
@@ -0,0 +1,241 @@
+NoSquint.dialogs.global = NoSquint.ns(function() { with (NoSquint) {
+ this.strings = getStringBundle('dlg-global');
+ var branchPI = NSQ.prefs.svc.getBranch('privacy.' + (is30() ? 'item.' : 'cpd.'));
+
+ this.init = function() {
+ NSQ.storage.dialogs.global = this;
+ this.dlg = $('nosquint-dialog-global');
+ this.url = window.arguments ? window.arguments[0] : null;
+
+ // General tab
+ $('rememberSites').selectedIndex = Number(!NSQ.prefs.rememberSites);
+ $('siteForget').checked = (NSQ.prefs.forgetMonths != 0);
+ $('siteForget-menu').value = NSQ.prefs.forgetMonths;
+ $('siteForget').addEventListener('CheckboxStateChange',
+ function() NSQ.dialogs.global.forgetMonthsChecked(), false);
+ $('siteSanitize').checked = branchPI.getBoolPref('extensions-nosquint');
+
+ // Zooming tab
+ $('fullZoomLevel').value = NSQ.prefs.fullZoomLevel;
+ $('textZoomLevel').value = NSQ.prefs.textZoomLevel;
+ $('zoomIncrement').value = NSQ.prefs.zoomIncrement;
+ // XXX: image zoom feature disabled for now.
+ //$('zoomImages').checked = NSQ.prefs.zoomImages;
+ $('showStatus').checked = !NSQ.prefs.hideStatus;
+ $('wheelZoomEnabled').checked = NSQ.prefs.wheelZoomEnabled;
+ $('primaryZoomMethod-menu').value = NSQ.prefs.fullZoomPrimary ? 'full' : 'text';
+ this.rememberSelect();
+
+ // Color tab
+ for (let [id, defcolor] in items(NSQ.prefs.defaultColors)) {
+ var color = NSQ.prefs[id];
+ $(id).parentNode.childNodes[1].color = (color == '0' ? defcolor : color);
+ $(id).addEventListener('CheckboxStateChange', this.colorChecked, false);
+ $(id).checked = (color == '0' ? false : true);
+ this.colorChecked.apply($(id));
+ }
+ $('colorBackgroundImages').checked = NSQ.prefs.colorBackgroundImages;
+ $('linksUnderline').checked = NSQ.prefs.linksUnderline;
+
+ // Exceptions tab
+ $('copyURL-button').style.display = this.url ? '' : 'none';
+ for (let exc in iter(NSQ.prefs.exceptions))
+ this.exceptionsListAdd(exc[0].replace(/%20/g, ' '), false);
+ $('exceptionsList').setUserData('nosquint.changed', false, null);
+ };
+
+ this.focus = function() {
+ window.focus();
+ };
+
+ this.cancel = function() {
+ this.finalize();
+ };
+
+ this.finalize = function() {
+ NSQ.storage.dialogs.global = null;
+ };
+
+ this.help = function() {
+ var tab = $('tabs').selectedPanel.id.replace(/tab$/, '');
+ window.openDialog('chrome://nosquint/content/dlg-help.xul', null, 'chrome', tab);
+ };
+
+ this.close = function() {
+ if ($('pattern').value != '')
+ /* User entered stuff in exception input but OK'd dialog without
+ * adding the exception. We assume here the user actually _wanted_
+ * the exception to be added, so add it automatically. This is
+ * a bit of do-what-I-mean behaviour.
+ */
+ this.buttonAddException();
+
+ // General tab
+ NSQ.prefs.rememberSites = !Boolean($('rememberSites').selectedIndex);
+ NSQ.prefs.forgetMonths = $('siteForget').checked ? $('siteForget-menu').value : 0;
+ branchPI.setBoolPref('extensions-nosquint', $('siteSanitize').checked);
+
+ // Zooming tab
+ NSQ.prefs.fullZoomLevel = parseInt($('fullZoomLevel').value);
+ NSQ.prefs.textZoomLevel = parseInt($('textZoomLevel').value);
+ NSQ.prefs.zoomIncrement = parseInt($('zoomIncrement').value);
+ // XXX: image zoom feature disabled for now.
+ //NSQ.prefs.zoomImages = $('zoomImages').checked;
+ NSQ.prefs.hideStatus = !$('showStatus').checked;
+ NSQ.prefs.wheelZoomEnabled = $('wheelZoomEnabled').checked;
+ NSQ.prefs.fullZoomPrimary = $('primaryZoomMethod-menu').value == 'full';
+
+ // Color tab
+ for (let [id, defcolor] in items(NSQ.prefs.defaultColors))
+ NSQ.prefs[id] = $(id).checked ? $(id).parentNode.childNodes[1].color : '0';
+ NSQ.prefs.colorBackgroundImages = $('colorBackgroundImages').checked;
+ NSQ.prefs.linksUnderline = $('linksUnderline').checked;
+
+ // Exceptions tab
+ var listbox = $('exceptionsList');
+ var exceptions = null;
+ if (listbox.getUserData('nosquint.changed')) {
+ exceptions = [];
+ for (let i = 0; i < listbox.getRowCount(); i++) {
+ var item = listbox.getItemAtIndex(i);
+ var pattern = item.childNodes[0].getAttribute('label');
+ exceptions.push(pattern.replace(/ /g, '%20'));
+ }
+ }
+ NSQ.prefs.saveAll(exceptions);
+ this.finalize();
+ };
+
+
+ /*********************************************
+ * General tab functions
+ */
+ this.forgetMonthsChecked = function() {
+ // Months optionlist is disabled if "Forget settings" checkbox isn't checked.
+ $('siteForget-menu').disabled = !$('siteForget').checked;
+ };
+
+
+ /*********************************************
+ * Zooming tab functions
+ */
+ // Called when the "Remember zoom and color settings per site" radio button
+ // is clicked.
+ this.rememberSelect = function() {
+ if (this.dlg === undefined)
+ // Happens on initial dialog open before init()
+ return;
+ // Enable nested options under "Remember zoom" radiobutton if the radio is active.
+ var disabled = $('rememberSites').selectedIndex == 1;
+ this.enableTree($('siteForget-box'), disabled);
+ };
+
+ // Enables or disables all elements in the given hierarchy
+ this.enableTree = function(node, state) {
+ for (let child in iter(node.childNodes)) {
+ if (state && child.disabled == false || child.disabled == true)
+ child.disabled = state;
+ if (child.childNodes.length)
+ this.enableTree(child, state);
+ }
+ };
+
+
+
+ /*********************************************
+ * Color tab functions
+ */
+
+ this.colorChecked = function(event) {
+ // Color picker button is enabled if the checkbox beside is is on.
+ var picker = this.parentNode.childNodes[1];
+ picker.disabled = !this.checked;
+ picker.style.opacity = this.checked ? 1.0 : 0.2;
+ };
+
+
+ /*********************************************
+ * Exceptions tab functions
+ */
+
+ this.exceptionsListAdd = function(pattern, check_dupe) {
+ var listbox = $('exceptionsList');
+ // Strip URI scheme from pattern (if it exists)
+ pattern = pattern.replace(/^\w+:\/\//, '');
+
+ if (check_dupe) {
+ for (let node in iter(listbox.childNodes)) {
+ if (node.childNodes[0].getAttribute('label') == pattern)
+ return;
+ }
+ }
+
+ // Append new exceptions pattern to the list.
+ var node = document.createElement("listitem");
+ var li1 = document.createElement("listcell");
+ li1.setAttribute('label', pattern);
+ node.appendChild(li1);
+ listbox.appendChild(node);
+ node.addEventListener('dblclick', function() NSQ.dialogs.global.buttonEditException(), false);
+ // Mark the listbox as having been changed from stored prefs.
+ listbox.setUserData('nosquint.changed', true, null);
+ };
+
+ this.textPatternKeyPress = function(event) {
+ if (event.keyCode == 13) {
+ // Pressed enter in the pattern input box.
+ this.buttonAddException();
+ return false;
+ }
+ };
+
+ this.textPatternChange = function() {
+ // Enable 'Add' button if the pattern input box isn't empty.
+ $('exceptionAdd-button').disabled = ($('pattern').value == '');
+ };
+
+ this.excListKeyPress = function(event) {
+ if (event.keyCode == 13) {
+ // Pressed enter on one of the listitems.
+ this.buttonEditException();
+ return false;
+ }
+ };
+
+ this.excListSelect = function() {
+ // Edit/Remove buttons enabled when one of the listitems is selected.
+ $('exceptionRemove-button').disabled = ($('exceptionsList').selectedItems.length == 0);
+ $('exceptionEdit-button').disabled = ($('exceptionsList').selectedItems.length != 1);
+ };
+
+ this.buttonCopyFromURL = function() {
+ // Copy button is hidden unless this.url is set.
+ $('pattern').value = this.url;
+ this.textPatternChange();
+ };
+
+ this.buttonAddException = function() {
+ this.exceptionsListAdd($('pattern').value, true);
+ $('pattern').value = '';
+ this.textPatternChange();
+ };
+
+ this.buttonEditException = function() {
+ var listcell = $('exceptionsList').selectedItem.childNodes[0];
+ var oldPattern = listcell.getAttribute('label');
+ var newPattern = popup('prompt', this.strings.editTitle, this.strings.editPrompt, oldPattern);
+ if (newPattern != null && newPattern != oldPattern) {
+ listcell.setAttribute('label', newPattern);
+ $('exceptionsList').setUserData('nosquint.changed', true, null);
+ }
+ };
+
+ this.buttonRemoveException = function() {
+ // Listbox is multi-select capable; remove all selected items.
+ var listbox = $('exceptionsList');
+ while (listbox.selectedItems.length)
+ listbox.removeChild(listbox.selectedItems[0]);
+ listbox.setUserData('nosquint.changed', true, null);
+ };
+
+}});
diff --git a/src/content/dlg-global.xul b/src/content/dlg-global.xul
new file mode 100644
index 0000000..86f0e78
--- /dev/null
+++ b/src/content/dlg-global.xul
@@ -0,0 +1,218 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://nosquint/content/dlg-style.css" type="text/css"?>
+<!DOCTYPE window SYSTEM "chrome://nosquint/locale/dlg-global.dtd">
+
+<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="help,accept,cancel"
+ ondialogaccept="NoSquint.dialogs.global.close()"
+ ondialogcancel="NoSquint.dialogs.global.cancel()"
+ ondialoghelp="NoSquint.dialogs.global.help()"
+ id="nosquint-dialog-global"
+ persist="screenX screenY">
+
+ <script type="application/x-javascript" src="chrome://nosquint/content/init.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/lib.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/prefs.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/dlg-global.js" />
+
+ <tabbox id='tabs' flex="1">
+ <tabs>
+ <tab label="&ns.pref.tab.general.label;" />
+ <tab label="&ns.pref.tab.zooming.label;" />
+ <tab label="&ns.pref.tab.colors.label;" />
+ <tab label="&ns.pref.tab.exceptions.label;" />
+ </tabs>
+
+ <tabpanels flex="1">
+ <!-- General Tab -->
+ <tabpanel id="generaltab" flex="1">
+ <vbox flex="1">
+ <groupbox id="persistence">
+ <caption label="&ns.pref.persistence.caption;" />
+ <radiogroup id="rememberSites" onselect="NoSquint.dialogs.global.rememberSelect()">
+ <radio label="&ns.pref.persistence.remember.label;" />
+ <vbox class="indent" id='siteForget-box'>
+ <hbox align="center">
+ <checkbox id="siteForget" label="&ns.pref.persistence.forget.label;"
+ checked="false" />
+ <menulist id="siteForget-menu">
+ <menupopup>
+ <menuitem value="12" label="&ns.pref.persistence.forget.year;"/>
+ <menuitem value="6" label="&ns.pref.persistence.forget.6months;"
+ selected="true" />
+ <menuitem value="3" label="&ns.pref.persistence.forget.3months;" />
+ <menuitem value="1" label="&ns.pref.persistence.forget.month;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <checkbox id="siteSanitize" label="&ns.pref.persistence.sanitize.label;"
+ checked="false" />
+ </vbox>
+ <radio label="&ns.pref.persistence.noRemember.label;" />
+ </radiogroup>
+ </groupbox>
+ </vbox>
+ </tabpanel>
+
+ <!-- Zooming Tab -->
+ <tabpanel id="zoomingtab" flex="1">
+ <vbox flex="1">
+ <groupbox>
+ <caption label="&ns.pref.zooming.caption;" />
+ <grid>
+ <columns>
+ <column />
+ <column />
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox>
+ <spacer flex="1" />
+ <label>&ns.pref.zooming.primaryMethod.label;:</label>
+ </hbox>
+ <menulist id="primaryZoomMethod-menu">
+ <menupopup>
+ <menuitem value="full" label="&ns.pref.zooming.primaryMethod.full;"
+ selected="true"/>
+ <menuitem value="text" label="&ns.pref.zooming.primaryMethod.text;"/>
+ </menupopup>
+ </menulist>
+ </row>
+
+ <row align="center">
+ <hbox>
+ <spacer flex="1" />
+ <label>&ns.pref.zooming.fullLevel.label;:</label>
+ </hbox>
+ <hbox align="center">
+ <textbox id="fullZoomLevel" size="2" type="number" min="40"
+ max="300" increment="5" />
+ <label class="percent">%</label>
+ </hbox>
+ </row>
+
+ <row align="center">
+ <hbox>
+ <spacer flex="1" />
+ <label>&ns.pref.zooming.textLevel.label;:</label>
+ </hbox>
+ <hbox align="center">
+ <textbox id="textZoomLevel" size="2" type="number" min="40"
+ max="300" increment="5" />
+ <label class="percent">%</label>
+ </hbox>
+ </row>
+
+ <row align="center">
+ <hbox>
+ <spacer flex="1" />
+ <label>&ns.pref.zooming.increment.label;:</label>
+ </hbox>
+ <hbox align="center">
+ <textbox id="zoomIncrement" size="2" type="number" min="1"
+ max="100" increment="1" />
+ <label class="percent">%</label>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <vbox>
+ <!-- XXX: image zoom feature disabled for now.
+ <checkbox id="zoomImages" label="&ns.pref.zooming.images.label;" checked="false" />
+ -->
+ <checkbox id="wheelZoomEnabled" label="&ns.pref.zooming.mousewheel.label;"
+ checked="false" />
+ <checkbox id="showStatus" label="&ns.pref.zooming.showstatus.label;" checked="false" />
+ </vbox>
+ </groupbox>
+ </vbox>
+ </tabpanel>
+
+ <!-- Colors Tab -->
+ <tabpanel id="colorstab" flex="1">
+ <vbox flex="1">
+ <html:p style="margin: 0 7px 0.5em 7px; padding: 0;">&ns.pref.colors.info;</html:p>
+ <hbox>
+ <groupbox flex='1'>
+ <caption label="&ns.pref.colors.colors.caption;" />
+ <vbox>
+ <hbox>
+ <checkbox id="colorText" label="&ns.pref.colors.colors.text.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <hbox>
+ <checkbox id="colorBackground" label="&ns.pref.colors.colors.background.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <checkbox id="colorBackgroundImages" label="&ns.pref.colors.colors.images.label;"
+ checked="false" flex="1" />
+ </vbox>
+ </groupbox>
+ <groupbox flex='1'>
+ <caption label="&ns.pref.colors.links.caption;" />
+ <vbox>
+ <hbox>
+ <checkbox id="linksUnvisited" label="&ns.pref.colors.links.unvisited.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <hbox>
+ <checkbox id="linksVisited" label="&ns.pref.colors.links.visited.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <checkbox id="linksUnderline" label="&ns.pref.colors.links.underline.label;"
+ checked="false" flex="1" />
+ </vbox>
+ </groupbox>
+ </hbox>
+ </vbox>
+ </tabpanel>
+
+ <!-- Exceptions Tab -->
+ <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="NoSquint.dialogs.global.textPatternChange()"
+ onkeypress="return NoSquint.dialogs.global.textPatternKeyPress(event)" />
+ <hbox>
+ <button label="&ns.pref.exceptions.copyButton.label;" id="copyURL-button"
+ accesskey="&ns.pref.exceptions.copyButton.accesskey;"
+ oncommand="NoSquint.dialogs.global.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="NoSquint.dialogs.global.buttonAddException()" />
+ </hbox>
+
+ <separator />
+
+ <listbox id="exceptionsList" flex="1" seltype="multiple" rows="5"
+ onkeypress="return NoSquint.dialogs.global.excListKeyPress(event)"
+ onselect="NoSquint.dialogs.global.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="NoSquint.dialogs.global.buttonEditException()" />
+ <button label="&ns.pref.exceptions.removeButton.label;"
+ icon="remove" id="exceptionRemove-button"
+ accesskey="&ns.pref.exceptions.removeButton.accesskey;"
+ oncommand="NoSquint.dialogs.global.buttonRemoveException()" />
+ </hbox>
+ </vbox>
+ </tabpanel>
+
+ </tabpanels>
+ </tabbox>
+</dialog>
diff --git a/src/content/dlg-help.js b/src/content/dlg-help.js
new file mode 100644
index 0000000..121548b
--- /dev/null
+++ b/src/content/dlg-help.js
@@ -0,0 +1,10 @@
+NoSquint.dialogs.help = NoSquint.ns(function() { with (NoSquint) {
+ this.init = function() {
+ var browser = $('nosquint-help-browser');
+ var uri = 'chrome://nosquint/locale/help.html';
+ if (window.arguments)
+ uri += '#' + window.arguments[0];
+ browser.loadURI(uri, null, null);
+ };
+
+}});
diff --git a/src/content/dlg-help.xul b/src/content/dlg-help.xul
new file mode 100644
index 0000000..d8f15e6
--- /dev/null
+++ b/src/content/dlg-help.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<!DOCTYPE window SYSTEM "chrome://nosquint/locale/dlg-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-dialog-help"
+ persist="width height"
+ width="800"
+ height="700">
+
+ <script type="application/x-javascript" src="chrome://nosquint/content/init.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/lib.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/dlg-help.js" />
+
+ <dialogheader title="&ns.help.subtitle;" description="&ns.help.title;" />
+ <groupbox flex="1">
+ <browser type="content" id="nosquint-help-browser" flex="1" />
+ </groupbox>
+</dialog>
diff --git a/src/content/dlg-site.js b/src/content/dlg-site.js
new file mode 100644
index 0000000..b314cc1
--- /dev/null
+++ b/src/content/dlg-site.js
@@ -0,0 +1,176 @@
+NoSquint.dialogs.site = NoSquint.ns(function() { with (NoSquint) {
+ this.strings = getStringBundle('dlg-site');
+
+ var updateTimer = null;
+
+ this.init = function() {
+ NSQ.storage.dialogs.site = this;
+ this.dlg = $('nosquint-dialog-site');
+
+ this.setBrowser(window.arguments[0], window.arguments[1]);
+
+ $('full-zoom-level').onchange = function() NSQ.dialogs.site.valueChange(this);
+ $('text-zoom-level').onchange = function() NSQ.dialogs.site.valueChange(this);
+
+ var restyle = function() NSQ.dialogs.site.style(true, false);
+ for (let id in NSQ.prefs.defaultColors) {
+ $(id).addEventListener('CheckboxStateChange', this.colorChecked, false);
+ $(id).parentNode.childNodes[1].onchange = restyle;
+ }
+ $('colorBackgroundImages').addEventListener('CheckboxStateChange', restyle, false);
+ $('linksUnderline').addEventListener('CheckboxStateChange', restyle, false);
+ };
+
+
+ // Immediately dismiss window. Used when transitioning from Private Browsing mode.
+ this.die = function() {
+ this.finalize();
+ window.close();
+ };
+
+ this.cancel = function() {
+ this.revert();
+ this.finalize();
+ };
+
+ this.close = function() {
+ this.zoom(true, true);
+ this.style(true, true);
+ this.finalize();
+ };
+
+ this.finalize = function() {
+ NSQ.storage.dialogs.site = null;
+ };
+
+
+ this.setBrowser = function(nsqBrowser, mozBrowser) {
+ NSQ.browser = nsqBrowser;
+ var site = mozBrowser.getUserData('nosquint').site;
+ if (this.site) {
+ if (this.browser != mozBrowser || this.site != site)
+ // Settings opened for new site, revert any changes for last site.
+ this.revert();
+ else
+ // Everything is the same.
+ return window.focus();
+ }
+ this.browser = mozBrowser;
+ this.site = site;
+
+ var [text, full] = NSQ.browser.getZoomForBrowser(this.browser);
+ var style = NSQ.prefs.getStyleForSite(this.site);
+
+ this.updateWarning();
+
+ $('caption').label = this.site;
+ $('text-zoom-slider').value = text;
+ $('full-zoom-slider').value = full;
+
+ for (let [id, defcolor] in items(NSQ.prefs.defaultColors)) {
+ $(id).parentNode.childNodes[1].color = (!style || style[id] == '0' ? defcolor : style[id]);
+ $(id).checked = (!style || style[id] == '0' ? false : true);
+ this.colorChecked.apply($(id));
+ }
+ window.focus();
+ window.sizeToContent();
+ };
+
+ this.updateWarning = function() {
+ var content = null;
+ if (NSQ.browser.observer.inPrivateBrowsing)
+ content = this.strings.warningPrivateBrowsing;
+ else if (!NSQ.prefs.rememberSites)
+ content = this.strings.warningForgetSites;
+
+ $('warning-box-content').innerHTML = content;
+ $('warning-box').style.display = content ? '' : 'none';
+ window.sizeToContent();
+ };
+
+ this.revert = function() {
+ this.zoom(false, false);
+ this.style(false, false);
+ };
+
+
+ this.openGlobalSettings = function() {
+ NSQ.cmd.openGlobalSettings(this.browser);
+ };
+
+
+ // Callback when text/full zoom text input is changed.
+ this.valueChange = function(target) {
+ $(target.id.replace('level', 'slider')).value = target.value;
+ this.queueUpdateZoom();
+ };
+
+ // Callback when text/full zoom slider is changed.
+ this.sliderChange = function(target) {
+ // Snap to increments of 5.
+ target.value = parseInt(target.value / 5) * 5;
+ // Sync slider value to text input field.
+ $(target.id.replace('slider', 'level')).value = target.value;
+ this.queueUpdateZoom();
+ };
+
+ this.buttonUseDefault = function(target) {
+ var [text, full] = NSQ.prefs.getZoomDefaults();
+ var input = $(target.id.replace('button', 'level'));
+ input.value = (input.id == 'text-zoom-level' ? text : full);
+ input.onchange();
+ };
+
+ this.queueUpdateZoom = function() {
+ if (updateTimer)
+ return;
+ updateTimer = setTimeout(function() {
+ clearTimeout(updateTimer);
+ updateTimer = null;
+ NSQ.dialogs.site.zoom(true, false);
+ }, 400);
+ };
+
+ this.zoom = function(fromForm, save) {
+ var text = fromForm ? $('text-zoom-level').value : null;
+ var full = fromForm ? $('full-zoom-level').value : null;
+ NSQ.browser.zoom(this.browser, text, full);
+ if (save)
+ NSQ.prefs.updateSiteList(this.site, [text, full]);
+ };
+
+
+ this.colorChecked = function(event) {
+ // Color picker button is enabled if the checkbox beside is is on.
+ var picker = this.parentNode.childNodes[1];
+ picker.disabled = !this.checked;
+ picker.style.opacity = this.checked ? 1.0 : 0.2;
+ if (event)
+ // Only style() if we've been triggered by user checking the checkbox,
+ // not a call from elsewhere in this file.
+ NSQ.dialogs.site.style(true, false);
+ };
+
+ this.style = function(fromForm, save) {
+ var style = null;
+ if (fromForm) {
+ var style = {enabled: false};
+ for (let attr in iter(NSQ.prefs.defaultColors)) {
+ style[attr] = $(attr).checked ? $(attr).parentNode.childNodes[1].color : null;
+ style.enabled = style.enabled || Boolean(style[attr]);
+ }
+ for (let attr in iter(['colorBackgroundImages', 'linksUnderline'])) {
+ style[attr] = $(attr).checked;
+ style.enabled = style.enabled || Boolean(style[attr]);
+ }
+ }
+ if (save)
+ NSQ.prefs.updateSiteList(this.site, null, style);
+ if (style)
+ style = NSQ.prefs.applyStyleGlobals(style);
+
+ NSQ.browser.style(this.browser, style);
+ };
+
+
+}});
diff --git a/src/content/dlg-site.xul b/src/content/dlg-site.xul
new file mode 100644
index 0000000..1f55415
--- /dev/null
+++ b/src/content/dlg-site.xul
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://nosquint/content/dlg-style.css" type="text/css"?>
+<!DOCTYPE window [
+ <!ENTITY % siteDTD SYSTEM "chrome://nosquint/locale/dlg-site.dtd" >
+ %siteDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://nosquint/locale/dlg-global.dtd" >
+ %globalDTD;
+]>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&ns.pref.title;"
+ buttons="extra1,accept,cancel"
+ ondialogaccept="NoSquint.dialogs.site.close()"
+ ondialogcancel="NoSquint.dialogs.site.cancel()"
+ ondialogextra1="NoSquint.dialogs.site.openGlobalSettings()"
+ buttonlabelextra1="&ns.pref.button.global.label;"
+ buttonaccesskeyextra1="&ns.pref.button.global.accesskey;"
+ id="nosquint-dialog-site"
+ persist="screenX screenY">
+
+ <script type="application/x-javascript" src="chrome://nosquint/content/init.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/lib.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/prefs.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/cmd.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/dlg-site.js" />
+
+ <groupbox id="site">
+ <caption id='caption' />
+ <vbox flex='1'>
+ <hbox id="warning-box" align="center">
+ <image class="alert-icon" />
+ <html:div id='warning-box-content'></html:div>
+ </hbox>
+
+ <grid id="siteZoom-box" flex="1">
+ <columns>
+ <column />
+ <column />
+ <column />
+ <column flex="1" />
+ <column />
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox>
+ <spacer flex="1" />
+ <label value="&ns.pref.fullZoom.label;:" />
+ </hbox>
+ <scale id="full-zoom-slider" min="40" increment="1" max="300" style="width:200px"
+ onchange="NoSquint.dialogs.site.sliderChange(this)"/>
+ <hbox align="center">
+ <textbox id="full-zoom-level" size="2" type="number" min="40" max="300" increment="5" />
+ <label class="percent">%</label>
+ </hbox>
+ <spacer flex="1" />
+ <button label="&ns.pref.button.useDefault.label;"
+ id="full-zoom-button" oncommand="NoSquint.dialogs.site.buttonUseDefault(this)" />
+ </row>
+ <row align="center">
+ <hbox>
+ <spacer flex="1" />
+ <label value="&ns.pref.textZoom.label;:" />
+ </hbox>
+ <scale id="text-zoom-slider" min="40" increment="1" max="300"
+ onchange="NoSquint.dialogs.site.sliderChange(this)"/>
+ <hbox align="center">
+ <textbox id="text-zoom-level" size="2" type="number" min="40" max="300" increment="5" />
+ <label class="percent">%</label>
+ </hbox>
+ <spacer flex="1" />
+ <button label="&ns.pref.button.useDefault.label;"
+ id="text-zoom-button" oncommand="NoSquint.dialogs.site.buttonUseDefault(this)" />
+ </row>
+ </rows>
+ </grid>
+
+ <hbox flex='1' style='padding-top: 1em'>
+ <groupbox flex='1'>
+ <caption label="&ns.pref.colors.colors.caption;" />
+ <vbox>
+ <hbox>
+ <checkbox id="colorText" label="&ns.pref.colors.colors.text.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <hbox>
+ <checkbox id="colorBackground" label="&ns.pref.colors.colors.background.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <checkbox id="colorBackgroundImages" label="&ns.pref.colors.colors.images.label;"
+ checked="false" flex="1" />
+ </vbox>
+ </groupbox>
+ <groupbox flex='1'>
+ <caption label="&ns.pref.colors.links.caption;" />
+ <vbox>
+ <hbox>
+ <checkbox id="linksUnvisited" label="&ns.pref.colors.links.unvisited.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <hbox>
+ <checkbox id="linksVisited" label="&ns.pref.colors.links.visited.label;"
+ checked="false" flex="1" />
+ <colorpicker type='button' />
+ </hbox>
+ <checkbox id="linksUnderline" label="&ns.pref.colors.links.underline.label;"
+ checked="false" flex="1" />
+ </vbox>
+ </groupbox>
+ </hbox>
+
+ </vbox>
+ </groupbox>
+</dialog>
diff --git a/src/content/dlg-style.css b/src/content/dlg-style.css
new file mode 100644
index 0000000..428bb52
--- /dev/null
+++ b/src/content/dlg-style.css
@@ -0,0 +1,35 @@
+p {
+ max-width: 40em;
+}
+
+label.percent {
+ margin-left: -0.18em;
+}
+
+.indent {
+ margin-left: 2.5em;
+}
+
+button[dlgtype="extra1"] {
+ list-style-image: url("chrome://nosquint/skin/icon-statusbar-16.png");
+}
+
+#warning-box {
+ border: 1px solid #e0cd64;
+ background-color: #fffac4;
+ padding: 3px 15px 3px 15px;
+ margin: 0.5em 2em;
+ vertical-align: middle;
+ font-weight: bold;
+ -moz-border-radius: 20px;
+}
+
+#warning-box div {
+ padding-left: 0.7em;
+ width: 400px;
+ font-weight: normal;
+}
+
+#warning-box image {
+}
+
diff --git a/src/content/init.js b/src/content/init.js
index 52099ce..c571967 100644
--- a/src/content/init.js
+++ b/src/content/init.js
@@ -1,46 +1,44 @@
-window.addEventListener("load", NoSquint.init, false);
-window.addEventListener("unload", NoSquint.destroy, false);
-
-// Hook ZoomManager in order to override Firefox's internal per-site
-// zoom memory feature.
+// Global object for NoSquint. 'NoSquint' is the only name added to the global
+// namespace by this addon.
+NoSquint = {
+ id: 'NoSquint',
+ namespaces: [],
+ _initialized: false,
+ dialogs: {}, // dialogs namespace
-ZoomManager._nosquintPendingZoom = null;
-ZoomManager._nosquintOrigZoomGetter = ZoomManager.__lookupGetter__('zoom');
-ZoomManager._nosquintOrigZoomSetter = ZoomManager.__lookupSetter__('zoom');
+ ns: function(fn) {
+ var scope = {
+ extend: function(o) {
+ for (var key in o)
+ this[key] = o[key];
+ }
+ };
+ scope = fn.apply(scope) || scope;
+ NoSquint.namespaces.push(scope);
+ return scope;
+ },
-ZoomManager.__defineSetter__('zoom', function(value) {
- /* XXX: Horrid hack, makes baby Jesus cry.
- *
- * Problem: on location change and tab change, some internal FF mechanism
- * sets zoom to some stored value (on a per site basis). NoSquint
- * must fully override this mechanism, as we implement our own approach.
- *
- * Solution: rather than update zoom on the current browser immediately,
- * we queue it with a timer, and give the location/tab change handlers
- * in nosquint.js a chance to abort the queued zoom via
- * NoSquint.abortPendingZoomManager()
- */
- ZoomManager._nosquintPendingZoom = value;
- if (NoSquint.zoomManagerTimeout == false) {
- dump("[nosquint] EATING ZOOM REQUEST: "+ value + "\n");
- NoSquint.zoomManagerTimeout = null;
- return;
- }
- NoSquint.zoomManagerTimeout = setTimeout(function() {
- dump("[nosquint] setting zoom through ZoomManager: " + value + "\n");
- ZoomManager._nosquintOrigZoomSetter(value);
- NoSquint.zoomManagerTimeout = null;
- ZoomManager._nosquintPendingZoom = null;
- }, 0);
-});
+ init: function() {
+ if (NoSquint._initialized)
+ return;
+ NoSquint._initialized = true;
+ for (let i = 0; i < NoSquint.namespaces.length; i++) {
+ var scope = NoSquint.namespaces[i];
+ if (scope.init !== undefined)
+ scope.init();
+ }
+ },
-ZoomManager.__defineGetter__('zoom', function() {
- if (ZoomManager._nosquintPendingZoom != null)
- return ZoomManager._nosquintPendingZoom;
- return ZoomManager._nosquintOrigZoomGetter();
-});
+ destroy: function() {
+ // Invoke destroy functions in all registered namespaces
+ for (let i = 0; i < NoSquint.namespaces.length; i++) {
+ var scope = NoSquint.namespaces[i];
+ if (scope.destroy !== undefined)
+ scope.destroy();
+ }
+ }
+};
-ZoomManager.enlarge = NoSquint.cmdEnlargePrimary;
-ZoomManager.reduce = NoSquint.cmdReducePrimary;
-ZoomManager.reset = NoSquint.cmdReset;
+window.addEventListener("load", NoSquint.init, false);
+window.addEventListener("unload", NoSquint.destroy, false);
diff --git a/src/content/interfaces.js b/src/content/interfaces.js
new file mode 100644
index 0000000..c999383
--- /dev/null
+++ b/src/content/interfaces.js
@@ -0,0 +1,211 @@
+NoSquint.interfaces = NoSquint.ns(function() { with (NoSquint) {
+ const CI = Components.interfaces;
+
+ this.id = 'NoSquint.interfaces';
+
+ /* Specifies at which state we will try to zoom and style the page. With
+ * 3.5+, we can style early with STATE_TRANSFERRING. With 3.0, we seem to
+ * have style later at STATE_STOP in order to get reliable results. (In 3.0
+ * using STATE_TRANSFERRING, on e.g. youtube.com the search bar is
+ * improperly rendered. [And this, quite perplexingly, is caused by
+ * accessing doc.documentElement in NSQ.browser.style()])
+ */
+ var stateFlag = is30() ? Components.interfaces.nsIWebProgressListener.STATE_STOP
+ : Components.interfaces.nsIWebProgressListener.STATE_TRANSFERRING;
+
+ /* Listener used to receive notifications when a new URI is about to be loaded.
+ * TODO: when support for Firefox 3.0 is dropped, use:
+ * https://developer.mozilla.org/En/Listening_to_events_on_all_tabs
+ */
+ this.ProgressListener = function(browser) {
+ this.id = 'NoSquint.interfaces.ProgressListener';
+ this.browser = browser;
+ }
+
+ this.ProgressListener.prototype = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(CI.nsIWebProgressListener) ||
+ aIID.equals(CI.nsISupportsWeakReference) ||
+ aIID.equals(CI.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onLocationChange: function(progress, request, uri) {
+ // Ignore url#foo -> url#bar location changes
+ if (!request)
+ return;
+
+ // If we're here, a new document will be loaded next.
+ this.contentType = this.browser.docShell.document.contentType;
+ this.styleApplied = false;
+ this.zoomApplied = false;
+
+ // Remove any stylers from the last document.
+ var userData = this.browser.getUserData('nosquint');
+ userData.stylers = [];
+
+ var site = NSQ.browser.getSiteFromBrowser(this.browser);
+ if (site == userData.site)
+ // New document on the same site.
+ return;
+
+ debug("onLocationChange(): old=" + userData.site + "new=" + site + ", uri=" + uri.spec);
+ /* Update timestamp for site. This isn't _quite_ perfect because
+ * the timestamp is only updated for the first page load on that site
+ * rather than the last. But it should be good enough in practice, and
+ * avoids updating the site list on _every_ page load.
+ */
+ NSQ.prefs.updateSiteTimestamp(site);
+ userData.site = site;
+
+ /* Now zoom the current browser for the proper zoom level for this site.
+ * It's expected that this zoom level will not get modified from under us.
+ * However, this has happened with a Firefox 3.6 nightly -- see bug
+ * #516513. That bug got fixed, so it seems to be safe to zoom here.
+ * If the problem resurfaces, we will need to move the zooming into
+ * onStateChange the way styling is currently hooked.
+ * XXX: 3.6 private browsing mode exhibits some problems, so zooming
+ * is back in onStateChange.
+ */
+ NSQ.browser.zoom(this.browser);
+
+ // If the site settings dialog was open from this browser, sync it.
+ var dlg = NSQ.storage.dialogs.site;
+ if (dlg && dlg.browser == this.browser)
+ dlg.setBrowser(NSQ.browser, this.browser);
+ },
+
+ onStateChange: function(progress, request, state, astatus) {
+ //debug("LISTENER: request=" + request + ", state=" + state + ", status=" +
+ // astatus + ", type=" + this.browser.docShell.document.contentType);
+
+ /* Check the current content type against the content type we initially got.
+ * This changes in the case when there's an error page (e.g. dns failure),
+ * which we treat as chrome and do not adjust.
+ */
+ var contentType = this.browser.docShell.document.contentType;
+ if (this.contentType != contentType) {
+ this.contentType = contentType;
+ if (isChrome(this.browser)) {
+ this.browser.getUserData('nosquint').site = null;
+ NSQ.browser.zoom(this.browser, 100, 100);
+ }
+ } else if (state & stateFlag) {
+ if (!this.zoomApplied) {
+ this.zoomApplied = true;
+ NSQ.browser.zoom(this.browser);
+ }
+ if (!this.styleApplied) {
+ if (!isChrome(this.browser) || isImage(this.browser))
+ this.styleApplied = NSQ.browser.style(this.browser);
+ else
+ this.styleApplied = true;
+ }
+ }
+ },
+
+ onProgressChange: function() 0,
+ onStatusChange: function() 0,
+ onSecurityChange: function() 0,
+ onLinkIconAvailable: function() 0,
+ };
+
+
+
+ /* Custom observer attached to nsIObserverService. Used to detect changes
+ * to private browsing state, and addon disable/uninstall. Some code
+ * borrowed from https://developer.mozilla.org/En/Supporting_private_browsing_mode
+ */
+ this.Observer = function() {
+ this.id = 'NoSquint.interfaces.Observer';
+ this.init();
+ };
+
+ this.Observer.prototype = {
+ _os: null,
+ _inPrivateBrowsing: false, // whether we are in private browsing mode
+ watcher: {}, // the watcher object
+ _hooked: false,
+
+ init: function () {
+ this._inited = true;
+ this._os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ this._hook();
+ },
+
+ _hook: function() {
+ this._os.addObserver(this, "private-browsing", false);
+ this._os.addObserver(this, "quit-application-granted", false);
+ this._os.addObserver(this, "em-action-requested", false);
+ try {
+ var pbs = Components.classes["@mozilla.org/privatebrowsing;1"]
+ .getService(Components.interfaces.nsIPrivateBrowsingService);
+ this._inPrivateBrowsing = pbs.privateBrowsingEnabled;
+ } catch(ex) {
+ // ignore exceptions in older versions of Firefox
+ }
+ this._hooked = true;
+ },
+
+ _unhook: function() {
+ this._os.removeObserver(this, "quit-application-granted");
+ this._os.removeObserver(this, "private-browsing");
+ this._hooked = false;
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case "private-browsing":
+ switch (data) {
+ case "enter":
+ this._inPrivateBrowsing = true;
+ if ("onEnterPrivateBrowsing" in this.watcher)
+ this.watcher.onEnterPrivateBrowsing();
+ break;
+
+ case "exit":
+ this._inPrivateBrowsing = false;
+ if ("onExitPrivateBrowsing" in this.watcher)
+ this.watcher.onExitPrivateBrowsing();
+ break;
+ }
+ break;
+
+ case "quit-application-granted":
+ NSQ.storage.quitting = true;
+ this._unhook();
+ break;
+
+ case "em-action-requested":
+ switch (data) {
+ case "item-disabled":
+ case "item-uninstalled":
+ var item = subject.QueryInterface(Components.interfaces.nsIUpdateItem);
+ if (item.id != 'nosquint at urandom.ca' || NSQ.storage.disabled)
+ break;
+
+ NSQ.storage.disabled = true;
+ if (popup('confirm', NSQ.strings.disableTitle, NSQ.strings.disablePrompt) == 1) {
+ // Clicked no
+ } else
+ NSQ.prefs.setSiteSpecific(true);
+ break;
+
+ case "item-cancel-action":
+ var item = subject.QueryInterface(Components.interfaces.nsIUpdateItem);
+ if (item.id != 'nosquint at urandom.ca' || NSQ.storage.disabled != true)
+ break;
+ NSQ.prefs.setSiteSpecific(false);
+ NSQ.storage.disabled = false;
+ }
+ break;
+ }
+ },
+
+ get inPrivateBrowsing() {
+ return this._inPrivateBrowsing;
+ }
+ };
+}});
diff --git a/src/content/lib.js b/src/content/lib.js
new file mode 100644
index 0000000..8669c6b
--- /dev/null
+++ b/src/content/lib.js
@@ -0,0 +1,220 @@
+(function() {
+ // Shorter alias
+ this.NSQ = NoSquint;
+
+ /* Setup global (spans all windows) storage object. The storage object
+ * exists once, and is referenced for each window. (In contrast, doing
+ * Application.storage.set('foo', [1,2]) will store a copy of the list.)
+ */
+ var extstorage = Application.extensions.get('nosquint at urandom.ca').storage;
+ this.storage = extstorage.get('global', null);
+ if (this.storage === null) {
+ // Initialize global defaults.
+ this.storage = {
+ disabled: false,
+ quitting: false,
+ origSiteSpecific: null,
+ dialogs: {}
+ };
+ extstorage.set('global', this.storage);
+ }
+
+
+ this.is30 = function() {
+ return Application.version.substr(0, 4) == '3.0.';
+ };
+
+ this.is36 = function() {
+ return Application.version.substr(0, 4) >= '3.6.';
+ };
+
+ this.$ = function(id, doc) {
+ if (doc === undefined)
+ doc = document;
+ return doc.getElementById(id);
+ };
+
+ // Loads a string bundle and returns a key -> value map.
+ this.getStringBundle = function(name) {
+ var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService)
+ .createBundle('chrome://nosquint/locale/' + name + '.properties');
+ var strings = {}
+ var enum = bundle.getSimpleEnumeration();
+ while (enum.hasMoreElements()) {
+ var str = enum.getNext().QueryInterface(Components.interfaces.nsIPropertyElement);
+ strings[str.key] = str.value;
+ }
+ return strings;
+ }
+
+ this.strings = this.getStringBundle('overlay');
+
+ /* Returns a list of lines from a URL (such as chrome://).
+ */
+ this.readLines = function(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");
+ };
+
+
+ /* Given a FQDN, returns only the base domain, and honors two-level TLDs.
+ * So for example, www.foo.bar.com returns bar.com, or www.foo.bar.co.uk
+ * returns bar.co.uk.
+ */
+ this.getBaseDomainFromHost = function(host) {
+ if (this.storage.TLDs === undefined) {
+ // First window opened, so parse from stored list, which is
+ // borrowed from http://www.surbl.org/two-level-tlds
+ this.storage.TLDs = {};
+ for each (let line in this.readLines('chrome://nosquint/content/two-level-tlds'))
+ this.storage.TLDs[line] = true;
+ }
+ 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 (this.storage.TLDs[level3])
+ return parts.slice(-4).join('.');
+ else if (this.storage.TLDs[level2])
+ return level3;
+ return level2;
+ };
+
+
+ // XXX: don't forget to disable this for releases.
+ this.debug = function(msg) {
+ dump("[nosquint] " + msg + "\n");
+ };
+
+ /* This function is called a lot, so we take some care to optimize for the
+ * common cases.
+ */
+ this.isChrome = function(browser) {
+ var document = browser.docShell.document;
+
+ if (document.URL == undefined)
+ return true;
+
+ /* In the common case, document.URL == browser.currentURI.spec, so we test
+ * this simple equality first before resorting to the probably unnecessary
+ * regexp call.
+ */
+ if (document.URL != browser.currentURI.spec &&
+ document.URL.replace(/#.*$/, '') != browser.currentURI.spec.replace(/#.*$/, ''))
+ /* Kludge: doc.URL doesn't match browser currentURI during host lookup failure,
+ * SSL cert errors, or other scenarios that result in an internal page being
+ * displayed that we consider chrome.
+ */
+ return true;
+
+ // A couple other common cases.
+ if (document.URL == undefined || document.URL.substr(0, 6) == 'about:')
+ return true;
+ if (document.contentType == 'text/html' || document.contentType == 'application/xhtml+xml')
+ return false;
+
+ // Less common cases that we'll cover with the more expensive regexp.
+ return document.contentType.search(/^text\/(plain|css|xml|javascript)/) != 0;
+ };
+
+ this.isImage = function(browser) {
+ return browser.docShell.document.contentType.search(/^image\//) == 0;
+ };
+
+ this.getImage = function(doc) {
+ // Not yet.
+ /*
+ var svg = doc.getElementsByTagName('svg');
+ if (svg.length > 0)
+ return svg[0];
+ */
+ return doc.body ? doc.body.firstChild : null;
+ };
+
+ this.foreachNSQ = (function() {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ return function(callback) {
+ var enumerator = wm.getEnumerator("navigator:browser");
+ var win;
+ while (win = enumerator.getNext())
+ if (win.NoSquint && callback(win.NoSquint) === false)
+ break;
+ return win;
+ };
+ })();
+
+
+ this.popup = function(type, title, text, value) {
+ var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ if (type == 'confirm')
+ return prompts.confirmEx(window, title, text,
+ prompts.STD_YES_NO_BUTTONS, null, null, null,
+ null, {value: null});
+ else if (type == 'alert')
+ return prompts.alert(window, title, text);
+ else if (type == 'prompt') {
+ var data = {value: value};
+ prompts.prompt(window, title, text, data, null, {});
+ return data.value;
+ }
+ return null;
+ };
+
+
+ // Pythonic general purpose iterators.
+ this.iter = function() {
+ for (let i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ // duck typing
+ if (arg.length !== undefined) {
+ for (let idx = 0; idx < arg.length; idx++)
+ yield arg[idx];
+ } else {
+ for (let key in arg)
+ yield key
+ }
+ }
+ };
+
+ this.items = function() {
+ for (let i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ for each (let [key, value] in Iterator(arg))
+ yield [key, value];
+ }
+ };
+
+ this.values = function() {
+ for (let i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ for each (let [key, value] in Iterator(arg))
+ yield value;
+ }
+ };
+
+ this.enumerate = function(o) {
+ var n = 0;
+ for (let i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ for (let value in this.iter(arg))
+ yield [n++, value];
+ }
+ };
+
+}).apply(NoSquint);
diff --git a/src/content/overlay.xul b/src/content/overlay.xul
index fb0b1e7..cc12c52 100644
--- a/src/content/overlay.xul
+++ b/src/content/overlay.xul
@@ -2,43 +2,55 @@
<!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" />
+<overlay id="nosquint-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <script type="application/x-javascript" src="chrome://nosquint/content/init.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/lib.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/interfaces.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/prefs.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/browser.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/cmd.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/zoommanager.js" />
<stringbundleset id="stringbundleset">
<stringbundle id="nosquint-overlay-bundle" src="chrome://nosquint/locale/overlay.properties" />
</stringbundleset>
<toolbarpalette id="BrowserToolbarPalette">
- <toolbaritem id="nosquint-toolbar">
- <toolbarbutton id="nosquint-button-reduce" class="toolbarbutton-1"
- label="Zoom Out" tooltiptext="Zoom out"
- oncommand="NoSquint.buttonReduce(event);" />
- <toolbarbutton id="nosquint-button-enlarge" class="toolbarbutton-1"
- label="Zoom In" tooltiptext="Zoom in"
- oncommand="NoSquint.buttonEnlarge(event);" />
- </toolbaritem>
+ <toolbarbutton id="nosquint-button-reduce" class="toolbarbutton-1"
+ label="Zoom Out" tooltiptext="Zoom out"
+ oncommand="NoSquint.cmd.buttonReduce(event);" />
+ <toolbarbutton id="nosquint-button-enlarge" class="toolbarbutton-1"
+ label="Zoom In" tooltiptext="Zoom in"
+ oncommand="NoSquint.cmd.buttonEnlarge(event);" />
+ <toolbarbutton id="nosquint-button-reset" class="toolbarbutton-1"
+ label="Reset Zoom" tooltiptext="Reset Zoom"
+ oncommand="NoSquint.cmd.buttonReset(event);" />
</toolbarpalette>
<keyset id="mainKeyset">
- <key id="key_noSquintEnlargeSecondary" key="+" modifiers="control shift"
+ <key id="key_noSquintEnlargeSecondary" key="+" modifiers="accel shift"
command="cmd_noSquintEnlargeSecondary" />
- <key id="key_noSquintReduceSecondary" key="_" modifiers="control shift"
+ <key id="key_noSquintReduceSecondary" key="_" modifiers="accel shift"
command="cmd_noSquintReduceSecondary" keytext="-" />
</keyset>
<commandset id="mainCommandSet">
- <command id="cmd_noSquintPrefs" oncommand="NoSquint.openGlobalPrefsDialog()" />
- <command id="cmd_fullZoomEnlarge" oncommand="NoSquint.cmdEnlargePrimary()" />
- <command id="cmd_fullZoomReduce" oncommand="NoSquint.cmdReducePrimary()" />
- <command id="cmd_fullZoomReset" oncommand="NoSquint.cmdReset()" />
- <command id="cmd_noSquintEnlargeSecondary" oncommand="NoSquint.cmdEnlargeSecondary()" />
- <command id="cmd_noSquintReduceSecondary" oncommand="NoSquint.cmdReduceSecondary()" />
+ <command id="cmd_noSquintPrefs" oncommand="NoSquint.cmd.openGlobalSettings()" />
+ <command id="cmd_fullZoomEnlarge" oncommand="NoSquint.cmd.enlargePrimary()" />
+ <command id="cmd_fullZoomReduce" oncommand="NoSquint.cmd.reducePrimary()" />
+ <command id="cmd_fullZoomReset" oncommand="NoSquint.cmd.reset()" />
+ <command id="cmd_noSquintEnlargeSecondary" oncommand="NoSquint.cmd.enlargeSecondary()" />
+ <command id="cmd_noSquintReduceSecondary" oncommand="NoSquint.cmd.reduceSecondary()" />
</commandset>
+ <popup id="contentAreaContextMenu">
+ <menuitem id="nosquint-menu-settings" label="&ns.menu.context.label;"
+ accesskey="&ns.menu.context.accesskey;" oncommand="NoSquint.cmd.openSiteSettings();"/>
+ </popup>
+
<statusbar id="status-bar">
- <tooltip id="nosquint-status-tooltip" orient="vertical" style="background-color: #33DD00;">
+ <tooltip id="nosquint-status-tooltip" orient="vertical">
<grid>
<columns>
<column />
@@ -52,6 +64,7 @@
</hbox>
<label value="" id="nosquint-status-tooltip-site" />
</row>
+ <row style='border-top: 1px solid black; margin: 5px; opacity: 0.15' />
<row>
<hbox>
<spacer flex="1" />
@@ -66,11 +79,35 @@
</hbox>
<label value="" id="nosquint-status-tooltip-text" />
</row>
+
+ <row style='border-top: 1px solid black; margin: 5px; opacity: 0.15' />
+
+ <row>
+ <hbox>
+ <spacer flex="1" />
+ <label value="&ns.tooltip.textColor.label;:" style="font-weight: bold" />
+ </hbox>
+ <hbox>
+ <label id="nosquint-status-tooltip-textcolor"
+ style='padding: 2px 10px; border: 1px solid black' />
+ </hbox>
+ </row>
+
+ <row>
+ <hbox>
+ <spacer flex="1" />
+ <label value="&ns.tooltip.linkColor.label;:" style="font-weight: bold" />
+ </hbox>
+ <hbox>
+ <label id="nosquint-status-tooltip-unvis-link" />
+ <label id="nosquint-status-tooltip-vis-link" />
+ </hbox>
+ </row>
</rows>
</grid>
</tooltip>
- <menupopup id="nosquint-status-popup" oncommand="NoSquint.popupItemSelect(event)">
+ <menupopup id="nosquint-status-popup" oncommand="NoSquint.cmd.popupItemSelect(event)">
<menuitem id="nosquint-popup-site" label="Site" disabled="true" style="font-style: italic" />
<menu label="&ns.menu.fullZoom.label;">
<menupopup id="nosquint-status-popup-full">
@@ -94,15 +131,15 @@
<menuitem type="radio" name="text" label="150%" />
</menupopup>
</menu>
- <menuitem label="&ns.menu.reset.label;" onclick="NoSquint.cmdReset()" />
- <menuitem label="&ns.menu.siteSettings.label;" onclick="NoSquint.openSitePrefsDialog()" />
+ <menuitem id="nosquint-status-reset" label="&ns.menu.reset.label;" onclick="NoSquint.cmd.reset()" />
+ <menuitem label="&ns.menu.siteSettings.label;" onclick="NoSquint.cmd.openSiteSettings()" />
<menuseparator />
- <menuitem label="&ns.menu.globalSettings.label;" onclick="NoSquint.openGlobalPrefsDialog()" />
+ <menuitem label="&ns.menu.globalSettings.label;" onclick="NoSquint.cmd.openGlobalSettings()" />
</menupopup>
<statusbarpanel class="statusbarpanel-iconic-text" id="nosquint-status" label="100%"
- onclick="NoSquint.statusPanelClick(event)"
- src="chrome://nosquint/skin/icon-enlarge-16.png"
+ onclick="NoSquint.cmd.statusPanelClick(event)"
+ src="chrome://nosquint/skin/icon-statusbar-16.png"
tooltip="nosquint-status-tooltip" />
</statusbar>
</overlay>
diff --git a/src/content/overlay_sanitize.xul b/src/content/overlay_sanitize.xul
new file mode 100644
index 0000000..9615a56
--- /dev/null
+++ b/src/content/overlay_sanitize.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay SYSTEM "chrome://nosquint/locale/overlay.dtd">
+<overlay id="nosquint-sanitize" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/x-javascript" src="chrome://nosquint/content/init.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/lib.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/prefs.js" />
+ <script type="application/x-javascript" src="chrome://nosquint/content/sanitize.js" />
+</overlay>
diff --git a/src/content/prefs.js b/src/content/prefs.js
index e381883..579eb74 100644
--- a/src/content/prefs.js
+++ b/src/content/prefs.js
@@ -1,216 +1,796 @@
-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");
+// chrome://browser/content/browser.xul
+
+/******************************************************************************
+ * Preferences (Singleton)
+ *
+ * Namespace for anything pref related, including service objects, any
+ * currently cached values, routines for parsing, or convenience functions
+ * for accessing preferences.
+ */
+
+NoSquint.prefs = NoSquint.ns(function() { with(NoSquint) {
+ // Namespace is a singleton, so return any previously instantiated prefs object.
+ if (NSQ.storage.prefs)
+ return NSQ.storage.prefs;
+ NSQ.storage.prefs = this;
+
+ this.id = 'NoSquint.prefs';
+ this.defaultColors = {
+ colorText: '#000000',
+ colorBackground: '#ffffff',
+ linksUnvisited: '#0000ee',
+ linksVisited: '#551a8b'
+ };
+
+ /* Active window we can use for window methods (e.g. setTimeout). Because
+ * NSQ.prefs is a singleton, it could be that the window we initialized
+ * with has been closed. In that case, setTimeout will fail with
+ * NS_ERROR_NOT_INITIALIZED. So we keep a reference to an available
+ * window here we can call window.* methods with, and if the window
+ * goes away, we find a new one using foreachNSQ().
+ */
+ this.window = window;
+
+ // Pref service.
+ var svc = Components.classes["@mozilla.org/preferences-service;1"].getService(
+ Components.interfaces.nsIPrefService);
+ svc.QueryInterface(Components.interfaces.nsIPrefBranch);
+ this.svc = svc;
+
+ // Pref Branches we're interested in.
+ var branchNS = svc.getBranch('extensions.nosquint.');
+ var branchBZ = svc.getBranch('browser.zoom.');
+
+ var saveTimer = null; // Timer for saveSiteList
+ var pruneTimer = null; // Timer for pruneSites
+ var ignoreNextSitesChange = false; // Ignore next update to sites pref
+ var origSiteSpecific = null; // Original value of browser.zoom.siteSpecific
+ var initialized = false;
+
+
+ this.init = function() {
+ if (initialized)
+ return;
+ initialized = true;
+
+ // Backward compatibility: convert old prefs.
+ if (branchNS.getCharPref('prefsVersion') < '2.0') {
+ try {
+ // In 2.0, zoomlevel was split into fullZoomLevel and textZoomLevel
+ var zoomlevel = branchNS.getIntPref('zoomlevel');
+ branchNS.clearUserPref('zoomlevel');
+ } catch (err) {
+ // this was the default zoomlevel for < 2.0.
+ var zoomlevel = 120;
+ }
+
+ /* Previous versions of NoSquint set mousewheel.withcontrolkey.action=0
+ * under the assumption that we won't see DOMMouseScroll events otherwise.
+ * This was true with Firefox < 3, but apparently no longer the case with
+ * 3 and later. So we restore the pref to its default value during this
+ * initial migration. (The user might not want it restored, but this is
+ * the best we can do given what we know, and the correct thing to do
+ * in the common case.)
+ */
+ svc.setIntPref('mousewheel.withcontrolkey.action', 3);
+
+ var fullZoomPrimary = branchNS.getBoolPref('fullZoomPrimary');
+ if (fullZoomPrimary) {
+ branchNS.setIntPref('fullZoomLevel', zoomlevel);
+ branchNS.setIntPref('textZoomLevel', 100);
+ } else {
+ branchNS.setIntPref('fullZoomLevel', 100);
+ branchNS.setIntPref('textZoomLevel', zoomlevel);
+ }
+ branchNS.setCharPref('prefsVersion', '2.0');
+ }
+
+ /* Disable browser.zoom.siteSpecific, which prevents Firefox from
+ * automatically applying zoom levels, as that is now NoSquint's job.
+ */
+ if (origSiteSpecific === null)
+ origSiteSpecific = branchBZ.getBoolPref('siteSpecific');
+ this.setSiteSpecific(false);
+
+ // Pull prefs from prefs branch into object attributes
+ this.preload();
+
+ // Attach observers to both branches.
+ branchNS.QueryInterface(Components.interfaces.nsIPrefBranch2);
+ branchNS.addObserver('', this, false);
+ branchBZ.QueryInterface(Components.interfaces.nsIPrefBranch2);
+ branchBZ.addObserver('', this, false);
+ };
+
+ this.destroy = function() {
+ if (this.rememberSites)
+ // In case the window shutting down is the one whose saveTimer is
+ // associated with, we should finish any pending save now.
+ this.finishPendingSaveSiteList();
+
+ if (!NSQ.storage.quitting)
+ // NSQ.prefs is a singleton so we only ever truly destroy on app
+ // shutdown.
+ return;
+
+ branchNS.removeObserver('', this);
+ branchBZ.removeObserver('', this);
+
+ if (!this.rememberSites)
+ // Per-site setting storage disabled.
+ branchNS.setCharPref('sites', '');
+
+ this.setSiteSpecific(origSiteSpecific);
+ };
+
+
+ /* Invoke a window method, such as setTimeout. We need to do this indirectly
+ * because NSQ.prefs is a singleton, and the window NSQ.prefs initialized with
+ * may not actually still be alive.
+ */
+ this.winFunc = function(func) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ try {
+ return this.window[func].apply(this.window, args);
+ } catch (e) {
+ // Presumably NS_ERROR_NOT_INITIALIZED. TODO: verify.
+ this.window = foreachNSQ(function() false);
+ return this.window[func].apply(this.window, args);
+ }
+ };
+
+ this.setSiteSpecific = function(value) {
+ branchBZ.setBoolPref('siteSpecific', value);
+ this.save();
+ };
+
+ this.preload = function() {
+ // Initialize preferences in this order; some of them require other prefs
+ // have been loaded. (e.g. forgetMonths needs rememberSites)
+ var prefs = [
+ 'fullZoomLevel', 'textZoomLevel', 'zoomIncrement', 'wheelZoomEnabled', 'hideStatus',
+ 'action', 'sitesSaveDelay', 'rememberSites', 'exceptions', 'sites', 'forgetMonths',
+ 'fullZoomPrimary', 'wheelZoomInvert', 'zoomImages', 'colorText', 'colorBackground',
+ 'colorBackgroundImages', 'linksUnvisited', 'linksVisited', 'linksUnderline'
+ ];
+ for (let pref in iter(prefs))
+ // Simulate pref change for each pref to populate attributes
+ this.observe(null, "nsPref:changed", pref);
+ };
+
+
+ this.observe = function(subject, topic, data) {
+ if (topic != "nsPref:changed")
+ // Not a pref change.
+ return;
+
+ debug('observe(): data=' + data);
+ switch (data) {
+ case 'siteSpecific':
+ if (branchBZ.getBoolPref('siteSpecific') == false || NSQ.storage.disabled || NSQ.storage.quitting)
+ // disabled, which is fine with us, so ignore.
+ break;
+
+ // yes == 0, no or close == 1
+ if (popup('confirm', NSQ.strings.siteSpecificTitle, NSQ.strings.siteSpecificPrompt) == 1)
+ popup('alert', NSQ.strings.siteSpecificBrokenTitle, NSQ.strings.siteSpecificBrokenPrompt);
+ else
+ this.setSiteSpecific(false);
+ break;
+
+ case 'fullZoomLevel':
+ this.fullZoomLevel = branchNS.getIntPref('fullZoomLevel');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueZoomAll() : null);
+ break;
+
+ case 'textZoomLevel':
+ this.textZoomLevel = branchNS.getIntPref('textZoomLevel');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueZoomAll() : null);
+ break;
+
+ case 'wheelZoomEnabled':
+ this.wheelZoomEnabled = branchNS.getBoolPref('wheelZoomEnabled');
+ break;
+
+ case 'wheelZoomInvert':
+ this.wheelZoomInvert = branchNS.getBoolPref('wheelZoomInvert');
+ break;
+
+ case 'zoomIncrement':
+ this.zoomIncrement = branchNS.getIntPref('zoomIncrement');
+ break;
+
+ case 'forgetMonths':
+ this.forgetMonths = branchNS.getIntPref('forgetMonths');
+ this.pruneSites();
+ break;
+
+ case 'fullZoomPrimary':
+ this.fullZoomPrimary = branchNS.getBoolPref('fullZoomPrimary');
+ foreachNSQ(function(NSQ) {
+ if (NSQ.browser) {
+ NSQ.browser.updateZoomMenu();
+ NSQ.browser.queueZoomAll();
+ }
+ });
+ break;
+
+ case 'zoomImages':
+ this.zoomImages = branchNS.getBoolPref('zoomImages');
+ break;
+
+ case 'hideStatus':
+ var hideStatus = branchNS.getBoolPref('hideStatus');
+ this.hideStatus = hideStatus;
+ foreachNSQ(function(NSQ) {
+ if (NSQ.browser) {
+ $('nosquint-status').hidden = hideStatus;
+ if (!hideStatus)
+ // Status now being shown; update it to reflect current values.
+ NSQ.browser.queueUpdateStatus();
+ }
+ });
+ break;
+
+ case 'rememberSites':
+ this.rememberSites = branchNS.getBoolPref('rememberSites');
+ if (NSQ.storage.dialogs.site)
+ // Toggle the warning in sites dialog.
+ NSQ.storage.dialogs.site.updateWarning();
+ // TODO: if false, remove stored sites settings immediately, but keep
+ // in memory until end of session.
+ break;
+
+ case 'sitesSaveDelay':
+ this.saveDelay = branchNS.getIntPref('sitesSaveDelay');
+ break;
+
+ case 'exceptions':
+ // Parse exceptions list from prefs
+ this.exceptions = this.parseExceptions(branchNS.getCharPref('exceptions'));
+ foreachNSQ(function(NSQ) {
+ if (NSQ.browser) {
+ NSQ.browser.updateZoomMenu();
+ NSQ.browser.queueZoomAll();
+ }
+ });
+ break;
+
+ case 'sites':
+ if (ignoreNextSitesChange) {
+ ignoreNextSitesChange = false;
+ break;
+ }
+ this.sites = this.parseSites(branchNS.getCharPref('sites'));
+ if (saveTimer) {
+ /* 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.
+ */
+ this.stopQueueSaveSiteList();
+ }
+ foreachNSQ(function(NSQ) {
+ if (NSQ.browser) {
+ NSQ.browser.queueZoomAll();
+ NSQ.browser.queueStyleAll();
+ }
+ });
+ break;
+
+ case 'colorText':
+ this.colorText = branchNS.getCharPref('colorText');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueStyleAll() : null);
+ break;
+
+ case 'colorBackground':
+ this.colorBackground = branchNS.getCharPref('colorBackground');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueStyleAll() : null);
+ break;
+
+ case 'colorBackgroundImages':
+ this.colorBackgroundImages = branchNS.getBoolPref('colorBackgroundImages');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueStyleAll() : null);
+ break;
+
+ case 'linksUnvisited':
+ this.linksUnvisited = branchNS.getCharPref('linksUnvisited');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueStyleAll() : null);
+ break;
+
+ case 'linksVisited':
+ this.linksVisited = branchNS.getCharPref('linksVisited');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueStyleAll() : null);
+ break;
+
+ case 'linksUnderline':
+ this.linksUnderline = branchNS.getBoolPref('linksUnderline');
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueStyleAll() : null);
+ break;
+ }
+ };
+
+ this.save = function() {
+ return svc.savePrefFile(null);
+ };
+
+
+ /* Parses a extensions.nosquint.sites pref into sites array.
+ */
+ this.parseSites = function(sitesStr) {
+ /* Parse site list from prefs. The prefs string a list of site specs,
+ * delimited by a space, in the form:
+ *
+ * sitename=text_level,timestamp,visits,full_level,textcolor,bgcolor,
+ * nobgimages,linkunvis,linkvis,linkunderline
+ *
+ * Spaces are not allowed in any value; sitename is a string, all other
+ * values are integers. The parsing code tries to be robust and handle
+ * malformed entries gracefully (in case the user edits them manually
+ * and screws up). Consequently it is ugly.
+ */
+ var sites = {};
// 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 sitesList = sitesStr.replace(/(^\s+|\s+$)/g, '').split(' ');
+ var now = new Date().getTime();
+
+ for (let defn in iter(sitesList)) {
+ var parts = defn.split('=');
+ if (parts.length != 2)
+ continue; // malformed
+ var [site, info] = parts;
+ var parts = info.split(',');
+ sites[site] = [parseInt(parts[0]) || 0, now, 1, 0, '0', '0', false, '0', '0', false];
+ if (parts.length > 1) // last visited timestamp
+ sites[site][1] = parseInt(parts[1]) || now;
+ if (parts.length > 2) // visit count
+ sites[site][2] = parseInt(parts[2]) || 1;
+ if (parts.length > 3) // full page zoom level
+ sites[site][3] = parseInt(parts[3]) || 0;
+ if (parts.length > 4) // text color
+ sites[site][4] = parts[4] || '0';
+ if (parts.length > 5) // bg color
+ sites[site][5] = parts[5] || '0';
+ if (parts.length > 6) // disable bg images
+ sites[site][6] = parts[6] == 'true' ? true : false;
+ if (parts.length > 7) // unvisited link color
+ sites[site][7] = parts[7] || '0';
+ if (parts.length > 8) // visited link color
+ sites[site][8] = parts[8] || '0';
+ if (parts.length > 9) // force underline links
+ sites[site][9] = parts[9] == 'true' ? true : false;
+
+ }
+ return sites;
+ };
+
+
+ /* Takes an array of exceptions as stored in prefs, and returns a sorted
+ * list, where each exception is converted to a regexp grammar. The list
+ * is sorted such that exceptions with the most literal (non-wildcard)
+ * characters are first.
+ */
+ this.parseExceptions = function(exStr) {
+ // Trim the space-delimited exceptions string and convert to array.
+ var exlist = exStr.replace(/(^\s+|\s+$)/g, '').split(' ');
+
+ /* This ugly function takes an exception, with our custom
+ * grammar, and converts it to a regular expression that we can
+ * match later. Hostname and path components are processed in
+ * separate calls; re_star and re_dblstar define the regexp syntax
+ * for * and ** wildcards for this pattern. (This is because
+ * wildcards have different semantics for host vs path.)
+ *
+ * Function returns a list of [length, pattern, sub] where length
+ * is the number of literal (non-wildcard) characters, pattern is
+ * the regexp that will be used to match against the URI, and sub is
+ * used (via regexp.replace) to create the site name based on the
+ * URI.
+ */
+ function regexpify(pattern, re_star, re_dblstar) {
+ var parts = pattern.split(/(\[\*+\]|\*+)/);
+ var pattern = [];
+ var sub = [];
+ var length = 0;
+
+ // Maps wildcards in custom grammar to regexp equivalent.
+ var wildcards = {
+ '*': '(' + re_star + ')',
+ '**': '(' + re_dblstar + ')',
+ '[*]': re_star,
+ '[**]': re_dblstar
+ };
+
+ var group = 1;
+ for (let part in iter(parts)) {
+ 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('$' + group++);
}
+ return [length, pattern.join(''), sub.join('')];
}
- 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;
- },
+ var exceptions = [];
+ for (var origexc in iter(exlist)) {
+ if (!origexc)
+ continue;
+ // Escape metacharacters except *
+ exc = origexc.replace(/([^\w:*\[\]])/g, '\\$1');
+ // Split into host and path parts, and regexpify separately.
+ var [_, exc_host, exc_path] = exc.match(/([^\/]*)(\\\/.*|$)/);
+ var [len_host, re_host, sub_host] = regexpify(exc_host, '[^.:/]+', '.*');
+ var [len_path, re_path, sub_path] = regexpify(exc_path, '[^/]+', '.*');
+ if (exc_host.search(':') == -1)
+ re_host += '(:\\d+)';
- textPatternKeyPress: function(event) {
- if (event.keyCode == 13) {
- NoSquintPrefs.buttonAddException();
- return false;
+ debug("regexpify(): exc_host=" + exc_host + ", re_host=" + re_host + ", sub_host=" + sub_host + ", exc_path=" + exc_path + ", re_path=" + re_path + ", sub_path=" + sub_path);
+ exceptions.push([origexc, len_host * 1000 + len_path, exc_host, re_host, sub_host, re_path, sub_path]);
+ }
+ // Sort the exceptions such that the ones with the highest weights
+ // (that is, the longest literal lengths) appear first.
+ exceptions.sort(function(a, b) b[1] - a[1]);
+ return exceptions;
+ };
+
+
+ /* Called periodically (on startup, and once a day after that) in order to
+ * remove remembered values for sites we haven't visited in forgetMonths.
+ */
+ this.pruneSites = function() {
+ if (!this.rememberSites || this.forgetMonths == 0)
+ return;
+
+ var remove = [];
+ var now = new Date();
+ for (let [site, settings] in items(this.sites)) {
+ if (!settings)
+ continue
+ var [text, timestamp, counter, full] = settings;
+ var age = now - new Date(timestamp);
+ var prune = (age > this.forgetMonths * 30*24*60*60*1000);
+ if (prune)
+ remove.push(site);
+ debug("pruneSites(): site=" + site + ", age=" + Math.round(age/1000/60/60/24) + " days, prune=" + prune);
+ }
+ if (remove.length) {
+ for (let site in iter(remove))
+ delete this.sites[site];
+ this.queueSaveSiteList();
+ }
+
+ // Fire timer once a day.
+ if (pruneTimer == null)
+ pruneTimer = this.winFunc('setTimeout', function() { pruneTimer = null; NSQ.prefs.pruneSites(); }, 24*60*60*1000);
+ };
+
+
+ /* Updates the site list for the given site name to set the given levels
+ * (2-tuple of [text, full]), and then queues a site list save.
+ */
+ this.updateSiteList = function(site, levels, style, update_timestamp) {
+ if (!site)
+ return;
+
+ if (!this.sites[site])
+ // new site record, initialize to defaults.
+ this.sites[site] = [0, new Date().getTime(), 1, 0, '0', '0', false, '0', '0', false];
+ var record = this.sites[site];
+
+ if (levels) {
+ // Update record with specified levels.
+ var [text_default, full_default] = this.getZoomDefaults();
+ var [text, full] = levels;
+ // Default zooms are stored as 0.
+ record[0] = text == text_default ? 0 : text;
+ record[3] = full == full_default ? 0 : full;
+ // Update all other tabs for this site.
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueZoomAll(site, 1000) : null);
+ }
+ if (style) {
+ record[4] = style.colorText || '0';
+ record[5] = style.colorBackground || '0';
+ record[6] = style.colorBackgroundImages || '0';
+ record[7] = style.linksUnvisited || '0';
+ record[8] = style.linksVisited || '0';
+ record[9] = style.linksUnderline || '0';
+ // Update all other tabs for this site.
+ foreachNSQ(function(NSQ) NSQ.browser ? NSQ.browser.queueStyleAll(site, 1000) : null);
}
- },
- 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 == '');
- },
+ // Check newly updated record against defaults. If all values are default, we
+ // remove the record.
+ if ([record[0]].concat(record.slice(3)).toString() == [0, 0, '0', '0', false, '0', '0', false].toString())
+ // All defaults.
+ delete this.sites[site];
- excListKeyPress: function(event) {
- if (event.keyCode == 13) {
- NoSquintPrefs.buttonEditException();
+ debug('updateSiteList(): site=' + site + ', record=' + record);
+
+ if (this.rememberSites)
+ this.queueSaveSiteList();
+ };
+
+
+ /* Updates the last-accessed timestamp for the given site, and then
+ * queues a site list save.
+ */
+ this.updateSiteTimestamp = function(site) {
+ if (!site || !this.sites[site])
+ return;
+
+ this.sites[site][1] = new Date().getTime();
+ this.sites[site][2] += 1;
+ if (this.rememberSites)
+ // Save updated timestamp. Timestamps are only updated on
+ // the first page accessed for a given visit to that site,
+ // so this shouldn't be too bad.
+ this.queueSaveSiteList();
+ };
+
+
+ /* Queues a save of 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.
+ */
+ this.queueSaveSiteList = function() {
+ this.stopQueueSaveSiteList();
+
+ /* 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.
+ */
+ debug("queueSaveSiteList(): delay=" + this.saveDelay + ', window=' + window);
+ saveTimer = this.winFunc('setTimeout', function() NSQ.prefs.saveSiteList(), this.saveDelay);
+ };
+
+
+ /* Store the sites list right now. */
+ this.saveSiteList = function(force) {
+ if (!this.rememberSites || (NSQ.browser && NSQ.browser.observer.inPrivateBrowsing && !force))
+ /* Private Browsing mode is enabled or rememberSites disabled; do
+ * not save site list.
+ */
+ return;
+ var t0 = new Date().getTime();
+ var sites = [];
+ for (let [site, settings] in items(this.sites)) {
+ if (!settings)
+ continue;
+ sites.push(site + "=" + settings.join(','));
+ }
+
+ /* We're modifying the sites pref here. Setting ignoreNextSitesChange=true
+ * causes the observer (in our current state) to not bother reparsing the
+ * sites pref because we know it's current. In other words, we needn't
+ * respond to our own changes.
+ */
+ ignoreNextSitesChange = true;
+ branchNS.setCharPref('sites', sites.join(' '));
+ this.save();
+ debug("saveSiteList(): took: " + (new Date().getTime() - t0) + "ms");
+ this.winFunc('clearTimeout', saveTimer);
+ saveTimer = null;
+ };
+
+
+ /* Stops a previously queued site list save. Returns true if a save was
+ * queued and aborted, or false if no save was queued.
+ */
+ this.stopQueueSaveSiteList = function() {
+ if (saveTimer === null)
return false;
+
+ this.winFunc('clearTimeout', saveTimer);
+ saveTimer = null;
+ return true;
+ };
+
+ /* If a site list save is queued, force it to happen now.
+ */
+ this.finishPendingSaveSiteList = function() {
+ if (saveTimer)
+ this.saveSiteList();
+ };
+
+ this.cloneSites = function() {
+ var sites = {};
+ for (let [site, values] in Iterator(this.sites))
+ sites[site] = values.slice();
+ return sites;
+ };
+
+ /* Returns a 2-tuple [text_default, full_default] representing the default
+ * zoom levels.
+ */
+ this.getZoomDefaults = function() {
+ return [this.textZoomLevel, this.fullZoomLevel];
+ };
+
+
+ /* Given a URI, returns the site name, as computed based on user-defined
+ * exceptions. If no exception matches the URI, we fall back to the base
+ * domain name.
+ */
+ this.getSiteFromURI = function(URI) {
+ var t0 = new Date().getTime();
+ if (!URI)
+ return null;
+
+ var uri_host = URI.asciiHost;
+ var uri_path = URI.path;
+
+ try {
+ var uri_port = URI.port < 0 ? 0 : URI.port;
+ } catch (err) {
+ var uri_port = '0';
}
- },
-
- 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;
- },
-
- buttonCopyFromURL: function() {
- var pattern = NoSquintPrefs.doc.getElementById("pattern");
- pattern.value = NoSquintPrefs.url;
- NoSquintPrefs.textPatternChange();
- },
-
- 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;
- }
+
+ var base = getBaseDomainFromHost(uri_host);
+ if (!base && !uri_host)
+ // file:// url, use base as /
+ base = '/';
+
+ uri_host += ':' + uri_port;
+
+ var match = null;
+
+ /* Iterate over each exception, trying to match it with the URI.
+ * We break the loop on the first match, because exceptions are
+ * sorted with highest weights first.
+ */
+ for (let exc in iter(this.exceptions)) {
+ var [_, weight, exc_host, re_host, sub_host, re_path, sub_path] = exc;
+ if (re_host.substr(0, 11) == '([^.:/]+)(:') // exc_host == *[:...]
+ // Single star is base name, so match just that, plus any port spec
+ // that's part of the exception.
+ re_host = '(' + base + ')' + re_host.substr(9);
+
+ var m1 = uri_host.match(new RegExp('(' + re_host + ')$'));
+ var m2 = uri_path.match(new RegExp('^(' + re_path + ')'));
+
+ //debug("getSiteFromURI(): host=" + uri_host + ", port=" + uri_port+ ", path=" + uri_path + ", base=" + base + " === exception info: re_host=" + re_host + ", sub_host=" + sub_host + ", re_path=" + re_path + ", sub_path=" + sub_path + " === results: m1=" + m1 + ", m2=" + m2);
+
+ if (!m1 || !m2)
+ // No match
+ continue;
+
+ var site_host = m1[1].replace(new RegExp(re_host), sub_host);
+ var site_path = m2[1].replace(new RegExp(re_path), sub_path);
+ match = site_host + site_path;
+ break;
}
- 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);
+ var t1 = new Date().getTime();
+ debug("getSiteFromURI(): took " + (t1-t0) + " ms: " + (match ? match : base) + ", uri=" + URI.spec);
+
+ return match ? match : base;
+ };
+
+
+ /* Gets the zoom levels for the given site name. (Note, this is the site
+ * name as gotten from getSiteFromURI(), not the URI itself.) Returns a
+ * 2-tuple [text_size, full_size], or [null, null] if the site is not
+ * found. (This signifies to the caller to use the default zoom.)
+ */
+ this.getZoomForSite = function(site) {
+ if (site && this.sites[site])
+ return [this.sites[site][0], this.sites[site][3]];
+ return [null, null];
+ };
+
+ /* Gets the style parameters for the given site name. Returns null if
+ * the site has no settings.
+ */
+ this.getStyleForSite = function(site) {
+ if (site && this.sites[site]) {
+ var s = this.sites[site];
+ return {
+ colorText: s[4],
+ colorBackground: s[5],
+ colorBackgroundImages: s[6],
+ linksUnvisited: s[7],
+ linksVisited: s[8],
+ linksUnderline: s[9]
+ };
+ }
+ return null;
+ };
+
+ /* Applies global styles to the given style object. Attributes that have
+ * no site-local or global value are null.
+ */
+ this.applyStyleGlobals = function(style) {
+ var newstyle = { enabled: false };
+ var boolDefaults = {colorBackgroundImages: false, linksUnderline: false};
+ var isDefault = function(o, attr) !o || !o[attr] || o[attr] in ['0', false];
+ for (let [key, value] in items(this.defaultColors, boolDefaults)) {
+ newstyle[key] = isDefault(style, key) ? (isDefault(this, key) ? null : this[key]) : style[key];
+ newstyle.enabled = newstyle.enabled || Boolean(newstyle[key]);
+ }
+ return newstyle;
+ };
+
+
+ // Saves all preferences, including exceptions BUT NOT sites.
+ this.saveAll = function(exceptions) {
+ const intPrefs = [
+ 'fullZoomLevel', 'textZoomLevel', 'zoomIncrement', 'forgetMonths'
+ ];
+ const boolPrefs = [
+ 'wheelZoomEnabled', 'wheelZoomInvert', 'fullZoomPrimary', 'zoomImages',
+ 'hideStatus', 'rememberSites', 'colorBackgroundImages', 'linksUnderline'
+ ];
+ const charPrefs = [
+ 'colorText', 'colorBackground', 'linksUnvisited', 'linksVisited'
+ ];
+
+ for (let pref in iter(intPrefs))
+ branchNS.setIntPref(pref, this[pref]);
+
+ for (let pref in iter(boolPrefs))
+ branchNS.setBoolPref(pref, this[pref]);
+
+ for (let pref in iter(charPrefs))
+ branchNS.setCharPref(pref, this[pref]);
+
+ var exChanged = false;
+ if (exceptions) {
+ // TODO: if there is a new exception that matches any currently open
+ // tab, copy site settings for that tab into the new site name. Also,
+ // any open site prefs dialog should be updated.
+ var exStr = exceptions.join(' ');
+ if (exStr != branchNS.getCharPref('exceptions')) {
+ branchNS.setCharPref('exceptions', exStr);
+ this.exceptions = this.parseExceptions(exStr);
+ exChanged = true;
}
- 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;
+ foreachNSQ(function(NSQ) {
+ if (!NSQ.browser)
+ return;
+ if (exChanged) {
+ // exceptions changed, site names may have changed, so regenerate
+ // site names for all browsers.
+ for (let browser in iter(NSQ.browser.gBrowser.browsers))
+ browser.getUserData('nosquint').site = NSQ.browser.getSiteFromBrowser(browser);
+ }
+ NSQ.browser.queueZoomAll();
+ NSQ.browser.queueStyleAll();
+ });
+ };
- var level = doc.getElementById("siteZoom").value;
- if (NoSquintPrefs.NoSquint.updateSiteList(NoSquintPrefs.site, level))
- NoSquintPrefs.NoSquint.queueZoomAll();
- //NoSquintPrefs.NoSquint.zoomAll(false);
- }
-};
+ /* Removes all site settings for sites that were modified within the given
+ * range. range is a 2-tuple (start, stop) where each are timestamps in
+ * milliseconds. The newly sanitized site list is then immediately stored.
+ * All browsers are updated to reflect any changes.
+ */
+ this.sanitize = function(range) {
+ if (range == undefined || !range) {
+ this.sites = {}
+ } else {
+ for (var site in this.sites) {
+ var timestamp = this.sites[site][1] * 1000;
+ if (timestamp >= range[0] && timestamp <= range[1])
+ delete this.sites[site];
+ }
+ }
+ this.saveSiteList();
+ foreachNSQ(function(NSQ) {
+ if (NSQ.browser) {
+ NSQ.browser.queueZoomAll();
+ NSQ.browser.queueStyleAll();
+ }
+ });
+ };
+}});
diff --git a/src/content/sanitize.js b/src/content/sanitize.js
new file mode 100644
index 0000000..cb375c5
--- /dev/null
+++ b/src/content/sanitize.js
@@ -0,0 +1,81 @@
+// chrome://browser/content/sanitize.xul
+// chrome://browser/content/preferences/sanitize.xul
+
+NoSquint.sanitizer = NoSquint.ns(function() { with (NoSquint) {
+ this.init = function() {
+ // Adds nosquint option to sanitizer UI
+ this.attachOption(NSQ.strings.sanitizeLabel)
+ if (typeof Sanitizer != 'undefined')
+ // Installs NoSquint hooks into the sanitizer
+ this.hookSanitizer();
+ };
+
+ this.attachOption = function(label) {
+ var inSanitizeDialog = typeof(gSanitizePromptDialog) == 'object';
+ // TODO: put this into a convenience function in lib.js
+ var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ // pref domain is privacy.cpd. for Firefox 3.1+, and privacy.item. for 3.0
+ // and earlier.
+ var domain = 'privacy.cpd.';
+ if ($('privacy.item.cache'))
+ domain = 'privacy.item.';
+ var prefs = document.getElementsByTagName('preferences')[0];
+ var pref = document.createElement('preference');
+ pref.setAttribute('id', domain + 'extensions-nosquint');
+ pref.setAttribute('name', domain + 'extensions-nosquint');
+ pref.setAttribute('type', 'bool');
+ var value = prefService.getBoolPref(domain + 'extensions-nosquint');
+ pref.setAttribute('value', value);
+ prefService.setBoolPref(domain + 'extensions-nosquint', value);
+ prefs.appendChild(pref);
+
+ if ($('itemList')) {
+ // In Clear Recent History dialog in Firefox 3.0
+ var check = $('itemList').appendItem(label);
+ check.setAttribute('type', 'checkbox');
+ } else {
+ // Firefox 3.0, or Firefox 3.5 in Settings, where the user sets which to enable/disable.
+ var check = document.createElement('checkbox');
+ check.setAttribute('label', label);
+ var rows = document.getElementsByTagName('rows');
+ if (rows.length) {
+ // Firefox 3.5
+ // Add new row to to rows. TODO: append to last row if only has one column
+ var row = document.createElement('row');
+ row.appendChild(check);
+ rows[0].appendChild(row);
+ } else
+ // Firefox 3.0
+ document.getElementsByTagName('checkbox')[0].parentNode.appendChild(check);
+ }
+ check.setAttribute('preference', domain + 'extensions-nosquint');
+ check.setAttribute('checked', value);
+
+ if (inSanitizeDialog) {
+ pref.setAttribute('readonly', 'true');
+ check.setAttribute('onsyncfrompreference', 'return gSanitizePromptDialog.onReadGeneric();');
+ if (prefService.getCharPref('extensions.nosquint.sites') == '') {
+ /* FIXME: a minor race condition: if user made first zoom change
+ * and immediately opened sanitizer (before 5s timeout to store sites)
+ * we will disable the checkbox when we shouldn't.
+ */
+ check.setAttribute('disabled', true);
+ check.setAttribute('checked', false);
+ }
+ }
+ };
+
+ this.hookSanitizer = function() {
+ Sanitizer.prototype.items['extensions-nosquint'] = {
+ clear: function() {
+ NSQ.prefs.sanitize(this.range);
+ },
+ get canClear() {
+ return true;
+ }
+ };
+ };
+
+}});
diff --git a/src/content/two-level-tlds b/src/content/two-level-tlds
index 52428b6..379f194 100644
--- a/src/content/two-level-tlds
+++ b/src/content/two-level-tlds
@@ -3,6 +3,7 @@ ab.ca
ab.se
abo.pa
ac.ae
+ac.am
ac.at
ac.bd
ac.be
@@ -14,6 +15,7 @@ ac.fj
ac.fk
ac.gg
ac.gn
+ac.hu
ac.id
ac.il
ac.im
@@ -23,6 +25,7 @@ ac.je
ac.jp
ac.ke
ac.kr
+ac.lk
ac.ma
ac.mw
ac.ng
@@ -30,6 +33,7 @@ ac.nz
ac.om
ac.pa
ac.pg
+ac.rs
ac.ru
ac.rw
ac.se
@@ -94,6 +98,7 @@ asso.ht
asso.mc
asso.re
astrakhan.ru
+at.tf
at.tt
atm.pl
ato.br
@@ -114,13 +119,17 @@ be.tt
bel.tr
belgie.be
belgorod.ru
+bg.tf
+bialystok.pl
bib.ve
bio.br
bir.ru
biz.az
+biz.bh
biz.cy
biz.et
biz.fj
+biz.ly
biz.mv
biz.nr
biz.om
@@ -143,11 +152,14 @@ bryansk.ru
buryatia.ru
busan.kr
c.se
+ca.tf
ca.tt
ca.us
casino.hu
cbg.ru
+cc.bh
cci.fr
+ch.tf
ch.vu
chambagri.fr
chel.ru
@@ -174,12 +186,15 @@ cng.br
cnt.br
co.ae
co.ag
+co.am
co.ao
co.at
+co.ba
co.bw
co.ck
co.cr
co.dk
+co.ee
co.fk
co.gg
co.hu
@@ -196,8 +211,10 @@ co.ls
co.ma
co.mu
co.mw
+co.mz
co.nz
co.om
+co.rs
co.rw
co.st
co.th
@@ -205,9 +222,11 @@ co.tj
co.tt
co.tv
co.tz
+co.ua
co.ug
co.uk
co.us
+co.uz
co.ve
co.vi
co.yu
@@ -220,11 +239,13 @@ com.af
com.ag
com.ai
com.al
+com.am
com.an
com.ar
com.au
com.aw
com.az
+com.ba
com.bb
com.bd
com.bh
@@ -270,6 +291,7 @@ com.jm
com.jo
com.kg
com.kh
+com.ki
com.kw
com.ky
com.kz
@@ -294,6 +316,7 @@ com.mx
com.my
com.na
com.nc
+com.nf
com.ng
com.ni
com.np
@@ -331,6 +354,8 @@ com.tt
com.tw
com.ua
com.uy
+com.uz
+com.vc
com.ve
com.vi
com.vn
@@ -353,6 +378,7 @@ csiro.au
ct.us
cul.na
cv.ua
+cz.tf
d.se
daegu.kr
daejeon.kr
@@ -360,6 +386,7 @@ dagestan.ru
dc.us
de.com
de.net
+de.tf
de.tt
de.us
de.vu
@@ -386,10 +413,12 @@ edu.ac
edu.af
edu.ai
edu.al
+edu.am
edu.an
edu.ar
edu.au
edu.az
+edu.ba
edu.bb
edu.bd
edu.bh
@@ -406,6 +435,7 @@ edu.dm
edu.do
edu.dz
edu.ec
+edu.ee
edu.eg
edu.er
edu.es
@@ -420,7 +450,9 @@ edu.gu
edu.hk
edu.hn
edu.ht
+edu.hu
edu.in
+edu.it
edu.jm
edu.jo
edu.kg
@@ -460,6 +492,7 @@ edu.ps
edu.pt
edu.py
edu.qa
+edu.rs
edu.ru
edu.rw
edu.sa
@@ -471,6 +504,7 @@ edu.sh
edu.sk
edu.st
edu.sv
+edu.tf
edu.tj
edu.tr
edu.tt
@@ -502,6 +536,7 @@ etc.br
eti.br
eu.com
eu.org
+eu.tf
eu.tt
eun.eg
experts-comptables.fr
@@ -551,6 +586,8 @@ gb.com
gb.net
gc.ca
gd.cn
+gda.pl
+gdansk.pl
geek.nz
gen.in
gen.nz
@@ -591,9 +628,11 @@ gov.ae
gov.af
gov.ai
gov.al
+gov.am
gov.ar
gov.au
gov.az
+gov.ba
gov.bb
gov.bd
gov.bf
@@ -627,6 +666,7 @@ gov.gn
gov.gr
gov.gu
gov.hk
+gov.hu
gov.ie
gov.il
gov.im
@@ -673,6 +713,7 @@ gov.ps
gov.pt
gov.py
gov.qa
+gov.rs
gov.ru
gov.rw
gov.sa
@@ -758,6 +799,7 @@ if.ua
il.us
imb.br
in-addr.arpa
+in.rs
in.th
in.ua
in.us
@@ -773,7 +815,9 @@ inf.br
inf.cu
info.au
info.az
+info.bh
info.co
+info.cu
info.cy
info.ec
info.et
@@ -794,6 +838,7 @@ info.vn
ing.pa
ingatlan.hu
inima.al
+int.am
int.ar
int.az
int.bo
@@ -804,6 +849,7 @@ int.mw
int.pt
int.ru
int.rw
+int.tf
int.tj
int.tt
int.ve
@@ -850,6 +896,7 @@ kamchatka.ru
kanagawa.jp
kanazawa.jp
karelia.ru
+katowice.pl
kawasaki.jp
kazan.ru
kchr.ru
@@ -880,6 +927,7 @@ komvux.se
konyvelo.hu
kostroma.ru
kr.ua
+krakow.pl
krasnoyarsk.ru
ks.ua
ks.us
@@ -905,6 +953,7 @@ lg.ua
lipetsk.ru
lkd.co.im
ln.cn
+lodz.pl
ltd.co.im
ltd.cy
ltd.gg
@@ -912,6 +961,7 @@ ltd.gi
ltd.je
ltd.lk
ltd.uk
+lublin.pl
lugansk.ua
lutsk.ua
lviv.ua
@@ -949,8 +999,10 @@ miasta.pl
mie.jp
mil.ac
mil.ae
+mil.am
mil.ar
mil.az
+mil.ba
mil.bd
mil.bo
mil.br
@@ -1073,10 +1125,12 @@ net.af
net.ag
net.ai
net.al
+net.am
net.an
net.ar
net.au
net.az
+net.ba
net.bb
net.bd
net.bh
@@ -1125,6 +1179,7 @@ net.jo
net.jp
net.kg
net.kh
+net.ki
net.kw
net.ky
net.kz
@@ -1141,12 +1196,14 @@ net.ma
net.mm
net.mo
net.mt
+net.mu
net.mv
net.mw
net.mx
net.my
net.na
net.nc
+net.nf
net.ng
net.ni
net.np
@@ -1174,6 +1231,7 @@ net.sg
net.sh
net.st
net.sy
+net.tf
net.th
net.tj
net.tn
@@ -1183,6 +1241,8 @@ net.tw
net.ua
net.uk
net.uy
+net.uz
+net.vc
net.ve
net.vi
net.vn
@@ -1190,6 +1250,7 @@ net.vu
net.ws
net.ye
net.za
+new.ke
news.hu
nf.ca
ngo.lk
@@ -1244,6 +1305,7 @@ nt.ca
nt.ro
ntr.br
nu.ca
+nui.hu
nv.us
nx.cn
ny.us
@@ -1258,8 +1320,10 @@ oita.jp
ok.us
okayama.jp
okinawa.jp
+olsztyn.pl
omsk.ru
on.ca
+opole.pl
or.at
or.cr
or.id
@@ -1276,10 +1340,12 @@ org.ae
org.ag
org.ai
org.al
+org.am
org.an
org.ar
org.au
org.az
+org.ba
org.bb
org.bd
org.bh
@@ -1333,6 +1399,7 @@ org.jo
org.jp
org.kg
org.kh
+org.ki
org.kw
org.ky
org.kz
@@ -1353,6 +1420,7 @@ org.mm
org.mn
org.mo
org.mt
+org.mu
org.mv
org.mw
org.mx
@@ -1377,6 +1445,7 @@ org.pt
org.py
org.qa
org.ro
+org.rs
org.ru
org.sa
org.sb
@@ -1396,6 +1465,8 @@ org.tw
org.ua
org.uk
org.uy
+org.uz
+org.vc
org.ve
org.vi
org.vn
@@ -1426,6 +1497,7 @@ per.sg
perm.ru
perso.ht
pharmacien.fr
+pl.tf
pl.ua
plc.co.im
plc.ly
@@ -1438,6 +1510,7 @@ police.uk
poltava.ua
port.fr
powiat.pl
+poznan.pl
pp.az
pp.ru
pp.se
@@ -1495,7 +1568,9 @@ rnrt.tn
rns.tn
rnu.tn
rovno.ua
+rs.ba
ru.com
+ru.tf
rubtsovsk.ru
rv.ua
ryazan.ru
@@ -1518,6 +1593,7 @@ sc.ug
sc.us
sch.ae
sch.gg
+sch.id
sch.ir
sch.je
sch.lk
@@ -1542,6 +1618,7 @@ sendai.jp
seoul.kr
sex.hu
sex.pl
+sg.tf
sh.cn
shiga.jp
shimane.jp
@@ -1555,6 +1632,7 @@ sklep.pl
sld.do
sld.pa
slg.br
+slupsk.pl
smolensk.ru
sn.cn
snz.ru
@@ -1577,6 +1655,7 @@ sumy.ua
surgut.ru
sx.cn
syzran.ru
+szczecin.pl
szex.hu
szkola.pl
t.se
@@ -1596,6 +1675,7 @@ ternopil.ua
test.ru
tirana.al
tj.cn
+tld.am
tlf.nr
tm.cy
tm.fr
@@ -1614,6 +1694,7 @@ tokushima.jp
tokyo.jp
tom.ru
tomsk.ru
+torun.pl
tottori.jp
tourism.pl
tourism.tn
@@ -1644,11 +1725,14 @@ uk.tt
ulan-ude.ru
ulsan.kr
unam.na
+unbi.ba
uniti.al
+unsa.ba
upt.al
uri.arpa
urn.arpa
us.com
+us.tf
us.tt
ut.us
utazas.hu
@@ -1679,9 +1763,12 @@ w.se
wa.au
wa.us
wakayama.jp
+warszawa.pl
+waw.pl
weather.mobi
web.co
web.do
+web.id
web.lk
web.pk
web.tj
@@ -1689,6 +1776,8 @@ web.tr
web.ve
web.za
wi.us
+wroc.pl
+wroclaw.pl
wv.us
www.ro
wy.us
@@ -1709,7 +1798,9 @@ yokohama.jp
yuzhno-sakhalinsk.ru
z.se
za.com
+za.pl
zaporizhzhe.ua
+zgora.pl
zgrad.ru
zhitomir.ua
zj.cn
diff --git a/src/content/zoommanager.js b/src/content/zoommanager.js
new file mode 100644
index 0000000..ed2365c
--- /dev/null
+++ b/src/content/zoommanager.js
@@ -0,0 +1,62 @@
+/* NoSquint hooks the ZoomManager, overriding its default functionality.
+ *
+ * The logic below should be well-behaved when the user uses exclusively
+ * full page or text-only zooms.
+ *
+ * If both zooms are in use (i.e. full and text != 100%), things can get
+ * a little dubious. In general, if full zoom is not 100%, then we pretend
+ * as if full zoom is the primary method, regardless of whether it actually
+ * is. The rationale is that full page zoom is more likely to affect logic
+ * used by people interfacing with ZoomManager.
+ *
+ * More details in ZoomManager.useFullZoom getter.
+ */
+
+// ZoomManager._nosquintOrigZoomGetter = ZoomManager.__lookupGetter__('zoom');
+// ZoomManager._nosquintOrigZoomSetter = ZoomManager.__lookupSetter__('zoom');
+
+ZoomManager.__defineSetter__('zoom', function(value) {
+ var viewer = getBrowser().mCurrentBrowser.markupDocumentViewer;
+ var updated = false;
+
+ if (ZoomManager.useFullZoom && viewer.fullZoom != value)
+ updated = viewer.fullZoom = value;
+ else if (!ZoomManager.useFullZoom && viewer.textZoom != value)
+ updated = viewer.textZoom = value;
+
+ if (updated != false) {
+ NoSquint.browser.saveCurrentZoom();
+ NoSquint.browser.updateStatus();
+ }
+});
+
+ZoomManager.__defineGetter__('zoom', function() {
+ var viewer = getBrowser().mCurrentBrowser.markupDocumentViewer;
+ return ZoomManager.useFullZoom ? viewer.fullZoom : viewer.textZoom;
+});
+
+ZoomManager.__defineGetter__('useFullZoom', function() {
+ /* Extensions (like all-in-one gestures) assume that zoom is either all
+ * full page or all text-only, which is of course quite reasonable given
+ * that the ZoomManager interface assumes this too.
+ *
+ * So, regardless of what the primary zoom method is set to, if the
+ * current page has a full zoom level != 100%, then we always return
+ * true here.
+ *
+ * This is to handle the uncommon case where the user has modified
+ * both text and full page zoom. Extensions like AIO need to base
+ * decisions on whether or not the page is full-zoomed, not whether
+ * or not the user prefers full or text zoom.
+ */
+ var viewer = getBrowser().mCurrentBrowser.markupDocumentViewer;
+ return viewer.fullZoom != 1.0 ? true : NoSquint.prefs.fullZoomPrimary;
+});
+
+ZoomManager.enlarge = NoSquint.cmd.enlargePrimary;
+ZoomManager.reduce = NoSquint.cmd.reducePrimary;
+ZoomManager.reset = NoSquint.cmd.reset;
+
+FullZoom.enlarge = NoSquint.cmd.enlargeFullZoom;
+FullZoom.reduce = NoSquint.cmd.reduceFullZoom;
+FullZoom.reset = NoSquint.cmd.reset;
diff --git a/src/defaults/preferences/nosquint.js b/src/defaults/preferences/nosquint.js
index ad31683..a67cf00 100644
--- a/src/defaults/preferences/nosquint.js
+++ b/src/defaults/preferences/nosquint.js
@@ -1,10 +1,22 @@
-pref("extensions.nosquint.zoomlevel", 120);
+pref("extensions.nosquint.fullZoomLevel", 120);
+pref("extensions.nosquint.textZoomLevel", 100);
pref("extensions.nosquint.zoomIncrement", 10);
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.zoomImages", true);
+pref("extensions.nosquint.wheelZoomEnabled", true);
+pref("extensions.nosquint.wheelZoomInvert", false);
pref("extensions.nosquint.hideStatus", false);
pref("extensions.nosquint.forgetMonths", 6);
pref("extensions.nosquint.fullZoomPrimary", true);
+pref("extensions.nosquint.prefsVersion", '0');
+pref("extensions.nosquint.colorText", '0');
+pref("extensions.nosquint.colorBackground", '0');
+pref("extensions.nosquint.colorBackgroundImages", false);
+pref("extensions.nosquint.linksUnvisited", '0');
+pref("extensions.nosquint.linksVisited", '0');
+pref("extensions.nosquint.linksUnderline", false);
+pref("privacy.cpd.extensions-nosquint", true);
+pref("privacy.item.extensions-nosquint", true);
diff --git a/src/install.rdf b/src/install.rdf
index 9250cce..0ce1b26 100644
--- a/src/install.rdf
+++ b/src/install.rdf
@@ -5,14 +5,14 @@
<Description about="urn:mozilla:install-manifest">
<em:id>nosquint at urandom.ca</em:id>
- <em:name>No Squint</em:name>
- <em:version>1.93.2.1</em:version>
- <em:description>Manage site-specific full page and text zoom levels</em:description>
+ <em:name>NoSquint</em:name>
+ <em:version>2.0.1b1</em:version>
+ <em:description>Manage site-specific zoom levels and color settings</em:description>
<em:creator>Jason Tackaberry</em:creator>
<!-- optional items -->
<em:homepageURL>http://urandom.ca/nosquint/</em:homepageURL>
- <em:optionsURL>chrome://nosquint/content/globalprefs.xul</em:optionsURL>
- <em:iconURL>chrome://nosquint/content/icon-32.png</em:iconURL>
+ <em:optionsURL>chrome://nosquint/content/dlg-global.xul</em:optionsURL>
+ <em:iconURL>chrome://nosquint/skin/logo-32.png</em:iconURL>
<em:type>2</em:type> <!-- type=extension -->
<!-- Firefox -->
@@ -20,7 +20,7 @@
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>3.0b4pre</em:minVersion>
- <em:maxVersion>3.0.*</em:maxVersion>
+ <em:maxVersion>3.7a1pre</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
diff --git a/src/locale/en-US/dlg-global.dtd b/src/locale/en-US/dlg-global.dtd
new file mode 100644
index 0000000..e430929
--- /dev/null
+++ b/src/locale/en-US/dlg-global.dtd
@@ -0,0 +1,53 @@
+<!ENTITY ns.pref.title "NoSquint Global Settings">
+<!ENTITY ns.pref.tab.general.label "General">
+<!ENTITY ns.pref.tab.zooming.label "Zooming">
+<!ENTITY ns.pref.tab.colors.label "Colors">
+<!ENTITY ns.pref.tab.exceptions.label "Exceptions">
+
+<!ENTITY ns.pref.zooming.caption "Default Zoom Options">
+
+<!ENTITY ns.pref.zooming.primaryMethod.label "Primary zoom method">
+<!ENTITY ns.pref.zooming.primaryMethod.full "Full Page Zoom (images and text)">
+<!ENTITY ns.pref.zooming.primaryMethod.text "Text Zoom (text only)">
+
+<!ENTITY ns.pref.zooming.fullLevel.label "Default full page zoom level">
+<!ENTITY ns.pref.zooming.textLevel.label "Default text-only zoom level">
+<!ENTITY ns.pref.zooming.increment.label "Zoom increment">
+<!ENTITY ns.pref.zooming.images.label "Zoom standalone images to fit browser window">
+<!ENTITY ns.pref.zooming.mousewheel.label "Enable zoom with ctrl-mousewheel">
+<!ENTITY ns.pref.zooming.showstatus.label "Show current zoom levels in status bar">
+
+<!ENTITY ns.pref.persistence.caption "Per-site settings (color and zoom)">
+<!ENTITY ns.pref.persistence.remember.label "Remember per-site settings between Firefox restarts">
+<!ENTITY ns.pref.persistence.noRemember.label "Forget all per-site settings when Firefox closes">
+<!ENTITY ns.pref.persistence.forget.label "Forget settings for sites not visited in the last">
+<!ENTITY ns.pref.persistence.forget.year "Year">
+<!ENTITY ns.pref.persistence.forget.6months "Six Months">
+<!ENTITY ns.pref.persistence.forget.3months "Three Months">
+<!ENTITY ns.pref.persistence.forget.month "Month">
+<!ENTITY ns.pref.persistence.sanitize.label "Erase NoSquint site history when Firefox clears private data">
+
+<!ENTITY ns.pref.colors.info "Enabling any of the color options below overrides the default for all pages.">
+<!ENTITY ns.pref.colors.colors.caption "Text and Background">
+<!ENTITY ns.pref.colors.colors.text.label "Text">
+<!ENTITY ns.pref.colors.colors.background.label "Background">
+<!ENTITY ns.pref.colors.colors.images.label "Disable background images">
+<!ENTITY ns.pref.colors.links.caption "Link Colors">
+<!ENTITY ns.pref.colors.links.unvisited.label "Unvisited">
+<!ENTITY ns.pref.colors.links.visited.label "Visited">
+<!ENTITY ns.pref.colors.links.underline.label "Always underline links">
+
+
+<!ENTITY ns.pref.exceptions.info "Exceptions are an advanced feature that controls how NoSquint distinguishes separate sites. Click the Help button below for full details.">
+
+<!ENTITY ns.pref.exceptions.pattern.label "Pattern for new exception">
+<!ENTITY ns.pref.exceptions.copyButton.label "Copy from URL">
+<!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/dlg-global.properties b/src/locale/en-US/dlg-global.properties
new file mode 100644
index 0000000..1608767
--- /dev/null
+++ b/src/locale/en-US/dlg-global.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/locale/en-US/dlg-help.dtd b/src/locale/en-US/dlg-help.dtd
new file mode 100644
index 0000000..6e6f28a
--- /dev/null
+++ b/src/locale/en-US/dlg-help.dtd
@@ -0,0 +1,2 @@
+<!ENTITY ns.help.title "NoSquint Help">
+<!ENTITY ns.help.subtitle "Help">
diff --git a/src/locale/en-US/dlg-site.dtd b/src/locale/en-US/dlg-site.dtd
new file mode 100644
index 0000000..e41f2aa
--- /dev/null
+++ b/src/locale/en-US/dlg-site.dtd
@@ -0,0 +1,7 @@
+<!ENTITY ns.pref.title "NoSquint Site Settings">
+<!ENTITY ns.pref.button.global.label "Global Settings">
+<!ENTITY ns.pref.button.global.accesskey "G">
+
+<!ENTITY ns.pref.fullZoom.label "Full zoom level">
+<!ENTITY ns.pref.textZoom.label "Text zoom level">
+<!ENTITY ns.pref.button.useDefault.label "Use Default">
diff --git a/src/locale/en-US/dlg-site.properties b/src/locale/en-US/dlg-site.properties
new file mode 100644
index 0000000..9e7f125
--- /dev/null
+++ b/src/locale/en-US/dlg-site.properties
@@ -0,0 +1,4 @@
+settingsFor=Settings for
+warningForgetSites=Values set here will be discarded when you quit Firefox. This behavior can be changed in Global Settings.
+warningPrivateBrowsing=Values set here will be discarded once you quit Firefox or exit Private Browsing mode.
+
diff --git a/src/locale/en-US/help.html b/src/locale/en-US/help.html
index bbc6660..7db3377 100644
--- a/src/locale/en-US/help.html
+++ b/src/locale/en-US/help.html
@@ -52,8 +52,77 @@
</style>
<body>
-<h2>Options Tab</h2>
-<h3>General Options</h3>
+<h2 id='general'>General Tab</h2>
+
+<p>To NoSquint, a site is a web location where all pages have the same zoom
+level and color settings, and the site name is derived from the page's URL.</p>
+
+<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 country-specific
+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
+<a href='#exceptions'>Exceptions Tab</a>.</p>
+
+<h3>Per-site settings (color and zoom)</h3>
+<ul>
+ <li>
+ <b>Remember per-site settings between Firefox restarts</b>
+ <p>With this option selected, NoSquint will remember any changes
+ you make to the zoom levels and color choices for a given site, and
+ the changes are stored on disk so they can be applied even if you
+ restart Firefox.</p>
+ <p>Both full page zoom and text zoom levels are remembered
+ independently. Next time you visit that site, NoSquint will use
+ the zoom and colors previously used on that site.</p>
+ </li>
+ <li>
+ <b>Forget settings for sites not visited in the last ...</b>
+ <p>With the "Remember per-site settings between Firefox restarts"
+ option enabled, NoSquint keeps track of all zoom level and color
+ changes for sites, even sites you only visit once. This option is
+ house cleaning: if you haven't visited a site (for which you've set
+ a non-default zoom or color setting) for the specified number of
+ months, NoSquint will forget the setting.</p>
+ </li>
+ <li>
+ <b>Erase NoSquint site history when Firefox clears private data</b>
+ <p>When you change the default zoom levels or colors for a given
+ site, NoSquint remembers that change, which means it must keep a
+ record of those sites. From a privacy perspective, this is similar
+ to your browsing history. If this option is selected, when Firefox
+ clears your private data (via Tools | Clear Private Data for Firefox
+ 3.0, and Tools | Clear Recent History for Firefox 3.5), it will
+ also purge all site-specific data associated with NoSquint.</p>
+ <p>Note that user-added <a href='#exceptions'>exceptions</a> are
+ not cleared.</p>
+ </li>
+ <li>
+ <b>Forget all per-site settings when Firefox closes</b>
+ <p>Per-site settings will be retained only for the current Firefox
+ session. They are never written to disk, and will therefore not
+ be remembered if you restart Firefox.</p>
+ <p>An alternative would be to use Firefox's Private Browsing mode
+ (Firefox 3.5 and later), which NoSquint supports. When Private
+ Browsing is activated, even if "Remember per-site settings between
+ Firefox restarts" is selected instead of this option, NoSquint will
+ never store any per-site settings to disk which have changed while
+ in that mode.</p>
+ </li>
+</ul>
+
+<h2 id='zooming'>Zooming Tab</h2>
+
+<p>The options in this tab let you control the default zooming behavior as they
+apply to all sites. You can override the zoom levels for individual sites using
+the Site Settings, which can be managed by left-clicking on NoSquint's status bar
+icon, or selecting NoSquint Site Settings from the page's context menu.</p>
+
+<h3>Default Zoom Options</h3>
<ul>
<li>
<b>Primary zoom method</b>
@@ -67,17 +136,30 @@
secondary zoom method. For example, if the primary zoom method is
set to Full Page Zoom, and ctrl-shift-plus is pressed, only text size will
be increased.</p>
+ </li>
+ <li>
+ <b>Default full page zoom level</b>
+ <p>This is the full page zoom level (affecting both images and
+ text) which will be applied to all pages by default. A value of
+ 100% is the standard Firefox zoom level without NoSquint. With
+ NoSquint, you can override this value to be larger or smaller.</p>
+
+ <p>Modifying the full page zoom level when visiting a web page will
+ override this value for that site.</p>
+ </li>
- <b>Default primary zoom level</b>
- <p>This is the zoom level of the primary zoom method as applied to
- all pages by default. A value of 100% is the standard Firefox zoom
- level without NoSquint. With NoSquint, you can override this value
- to be larger or smaller.</p>
-
- <p>Modifying the zoom when visiting a web page will override this
- value for that site.</p>
+ <li>
+ <b>Default text-only zoom level</b>
+ <p>This is the text zoom level (affecting <i>only</i> text) which
+ will be applied to all pages by default. A value of 100% is the
+ standard Firefox zoom level without NoSquint. With NoSquint, you
+ can override this value to be larger or smaller.</p>
+
+ <p>Modifying the text zoom level when visiting a web page will
+ override this value for that site.</p>
</li>
+
<li>
<b>Zoom increment</b>
<p>You can change the zoom level for a page from the View menu, by
@@ -102,61 +184,63 @@
</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>
+<h2 id='colors'>Colors Tab</h2>
+<p>Sometimes website creators choose to use rather questionable color choices
+which can significantly hinder readability. NoSquint lets you override the
+standard text colors for <i>all</i> sites here. Or (probably more usefully),
+you can modify per-site colors via the Site Settings, which can be managed by
+left-clicking on NoSquint's status bar icon, or selecting NoSquint Site
+Settings from the page's context menu.</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>
+<h3>Text and Background</h3>
+<ul>
+ <li>
+ <b>Text</b>
+ <p>Overrides the foreground color for all text on the page.</p>
+ </li>
+ <li>
+ <b>Background</b>
+ <p>Overrides the background color for the page.</p>
+ </li>
+ <li>
+ <b>Disable background images</b>
+ <p>Some sites may overlay text on background images, rendering the
+ user-selected background color (above) ineffective. You can disable
+ those images by selecting this options.</p>
+ </li>
+</ul>
+<h3>Link Colors</h3>
<ul>
<li>
- <b>Remember zoom level per site</b>
- <p>With this option selected, NoSquint will remember any changes
- you make to the zoom levels for a given site. Both full page zoom
- and text zoom levels are remembered independently. Next time you
- visit that site, NoSquint will change the zoom to the levels
- previously used on that site.</p>
+ <b>Unvisited</b>
+ <p>The foreground text color to use for all links that have not been visited before.</p>
</li>
<li>
- <b>Forget zoom settings for sites not visited in the last ...</b>
- <p>With the "remember zoom levels 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>
+ <b>Visited</b>
+ <p>The foreground text color to use for all links that <i>have</i> been visited before.</p>
</li>
<li>
- <b>Use the default zoom level for all sites</b>
- <p>One of NoSquint's features is the ability to remember custom
- 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>
+ <b>Always underline links</b>
+ <p>Some websites make it difficult to tell the difference between links and regular text.
+ Selecting this option will force hyperlinks to always be underlined.</p>
</li>
</ul>
-<h2>Exceptions Tab</h2>
+
+<h2 id='exceptions'>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>
+to. Using 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>
+They are specified using a simple custom grammar, <i>not</i> regular
+expressions. 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>
@@ -221,7 +305,7 @@ use-cases. Hopefully one of these examples applies to your case.</p>
<code>example.com/[*]/apps/*</code>
</li>
<li>
- <b>Problem:</b> same scenario the previous one, but sometimes the
+ <b>Problem:</b> same scenario as 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/>
@@ -255,8 +339,9 @@ 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>
+site name, as determined by NoSquint, can be viewed in the tooltip by hovering
+over the magnifying glass in the status bar, or by opening the Site Settings
+dialog by left clicking the magnifying glass in the status bar.</p>
<p>When a wildcard is enclosed in square brackets (i.e. <code>[*]</code> or
<code>[**]</code>), the literal wildcard (<code>*</code> or <code>**</code>)
@@ -278,10 +363,24 @@ 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 first one that matched is chosen.</p>
+<p>Hostname parts can optionally contain a port number, which is delimited from
+the hostname with a <code>:</code> (colon). For example, <code>*:8080</code> will
+cause port 8080 for all domains to be treated separately, so that
+<code>www.example.com</code> is considered a different site than
+<code>www.example.com:8080</code> (but, assuming no other exceptions than
+this, <code>www.example.com:8001</code> is still considered the same site
+as <code>www.example.com</code> or <code>www.example.com:4242</code>).</p>
+
+<p>Any exception whose host part is the null string – in other words, the
+exception begins with a <code>/</code> (front slash) – is applied only to
+<code>file://</code> URLs. So the exception <code>/home/*</code> for Linux or
+<code>/C:/Users/*</code> for Windows Vista causes all home directories to be
+treated distinctly from one another.</p>
+
+<p>When multiple exception patterns match a page's URL, NoSquint will use the
+exception that matches the most non-wildcard characters in the host name. If
+there are still multiple exceptions in that narrowed list, the exception that
+matches the most non-wildcard characters in the path is then chosen. If still
+there are multiple exceptions, the first one that matched is chosen.</p>
</body>
</html>
diff --git a/src/locale/en-US/overlay.dtd b/src/locale/en-US/overlay.dtd
index cdd380c..df25813 100644
--- a/src/locale/en-US/overlay.dtd
+++ b/src/locale/en-US/overlay.dtd
@@ -1,9 +1,16 @@
<!ENTITY ns.tooltip.site.label "Site Name">
<!ENTITY ns.tooltip.fullZoom.label "Full Zoom">
<!ENTITY ns.tooltip.textZoom.label "Text Zoom">
+<!ENTITY ns.tooltip.textColor.label "Text Color">
+<!ENTITY ns.tooltip.linkColor.label "Link Color">
<!ENTITY ns.menu.fullZoom.label "Full Zoom">
<!ENTITY ns.menu.textZoom.label "Text Zoom">
<!ENTITY ns.menu.reset.label "Reset Levels">
<!ENTITY ns.menu.siteSettings.label "Site Settings">
<!ENTITY ns.menu.globalSettings.label "Global Settings">
+
+<!ENTITY ns.menu.context.label "NoSquint Site Settings">
+<!ENTITY ns.menu.context.accesskey "q">
+
+<!ENTITY ns.sanitize.label "NoSquint Site History">
diff --git a/src/locale/en-US/overlay.properties b/src/locale/en-US/overlay.properties
index 3dfe0ec..98fa412 100644
--- a/src/locale/en-US/overlay.properties
+++ b/src/locale/en-US/overlay.properties
@@ -3,3 +3,13 @@ zoomMenuOutText=Text Zoom Out
zoomMenuInFull=Full Zoom In
zoomMenuOutFull=Full Zoom Out
zoomMenuSettings=Zoom Settings (NoSquint)
+
+siteSpecificTitle=Override Native Per-Site Zooming?
+siteSpecificPrompt=Either another extension or a user action caused Firefox's native per-site zooming to be enabled. This will cause NoSquint to stop working properly. Would you like to revert this action so that NoSquint will continue working?
+siteSpecificBrokenTitle=NoSquint Is Broken!
+siteSpecificBrokenPrompt=NoSquint will now no longer work correctly until the browser is restarted.
+
+disableTitle=Restore Native Firefox Behavior?
+disablePrompt=Firefox natively provides less sophisticated per-site zooming, which NoSquint replaces. Having disabled NoSquint, would you now like to restore Firefox's native behavior?
+
+sanitizeLabel=NoSquint Site History
diff --git a/src/skin/icon-enlarge-16.png b/src/skin/icon-enlarge-16.png
index e1bf549..9a48f6b 100644
Binary files a/src/skin/icon-enlarge-16.png and b/src/skin/icon-enlarge-16.png differ
diff --git a/src/skin/icon-enlarge-24.png b/src/skin/icon-enlarge-24.png
index 54937bd..4a7bad6 100644
Binary files a/src/skin/icon-enlarge-24.png and b/src/skin/icon-enlarge-24.png differ
diff --git a/src/skin/icon-reduce-16.png b/src/skin/icon-reduce-16.png
index 385fec0..34a14c7 100644
Binary files a/src/skin/icon-reduce-16.png and b/src/skin/icon-reduce-16.png differ
diff --git a/src/skin/icon-reduce-24.png b/src/skin/icon-reduce-24.png
index 7543ac4..5b92e15 100644
Binary files a/src/skin/icon-reduce-24.png and b/src/skin/icon-reduce-24.png differ
diff --git a/src/skin/icon-reset-16.png b/src/skin/icon-reset-16.png
new file mode 100644
index 0000000..34eff11
Binary files /dev/null and b/src/skin/icon-reset-16.png differ
diff --git a/src/skin/icon-reset-24.png b/src/skin/icon-reset-24.png
new file mode 100644
index 0000000..8cfc2a1
Binary files /dev/null and b/src/skin/icon-reset-24.png differ
diff --git a/src/skin/icon-statusbar-16.png b/src/skin/icon-statusbar-16.png
new file mode 100644
index 0000000..26a0661
Binary files /dev/null and b/src/skin/icon-statusbar-16.png differ
diff --git a/src/skin/logo-32.png b/src/skin/logo-32.png
new file mode 100644
index 0000000..b465a31
Binary files /dev/null and b/src/skin/logo-32.png differ
diff --git a/src/skin/toolbar.css b/src/skin/toolbar.css
index f9e05fc..c1a8337 100644
--- a/src/skin/toolbar.css
+++ b/src/skin/toolbar.css
@@ -13,3 +13,19 @@ toolbar[iconsize="small"] #nosquint-button-reduce {
toolbar[iconsize="small"] #nosquint-button-enlarge {
list-style-image: url("chrome://nosquint/skin/icon-enlarge-16.png");
}
+
+#nosquint-button-reset {
+ list-style-image: url("chrome://nosquint/skin/icon-reset-24.png");
+}
+
+toolbar[iconsize="small"] #nosquint-button-reset {
+ list-style-image: url("chrome://nosquint/skin/icon-reset-16.png");
+}
+
+#nosquint-menu-settings, #nosquint-view-menu-settings {
+ list-style-image: url("chrome://nosquint/skin/icon-statusbar-16.png");
+}
+
+#nosquint-status-reset {
+ list-style-image: url("chrome://nosquint/skin/icon-reset-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